Esegui due comandi su un argomento (senza script)


28

Come posso eseguire due comandi su un input, senza digitare quell'input due volte?

Ad esempio, il comando stat dice molto su un file, ma non indica il suo tipo di file:

stat fileName

Il comando file indica quale tipo di file è:

file fileName

Puoi eseguire questa operazione in una riga in questo modo:

stat fileName ; file fileName

Tuttavia, è necessario digitare due volte il nomefile.

Come è possibile eseguire entrambi i comandi sullo stesso input (senza digitare due volte l'input o una variabile dell'input)?

In Linux, so come eseguire il pipe degli output, ma come si pipe gli input?


14
Giusto per essere chiari, quelli non sono "input", sono argomenti (aka parametri). L'input può essere reindirizzato tramite <(input dal file sul lato sinistro) o |(input dal flusso al lato destro). C'è una differenza
Riccioli d'oro,

6
Non una risposta alla tua domanda, ma forse una risposta al tuo problema: in bash, il yank-last-argcomando (scorciatoia di default Meta-.o Meta-_; Metaspesso è il Alttasto sinistro ) copierà l'ultimo parametro dalla precedente riga di comando in quella corrente. Quindi, dopo aver eseguito stat fileNamel' esecuzione , digitare file [Meta-.]e non è necessario digitarlo fileNamenuovamente. Lo uso sempre.
Dubu,

2
E la differenza è che <e >sono reindirizzamento , non tubazioni . Una pipeline collega due processi, mentre il reindirizzamento riassegna semplicemente stdin / stdout.
bsd

@bdowning: Sì, ma stai reindirizzando la pipe anche se non lo sei. È possibile reindirizzare l'inizio di una pipeline da leggere da un file su disco oppure reindirizzare la fine di una pipeline per scrivere su un file su disco. Tubazioni ancora. ;-)
James Haigh,

Risposte:


22

Ecco un altro modo:

$ stat filename && file "$_"

Esempio:

$ stat /usr/bin/yum && file "$_"
  File: ‘/usr/bin/yum’
  Size: 801         Blocks: 8          IO Block: 4096   regular file
Device: 804h/2052d  Inode: 1189124     Links: 1
Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:rpm_exec_t:s0
Access: 2014-06-11 22:55:53.783586098 +0700
Modify: 2014-05-22 16:49:35.000000000 +0700
Change: 2014-06-11 19:15:30.047017844 +0700
 Birth: -
/usr/bin/yum: Python script, ASCII text executable

Funziona in bashe zsh. Funziona anche in mkshe dashma solo quando interattivo. In AT&T ksh, funziona solo quando si file "$_"trova su una linea diversa da statquella.


1
Quello !$non funzionerà perché si !$espande fino all'ultima parola dell'ultimo evento della cronologia (quindi della riga di comando immessa in precedenza, non del comando stat).
Stéphane Chazelas,

@ StéphaneChazelas: Oh, sì, errore mio, l'ho eliminato.
cuonglm,

32

Probabilmente riuscirò a farmi rappare le mie nocche per questo, ecco una combinazione caotica di espansione e valutazione del bash brace che sembra fare il trucco

eval {stat,file}" fileName;"


2
espansione parentesi originata csh, non bash. bashcopiato da ksh. Quel codice funzionerà in tutti i file csh, tcsh, ksh, zsh, fish e bash.
Stéphane Chazelas,

1
Peccato che perdi la possibilità di "tab out" (completamento automatico) del fileName, a causa del preventivo.
Lonniebiz,

Scusa, ma non ho resistito neanche
io

scusa qual è il problema qui eval? (riferendosi ai commenti principali)
user13107

28

Con zsh, è possibile utilizzare funzioni anonime:

(){stat $1; file $1} filename

Con esLambdas:

@{stat $1; file $1} filename

Puoi anche fare:

{ stat -; file -;} < filename

(facendo il statprimo come fileaggiornerà il tempo di accesso).

Farei:

f=filename; stat "$f"; file "$f"

tuttavia, ecco a cosa servono le variabili.


3
{ stat -; file -;} < filenameè magico. statdeve verificare che il suo stdin sia collegato a un file e riportare gli attributi del file? Presumibilmente non avanzare il puntatore su stdin permettendo quindi filedi annusare il contenuto di stdin
iruvar

1
@ 1_CR, sì. stat -dice statdi fare un fstat()suo fd 0.
Stéphane Chazelas l'

4
La { $COMMAND_A -; $COMMAND_B; } < filenamevariante non funzionerà nel caso generale se $ COMMAND_A legge effettivamente qualsiasi input; ad es. { cat -; cat -; } < filenamestamperà il contenuto del nome file una sola volta.
Max Nanasy,

2
stat -è gestito appositamente da coreutils-8.0 (ottobre 2009), nelle versioni precedenti si otterrà un errore (a meno che non si disponga di un file chiamato -da un esperimento precedente, nel qual caso si otterranno risultati errati ...)
mr .spuratic

