Come eseguire l'output di un comando all'interno della shell corrente?


92

Sono ben consapevole dell'utilità source( nota anche come .), che preleverà i contenuti da un file e li eseguirà all'interno della shell corrente.

Ora sto trasformando del testo in comandi di shell e poi li sto eseguendo, come segue:

$ ls | sed ... | sh

lsè solo un esempio casuale, il testo originale può essere qualsiasi cosa. sedanche solo un esempio per trasformare il testo. La parte interessante è sh. Faccio tutto quello che ho she lo fa funzionare.

Il mio problema è che questo significa avviare una nuova sotto shell. Preferisco che i comandi vengano eseguiti all'interno della mia shell corrente. Come sarei in grado di fare con source some-file, se avessi i comandi in un file di testo.

Non voglio creare un file temporaneo perché sembra sporco.

In alternativa, vorrei avviare la mia sub shell con le stesse identiche caratteristiche della mia shell attuale.

aggiornare

Ok, le soluzioni che utilizzano il backtick funzionano sicuramente, ma spesso ho bisogno di farlo mentre controllo e modifico l'output, quindi preferirei di gran lunga se ci fosse un modo per incanalare il risultato in qualcosa alla fine.

triste aggiornamento

Ah, la /dev/stdincosa sembrava così carina, ma, in un caso più complesso, non ha funzionato.

Quindi, ho questo:

find . -type f -iname '*.doc' | ack -v '\.doc$' | perl -pe 's/^((.*)\.doc)$/git mv -f $1 $2.doc/i' | source /dev/stdin

Ciò garantisce che tutti i .docfile abbiano l'estensione in minuscolo.

E che per inciso, può essere gestito xargs, ma questo è oltre il punto.

find . -type f -iname '*.doc' | ack -v '\.doc$' | perl -pe 's/^((.*)\.doc)$/$1 $2.doc/i' | xargs -L1 git mv

Quindi, quando eseguo il primo, uscirà subito, non succede nulla.


Il tuo comando complesso funziona quando esegui il pipe prima su un file temporaneo e poi lo provi? In caso contrario, qual è il problema con l'output generato? L'output del comando non funzionerà se i nomi dei file contengono spazi o se alcune sequenze non vengono salvate correttamente. Vorrei aggiungere virgolette intorno a $ 1 e $ 2 doc come minimo.
Kaleb Pederson

C'è qualche buona ragione per doverlo eseguire nella shell originale? - questi esempi non manipolano la shell corrente quindi non ottieni nulla in questo modo. La soluzione rapida è reindirizzare l'output a un file e generare quel file però
nos

@kaleb l'output funziona correttamente. in questo caso particolare, anche se pipo a sh. i nomi dei file sono salvaspazio, ma grazie per averlo notato. Variabili di ambiente git @nos nella shell originale. e ancora, questi sono solo esempi. la domanda è per la vita.
kch

source / dev / stdin non ha funzionato per me quando avevo bisogno di variabili assegnate per rimanere in giro. geirha su freenode bash mi ha indirizzato a mywiki.wooledge.org/BashFAQ/024 e mi ha suggerito di provare una sorgente di sostituzione del processo <(comando) che ha funzionato per me
srcerer

Risposte:


88
$ ls | sed ... | source /dev/stdin

AGGIORNAMENTO: funziona in bash 4.0, così come tcsh e dash (se si sourcepassa a .). Apparentemente questo era bacato in bash 3.2. Dalle note di rilascio di bash 4.0 :

Risolto un bug che causava "." non riuscire a leggere ed eseguire comandi da file non regolari come dispositivi o named pipe.


Oh, e dato che stai elencando le shell, funziona anche in zsh.
kch

2
Ho pensato che fosse geniale fino a quando non l'ho provato in msys / mingw (dove non c'è la cartella / dev (o anche i dispositivi)! Ho provato molte combinazioni di eval, $ () e backtick ``. Non riuscivo a far funzionare nulla , quindi ho finalmente reindirizzato l'output in un file temporaneo, ho preso il file temporaneo e l'ho rimosso. Fondamentalmente "sed ...> /tmp/$$.tmp &&. /tmp/$$.tmp && rm / tmp / $$. tmp ". Qualcuno ha ottenuto una soluzione msys / mingw senza file temporanei ???
chriv

10
Solo un'osservazione: questo non sembra gestire le istruzioni di esportazione come previsto. Se la stringa reindirizzata ha un'istruzione export, la variabile exportet non è disponibile successivamente nell'ambiente terminale, mentre con eval export funziona perfettamente.
Phil

In bash senza l'opzione lastpipe impostata, questo crea una subshell proprio come | bashfarebbe
quell'altro ragazzo il

1
Dato che MacOS X di solito ha bash 3.2 (forse Yosemite ha 4.0?), E la necessità di trucchi lastpipe nel mio caso d'uso (esportando tutte le variabili in un file pre-elaborando ogni riga con sed per aggiungere 'export') ho scelto per seguire l' eval "$(sed ...)"approccio - ma grazie per l'avviso di bug 3.2!
Alex Dupuy

135

Il evalcomando esiste proprio per questo scopo.

eval "$( ls | sed... )"

Altro da manuale di bash :

eval

          eval [arguments]

