Por que o comando “ls | arquivo ”funciona?

32

Eu estudei sobre a linha de comando e aprendi que |(pipeline) destina-se a redirecionar a saída de um comando para a entrada de outro. Então, por que o comando ls | filenão funciona?

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

lsoutput é uma lista de diretórios e arquivos em uma pasta, então pensei ls | fileem 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 filecomando

IanC
fonte
2
Se você estiver usando simples ls, isso indica que você deseja que todos os arquivos no diretório atual sejam tratados com o filecomando ... Então, por que simplesmente não fazer:, file *que responderá com uma linha para cada arquivo, pasta.
precisa
file *é a maneira mais inteligente, eu só estava me perguntando por que usar a lssaída não estava funcionando. Dúvida apuradas :)
CNI
6
A premissa é falha: "a entrada do arquivo é um dos mais nomes de arquivos, como o arquivo filename1 filename2". Essa não é a entrada. Esses são argumentos da linha de comando, como @John Kugelman aponta abaixo.
Monty Harder
3
Tangencialmente, a análisels geralmente é uma má ideia.
Kojiro

Respostas:

71

A questão fundamental é que fileespera nomes de arquivos como argumentos de linha de comando, não no stdin. Quando você escreve, ls | filea saída de lsestá 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ê escreve sinalizadores e nomes de arquivos após um comando, como em cmd arg1 arg2 arg3. Em scripts shell esses argumentos estão disponíveis como as variáveis $1, $2, $3, etc. Em C você acessá-los através do char **argve int argcargumentos para main().

  • A entrada padrão, stdin, é um fluxo de dados. Alguns programas gostam catou wclêem do stdin quando não recebem argumentos da linha de comando. Em um script de shell, você pode usar readpara obter uma única linha de entrada. Em C você pode usar scanf()ou getchar(), entre várias opções.

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

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

file *
John Kugelman apoia Monica
fonte
2
Ou force filea obter nomes de arquivos de sua entrada, usando ls | file -f -. Ainda é uma péssima ideia de c.
espectros
2
@Braiam> Esse é o ponto. E isso gera lsa saída para fileo stdin. Experimente.
espectros
4
@Braiam> Na verdade, é um desperdício e perigoso. Mas funciona e é bom comparar com as melhores opções se o OP estiver aprendendo a usar redirecionamentos. Para completar, eu também poderia mencionar file $(ls), o que também funciona, de outra maneira.
espectros
2
Acho que depois de ler todas as respostas, tenho uma visão mais ampla do problema, apesar de achar que vou precisar de mais leitura para realmente entender tudo. Primeiro, aparentemente o uso de canal e redirecionamento não analisa a saída como argumentos , mas como STDIN . O que eu ainda preciso ler mais para entender melhor, mas criar argumentos de pesquisa superficiais parece que o texto está sendo analisado no programa em uma matriz e STDIN como uma maneira de agrupar informações para um arquivo ou uma saída (nem todos os programas projetados para trabalhar com esse "pool")
IanC
3
Segundo, usar ls para fazer uma lista de nomes de arquivos parece uma péssima idéia, devido a caracteres especiais que são aceitos nos nomes de arquivos, mas que podem resultar em uma saída enganosa em ls . Como ele usa novas linhas como um separador entre nomes de arquivos e nomes de arquivos pode conter novas linhas e outros caracteres especiais, a saída final pode não ser precisa.
586 IanCel
18

Porque, como você diz, a entrada de filedeve ser um nome de arquivo . A saída de ls, no entanto, é apenas texto. O fato de ser uma lista de nomes de arquivos não altera o fato de ser apenas texto e não o local dos arquivos no disco rígido.

