Como executar a saída de um comando dentro do shell atual?


92

Estou bem ciente do utilitário source(também conhecido como .), que pega o conteúdo de um arquivo e o executa no shell atual.

Agora, estou transformando algum texto em comandos shell e, em seguida, executando-os, da seguinte maneira:

$ ls | sed ... | sh

lsé apenas um exemplo aleatório, o texto original pode ser qualquer coisa. sedtambém, apenas um exemplo para transformar texto. A parte interessante é sh. Eu canalizo tudo o que tenho she ele executa.

Meu problema é que isso significa iniciar um novo sub shell. Prefiro que os comandos sejam executados no meu shell atual. Como eu seria capaz de fazer source some-filese tivesse os comandos em um arquivo de texto.

Não quero criar um arquivo temporário porque parece sujo.

Como alternativa, gostaria de iniciar meu sub shell com as mesmas características do meu shell atual.

atualizar

Ok, as soluções usando crase certamente funcionam, mas geralmente preciso fazer isso enquanto estou verificando e alterando a saída, então eu preferiria que houvesse uma maneira de canalizar o resultado em algo no final.

atualização triste

Ah, a /dev/stdincoisa parecia tão bonita, mas, em um caso mais complexo, não funcionou.

Então, eu tenho isso:

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

O que garante que todos os .docarquivos tenham sua extensão em letras minúsculas.

E que, aliás, pode ser resolvido xargs, mas isso está além do ponto.

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

Então, quando eu executar o anterior, ele sairá imediatamente, nada acontece.


Seu comando complexo funciona quando você envia para um arquivo temporário primeiro e, em seguida, origina-o? Se não, qual é o problema com a saída gerada? A saída do seu comando não funcionará se os nomes dos seus arquivos contiverem espaços ou se certas sequências não tiverem o escape adequado. Eu gostaria de adicionar cotações em torno de $ 1 e $ 2.doc, no mínimo.
Kaleb Pederson de

Existe algum bom motivo para ter que executar isso no shell original? - esses exemplos não manipulam o shell atual, então você não ganha nada ao fazer isso. A solução rápida é redirecionar a saída para um arquivo e originar esse arquivo
nos

@kaleb a saída funciona bem. neste caso particular, mesmo se eu canalizar para sh. os nomes dos arquivos são protegidos por espaço, mas obrigado por notar. Variáveis ​​de ambiente @nos git no shell original. e, novamente, esses são apenas exemplos. a questão é para toda a vida.
kch

source / dev / stdin não funcionou para mim quando precisava de variáveis ​​atribuídas para ficar por aqui. geirha em freenode bash me apontou para mywiki.wooledge.org/BashFAQ/024 e sugeriu que eu tentasse uma fonte de substituição de processo <(comando) que funcionasse para mim
srcerer

Respostas:


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

ATUALIZAÇÃO: Funciona no bash 4.0, bem como no tcsh e no traço (se você mudar sourcepara .). Aparentemente, havia erros no bash 3.2. Das notas de lançamento do bash 4.0 :

