Execute dois comandos em um argumento (sem script)

28

Como posso executar dois comandos em uma entrada, sem digitar essa entrada duas vezes?

Por exemplo, o comando stat diz muito sobre um arquivo, mas não indica seu tipo de arquivo:

stat fileName

O comando file indica o tipo de arquivo:

file fileName

Você pode fazer isso de uma linha desta maneira:

stat fileName ; file fileName

No entanto, você deve digitar o nome do arquivo duas vezes.

Como você pode executar os dois comandos na mesma entrada (sem digitar a entrada ou uma variável da entrada duas vezes)?

No Linux, eu sei canalizar saídas, mas como canalizar entradas?

Lonniebiz
fonte
14
Só para esclarecer, essas não são "entradas", são argumentos (também conhecidos como parâmetros). A entrada pode ser canalizada via <(entrada do arquivo para o lado esquerdo) ou |(entrada do fluxo para o lado direito). Há uma diferença.
Goldilocks
6
Não é uma resposta para sua pergunta, mas talvez uma resposta para seu problema: No bash, o yank-last-argcomando (atalho padrão Meta-.ou Meta-_; Metageralmente sendo a Alttecla esquerda ) copiará o último parâmetro da linha de comando anterior para o atual. Portanto, após a execução, stat fileNamevocê digita file [Meta-.]e não precisa digitá-lo fileNamenovamente. Eu sempre uso isso.
Dubu
2
E a diferença é isso <e >é redirecionamento , não canalização . Um pipeline conecta dois processos, enquanto o redirecionamento simplesmente reatribui stdin / stdout.
BSD
@downdown: Sim, mas você está redirecionando o canal, não é? Você pode redirecionar o início de um pipeline para leitura de um arquivo em disco ou pode redirecionar o final de um pipeline para gravar em um arquivo em disco. Ainda está bem. ;-)
James Haigh
Relacionado (mas mais especializado): Existe uma linha que me permita criar um diretório e movê-lo ao mesmo tempo?
G-Man diz 'Restabelecer Monica'

Respostas:

22

Aqui está outra maneira:

$ stat filename && file "$_"

Exemplo:

$ 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

Isso funciona em bashe zsh. Isso também funciona mkshe, dashapenas, quando interativo. No AT&T ksh, isso só funciona quando o file "$_"está em uma linha diferente statdaquela.

cuonglm
fonte
1
O !$que não funciona porque se !$expande para a última palavra do último evento histórico (portanto, da linha de comando inserida anteriormente, não do comando stat).
Stéphane Chazelas
@ StéphaneChazelas: Ah, sim, meu erro, eu o apaguei.
cuonglm
32

Provavelmente vou acertar meus dedos por isso, aqui está uma combinação hacky de expansão e avaliação do bash brace que parece fazer o truque

eval {stat,file}" fileName;"
iruvar
fonte
8
Desculpe, mas não posso resistir .
Andreas Wiese
2
expansão de braçadeira originada em csh, não bash. bashcopiou de ksh. Esse código funcionará em todos os csh, tcsh, ksh, zsh, fish e bash.
Stéphane Chazelas
1
Pena que você perde a capacidade de "tabular" (auto-completar) o nome do arquivo, devido à cotação.
Lonniebiz
Desculpe, mas eu não pude resistir a tanto
Tomd
desculpe qual é o problema aqui eval? (referindo-se aos principais comentários)
user13107 11/1116
28

Com zsh, você pode usar funções anônimas:

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

Com eslambdas:

@{stat $1; file $1} filename

Você também pode fazer:

{ stat -; file -;} < filename

(fazendo o statprimeiro como fileatualizará o tempo de acesso).

Eu faria:

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

no entanto, é para isso que servem as variáveis.

Stéphane Chazelas
fonte
3
{ stat -; file -;} < filenameé mágico. statdeve estar verificando se o stdin está conectado a um arquivo e relatando os atributos do arquivo? Presumivelmente não avançar o apontador em stdin, portanto, permitindo fileaos conteúdos stdin Sniff
Iruvar
1
@ 1_CR, sim. stat -diz statpara fazer um fstat()em seu fd 0.
Stéphane Chazelas
4
A { $COMMAND_A -; $COMMAND_B; } < filenamevariante não funcionará no caso geral se $ COMMAND_A realmente ler qualquer entrada; por exemplo { cat -; cat -; } < filename, imprimirá o conteúdo do nome do arquivo apenas uma vez.
Max Nanasy
2
stat -é tratado especialmente desde o coreutils-8.0 (outubro de 2009), nas versões anteriores a isso você receberá um erro (a menos que você tenha um arquivo chamado -de uma experiência anterior, nesse caso, obterá os resultados errados ...)
mr .spuratic
1
@ mr.spuratic. Sim, e as estatísticas BSDs, IRIX e zsh também não a reconhecerão. Com zsh e IRIX stat, você pode usar stat -f0.
Stéphane Chazelas
9