1
@ mr.spuratic. Sì, e nemmeno le statistiche BSD, IRIX e zsh non lo riconoscono. Con zsh e IRIX stat, puoi stat -f0invece usare .
Stéphane Chazelas,

9

Tutte queste risposte mi sembrano script, in misura maggiore o minore. Per una soluzione che in realtà non prevede scripting e presuppone che tu sia seduto alla tastiera digitando in una shell, puoi provare:

stat Enter
file somefile Esc_   Enter

Almeno in bash, zsh e ksh, in entrambe le modalità vi ed emacs, premendo Esc e poi il trattino basso inserisce l'ultima parola della riga precedente nel buffer di modifica per la riga corrente.

In alternativa, è possibile utilizzare la sostituzione della cronologia:

stat Enter
file somefile ! $Enter

In bash, zsh, csh, tcsh e la maggior parte delle altre shell, !$viene sostituito con l'ultima parola della riga di comando precedente quando viene analizzata la riga di comando corrente.


quali altre opzioni sono disponibili in zsh / bash Esc _? Puoi collegarti con la documentazione ufficiale?
Abhijeet Rastogi,


1
zsh: linux.die.net/man/1/zshzle e cerca "insert-last-word"
godlygeek

1
@shadyabhi ^ Collegamenti per entrambe le combinazioni di tasti standard di bash e zsh. Nota che bash sta usando solo readline, quindi (la maggior parte) di quelli bash funzionerà in qualsiasi applicazione che utilizza la libreria readline.
godlygeek,

3

Il modo in cui ho scoperto di farlo ragionevolmente è semplicemente usando xargs, prende un file / pipe e converte il suo contenuto in argomenti di un programma. Questo può essere combinato con un tee che divide uno stream e lo invia a due o più programmi. Nel tuo caso hai bisogno di:

echo filename | tee >(xargs stat) >(xargs file) | cat

A differenza di molte altre risposte, questo funzionerà in bash e nella maggior parte delle altre shell sotto Linux. Suggerirò che questo è un buon caso d'uso di una variabile ma se non puoi assolutamente usarne una, questo è il modo migliore per farlo solo con pipe e utility semplici (supponendo che tu non abbia una shell particolarmente elaborata).

Inoltre puoi farlo per qualsiasi numero di programmi semplicemente aggiungendoli allo stesso modo di questi due dopo il tee.

MODIFICARE

Come suggerito nei commenti ci sono un paio di difetti in questa risposta, il primo è il potenziale per l'interlacciamento dell'output. Questo può essere risolto in questo modo:

echo filename | tee >(xargs stat) >(( wait $!; xargs file )) | cat

Ciò forzerà l'esecuzione a turno dei comandi e l'output non verrà mai interlacciato.

Il secondo problema è evitare la sostituzione del processo che non è disponibile in alcune shell, questo può essere ottenuto usando tpipe invece di tee in questo modo:

echo filename | tpipe 'xargs stat' | ( wait $!; xargs file )

Questo dovrebbe essere portatile per quasi tutte le shell (spero) e risolve i problemi nell'altro encomio, tuttavia sto scrivendo quest'ultimo dalla memoria poiché il mio sistema attuale non ha tpipe disponibile.


1
La sostituzione del processo è una funzione ksh che funziona solo in ksh(non le varianti di dominio pubblico) bashe zsh. Si noti che state filefunzionerà contemporaneamente, quindi probabilmente il loro output sarà intrecciato (suppongo che tu stia usando | catper forzare il buffering dell'output per limitare quell'effetto?).
Stéphane Chazelas,

@ StéphaneChazelas Hai ragione su cat, quando lo uso non sono mai riuscito a produrre un caso di input interlacciato quando lo uso. Comunque proverò qualcosa per produrre momentaneamente una soluzione più portatile.
Vality,

3

Questa risposta è simile a un paio di altri:

nome   file stat & file &!! #: 1

A differenza delle altre risposte, che ripetono automaticamente l' ultimo argomento del comando precedente, questo richiede di contare:

ls -ld nomefile   && file! #: 2

A differenza di alcune delle altre risposte, questo non richiede virgolette:

stat "useless cat"  &&  file !#:1

o

echo Sometimes a '$cigar' is just a !#:3.

2

Questo è simile a un paio di altre risposte, ma è più portatile di questo e molto più semplice di questo :

sh -c 'stat "$ 0"; file "$ 0" ' nomefile

o

sh -c 'stat "$ 1"; file "$ 1" 'sh nomefile

in cui shè assegnato $0. Sebbene ci siano altri tre caratteri da digitare, questa (seconda) forma è preferibile perché $0è il nome che dai a quello script in linea. Viene utilizzato, ad esempio, nei messaggi di errore dalla shell per quello script, come quando fileo statnon può essere trovato o non può essere eseguito per nessun motivo.


Se vuoi farlo

stat nomefile 1  nomefile 2  nomefile 3 …; file nome  file 1 nome file 2  nome file 3 ...

