Perché il comando “ls | file "funziona?


32

Ho studiato la riga di comando e ho appreso che |(pipeline) ha lo scopo di reindirizzare l'output da un comando all'input di un altro. Quindi perché il comando ls | filenon funziona?

file input è uno o più nomi di file, come file filename1 filename2

lsl'output è un elenco di directory e file in una cartella, quindi ho pensato ls | fileche dovesse mostrare il tipo di file di ogni file in una cartella.

Quando lo uso, tuttavia, l'output è:

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

Poiché si è verificato un errore durante l'utilizzo del filecomando


2
Se si utilizza plain ls, indica che si desidera gestire tutti i file nella directory corrente con il filecomando. ... Quindi perché non semplicemente fare:, file *che risponderà con una riga per ogni file, cartella.
Knud Larsen,

file *è il modo più intelligente, mi stavo solo chiedendo perché l'utilizzo lsdell'output non funzionasse. Dubbio eliminato :)
IanC

6
La premessa è errata: "l'input del file è uno o più nomi di file, come file nomefile1 nomefile2" Questo non è input. Questi sono argomenti da riga di comando, come sottolinea @John Kugelman di seguito.
Monty Harder,

3
Tangenzialmente, l' analisils è generalmente una cattiva idea.
Kojiro,

Risposte:


71

Il problema fondamentale è che si fileaspettano nomi di file come argomenti della riga di comando, non su stdin. Quando scrivi ls | filel'output di lsviene passato come input a file. Non come argomenti, come input.

Qual è la differenza?

  • Gli argomenti della riga di comando sono quando si scrivono flag e nomi di file dopo un comando, come in cmd arg1 arg2 arg3. In script di shell questi argomenti sono disponibili come le variabili $1, $2, $3, etc. In C si sarebbero loro accesso tramite la char **argve int argcargomenti per main().

  • L'input standard, stdin, è un flusso di dati. Ad alcuni programmi piace cato wclegge da stdin quando non vengono forniti argomenti della riga di comando. In uno script shell è possibile utilizzare readper ottenere una singola riga di input. In C puoi usare scanf()o getchar(), tra le varie opzioni.

filenormalmente non legge da stdin. Si aspetta che almeno un nome di file venga passato come argomento. Ecco perché stampa l'utilizzo quando scrivi ls | file, perché non hai superato un argomento.

È possibile utilizzare xargsper convertire stdin in argomenti, come in ls | xargs file. Tuttavia, come afferma Terdon , l'analisi lsè una cattiva idea. Il modo più diretto per farlo è semplicemente:

file *

2
O forzare fileper ottenere i nomi dei file dal suo input, usando ls | file -f -. Ancora una cattiva idea ofc.
extra il

2
@Braiam> Questo è il punto. E quell'uscita lsdei tubi nello filestdin. Provalo.
extra il

4
@Braiam> In effetti è dispendioso e pericoloso. Ma funziona ed è bello confrontarlo con opzioni migliori se l'OP sta imparando a usare i reindirizzamenti. Per completezza, potrei anche menzionare file $(ls), che funziona anche, in un altro modo.
extra il

2
Penso che dopo aver letto tutte le risposte ho una visione più ampia del problema, anche se penso che avrò bisogno di ulteriori letture per capire davvero tutto. Innanzitutto, apparentemente l'utilizzo di piping e reindirizzamento non analizza l'output come argomenti , ma come STDIN . Che devo ancora leggere ulteriormente per capire meglio, ma fare argomenti di ricerca superficiali sembra che il testo venga analizzato nel programma in un array e STDIN come un modo per unire le informazioni per un file o un output (non tutti i programmi sono progettati per lavorare con questo "pooling")
IanC

3
In secondo luogo, usare ls per creare un elenco di nomi di file sembra una cattiva idea, a causa di caratteri speciali che sono accettati sui nomi di file ma che possono finire in un output fuorviante su ls . Dal momento che utilizza newline come separatore tra nomi di file e nomi di file può contenere newline e altri caratteri speciali, l'output finale potrebbe non essere preciso.
IanC,

18

Perché, come dici tu, l'input di filedeve essere un nome di file . L'output di ls, tuttavia, è solo testo. Il fatto che sia un elenco di nomi di file non cambia il fatto che si tratta semplicemente di testo e non della posizione dei file sul disco rigido.

