Por que o comando “ls | arquivo ”funciona?

32

Eu tenho estudado sobre a linha de comando e aprendi que | (pipeline) serve para redirecionar a saída de um comando para a entrada de outro. Então, por que o comando ls | file não funciona?

file input é um dos mais nomes de arquivo, como file filename1 filename2

ls output é uma lista de diretórios e arquivos em uma pasta, então eu pensei que ls | file deveria mostrar o tipo de arquivo de cada arquivo em uma pasta.

Quando eu uso, no entanto, a saída é:

    Usage: file [-bcEhikLlNnprsvz0] [--apple] [--mime-encoding] [--mime-type]
        [-e testname] [-F separator] [-f namefile] [-m magicfiles] file ...
    file -C [-m magicfiles]
    file [--help]

Como houve algum erro com o uso do comando file

    
por IanC 06.07.2016 / 17:51

8 respostas

71

A questão fundamental é que file espera nomes de arquivos como argumentos de linha de comando, não em stdin. Quando você escreve ls | file , a saída de ls está sendo passada como entrada para file . Não como argumentos, como entrada.

Qual é a diferença?

  • Os argumentos da linha de comando são quando você grava sinalizadores e nomes de arquivos após um comando, como em cmd arg1 arg2 arg3 . Em scripts de shell, esses argumentos estão disponíveis como as variáveis $1 , $2 , $3 etc. Em C, você os acessaria por meio dos argumentos char **argv e int argc para main() .

  • A entrada padrão, stdin, é um fluxo de dados. Alguns programas como cat ou wc lêem de stdin quando não recebem nenhum argumento de linha de comando. Em um shell script, você pode usar read para obter uma única linha de entrada. Em C você pode usar scanf() ou getchar() entre várias opções.

file normalmente não lê de stdin. Ele espera que pelo menos um nome de arquivo seja passado como um argumento. É por isso que imprime o uso quando você escreve ls | file , porque você não passou um argumento.

Você pode usar xargs para converter stdin em argumentos, como em ls | xargs file . Ainda assim, como terdon menciona , analisando ls é uma má ideia. A maneira mais direta de fazer isso é simplesmente:

file *
    
por John Kugelman 06.07.2016 / 19:15
18

Porque, como você diz, a entrada de file tem que ser nomes de arquivos . A saída de ls , no entanto, é apenas texto. Que acontece de ser uma lista de nomes de arquivos não altera o fato de que é simplesmente texto e não a localização de arquivos no disco rígido.

Quando você vê a saída impressa na tela, o que você vê é texto. Se o texto é um poema ou uma lista de nomes de arquivos, não faz diferença para o computador. Tudo o que sabe é que é texto. É por isso que você pode passar a saída de ls para programas que usam texto como entrada (embora você realmente não deva ):

$ ls / | grep etc
etc

Portanto, para usar a saída de um comando que lista nomes de arquivos como texto (como ls ou find ) como entrada para um comando que usa nomes de arquivos, é necessário usar alguns truques. A ferramenta típica para isso é xargs :

$ ls
file1 file2

$ ls | xargs wc
 9  9 38 file1
 5  5 20 file2
14 14 58 total

Como eu disse antes, você não quer analisar a saída de ls . Algo como find é melhor (o print0 imprime -0 em vez de newilne após cada nome de arquivo e xargs de xargs permite lidar com essa entrada; esse é um truque para fazer com que seus comandos funcionem com nomes de arquivos contendo novas linhas):

$ find . -type f -print0 | xargs -0 wc
 9  9 38 ./file1
 5  5 20 ./file2
14 14 58 total

Que também tem o seu próprio jeito de fazer isso, sem precisar xargs :

$ find . -type f -exec wc {} +
 9  9 38 ./file1
 5  5 20 ./file2
14 14 58 total

Finalmente, você também pode usar um loop de shell. No entanto, observe que na maioria dos casos, %code% será muito mais rápido e eficiente. Por exemplo:

$ for file in *; do wc "$file"; done
 9  9 38 file1
 5  5 20 file2
    
por terdon 06.07.2016 / 18:47
6

learned that '|' (pipeline) is meant to redirect the output from a command to the input of another one.

Ele não "redireciona" a saída, mas pega a saída de um programa e usa-o como entrada, enquanto o arquivo não recebe entradas, mas filenames como argumentos , que são então testados. Redirecionamentos não passam esses nomes de arquivos como argumentos nem encanamentos , quanto mais tarde você estão fazendo.

O que você pode fazer é ler os nomes de arquivos de um arquivo com a opção --files-from se você tiver um arquivo que liste todos os arquivos que deseja testar, senão passe os caminhos para seus arquivos como argumentos.

    
por Braiam 06.07.2016 / 19:26
6

A resposta aceita explica por que o comando pipe não funciona imediatamente e, com o comando file * , oferece uma solução simples e direta.

Gostaria de sugerir outra alternativa que possa ser útil em algum momento. O truque é usar o caractere backtick (') . O backtick é explicado detalhadamente aqui . Em suma, ele pega a saída do comando contido nos backticks e o substitui como uma string no comando restante.

Portanto, find 'ls' terá a saída do comando ls e o substituirá como argumentos para o comando find . Isso é mais longo e mais complicado do que a solução aceita, mas variantes disso podem ser úteis em outras situações.

    
por Schmuddi 07.07.2016 / 01:17
5

A saída de ls através de um pipe é um bloco sólido de dados com 0x0a separando cada linha - isto é, um caractere de alimentação de linha - e file obtém isso como um parâmetro, onde espera que vários caracteres funcionem em um tempo.

Como regra geral, nunca use ls para gerar uma fonte de dados para outros comandos - um dia ele canalizará ... em rm e você estará com problemas!

É melhor usar um loop, como for i in *; do file "$i" ; done , que produzirá a saída desejada, de maneira previsível. As aspas estão lá no caso de nomes de arquivos com espaços.

    
por Mark Williams 06.07.2016 / 18:03
4

Se você quiser usar um canal para alimentar file use a opção -f , que normalmente é seguida por um nome de arquivo, mas você também pode usar um único hífen - para ler stdin, então

$ ls
cow.pdf  some.txt
$ ls | file -f -
cow.pdf:       PDF document, version 1.4
some.txt:        ASCII text

O truque com o hífen - funciona com muitos dos utilitários de linha de comando padrão (embora seja -- às vezes), então vale sempre a pena tentar.

A ferramenta xarg é muito mais poderosa e, na maioria dos casos, necessária apenas se a lista de argumentos for muito longa (consulte este post para detalhes).

    
por deamentiaemundi 06.07.2016 / 20:55
2

Funciona usando comando como abaixo

ls | xargs file

Vai funcionar melhor para mim

    
por SuperKrish 08.07.2016 / 09:54
1

Isso também deve funcionar:

file $(ls)

como também discutido aqui: link

    
por matth 15.02.2017 / 12:02