¿Por qué el comando "ls | archivo "trabajo?

32

He estado estudiando sobre la línea de comando y aprendí que |(pipeline) está destinado a redirigir la salida de un comando a la entrada de otro. Entonces, ¿por qué el comando ls | fileno funciona?

file input es uno de los más nombres de archivo, como file filename1 filename2

lsLa salida es una lista de directorios y archivos en una carpeta, así que pensé que ls | filese suponía que debía mostrar el tipo de archivo de cada archivo en una carpeta.

Sin embargo, cuando lo uso, el resultado es:

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

Como hubo algún error con el uso del filecomando

IanC
fuente
2
Si usa plain ls, indica que desea que todos los archivos en el directorio actual se manejen con el filecomando. ... Entonces, ¿por qué no hacer simplemente:, file *que responderá con una línea para cada archivo, carpeta.
Knud Larsen
file *es la forma más inteligente, me preguntaba por qué lsno funcionaba la salida. Duda despejada :)
IanC
66
La premisa es defectuosa: "la entrada de archivo es uno de los más nombres de archivo, como archivo filename1 filename2" Eso no es entrada. Esos son argumentos de línea de comandos, como @John Kugelman señala a continuación.
Monty Harder
3
Tangencialmente, analizarls es generalmente una mala idea.
kojiro

Respuestas:

71

El problema fundamental es que fileespera nombres de archivo como argumentos de línea de comandos, no en stdin. Cuando escribe, ls | filela salida de lsse pasa como entrada a file. No como argumentos, como entrada.

¿Cual es la diferencia?

  • Los argumentos de la línea de comandos son cuando escribe banderas y nombres de archivos después de un comando, como en cmd arg1 arg2 arg3. En los scripts de shell estos argumentos están disponibles como las variables $1, $2, $3, etc. En C es posible acceder a ellos a través de char **argvy int argcargumentos a main().

  • La entrada estándar, stdin, es un flujo de datos. Algunos programas tienen gusto cato wcleen de stdin cuando no se les da ningún argumento de línea de comandos. En un script de shell puede usar readpara obtener una sola línea de entrada. En C puedes usar scanf()o getchar(), entre varias opciones.

filenormalmente no lee de stdin. Espera que se pase al menos un nombre de archivo como argumento. Es por eso que imprime el uso cuando escribe ls | file, porque no pasó una discusión.

Puede usar xargspara convertir stdin en argumentos, como en ls | xargs file. Aún así, como menciona Terdon , analizar lses una mala idea. La forma más directa de hacer esto es simplemente:

file *
John Kugelman apoya a Monica
fuente
2
O forzar filea obtener nombres de archivos de su entrada, usando ls | file -f -. Sigue siendo una mala idea de c.
spectras
2
@Braiam> Ese es el punto. Y eso canaliza lsla salida en fileel stdin. Pruébalo.
spectras
44
@Braiam> De hecho, es un desperdicio y peligroso. Pero funciona y es bueno tenerlo para compararlo con mejores opciones si el OP está aprendiendo a usar redirecciones. Para completar, también podría mencionar file $(ls), que también funciona, de otra manera.
spectras
2
Creo que después de leer todas las respuestas tengo una visión más amplia del problema, aunque creo que necesitaré más lecturas para comprenderlo realmente. Primero, aparentemente el uso de tuberías y redireccionamientos no analiza el resultado como argumentos , sino como STDIN . El cual todavía tengo que leer más para comprender mejor, pero hacer una superficiales de búsqueda argumentos parece que el texto se analiza para el programa en una matriz, y STDIN como una forma de poner en común la información de un archivo o una salida (no todos los programas están diseñados para trabajar con esta "agrupación")
IanC
3
En segundo lugar, usar ls para hacer una lista de nombres de archivos parece una mala idea, debido a los caracteres especiales que se aceptan en los nombres de archivos pero que pueden terminar en una salida engañosa en ls . Dado que usa líneas nuevas como separador entre los nombres de archivo y los nombres de archivo pueden contener líneas nuevas y otros caracteres especiales, la salida final puede no ser precisa.
IanC
18

Porque, como dices, la entrada de filetiene que ser nombres de archivo . La salida de ls, sin embargo, es solo texto. El hecho de que sea una lista de nombres de archivos no cambia el hecho de que es simplemente texto y no la ubicación de los archivos en el disco duro.