per un numero arbitrario di file, fare

sh -c 'stat "$ @"; file "$ @" 'sh nomefile 1  nomefile 2  nomefile 3 ...

che funziona perché "$@"equivale a "$1" "$2" "$3" ….


Questo $0è il nome che vuoi dare a quello script in linea. Viene utilizzato ad esempio nei messaggi di errore dalla shell per quello script. Come quando fileo statnon può essere trovato o non può essere eseguito per nessun motivo. Quindi, vuoi qualcosa di significativo qui. Se non ispirato, basta usare sh.
Stéphane Chazelas,

1

Penso che tu stia facendo una domanda sbagliata, almeno per quello che stai cercando di fare. state i filecomandi stanno prendendo parametri che sono il nome di un file e non il suo contenuto. È in seguito il comando che esegue la lettura del file identificato dal nome che si sta specificando.

Una pipe dovrebbe avere due estremità, una utilizzata come input e un'altra come output e questo ha senso in quanto potrebbe essere necessario eseguire elaborazioni diverse lungo il percorso con strumenti diversi fino a ottenere il risultato necessario. Dire che sai come convogliare le uscite ma non sai come convogliare gli ingressi non è corretto in linea di principio.

Nel tuo caso non hai affatto bisogno di una pipe poiché devi fornire l'input sotto forma di un nome di file. E anche se quegli strumenti ( state file) leggessero lo stdin, pipe non è più rilevante in questo caso poiché l'input non dovrebbe essere modificato da nessuno degli strumenti quando arriva al secondo.


1

Questo dovrebbe funzionare per tutti i nomi di file nella directory corrente.

sh -c "$(printf 'stat -- "$1";file -- "$1";shift%.0b\n' *)" -- *

1
Supponendo che i nomi dei file non contengano caratteri di virgolette doppie, barra rovesciata, dollaro, backtick, }... e non iniziare con -. Alcune printfimplementazioni (zsh, bash, ksh93) hanno a %q. Con zshquello potrebbe essere:printf 'stat %q; file %1$q\n' ./* | zsh
Stéphane Chazelas l'

@StephaneChezales - tutto ciò è stato risolto ora, tranne la possibilità dell'hardquote. Potrei gestirlo con un singolo sed s///immagino, ma si tratta di portata - e se le persone lo inseriscono nei loro nomi di file dovrebbero usare Windows.
Mikeserv,

@ StéphaneChazelas - nevermind - Ho dimenticato quanto fossero facili da gestire.
Mikeserv,

A rigor di termini, questa non è una risposta alla domanda posta: "Come è possibile eseguire entrambi i comandi sullo stesso input ( senza digitare l'input ... due volte )?" (Enfasi aggiunta). La tua risposta si applica a tutti i file nella directory corrente e include *due volte . Potresti anche dire stat -- *; file -- *.
Scott,

@Scott - l'ingresso non viene digitato due volte - *viene espanso. L' espansione di *più i due comandi per ogni argomento in * è l'input. Vedere? printf commands\ %s\;shift *
Mikeserv,

1

Ci sono alcuni riferimenti diversi a "input" qui, quindi fornirò alcuni scenari con la comprensione prima in mente. Per la tua rapida risposta alla domanda nella forma più breve :

stat testfile < <($1)> outputfile

Quanto sopra eseguirà una stat sul file di test, prenderà (reindirizzamento) è STDOUT e lo includerà nella successiva funzione speciale (la parte <()), quindi produrrà i risultati finali di qualunque cosa fosse, in un nuovo file (file di output). Il file viene chiamato, quindi referenziato con built-in bash ($ 1 ogni volta dopo, fino a quando non si inizia un nuovo set di istruzioni).

La tua domanda è fantastica e ci sono diverse risposte e modi per farlo, ma cambia davvero con quello che stai facendo in modo specifico.

Ad esempio, puoi anche eseguire il loop, il che è abbastanza utile. Un uso comune di questo è, nella mentalità psuedo-code, è:

run program < <($output_from_program)> my_own.log

Comprenderlo e ampliare tale conoscenza ti consente di creare cose come:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

Questo eseguirà un semplice ls -A sulla tua directory corrente, quindi dirai mentre per scorrere ogni risultato da ls -A a (ed ecco dove è complicato!) Grep "thatword" in ciascuno di questi risultati, ed eseguirà solo il precedente printf (in rosso) se in realtà ha trovato un file con "thatword" al suo interno. Registrerà anche i risultati di grep in un nuovo file di testo, files_that_matched_thatword.

Esempio di output:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

index.html

Tutto ciò ha semplicemente stampato l's-Un risultato, niente di speciale. Aggiungi qualcosa per farlo grep questa volta:

echo "thatword" >> newfile

Ora rieseguilo:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

files_that_matched_thatword  index.html  newfile

Found a file: newfile:thatword

Anche se forse una risposta più estenuante di quella che stai cercando al momento, credo che tenere a portata di mano appunti come questo in giro ti gioverà molto di più nelle attività future.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.