Gli argomenti vengono concatenati insieme in un unico comando, che viene quindi letto ed eseguito e il suo stato di uscita restituito come stato di uscita di eval. Se non ci sono argomenti o solo argomenti vuoti, lo stato di ritorno è zero.


8
L'unico problema qui è che potresti dover inserire; per separare i tuoi comandi. Io stesso utilizzo questo metodo per lavorare su AIX, Sun, HP e Linux.
Tanktalus

Tanktalus, grazie per quel commento, ha appena fatto funzionare la mia sceneggiatura. Sulla mia macchina eval non separa i comandi sui newline e l'utilizzo di source non funziona nemmeno con i punti e virgola. Eval con punto e virgola è la soluzione. Vorrei poterti dare alcuni punti.
Milan Babuškov

2
@ MilanBabuškov: l'ho classificato per te ;-)
Phil

3
Questo è eccellente. Tuttavia, per il mio caso d'uso, ho finito per dover inserire virgolette attorno al mio $ (), in questo modo: eval "$( ssh remote-host 'cat ~/.bash_profile' )"Nota che sto effettivamente usando bash 3.2.
Zachary Murray

3
Questo funziona per me dove la risposta accettata non ha funzionato.
Dan Tenenbaum

37

Wow, so che questa è una vecchia domanda, ma recentemente mi sono ritrovato con lo stesso identico problema (è così che sono arrivato qui).

Comunque, non mi piace la source /dev/stdinrisposta, ma penso di averne trovata una migliore. In realtà è apparentemente semplice:

echo ls -la | xargs xargs

Bello, vero? In realtà, questo non fa ancora quello che vuoi, perché se hai più righe le concatenerà in un singolo comando invece di eseguire ciascun comando separatamente. Quindi la soluzione che ho trovato è:

ls | ... | xargs -L 1 xargs

l' -L 1opzione significa che usi (al massimo) 1 riga per l'esecuzione del comando. Nota: se la tua riga termina con uno spazio finale, verrà concatenata con la riga successiva! Quindi assicurati che ogni riga termini con un non spazio.

Finalmente puoi farlo

ls | ... | xargs -L 1 xargs -t

per vedere quali comandi vengono eseguiti (-t è dettagliato).

Spero che qualcuno legga questo!


31

Prova a utilizzare la sostituzione del processo , che sostituisce l'output di un comando con un file temporaneo che può quindi essere recuperato:

source <(echo id)

7
Che tipo di stregoneria è questa?
paulotorrens

1
Questo è stato anche il mio primo pensiero. Anche se mi piace di più la soluzione eval. @PauloTorrens <(xyz) esegue semplicemente xyz e sostituisce <(xyz) con il nome di un file che scriverà l'output di xyz. È davvero facile capire come funziona facendo ad esempio echo <(echo id)/dev/fd/12cat <(echo id)idsource <(echo id)id
:,

2
@PauloTorrens, questa è chiamata sostituzione del processo . Vedere i documenti collegati per una spiegazione ufficiale, ma la risposta breve è che "<()" è una sintassi speciale progettata per casi come questo in mente.
Mark Stosberg

1
@MarkStosberg, sai se questa è una sintassi speciale o solo una sottoshell (...)reindirizzata?
paulotorrens

2
@PauloTorrens, è una sintassi speciale solo per la sostituzione del processo.
Mark Stosberg

6

Credo che questa sia "la risposta giusta" alla domanda:

ls | sed ... | while read line; do $line; done

Cioè, si può collegare in un whileloop; il readcomando comando prende una riga dalla sua stdine la assegna alla variabile $line. $linequindi diventa il comando eseguito all'interno del ciclo; e continua fino a quando non ci sono ulteriori righe nel suo input.

Questo ancora non funzionerà con alcune strutture di controllo (come un altro loop), ma in questo caso si adatta al conto.


5
`ls | sed ...`

Mi sembra che ls | sed ... | source -sarebbe più carino, ma sfortunatamente sourcenon capisco cosa -significhi stdin.


dopo aver visto la risposta di mark4o, non ti sembra che fosse giusto nei nostri volti per tutto questo tempo?
kch

Eh, sì. Non ricordo mai che quella roba esista.
caos

1

Penso che la tua soluzione sia la sostituzione del comando con i backtick: http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html

Vedere la sezione 3.4.5


3
Se stai usando bash, la $( )sintassi potrebbe essere preferita. I backtick del corso funzionano in più shell ...
dmckee --- ex-moderator kitten

6
$ (comando) e $ ((1 + 1)) funzionano in tutte le shell posix. Penso che molti siano scoraggiati dal fatto che vim li contrassegni come errori di sintassi, ma è solo perché vim sta evidenziando per la shell Bourne originale che pochissimi usano. Per fare in modo che vim evidenzi correttamente, mettilo nel tuo .vimrc: let g: is_posix = 1
pixelbeat

1

Per utilizzare la soluzione di mark4o su bash 3.2 (macos) è possibile utilizzare una stringa here al posto delle pipeline come in questo esempio:

. /dev/stdin <<< "$(grep '^alias' ~/.profile)"

o usa i backtick:. / dev / stdin <<< `grep '^ alias' ~ / .profile`
William H. Hooper

0

Perché non usare sourceallora?

$ ls | sed ... > out.sh ; source out.sh

Ha detto che i file temporanei sono icky.
caos
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.