Cuando ve un resultado impreso en la pantalla, lo que ve es texto. Si ese texto es un poema o una lista de nombres de archivos no hace ninguna diferencia para la computadora. Todo lo que sabe es que es texto. Esta es la razón por la que puede pasar la salida de los lsprogramas que toman el texto como entrada (aunque realmente no debería ):

$ ls / | grep etc
etc

Entonces, para usar la salida de un comando que enumera los nombres de los archivos como texto (como lso find) como entrada para un comando que toma nombres de archivos, debe usar algunos trucos. La herramienta típica para esto es xargs:

$ ls
file1 file2

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

Sin embargo, como dije antes, realmente no quieres analizar la salida de ls. Algo así findes mejor ( print0imprime un en \0lugar de una nueva línea después de cada nombre de archivo y el -0de xargspermite que se ocupe de dicha entrada; este es un truco para que sus comandos funcionen con nombres de archivo que contienen nuevas líneas):

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

Que también tiene su propia forma de hacerlo, sin necesidad xargsde nada:

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

Finalmente, también puedes usar un bucle de shell. Sin embargo, tenga en cuenta que, en la mayoría de los casos, xargsserá mucho más rápido y más eficiente. Por ejemplo:

$ for file in *; do wc "$file"; done
 9  9 38 file1
 5  5 20 file2
terdon
fuente
Un tema secundario es que fileno parece que realmente leer la entrada estándar a menos que reciba una explícita -marcador de posición: comparar file foo, echo foo | filey echo foo | file -; de hecho, que es probablemente la razón para el mensaje de uso en el caso de los programas operativos (es decir, que no es realmente debido a la salida de lses "simplemente el texto", sino más bien porque la lista de argumentos que fileestá vacía)
steeldriver
@steeldriver sí. AFAIK es el caso de todos los programas que esperan archivos y no texto como entrada. Simplemente ignoran stdin por defecto. Tenga en cuenta que en echo foo | file -realidad no se ejecuta fileen el archivo foosino en la secuencia stdin.
terdon
Bueno, hay patos impares (?!) Como cateso, excepto stdin sin, -excepto cuando se dan argumentos de archivo, creo?
steeldriver
3
Esta respuesta no explica la diferencia entre los argumentos estándar y de línea de comando, por lo que, a pesar de ser más acertada que la respuesta aceptada, sigue siendo profundamente engañosa por la misma razón.
zwol
55
@terdon Creo que es un error grave en este caso. "el archivo (1) toma la lista de archivos para operar como argumentos de línea de comando, no como entrada estándar" es fundamental para entender por qué el comando del OP no funcionó, y la distinción es fundamental para las secuencias de comandos de shell en general; no les estás haciendo ningún favor al pasarlo por alto.
zwol
6

aprendí que '|' (canalización) está destinado a redirigir la salida de un comando a la entrada de otro.

No "redirige" la salida, sino que toma la salida de un programa y la usa como entrada, mientras que el archivo no toma entradas sino los nombres de archivo como argumentos , que luego se prueban. Las redirecciones no pasan estos nombres de archivo como argumentos ni las tuberías , más tarde lo que está haciendo.

Lo que puede hacer es leer los nombres de archivo de un archivo con la --files-fromopción si tiene un archivo que enumera todos los archivos que desea probar, de lo contrario, simplemente pase las rutas a sus archivos como argumentos.

Braiam
fuente
6

La respuesta aceptada explica por qué el comando de tubería no funciona de inmediato, y con el file *comando, ofrece una solución simple y directa.