Quando vedi l'output stampato sullo schermo, quello che vedi è il testo. Che quel testo sia una poesia o un elenco di nomi di file non fa differenza per il computer. Tutto quello che sa è che è un testo. Questo è il motivo per cui puoi trasmettere l'output di lsa programmi che accettano testo come input (anche se in realtà non dovresti ):

$ ls / | grep etc
etc

Pertanto, per utilizzare l'output di un comando che elenca i nomi dei file come testo (come lso find) come input per un comando che accetta nomi di file, è necessario utilizzare alcuni trucchi. Lo strumento tipico per questo è xargs:

$ ls
file1 file2

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

Come ho detto prima, però, non vuoi davvero analizzare l'output di ls. Qualcosa di simile findè meglio ( print0stampa un \0anziché un newilne dopo ogni nome di file e il -0di xargsconsente di gestire tale input; questo è un trucco per far funzionare i tuoi comandi con nomi di file contenenti newline):

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

Che ha anche il suo modo di farlo, senza bisogno xargsdi nulla:

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

Infine, puoi anche usare un loop shell. Tuttavia, si noti che nella maggior parte dei casi, xargssarà molto più veloce ed efficiente. Per esempio:

$ for file in *; do wc "$file"; done
 9  9 38 file1
 5  5 20 file2

Un side-problema è che filenon sembra di leggere in realtà stdin a meno che dato un esplicito -segnaposto: confronta file foo, echo foo | filee echo foo | file -; in effetti, questo è probabilmente il motivo del messaggio di utilizzo nel caso dei PO (ovvero non è proprio perché l'output di lsè "semplicemente testo", ma piuttosto perché l'elenco degli argomenti fileè vuoto)
steeldriver

@steeldriver sì. AFAIK è il caso di tutti i programmi che prevedono file e non testo come input. Ignorano semplicemente lo stdin per impostazione predefinita. Nota che in echo foo | file -realtà non viene eseguito filesul file fooma sul flusso stdin.
terdon,

Beh, ci sono anatre strane (?!) Come catquella tranne Stdin senza -tranne quando penso anche agli argomenti del file?
Steeldriver,

3
Questa risposta non spiega la differenza tra gli argomenti di stdin e quelli della riga di comando, e quindi, nonostante sia più puntuale della risposta accettata, è ancora profondamente fuorviante per lo stesso motivo.
zwol,

5
@terdon Penso che sia un errore grave in questo caso. "file (1) prende l'elenco dei file su cui operare come argomenti della riga di comando, non come input standard" è fondamentale per comprendere perché il comando dell'OP non ha funzionato e la distinzione è fondamentale per lo scripting della shell in generale; non stai facendo loro alcun favore passandoci sopra.
zwol,

6

imparato che "|" (pipeline) ha lo scopo di reindirizzare l'output da un comando all'input di un altro.

Non "reindirizza" l'output, ma prende l'output di un programma e lo utilizza come input, mentre il file non accetta input ma nomi di file come argomenti , che vengono quindi testati. I reindirizzamenti non passano questi nomi di file come argomenti né piping , più tardi cosa stai facendo.

Quello che puoi fare è leggere i nomi dei file da un file con l' --files-fromopzione se hai un file che elenca tutti i file che vuoi testare, altrimenti passa semplicemente i percorsi ai tuoi file come argomenti.


6

La risposta accettata spiega perché il comando pipe non funziona immediatamente e con il file *comando offre una soluzione semplice e diretta.

Vorrei suggerire un'altra alternativa che potrebbe tornare utile in qualche momento. Il trucco sta usando il (`)personaggio backtick . Il backtick è spiegato in dettaglio qui . In breve, prende l'output del comando racchiuso nei backtick e lo sostituisce come una stringa nel comando rimanente.

Quindi, find `ls`prenderà l'output del lscomando e lo sostituirà come argomento per il findcomando. Questo è più lungo e più complicato della soluzione accettata, ma varianti di ciò possono essere utili in altre situazioni.