Todas essas respostas parecem scripts para mim, em maior ou menor grau. Para uma solução que realmente não envolve scripts e pressupõe que você esteja sentado ao teclado digitando em um shell, tente:

Enter
arquivo de algum arquivo stat Esc_   Enter

No bash, zsh e ksh, pelo menos, nos modos vi e emacs, pressionar Escape e sublinhado insere a última palavra da linha anterior no buffer de edição da linha atual.

Ou você pode usar a substituição do histórico:

Enter
arquivo de algum arquivo stat ! $Enter

No bash, zsh, csh, tcsh e a maioria dos outros shells, !$é substituído pela última palavra da linha de comando anterior quando a linha de comando atual é analisada.

godlygeek
fonte
que outras opções estão disponíveis no zsh / bash Esc _? Você pode fazer o link com a documentação oficial?
Abhijeet Rastogi
1
zsh: linux.die.net/man/1/zshzle e procure por "inserir-last-palavra"
godlygeek
1
@shadyabhi ^ Links para as combinações de teclas padrão do bash e do zsh. Observe que o bash está apenas usando o readline, portanto (a maioria) do bash funcionará em qualquer aplicativo que use a biblioteca readline.
godlygeek
3

A maneira que descobri que isso é razoavelmente simples é usar xargs, ele pega um arquivo / pipe e converte seu conteúdo em argumentos de um programa. Isso pode ser combinado com o tee, que divide um fluxo e o envia para dois ou mais programas. No seu caso, você precisa:

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

Ao contrário de muitas das outras respostas, isso funcionará no bash e na maioria dos outros shells no Linux. Sugerirei que este é um bom caso de uso de uma variável, mas se você absolutamente não puder usá-lo, é a melhor maneira de fazê-lo apenas com pipes e utilitários simples (supondo que você não tenha um shell particularmente sofisticado).

Além disso, você pode fazer isso para qualquer número de programas, simplesmente adicionando-os da mesma maneira que esses dois após o tee.

EDITAR

Como sugerido nos comentários, existem algumas falhas nesta resposta, a primeira é o potencial de entrelaçamento de saída. Isso pode ser corrigido da seguinte maneira:

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

Isso forçará os comandos a serem executados alternadamente e a saída nunca será entrelaçada.

A segunda questão é evitar a substituição do processo que não está disponível em alguns shells, isso pode ser alcançado usando tpipe em vez de tee da seguinte forma:

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

Isso deve ser portátil para praticamente qualquer shell (espero) e resolver os problemas no outro elogio, no entanto, estou escrevendo este último na memória, pois meu sistema atual não possui o tpipe disponível.

Validade
fonte
1
A substituição de processo é um recurso do ksh que funciona apenas ksh(e não nas variantes de domínio público), bashe zsh. Observe que state fileserá executado simultaneamente, portanto, possivelmente a saída deles será entrelaçada (suponho que você esteja usando | catpara forçar o buffer de saída para limitar esse efeito?).
Stéphane Chazelas
@ StéphaneChazelas Você está certo sobre o gato, ao usá-lo, nunca consegui produzir um caso de entrada entrelaçada ao usá-lo. No entanto, vou tentar algo para produzir momentaneamente uma solução mais portátil.
Vality
3

Esta resposta é semelhante a algumas outras:

nome do   arquivo stat && file! #: 1

Diferente das outras respostas, que repetem automaticamente o último argumento do comando anterior, este requer que você conte:

ls -ld nome do   arquivo && arquivo! #: 2

Ao contrário de algumas das outras respostas, isso não requer aspas:

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

ou

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

Isso é semelhante a algumas das outras respostas, mas é mais portátil que esta e muito mais simples que esta :

sh -c 'stat "$ 0"; arquivo "$ 0" ' nome do arquivo

ou

sh -c 'stat "$ 1"; arquivo "$ 1" 'sh nome do arquivo

na qual shestá atribuído $0. Embora haja mais três caracteres para digitar, esse (segundo) formulário é preferível, porque esse $0é o nome que você atribui ao script embutido. É usado, por exemplo, em mensagens de erro do shell para esse script, como quando fileou statnão pode ser encontrado ou não pode ser executado por qualquer motivo.


Se você quer fazer

nome do arquivo  stat 1 nome do arquivo 2  nome do arquivo 3 …; nome do  arquivo 1 nome do arquivo 2  nome do arquivo 3

para um número arbitrário de arquivos, faça