Me gustaría sugerir otra alternativa que podría ser útil en algún momento. El truco es usar el (`)carácter de retroceso . El backtick se explica con gran detalle aquí . En resumen, toma el resultado del comando encerrado en los backticks y lo sustituye como una cadena en el comando restante.

Por lo tanto, find `ls`tomará la salida del lscomando y la sustituirá como argumentos para el findcomando. Esto es más largo y más complicado que la solución aceptada, pero sus variantes pueden ser útiles en otras situaciones.

Schmuddi
fuente
Estoy leyendo un libro sobre el uso de la línea de comandos en Linux (la duda vino de mí experimentando con ella), y coincidentemente acabo de leer acerca de la "sustitución de comandos". Puede usar $ (comando) o command(no puedo encontrar el código de barra diagonal inversa en mi teléfono) para expandir la salida de un comando en el bash y usarlo como parámetro para otros comandos. Realmente útil, aunque usarlo en este caso (con ls ) aún resultaría en algunos problemas debido a los caracteres especiales en algunos nombres de archivo.
IanC
@IanC Desafortunadamente, la mayoría de los libros y tutoriales sobre bash son basura, contaminados con malas prácticas, sintaxis obsoleta, errores sutiles; (las únicas) referencias confiables que existen son los desarrolladores de bash, es decir, el manual y el canal IRC #bash en freenode (también verifique los recursos vinculados en el tema del canal).
ignis
1
Usar la sustitución de comandos puede ser realmente útil a veces, pero en este contexto es bastante perverso, especialmente con ls.
Joe
También relacionado: unix.stackexchange.com/a/5782/107266
Mt
5

La salida de a lstravés de una tubería es un bloque sólido de datos con 0x0a que separa cada línea, es decir, un carácter de salto de línea, y fileobtiene esto como un parámetro, donde espera que varios caracteres funcionen en uno a la vez.

Como regla general, nunca lo use lspara generar una fuente de datos para otros comandos: un día se canalizará ... ¡ rmy luego tendrá problemas!

Es mejor usar un bucle, como el for i in *; do file "$i" ; doneque producirá la salida que desea, de manera predecible. Las comillas están ahí en caso de nombres de archivo con espacios.

Mark Williams
fuente
8
más fácil: file *;-)
Wayne_Yux
3
@IanC Realmente no puedo enfatizar lo suficiente que analizar la salida de lses una muy, muy mala idea . No solo porque puede pasarlo a algo dañino como rm, más importante porque se rompe en cualquier nombre de archivo no estándar.
terdon
55
El primer párrafo está en algún lugar entre engañoso y sin sentido. Los avances de línea no tienen relevancia. El segundo párrafo es correcto por la razón incorrecta. Es malo analizar ls, pero no porque de alguna manera pueda ser "canalizado" mágicamente a rm.
John Kugelman apoya a Monica el
1
¿ rmToma nombres de archivos de entrada estándar? Yo creo que no. Además, como regla general, lsha sido uno de los principales ejemplos de una fuente de datos para el uso de tuberías de Unix desde el comienzo de Unix. Es por eso que el valor predeterminado es un simple nombre de archivo por línea sin atributos ni adornos cuando su salida es una tubería, a diferencia de su formato predeterminado habitual cuando la salida es el terminal.
davidbak
2
@DewiMorgan Este sitio web está dirigido principalmente a una audiencia no técnica, por lo que difundir / alentar los malos hábitos aquí hace daño y no hace nada bueno. En unix.SE u otra comunidad tecnológica, cuyos usuarios tienen el conocimiento / los medios para apuntar muy cerca de sus pies sin disparar ellos mismos, su punto podría mantenerse (con respecto a otras prácticas), pero aquí no hace que su comentario se vea inteligente.
ignis
4

Si desea usar una tubería para alimentar, fileuse la opción -fque normalmente va seguida de un nombre de archivo, pero también puede usar un solo guión -para leer desde la entrada estándar, entonces

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

El truco con el guión -funciona con muchas de las utilidades de línea de comandos estándar (aunque a --veces lo es ), por lo que siempre vale la pena intentarlo.

La herramienta xarges mucho más poderosa y en la mayoría de los casos solo es necesaria si la lista de argumentos es demasiado larga (vea esta publicación para más detalles).

deamentiaemundi
fuente
¿Cuándo es --? Nunca he visto eso. --es típicamente el indicador de "fin de banderas".
John Kugelman apoya a Monica el
Sí, pero lo encontré en un par de instancias (ab) utilizadas de esa manera por el programador. No puedo recordar exactamente dónde (agregaré un comentario si lo hago) pero recuerdo las maldiciones que pronuncié cuando lo descubrí y estas maldiciones fueron definitivamente NSFW ;-)
deamentiaemundi
2

Funciona usando el comando como a continuación

ls | xargs file

Me va a funcionar mejor

SuperKrish
fuente