Corrigido um bug que causava `. ' falhar ao ler e executar comandos de arquivos não regulares, como dispositivos ou canais nomeados.


Ah, e como você está listando os shells, ele também funciona no zsh.
kch

2
Achei isso genial até que tentei em msys / mingw (onde não há pasta / dev (ou mesmo dispositivos)! Tentei muitas combinações de eval, $ () e crases ``. Não consegui fazer nada funcionar , então eu finalmente redirecionei a saída para um arquivo temporário, obtive o arquivo temporário e o removi. Basicamente "sed ...> /tmp/$$.tmp &&. /tmp/$$.tmp && rm / tmp / $$. tmp ". Alguém conseguiu uma solução msys / mingw sem arquivos temporários ???
chriv

10
Apenas uma observação: este não parece lidar com as declarações de exportação conforme o esperado. Se a string canalizada tiver uma declaração de exportação, a variável exportet não estará disponível em seu ambiente de terminal posteriormente, enquanto com a exportação eval também funciona perfeitamente.
Phil

No bash sem a opção lastpipe definida, isso cria um subshell exatamente como | bashfaria
aquele outro cara

1
Dado que o MacOS X geralmente tem bash 3.2 (talvez Yosemite tenha 4.0?), E a necessidade de truques de lastpipe no meu caso de uso (exportar todas as variáveis ​​em um arquivo pré-processando cada linha com sed para adicionar 'export'), eu escolhi para acompanhar a eval "$(sed ...)"abordagem - mas obrigado pelo bug do 3.2!
Alex Dupuy

135

O evalcomando existe para este propósito.

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

Mais do manual do bash :

avaliação

          eval [arguments]

Os argumentos são concatenados em um único comando, que é lido e executado, e seu status de saída é retornado como o status de saída de eval. Se não houver argumentos ou apenas argumentos vazios, o status de retorno será zero.


8
O único problema aqui é que você pode precisar inserir; para separar seus comandos. Eu mesmo uso esse método para trabalhar no AIX, Sun, HP e Linux.
Tanktalus

Tanktalus, obrigado por esse comentário, apenas fez meu roteiro funcionar. Na minha máquina, eval não separa comandos em novas linhas e usar o código-fonte não funciona mesmo com ponto e vírgula. Eval com ponto e vírgula é a solução. Eu gostaria de poder dar-lhe alguns pontos.
Milan Babuškov,

2
@ MilanBabuškov: Eu o classifiquei para você ;-)
Phil

3
Isto e excelente. No entanto, para meu caso de uso, acabei tendo que colocar aspas em torno do meu $ (), assim: eval "$( ssh remote-host 'cat ~/.bash_profile' )"Observe que estou realmente usando o bash 3.2.
Zachary Murray

3
Isso funciona para mim onde a resposta aceita não funcionou.
Dan Tenenbaum

37

Uau, eu sei que essa é uma pergunta antiga, mas me encontrei exatamente com o mesmo problema recentemente (foi assim que cheguei aqui).

De qualquer forma - não gosto da source /dev/stdinresposta, mas acho que encontrei uma melhor. Na verdade, é enganosamente simples:

echo ls -la | xargs xargs

Legal, certo? Na verdade, isso ainda não faz o que você deseja, porque se você tiver várias linhas, ele as concatará em um único comando em vez de executar cada comando separadamente. Portanto, a solução que encontrei é:

ls | ... | xargs -L 1 xargs

a -L 1opção significa que você usa (no máximo) 1 linha por execução de comando. Nota: se sua linha terminar com um espaço final, ele será concatenado com a próxima linha! Portanto, certifique-se de que cada linha termine com um não espaço.

Finalmente, você pode fazer

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

para ver quais comandos são executados (-t é detalhado).

Espero que alguém leia isso!


31

Tente usar a substituição de processo , que substitui a saída de um comando por um arquivo temporário que pode ser obtido:

source <(echo id)

7
Que tipo de feitiçaria é essa?
paulotorrens 09/09/2015

1
Este foi meu primeiro pensamento também. Embora eu goste mais da solução de avaliação. @PauloTorrens <(xyz) simplesmente executa xyz e substitui <(xyz) pelo nome de um arquivo que escreverá a saída de xyz. É realmente fácil entender como isso funciona fazendo, por exemplo echo <(echo id)/dev/fd/12cat <(echo id)idsource <(echo id)id
:,

2
@PauloTorrens, isso é chamado de substituição de processo . Consulte os documentos vinculados para obter uma explicação oficial, mas a resposta curta é que "<()" é uma sintaxe especial projetada para casos como este em mente.
Mark Stosberg

1
@MarkStosberg, você sabe se esta é uma sintaxe especial ou apenas um subshell (...)redirecionado?
paulotorrens

2
@PauloTorrens, é uma sintaxe especial apenas para substituição de processos.
Mark Stosberg

6

Acredito que esta seja a "resposta certa" para a pergunta:

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

Ou seja, pode-se fazer um tubo em um whileloop; o readcomando comando pega uma linha stdindela e a atribui à variável $line. $lineentão se torna o comando executado dentro do loop; e continua até que não haja mais linhas em sua entrada.

Isso ainda não funcionará com algumas estruturas de controle (como outro loop), mas se encaixa nesse caso.


5
`ls | sed ...`

Eu meio que sinto que ls | sed ... | source -seria mais bonita, mas infelizmente sourcenão entendo o -que significa stdin.


depois de ver a resposta da mark4o, não parece que estava certo em nossos rostos todo esse tempo?
kch

Heh, sim. Nunca me lembro que essa coisa existe.
caos de

1

Acho que sua solução é a substituição do comando com crases: http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html

Consulte a seção 3.4.5


3
Se você estiver usando bash, a $( )sintaxe pode ser a preferida. Os backticks do curso funcionam em mais shells ...
dmckee --- ex-gatinho moderador

6
$ (command) e $ ((1 + 1)) funcionam em todos os shells posix. Acho que muitos desanimam por vim marcá-los como erros de sintaxe, mas isso é só porque o vim está destacando o shell Bourne original que poucos usam. Para que o vim destaque corretamente, coloque-o em seu .vimrc: deixe g: is_posix = 1
pixelbeat

1

Para usar a solução do mark4o no bash 3.2 (macos), uma string here pode ser usada em vez de pipelines como neste exemplo:

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

ou use crostas:. / dev / stdin <<< `grep '^ alias' ~ / .profile`
William H. Hooper

0

Por que não usar sourceentão?

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

Ele mencionou que os arquivos temporários são nojentos.
caos de
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.