sh -c 'stat "$ @"; arquivo "$ @" 'sh nome do arquivo 1  nome do arquivo 2  nome do arquivo 3

que funciona porque "$@"é equivalente a "$1" "$2" "$3" ….

Scott
fonte
Esse $0é o nome que você deseja atribuir a esse script embutido. É usado, por exemplo, em mensagens de erro do shell para esse script. Como quando fileou statnão pode ser encontrado ou não pode ser executado por qualquer motivo. Então, você quer algo significativo aqui. Se não for inspirado, basta usar sh.
Stéphane Chazelas
1

Eu acho que você está fazendo uma pergunta errada, pelo menos para o que você está tentando fazer. state filecomandos estão recebendo parâmetros que são o nome de um arquivo e não o conteúdo dele. Mais tarde, é o comando que faz a leitura do arquivo identificado pelo nome que você está especificando.

Um tubo deve ter duas extremidades, uma usada como entrada e outra como saída, e isso faz sentido, pois você pode precisar processar diferentes ao longo do caminho com ferramentas diferentes até obter o resultado necessário. Dizer que você sabe como canalizar saídas, mas não sabe como canalizar entradas não é correto em princípio.

No seu caso, você não precisa de um canal, pois é necessário fornecer a entrada na forma de um nome de arquivo. E mesmo que essas ferramentas ( state file) leiam stdin, o pipe não é relevante aqui novamente, pois a entrada não deve ser alterada por nenhuma ferramenta quando se trata da segunda.

vadimbog
fonte
1

Isso deve funcionar para todos os nomes de arquivos no diretório atual.

sh -c "$(printf 'stat -- "$1";file -- "$1";shift%.0b\n' *)" -- *
mikeserv
fonte
1
Supondo que os nomes dos arquivos não contenham aspas duplas, barra invertida, dólar, backtick }... caracteres e não comece com -. Algumas printfimplementações (zsh, bash, ksh93) possuem a %q. Com zsh, isso pode ser:printf 'stat %q; file %1$q\n' ./* | zsh
Stéphane Chazelas
@StephaneChezales - tudo isso foi corrigido agora, exceto pela possibilidade de citação. Eu poderia lidar com isso com um único, sed s///eu acho, mas é sobre o escopo - e se as pessoas estão colocando isso em seus nomes de arquivos, devem usar o Windows.
mikeserv
@ StéphaneChazelas - deixa pra lá - esqueci como eram fáceis de lidar.
mikeserv
Estritamente falando, essa não é uma resposta à pergunta: "Como você pode executar os dois comandos na mesma entrada ( sem digitar a entrada ... duas vezes )?" (Ênfase adicionada). Sua resposta se aplica a todos os arquivos no diretório atual - e inclui *duas vezes . Você poderia muito bem dizer stat -- *; file -- *.
Scott
@ Scott - a entrada não é digitada duas vezes - *é expandida. A expansão de *mais os dois comandos para cada argumento * é a entrada. Vejo? printf commands\ %s\;shift *
mikeserv
1

Existem algumas referências diferentes para 'entrada' aqui, então darei alguns cenários com o entendimento em mente primeiro. Para sua resposta rápida à pergunta da forma mais curta :

stat testfile < <($1)> outputfile

O acima irá executar uma estatística no arquivo de teste, pegar (redirecionar) o seu STDOUT e incluir isso na próxima função especial (a parte <()), em seguida, produzirá os resultados finais do que quer que fosse, em um novo arquivo (arquivo de saída). O arquivo é chamado e, em seguida, referenciado com bash built-ins ($ 1 cada vez depois, até você iniciar um novo conjunto de instruções).

Sua pergunta é ótima e existem várias respostas e maneiras de fazer isso, mas isso realmente muda com o que você está fazendo especificamente.

Por exemplo, você pode repetir isso também, o que é bastante útil. Um uso comum disso é, na mentalidade do código psuedo, é:

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

Aceitar isso e expandir esse conhecimento permite criar coisas como:

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)

Isso executará um simples ls -A em seu diretório atual e, em seguida, informará enquanto percorrer cada resultado do ls -A para (e é aqui que é complicado!) Grep "thatword" em cada um desses resultados, e somente realizará o anterior printf (em vermelho) se ele realmente encontrou um arquivo com "essa palavra". Ele também registrará os resultados do grep em um novo arquivo de texto, files_that_matched_thatword.

Exemplo de saída:

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

Tudo isso simplesmente imprimiu o resultado ls -A, nada de especial. Adicione algo para grep desta vez:

echo "thatword" >> newfile

Agora, execute-o novamente:

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

Embora talvez seja uma resposta mais exaustiva do que você está procurando no momento, acredito que manter anotações úteis como essa ajudará muito mais em empreendimentos futuros.

keypulse
fonte