Execute dois comandos em um argumento (sem script)

26

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

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

stat fileName

O comando file informa o tipo de arquivo:

file fileName

Você pode fazer isso em uma linha dessa maneira:

stat fileName ; file fileName

No entanto, você precisa 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 como canalizar saídas, mas como você canaliza entradas?

    
por Lonniebiz 11.06.2014 / 16:31

10 respostas

20

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 bash e zsh . Isso também funciona em mksh e dash , mas somente quando interativo. Em AT & T ksh, isso só funciona quando o file "$_" está em uma linha diferente da stat one.

    
por 11.06.2014 / 19:01
31

Provavelmente vou bater meus dedos por isso, aqui está uma combinação hacky de expansão de chave e eval que parece fazer o truque

eval {stat,file}" fileName;"
    
por 11.06.2014 / 16:44
26

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

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

Com es lambdas:

@{stat $1; file $1} filename

Você também pode fazer:

{ stat -; file -;} < filename

(fazendo o stat primeiro como file atualizará o tempo de acesso).

Eu faria:

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

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

    
por 11.06.2014 / 16:51
8

Todas essas respostas parecem scripts para mim, em maior ou menor grau. Para uma solução que realmente não envolve scripts e presume que você está sentado no teclado digitando em um shell, você pode tentar:

stat somefile Enter
file Esc_   Enter

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

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

stat somefile Enter
file !$ Enter

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

    
por 12.06.2014 / 17:47
3

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

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

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

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, há algumas falhas nesta resposta, a primeira é o potencial de entrelaçamento de saída. Isso pode ser corrigido assim:

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

Isso forçará os comandos a serem executados 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 conseguido usando tpipe em vez de tee assim:

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

Isso deve ser portável para praticamente qualquer shell (espero) e resolve os problemas no outro, no entanto, estou escrevendo este último da memória, já que meu sistema atual não tem o tpipe disponível.

    
por 11.06.2014 / 18:54
3

Esta resposta é semelhante a algumas das outras:

stat filename  &&  file !#:1

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

ls -ld filename  &&  file !#:2

Ao contrário de algumas das outras respostas, isso não requer citações:

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

ou

echo Sometimes a '$cigar' is just a !#:3.
    
por 12.06.2014 / 20:14
2

Isso é parecido com algumas outras respostas, mas é mais portátil do que este e muito mais simples do que este :

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

ou

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

em que sh é atribuído a $0 . Embora tenha mais três caracteres para digitar, esta (segunda) forma é preferível porque $0 é o nome que você dá para esse script inline. É usado, por exemplo, em mensagens de erro pelo shell para esse script, como quando file ou stat não pode ser encontrado ou não pode ser executado por qualquer razão.

Se você quiser fazer

stat filename1 filename2 filename3 …; file filename1 filename2 filename3

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

sh -c 'stat "$@"; file "$@"' sh filename1 filename2 filename3

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

    
por 18.07.2014 / 19:36
1

Acho que você está fazendo uma pergunta errada, pelo menos pelo que você está tentando fazer. Os comandos stat e file estão tomando 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 pipe deve ter duas extremidades, uma usada como entrada e outra como saída e isso faz sentido, já que você pode precisar fazer um processamento diferente ao longo do caminho com diferentes ferramentas 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 pipe, pois precisa fornecer a entrada na forma de um nome de arquivo. E mesmo que essas ferramentas ( stat e file ) leiam stdin, o canal não é relevante aqui novamente, pois a entrada não deve ser alterada por nenhuma ferramenta quando chegar ao segundo.

    
por 11.06.2014 / 17:43
1

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

sh -c "$(printf 'stat -- "$1";file -- "$1";shift%.0b\n' *)" -- *
    
por 11.06.2014 / 19:45
1

Existem algumas referências diferentes para 'input' aqui, por isso vou dar alguns cenários com a compreensão em mente primeiro. Para sua resposta rápida à pergunta no menor formato :

stat testfile < <($1)> outputfile

O exemplo acima irá executar um stat no testfile, pegar (redirecionar) o STDOUT e incluí-lo na próxima função especial (a parte < ()) então mostrar os resultados finais do que quer que seja, em um novo arquivo (outputfile ). O arquivo é chamado e, em seguida, referenciado com os built-ins do bash ($ 1 cada vez, até que você inicie um novo conjunto de instruções).

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

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

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

Aproveitar e expandir esse conhecimento permite que você crie 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)

Isto irá executar um simples ls -A no seu diretório atual, então diga while para percorrer cada resultado do ls -A para (e aqui é onde é complicado!) grep "thatword" em cada um desses resultados, e somente execute o printf anterior (em vermelho) se ele realmente encontrou um arquivo com "thatword" nele. 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 ele para grep desta vez:

echo "thatword" >> newfile

Agora execute 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 notas úteis como essa em torno de você irá beneficiá-lo muito mais em futuros empreendimentos.

    
por 13.06.2014 / 18:17