Sto leggendo un libro sull'uso della riga di comando su Linux (il dubbio è venuto da me sperimentandolo) e, per coincidenza, ho appena letto della "sostituzione dei comandi". Puoi usare $ (comando) o command(non riesco a trovare il codice barra rovesciata sul mio telefono) per espandere l'output di un comando nella bash e usarlo come parametro per altri comandi. Davvero utile, anche se usarlo in questo caso (con ls ) comporterebbe comunque alcuni problemi a causa dei caratteri speciali su alcuni nomi di file.
IanC

@IanC Sfortunatamente, la maggior parte dei libri e tutorial su bash sono spazzatura, contaminati da cattive pratiche, sintassi deprecata, bug sottili; (gli unici) riferimenti affidabili là fuori ci sono gli sviluppatori bash, cioè il manuale e il canale IRC #bash su freenode (controlla anche le risorse collegate nell'argomento del canale).
ignis,

1
L'uso della sostituzione dei comandi può essere molto utile a volte, ma in questo contesto è abbastanza perverso, specialmente con ls.
Joe,


5

L'output di lsattraverso una pipe è un solido blocco di dati con 0x0a che separa ogni riga - ovvero un carattere di avanzamento riga - e fileottiene questo come un parametro, dove si aspetta che più caratteri lavorino su uno alla volta.

Come regola generale, non utilizzare mai lsper generare una fonte di dati per altri comandi: un giorno verrà convogliato in ... rme poi sei nei guai!

Meglio usare un ciclo, come quello for i in *; do file "$i" ; doneche produrrà l'output desiderato, prevedibilmente. Le virgolette ci sono in caso di nomi di file con spazi.


8
più facile: file *;-)
Wayne_Yux il

3
@IanC Non posso davvero sottolineare abbastanza che analizzare l'output di lsè una pessima idea . Non solo perché potresti passarlo a qualcosa di dannoso come rm, soprattutto, perché si interrompe su qualsiasi nome di file non standard.
terdon,

5
Il primo paragrafo si trova a metà strada tra un'assurdità ingannevole e diretta. Gli avanzamenti di riga non hanno rilevanza. Il secondo paragrafo è giusto per la ragione sbagliata. È male analizzarlo, ma non perché potrebbe essere in qualche modo magicamente "convogliato" a rm.
John Kugelman sostiene Monica il

1
Prende i rmnomi di file dall'input standard? Penso di no. Inoltre, come regola generale, lsè stato uno dei principali esempi di una fonte di dati per l'uso di condotte Unix sin dall'inizio di Unix. Ecco perché per impostazione predefinita è un semplice nome di file per riga senza attributi o ornamenti quando l'output è una pipe, a differenza della normale formattazione predefinita quando l'output è il terminale.
davidbak,

2
@DewiMorgan Questo sito Web è rivolto principalmente a un pubblico non tecnico, quindi diffondere / incoraggiare cattive abitudini qui fa male e non fa nulla di buono. Su unix.SE o su altre comunità tecnologiche, i cui utenti hanno le conoscenze / i mezzi per mirare molto vicino ai loro piedi senza sparare ai piedi stessi, il tuo punto potrebbe essere valido (rispetto ad altre pratiche) ma qui non rende il tuo commento intelligente.
ignis,

4

Se vuoi usare una pipe per alimentare fileusa l'opzione -fche normalmente è seguita da un nome file ma puoi anche usare un singolo trattino -per leggere dallo stdin, quindi

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

Il trucco con il trattino -funziona con molte utility della riga di comando standard (anche se a --volte lo è ), quindi vale sempre la pena provare.

Lo strumento xargè molto più potente e nella maggior parte dei casi è necessario solo se l'elenco degli argomenti è troppo lungo (vedere questo post per i dettagli).


Quando è --? Non l'ho mai visto. --è in genere l'indicatore "end of flags".
John Kugelman supporta Monica il

Sì, ma l'ho trovato in un paio di istanze (ab) utilizzate in quel modo dal programmatore. Non riesco a ricordare dove esattamente (aggiungerò un commento se lo faccio) ma ricordo le maledizioni che ho pronunciato quando l'ho scoperto e queste maledizioni erano sicuramente NSFW ;-)
deamentiaemundi

2

Funziona con il comando come di seguito

ls | xargs file

Funzionerà meglio per me


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.