Quando você vê a saída impressa na tela, o que vê é texto. Se esse 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 lspara programas que recebem 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 lsou find) como entrada para um comando que utiliza 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, porém, você realmente não deseja analisar a saída de ls. Algo como findé melhor (o que print0imprime uma em \0vez de uma nova coluna após cada nome de arquivo e o -0de xargspermite lidar com essa entrada; esse é um truque para fazer com que seus comandos funcionem com nomes de arquivos que contêm 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 sua própria maneira de fazer isso, sem a necessidade de 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, xargsserá muito mais rápido e mais eficiente. Por exemplo:

$ for file in *; do wc "$file"; done
 9  9 38 file1
 5  5 20 file2
terdon
fonte
A questão secundária é que filenão parece realmente ler stdin a menos dado um explícito -marcador: comparar file foo, echo foo | filee echo foo | file -; na verdade, isso é provavelmente a razão para a mensagem de uso no caso PO (ou seja, não é realmente porque a saída de lsé "simplesmente texto", mas sim porque a lista de argumentos para fileestá vazia)
steeldriver
@steeldriver yeah. AFAIK é o caso de todos os programas que esperam arquivos e não texto como entrada. Eles simplesmente ignoram stdin por padrão. Observe que echo foo | file -, na verdade, não é executado fileno arquivo, foomas no fluxo stdin.
terdon
Bem, existem patos estranhos (?!) Assim, catexceto stdin sem, -exceto quando dados argumentos de arquivo também, eu acho?
steeldriver
3
Essa resposta falha em explicar a diferença entre stdin e argumentos de linha de comando, e, apesar de ser mais pertinente do que a resposta aceita, ainda é profundamente enganosa pelo mesmo motivo.
Zwol 06/07/19
5
@terdon Eu acho que é um erro grave neste caso. "file (1) pega a lista de arquivos para operar como argumentos de linha de comando, não como entrada padrão" é fundamental para entender por que o comando do OP não funcionou e a distinção é fundamental para o shell script em geral; você não está fazendo nenhum favor a eles.
Zwol 06/07/19
6

aprendi que '|' (pipeline) destina-se a redirecionar a saída de um comando para a entrada de outro.

Ele não "redireciona" a saída, mas pega a saída de um programa e a usa como entrada, enquanto o arquivo não recebe entradas, mas os nomes de arquivos como argumentos , que são então testados. Os redirecionamentos não passam esses nomes de arquivo como argumentos, nem a tubulação , quanto mais tarde você estiver fazendo.

O que você pode fazer é ler os nomes de arquivos de um arquivo com a --files-fromopção se você tiver um arquivo que liste todos os arquivos que você deseja testar; caso contrário, apenas passe os caminhos para seus arquivos como argumentos.

Braiam
fonte
6

A resposta aceita explica por que o comando pipe não funciona imediatamente e, com o file *comando, 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 em grandes detalhes aqui . Em resumo, ele pega a saída do comando incluído nos backticks e a substitui como uma string no comando restante.

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

Schmuddi
fonte
Estou lendo um livro sobre o uso da linha de comando no Linux (a dúvida veio de mim experimentá-lo) e, coincidentemente, acabei de ler sobre "substituição de comando". Você pode usar $ (comando) ou command(não pode encontrar o código de barra invertida no meu telefone) para expandir a saída de um comando no bash e usá-lo como parâmetro para outros comandos. Realmente útil, embora o uso nesse caso (com ls ) ainda resulte em alguns problemas devido aos caracteres especiais em alguns nomes de arquivos.
7266 IanC
@IanC Infelizmente, a maioria dos livros e tutoriais disponíveis sobre o bash são lixo, contaminado por más práticas, sintaxe obsoleta, bugs sutis; (as únicas) referências confiáveis ​​disponíveis são os desenvolvedores do bash, ou seja, o manual e o canal IRC #bash no freenode (também verifique os recursos vinculados no tópico do canal).
Ignis
1
O uso da substituição de comandos pode ser realmente útil às vezes, mas nesse contexto é bastante perverso - especialmente com ls.
21716 Joe
Também relacionado: unix.stackexchange.com/a/5782/107266
matth
5

A saída de lsum canal é um sólido bloco de dados com 0x0a separando cada linha - isto é, um caractere de avanço de linha - e fileobtém isso como um parâmetro, onde espera que vários caracteres funcionem um de cada vez.

Como regra geral, nunca use lspara gerar uma fonte de dados para outros comandos - um dia ela será direcionada para ... rme você terá problemas!

Melhor usar um loop, como o for i in *; do file "$i" ; doneque produzirá a saída desejada, previsivelmente. As aspas existem no caso de nomes de arquivos com espaços.

Mark Williams
fonte
8
mais fácil: file *;-)
Wayne_Yux
3
@IanC Eu realmente não posso enfatizar o suficiente para analisar a saída de lsuma idéia muito, muito ruim . Não apenas porque você pode passá-lo para algo prejudicial, como rm, mais importante, porque ele quebra em qualquer nome de arquivo não padrão.
terdon
5
O primeiro parágrafo está em algum lugar entre absurdo enganoso e direto. Os feeds de linha não têm relevância. O segundo parágrafo está correto pelo motivo errado. É ruim analisar ls, mas não porque possa ser de alguma forma magicamente "canalizado" para a empresa.
John Kugelman apoia Monica
1
Leva rmnomes de arquivos da entrada padrão? Eu acho que não. Além disso, como regra geral, lstem sido um dos principais exemplos de uma fonte de dados para o uso de pipelines Unix desde o início do Unix. É por isso que o padrão é um simples nome de arquivo por linha sem atributos ou adornos quando a saída é um canal, diferente da formatação padrão usual quando a saída é o terminal.
Davidbak
2
@DewiMorgan Este site é voltado principalmente para um público não técnico, por isso divulgar / incentivar maus hábitos aqui prejudica e não faz nada de bom. No unix.SE ou em outra comunidade tecnológica, cujos usuários têm o conhecimento / meios para mirar muito perto dos pés sem atirar nos próprios pés, seu argumento pode valer (em relação a outras práticas), mas aqui não faz o seu comentário parecer inteligente.
Ignis
4

Se você deseja usar um pipe para alimentar, fileuse a opção -fque normalmente é seguida por um nome de arquivo, mas você também pode usar um único hífen -para ler em stdin, portanto

$ 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 utilitários de linha de comando padrão (embora --algumas vezes), por isso 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 esta publicação para obter detalhes).

deamentiaemundi
fonte
Quando é isso --? Eu nunca vi isso. --normalmente é o indicador "fim de sinalizadores".
John Kugelman apoia Monica
Sim, mas eu o encontrei em alguns casos (ab) usados ​​dessa maneira pelo programador. Não me lembro exatamente onde (irá adicionar um comentário, se eu faço), mas eu me lembro das maldições que proferi quando descobri-lo e estas maldições foram definitivamente NSFW ;-)
deamentiaemundi
2

Funciona com o comando use abaixo

ls | xargs file

Funcionará melhor para mim

SuperKrish
fonte