¿Uso inútil de gato?

101

Probablemente se encuentre en muchas preguntas frecuentes, en lugar de usar:

cat file | command

(que se llama uso inútil de gato), la forma correcta se supone que es:

command < file

De la segunda forma, "correcta": el sistema operativo no tiene que generar un proceso adicional.
A pesar de saber eso, seguí usando gato inútil por 2 razones.

  1. más estético: me gusta cuando los datos se mueven uniformemente solo de izquierda a derecha. Y es más fácil de reemplazar catcon otra cosa ( gzcat, echo, ...), añadir un segundo archivo o insertar el nuevo filtro ( pv, mbuffer, grep...).

  2. "Sentí" que podría ser más rápido en algunos casos. Más rápido porque hay 2 procesos, el primero ( cat) hace la lectura y el segundo hace lo que sea. Y pueden ejecutarse en paralelo, lo que a veces significa una ejecución más rápida.

¿Es correcta mi lógica (por segunda razón)?

Leonid Volnitsky
fuente
22
cates una pipa de identidad . Solo transmite su entrada a su salida. Si el segundo programa de la cadena puede tomar su entrada del mismo argumento al que le pasa cat(o de la entrada estándar, si no pasa ningún argumento), entonces cates absolutamente inútil y solo resulta en un proceso adicional que se bifurca y una tubería adicional creado.
Frédéric Hamidi
11
@ FrédéricHamidi cuando el gato no tiene argumentos o su argumento es -, es una tubería de identidad. Sin embargo, cuando tiene más de un argumento de nombre de archivo que no es guión, se convierte en algo más que una tubería de identidad y comienza a tener un propósito real.
kojiro
3
Desafortunadamente, el enlace anteriormente popular a partmaps.org está muerto. El contenido está ahora en porkmail.org/era/unix/award.html
tripleee
1
Véase también: unix.stackexchange.com/q/511827/20336
Mikko Rantalainen
2
Observo que si desea mostrar el flujo de datos hacia la derecha (razón 1), puede hacerlo colocando la redirección del archivo antes del comando, como en <file command1 | command2, aunque habría desacuerdo sobre la estética.
holdenweb

Respuestas:

81

No estaba al tanto del premio hasta hoy, cuando un novato trató de acusarme del UUOC por una de mis respuestas. Fue un cat file.txt | grep foo | cut ... | cut .... Le di una parte de mi mente, y solo después de hacerlo visité el enlace que me dio refiriéndose a los orígenes del premio y la práctica de hacerlo. Una búsqueda más profunda me llevó a esta pregunta. Algo desafortunadamente, a pesar de una consideración consciente, ninguna de las respuestas incluía mi fundamento.

No había tenido la intención de estar a la defensiva al responderle. Después de todo, en mis años más jóvenes, habría escrito el comando grep foo file.txt | cut ... | cut ...porque cada vez que haces los sencillos frecuentes grep, aprendes la ubicación del argumento del archivo y ya sabes que el primero es el patrón y los últimos son los nombres de los archivos.

Fue una elección consciente de usar catcuando respondí a la pregunta, en parte por una razón de "buen gusto" (en palabras de Linus Torvalds) pero principalmente por una razón imperiosa de función.

La última razón es más importante, así que la publicaré primero. Cuando ofrezco una tubería como solución, espero que sea reutilizable. Es muy probable que una tubería se agregue al final o se empalme en otra tubería. En ese caso, tener un argumento de archivo para grep arruina la reutilización, y muy posiblemente hacerlo en silencio sin un mensaje de error si el argumento de archivo existe. I. e. grep foo xyz | grep bar xyz | wcle dará cuántas líneas xyzcontiene barmientras espera la cantidad de líneas que contienen tanto fooy bar. Tener que cambiar argumentos a un comando en una canalización antes de usarlo es propenso a errores. Si a eso le sumamos la posibilidad de fallos silenciosos, se convierte en una práctica particularmente insidiosa.

La primera razón tampoco carece de importancia, ya que mucho " buen gusto " es simplemente una razón subconsciente intuitiva para cosas como los fallos silenciosos anteriores en los que no se puede pensar justo en el momento en que una persona que necesita educación dice "pero no es ese gato inútil ".

Sin embargo, intentaré también hacer consciente la antigua razón de "buen gusto" que mencioné. Esa razón tiene que ver con el espíritu de diseño ortogonal de Unix. grepno lo hace cuty lsno lo hace grep. Por lo tanto, al menos grep foo file1 file2 file3va en contra del espíritu de diseño. La forma ortogonal de hacerlo es cat file1 file2 file3 | grep foo. Ahora, grep foo file1es simplemente un caso especial de grep foo file1 file2 file3, y si no lo trata de la misma manera, al menos está agotando los ciclos del reloj cerebral tratando de evitar el premio inútil del gato.

Eso nos lleva al argumento de que grep foo file1 file2 file3está concatenando, y catconcatena por lo que es apropiado cat file1 file2 file3pero porque catno está concatenando en, cat file1 | grep foopor lo tanto, estamos violando el espíritu tanto del catUnix como del todopoderoso. Bueno, si ese fuera el caso, Unix necesitaría un comando diferente para leer la salida de un archivo y escupirlo en stdout (no paginarlo ni nada más que un simple escupir en stdout). Entonces, tendría la situación en la que dice cat file1 file2o dice dog file1y recuerda concienzudamente evitar cat file1para evitar obtener el premio, al mismo tiempo que evita, dog file1 file2ya que con suerte el diseño dogarrojaría un error si se especifican varios archivos.

Con suerte, en este punto, simpatizará con los diseñadores de Unix por no incluir un comando separado para escupir un archivo en la salida estándar, al mismo tiempo que nombra catpara concatenar en lugar de darle otro nombre. <edit>eliminó los comentarios incorrectos <, de hecho, <es una función eficiente sin copia para escupir un archivo a la salida estándar que puede colocar al comienzo de una canalización para que los diseñadores de Unix incluyan algo específicamente para esto</edit>

La siguiente pregunta es ¿por qué es importante tener comandos que simplemente escupen un archivo o la concatenación de varios archivos a la salida estándar, sin ningún procesamiento adicional? Una razón es evitar tener cada comando de Unix que opera en una entrada estándar para saber cómo analizar al menos un argumento de archivo de línea de comando y usarlo como entrada si existe. La segunda razón es evitar que los usuarios tengan que recordar: (a) dónde van los argumentos del nombre de archivo; y (b) evitar el error silencioso de la tubería como se mencionó anteriormente.

Eso nos lleva al por qué greptiene la lógica adicional. La razón es permitir la fluidez del usuario para los comandos que se utilizan con frecuencia y de forma independiente (en lugar de como una canalización). Es un ligero compromiso de ortogonalidad para una ganancia significativa en usabilidad. No todos los comandos deben diseñarse de esta manera y los comandos que no se utilizan con frecuencia deben evitar por completo la lógica adicional de los argumentos del archivo (recuerde que la lógica adicional conduce a una fragilidad innecesaria (la posibilidad de un error)). La excepción es permitir argumentos de archivo como en el caso de grep. (Por cierto, tenga en cuenta que lstiene una razón completamente diferente para no solo aceptar, sino también requerir argumentos de archivo)

Finalmente, lo que podría haberse hecho mejor es si comandos excepcionales como grep(pero no necesariamente ls) generan un error si la entrada estándar también está disponible cuando se especifican los argumentos del archivo.

nigromante
fuente
52
Tenga en cuenta que cuando grepse invoca con varios nombres de archivo, antepone las líneas encontradas con el nombre del archivo en el que se encontró (a menos que desactive ese comportamiento). También puede informar los números de línea en los archivos individuales. Si solo se usa catpara alimentar grep, perderá los nombres de archivo y los números de línea serán continuos en todos los archivos, no por archivo. Por lo tanto, existen razones para tener que grepmanejar varios archivos por sí mismo que catno puede manejar. Los casos de archivo único y archivo cero son simplemente casos especiales del uso general de archivos múltiples de grep.
Jonathan Leffler
38
Como se señaló en la respuesta de kojiro , es perfectamente posible y legal comenzar la tubería con < file command1 .... Aunque la posición convencional para los operadores de redirección de E / S es después del nombre del comando y sus argumentos, esa es solo la convención y no una ubicación obligatoria. El <tiene que preceder al nombre del archivo. Por lo tanto, hay una casi perfecta simetría entre >outputy <inputredirecciones: <input command1 -opt 1 | command2 -o | command3 >output.
Jonathan Leffler
15
Creo que una de las razones por las que la gente lanza la piedra de UUoC (incluyéndome a mí) es principalmente para educar. A veces, las personas procesan archivos de texto gigantes de gigabytes, en cuyo caso minimizar las tuberías (UUoC, colapsar greps secuenciales en uno, etc.) es crucial y, a menudo, se puede asumir con seguridad basándose en la pregunta de que el OP realmente simplemente no sabe que los pequeños ajustes pueden tener enormes impactos en el rendimiento. Estoy totalmente de acuerdo con tu punto sobre los ciclos cerebrales y es por eso que me encuentro usando cat con regularidad incluso cuando no es necesario. Pero es importante saber que no es necesario.
Adrian Frühwirth
13
Por favor entiende; De ninguna manera estoy diciendo que eso catsea ​​inútil. No es que catsea ​​inútil; es que una construcción particular no necesita el uso de cat. Si lo desea, tenga en cuenta que es UUoC (Uso inútil de cat) y no UoUC (Uso de inútil cat). Hay muchas ocasiones en las que cates la herramienta correcta a utilizar; No tengo ningún problema con que se use cuando es la herramienta correcta (y, de hecho, menciono un caso en mi respuesta).
Jonathan Leffler
6
@randomstring Te escucho, pero creo que realmente depende del caso de uso. Cuando se usa en la línea de comando, uno adicional caten la tubería puede no ser un gran problema dependiendo de los datos, pero cuando se usa como un entorno de programación, puede ser absolutamente necesario implementar estas cosas críticas para el rendimiento; especialmente cuando se trata de bashque, en términos de rendimiento, es como una rueda de forma rectangular (en comparación con de kshtodos modos. Estoy hablando hasta 10 veces más lento aquí, no es broma). Usted no desea optimizar su horquillas (y no sólo eso) cuando se trata de secuencias de comandos más grandes o enormes bucles.
Adrian Frühwirth
58

¡No!

En primer lugar, no importa en qué parte de un comando ocurra la redirección. Entonces, si le gusta su redirección a la izquierda de su comando, está bien:

< somefile command

es lo mismo que

command < somefile

En segundo lugar, hay n + 1 procesos y una subcapa cuando se usa una tubería. Es decididamente más lento. En algunos casos, n habría sido cero (por ejemplo, cuando está redirigiendo a un shell incorporado), por lo que al usarlo catestá agregando un nuevo proceso completamente innecesariamente.

Como generalización, siempre que te encuentres usando una tubería, vale la pena tomar 30 segundos para ver si puedes eliminarla. (Pero probablemente no valga la pena tomar más de 30 segundos). A continuación, se muestran algunos ejemplos en los que las tuberías y los procesos se utilizan con frecuencia innecesariamente:

for word in $(cat somefile);  # for word in $(<somefile); … (or better yet, while read < somefile)

grep something | awk stuff; # awk '/something/ stuff' (similar for sed)

echo something | command; # command <<< something (although echo would be necessary for pure POSIX)

Siéntase libre de editar para agregar más ejemplos.

kojiro
fuente
2
Bueno, el aumento de velocidad no será mucho.
Dakkaron
9
colocar el "<algún archivo" antes de "comando" técnicamente le da de izquierda a derecha, pero hace una lectura ambigua porque no hay demarcación sintáctica: < cat grep doges un ejemplo artificial para mostrar que no se puede distinguir fácilmente entre el archivo de entrada, el comando que recibe la entrada y los argumentos del comando.
nigromante
2
La regla general que he adoptado para decidir a dónde va la redirección STDIN es hacer lo que sea que minimice la apariencia de ambigüedad / potencial de sorpresa. Decir dogmáticamente que va antes trae a colación el problema del nigromante, pero decir dogmáticamente que va después puede hacer lo mismo. Considere lo siguiente: stdout=$(foo bar -exec baz <qux | ENV=VAR quux). P. ¿Se <quxaplica a foo, o para baz, que es -exec'd by foo? R. Se aplica a foo, pero puede parecer ambiguo. Poner <qux antes foo en este caso es más claro, aunque menos común, y es análogo al final ENV=VAR quux.
Mark G.
3
@necromancer, <"cat" grep doges más fácil de leer, ahí. (Normalmente soy pro-espacios en blanco, pero este caso en particular es una excepción).
Charles Duffy
1
@kojiro "Es decididamente más lento". No puedes escribir eso sin respaldarlo con números. Mis números están aquí: oletange.blogspot.com/2013/10/useless-use-of-cat.html (y muestran que solo es más lento cuando tienes un alto rendimiento) ¿Dónde están los tuyos?
Ole Tange
30

No estoy de acuerdo con la mayoría de los casos del Premio UUOC excesivamente presumido porque, cuando se enseña a otra persona, cates un marcador de posición conveniente para cualquier comando o canalización de comandos crujiente y complicada que produce resultados adecuados para el problema o la tarea que se está discutiendo.

Esto es especialmente cierto en sitios como Stack Overflow, ServerFault, Unix y Linux o cualquiera de los sitios SE.

Si alguien pregunta específicamente sobre la optimización, o si desea agregar información adicional al respecto, entonces, excelente, hable sobre cómo usar cat es ineficiente. ¡Pero no reprenda a las personas porque eligieron apuntar a la simplicidad y la facilidad de comprensión en sus ejemplos en lugar de mirarme-qué-genial-soy-! complejidad.

En resumen, porque el gato no siempre es gato.

También porque la mayoría de las personas que disfrutan de la concesión de UUOC lo hacen porque están más preocupados por presumir de lo "inteligentes" que son que por ayudar o enseñar a las personas. En realidad, demuestran que probablemente son solo otro novato que ha encontrado un palo pequeño para golpear a sus compañeros.


Actualizar

Aquí hay otro UUOC que publiqué en una respuesta en https://unix.stackexchange.com/a/301194/7696 :

sqlq() {
  local filter
  filter='cat'

  # very primitive, use getopts for real option handling.
  if [ "$1" == "--delete-blank-lines" ] ; then
    filter='grep -v "^$"'
    shift
  fi

  # each arg is piped into sqlplus as a separate command
  printf "%s\n" "$@" | sqlplus -S sss/eee@sid | $filter
}

Los pedantes de UUOC dirían que eso es un UUOC porque es fácilmente posible establecer $filterla cadena vacía por defecto y hacer que la ifdeclaración lo haga, filter='| grep -v "^$"'pero en mi opinión, al no incrustar el carácter de tubería en $filter, este "inútil" cattiene el propósito extremadamente útil de autodocumentar el hecho que $filteren la printflínea no es solo otro argumento sqlplus, es un filtro de salida opcional seleccionable por el usuario.

Si hay alguna necesidad de tener múltiples filtros de salida opcionales, el procesamiento de las opciones podría simplemente anexados | whatevera $filtertan a menudo como sea necesario - una adicional caten la tubería no se va a hacer daño a nada ni causa ninguna pérdida notable de rendimiento.

cas
fuente
11
Además , POSIX no especifica el ==interior [ ]y no todas las implementaciones lo aceptan. El operador estandarizado es solo =.
Charles Duffy
26

Con la versión UUoC, cattiene que leer el archivo en la memoria, luego escribirlo en la tubería, y el comando tiene que leer los datos de la tubería, por lo que el kernel tiene que copiar todo el archivo tres veces, mientras que en el caso redirigido, el kernel solo tiene que copiar el archivo una vez. Es más rápido hacer algo una vez que hacerlo tres veces.

Utilizando:

cat "$@" | command

es un uso completamente diferente y no necesariamente inútil de cat. Aún es inútil si el comando es un filtro estándar que acepta cero o más argumentos de nombre de archivo y los procesa a su vez. Considere el trcomando: es un filtro puro que ignora o rechaza los argumentos del nombre de archivo. Para alimentarlo con varios archivos, debe usar catcomo se muestra. (Por supuesto, hay una discusión separada de que el diseño de trno es muy bueno; no hay una razón real por la que no pudo haber sido diseñado como un filtro estándar). Esto también podría ser válido si desea que el comando trate todas las entradas como un un solo archivo en lugar de varios archivos separados, incluso si el comando acepta varios archivos separados: por ejemplo, wces un comando de este tipo.

Es el cat single-filecaso que es incondicionalmente inútil.

Jonathan Leffler
fuente
26

En defensa del gato:

Si,

   < input process > output 

o

   process < input > output 

es más eficiente, pero muchas invocaciones no tienen problemas de rendimiento, por lo que no le importa.

razones ergonómicas:

Estamos acostumbrados a leer de izquierda a derecha, por lo que un comando como

    cat infile | process1 | process2 > outfile

es trivial de entender.

    process1 < infile | process2 > outfile

tiene que saltar sobre el proceso1 y luego leer de izquierda a derecha. Esto se puede curar con:

    < infile process1 | process2 > outfile

parece de alguna manera, como si hubiera una flecha apuntando hacia la izquierda, donde no hay nada. Más confuso y que parece una cita elegante es:

    process1 > outfile < infile

y la generación de scripts suele ser un proceso iterativo,

    cat file 
    cat file | process1
    cat file | process1 | process2 
    cat file | process1 | process2 > outfile

donde ve su progreso paso a paso, mientras

    < file 

ni siquiera funciona. Las formas simples son menos propensas a errores y la catenación de comandos ergonómica es simple con cat.

Otro tema es que la mayoría de las personas estuvieron expuestas a> y <como operadores de comparación, mucho antes de usar una computadora y cuando usan una computadora como programadores, están mucho más expuestas a estos como tales.

Y comparar dos operandos con <y> es contra conmutativo, lo que significa

(a > b) == (b < a)

Recuerdo la primera vez que usé <para la redirección de entrada, temí

a.sh < file 

podría significar lo mismo que

file > a.sh

y de alguna manera sobrescribir mi script a.sh. Quizás este sea un problema para muchos principiantes.

diferencias raras

wc -c journal.txt
15666 journal.txt
cat journal.txt | wc -c 
15666

Este último se puede utilizar en los cálculos directamente.

factor $(cat journal.txt | wc -c)

Por supuesto, el <también se puede usar aquí, en lugar de un parámetro de archivo:

< journal.txt wc -c 
15666
wc -c < journal.txt
15666
    

pero ¿a quién le importa - 15k?

Si de vez en cuando me encontrara con problemas, seguramente cambiaría mi hábito de invocar a cat.

Cuando se utilizan archivos muy grandes o muchos, muchos, está bien evitar el uso de gatos. Para la mayoría de las preguntas, el uso de cat es ortogonal, fuera de tema, no es un problema.

Comenzar con estos inútiles e inútiles debates sobre gatos sobre cada segundo tema de caparazón es solo molesto y aburrido. Consiga una vida y espere su minuto de fama, cuando se trata de cuestiones de rendimiento.

usuario desconocido
fuente
5
+11111 .. Como autor de la respuesta actualmente aceptada, recomiendo este delicioso complemento. Los ejemplos específicos aclaran mis argumentos, a menudo abstractos y prolijos, y la risa que se obtiene de la inquietud inicial del autor por file > a.shsí sola vale la pena el tiempo de leer esto :) ¡Gracias por compartir!
nigromante
En esta invocación cat file | wc -c, wcnecesita leer stdin hasta EOF, contando bytes. Pero en esto, wc -c < filesolo stats stdin, descubre que es un archivo normal e imprime st_size en lugar de leer cualquier entrada. Para un archivo grande, la diferencia de rendimiento sería claramente visible.
oguz ismail
18

Un problema adicional es que la tubería puede enmascarar silenciosamente una subcapa. Para este ejemplo, lo reemplazaré catpor echo, pero existe el mismo problema.

echo "foo" | while read line; do
    x=$line
done

echo "$x"

Puede esperar xcontener foo, pero no es así. El xque estableciste se generó en una subcapa para ejecutar el whilebucle. xen el shell que inició la canalización tiene un valor no relacionado o no está configurado en absoluto.

En bash4, puede configurar algunas opciones de shell para que el último comando de un pipeline se ejecute en el mismo shell que el que inicia el pipeline, pero luego puede intentar esto

echo "foo" | while read line; do
    x=$line
done | awk '...'

y xes una vez más local para el whilesubshell de.

chepner
fuente
5
En shells estrictamente POSIX, esto puede ser un problema complicado porque aquí no tiene cadenas o sustituciones de procesos para evitar la tubería. BashFAQ 24 tiene algunas soluciones útiles incluso en ese caso.
kojiro
4
En algunas conchas, la tubería ilustrada no crea una subcapa. Los ejemplos incluyen Korn y Z. También admiten la sustitución de procesos y aquí cadenas. Por supuesto que no son estrictamente POSIX. Bash 4 tiene shopt -s lastpipeque evitar la creación de la subcapa.
Pausado hasta nuevo aviso.
13

Como alguien que regularmente señala esto y otros antipatrones de programación de shell, me siento obligado, tardíamente, a intervenir.

El script de shell es en gran medida un lenguaje de copiar / pegar. Para la mayoría de las personas que escriben scripts de shell, no están en él para aprender el idioma; es solo un obstáculo que deben superar para poder seguir haciendo cosas en los idiomas con los que están familiarizados.

En ese contexto, lo veo como disruptivo y potencialmente incluso destructivo para propagar varios anti-patrones de secuencias de comandos de shell. El código que alguien encuentra en Stack Overflow idealmente debería ser posible copiar / pegar en su entorno con cambios mínimos y una comprensión incompleta.

Entre los muchos recursos de scripts de shell en la red, Stack Overflow es inusual ya que los usuarios pueden ayudar a dar forma a la calidad del sitio editando las preguntas y respuestas en el sitio. Sin embargo, las ediciones de código pueden ser problemáticas porque es fácil realizar cambios que no fueron previstos por el autor del código. Por lo tanto, tendemos a dejar comentarios para sugerir cambios en el código.

La UUCA y los comentarios de antipatrón relacionados no son solo para los autores del código que comentamos; son también una advertencia para ayudar a los lectores del sitio a darse cuenta de los problemas en el código que encuentran aquí.

No podemos esperar lograr una situación en la que ninguna respuesta en Stack Overflow recomiende cats inútiles (o variables sin comillas chmod 777, o una gran variedad de otras plagas antipatrón), pero al menos podemos ayudar a educar al usuario que está a punto de copiar / pegue este código en el bucle cerrado más interno de su script que se ejecuta millones de veces.

En cuanto a razones técnicas, la sabiduría tradicional es que debemos tratar de minimizar el número de procesos externos; esto sigue siendo una buena guía general al escribir scripts de shell.

triples
fuente
1
Además, para archivos grandes, la canalización cates una gran cantidad de cambios de contexto adicionales y ancho de banda de memoria (y contaminación de la caché L3 por copias adicionales de datos en catel búfer de lectura y los búferes de canalización). Especialmente en una gran máquina de varios núcleos (como muchas configuraciones de alojamiento), el ancho de banda de memoria / caché es un recurso compartido.
Peter Cordes
1
@PeterCordes Publique sus medidas. Entonces podemos si realmente importa en la práctica. Mi experiencia es que normalmente no importa: oletange.blogspot.com/2013/10/useless-use-of-cat.html
Ole Tange
1
Su propio blog muestra una desaceleración del 50% para un alto rendimiento, y ni siquiera está viendo el impacto en el rendimiento total (si tuviera cosas que mantuvieran ocupados a los otros núcleos). Si lo hago, podría ejecutar sus pruebas mientras x264 o x265 están codificando un video usando todos los núcleos, y ver cuánto ralentiza la codificación del video. bzip2y la gzipcompresión son muy lentas en comparación con la cantidad de gastos generales que se catagregan a eso solo (con la máquina inactiva). Es difícil leer sus tablas (¿ajuste de línea en medio de un número?). sysel tiempo aumenta mucho, pero sigue siendo pequeño en comparación con el usuario o real?
Peter Cordes
8

A menudo lo uso cat file | myprogramen ejemplos. En algún momento me acusan de uso inútil del gato ( http://porkmail.org/era/unix/award.html ). No estoy de acuerdo por las siguientes razones:

  • Es fácil comprender lo que está pasando.

    Cuando lee un comando de UNIX, espera un comando seguido de argumentos seguidos de una redirección. Es posible colocar la redirección en cualquier lugar, pero rara vez se ve, por lo que las personas tendrán más dificultades para leer el ejemplo. Yo creo

    cat foo | program1 -o option -b option | program2

    es más fácil de leer que

    program1 -o option -b option < foo | program2

    Si mueve la redirección al principio, está confundiendo a las personas que no están acostumbradas a esta sintaxis:

    < foo program1 -o option -b option | program2

    y los ejemplos deben ser fáciles de entender.

  • Es fácil de cambiar.

    Si sabe que el programa puede leer cat, normalmente puede asumir que puede leer la salida de cualquier programa que envíe a STDOUT y, por lo tanto, puede adaptarlo a sus propias necesidades y obtener resultados predecibles.

  • Destaca que el programa no falla si STDIN no es un archivo.

    No es seguro asumir que si program1 < foofunciona cat foo | program1, también funcionará. Sin embargo, es seguro asumir lo contrario. Este programa funciona si STDIN es un archivo, pero falla si la entrada es una tubería, porque usa seek:

    # works
    < foo perl -e 'seek(STDIN,1,1) || die;print <STDIN>'
    
    # fails
    cat foo | perl -e 'seek(STDIN,1,1) || die;print <STDIN>'

Costo de desempeño

Hay un costo por hacer el adicional cat. Para dar una idea de cuánto ejecuté algunas pruebas para simular la línea base ( cat), el rendimiento bajo ( bzip2), el rendimiento medio ( gzip) y el rendimiento alto ( grep).

cat $ISO | cat
< $ISO cat
cat $ISO | bzip2
< $ISO | bzip2
cat $ISO | gzip
< $ISO gzip
cat $ISO | grep no_such_string
< $ISO grep no_such_string

Las pruebas se ejecutaron en un sistema de gama baja (0,6 GHz) y una computadora portátil normal (2,2 GHz). Se ejecutaron 10 veces en cada sistema y se eligió el mejor momento para imitar la situación óptima para cada prueba. El $ ISO era ubuntu-11.04-desktop-i386.iso. (Tablas más bonitas aquí: http://oletange.blogspot.com/2013/10/useless-use-of-cat.html )

CPU                       0.6 GHz ARM
Command                   cat $ISO|                        <$ISO                            Diff                             Diff (pct)
Throughput \ Time (ms)    User       Sys        Real       User       Sys        Real       User       Sys        Real       User       Sys        Real
Baseline (cat)                     55      14453      33090         23       6937      33126         32       7516        -36        239        208         99
Low (bzip2)                   1945148      16094    1973754    1941727       5664    1959982       3420      10430      13772        100        284        100
Medium (gzip)                  413914      13383     431812     407016       5477     416760       6898       7906      15052        101        244        103
High (grep no_such_string)      80656      15133      99049      79180       4336      86885       1476      10797      12164        101        349        114

CPU                       Core i7 2.2 GHz
Command                   cat $ISO|           <$ISO             Diff          Diff (pct)
Throughput \ Time (ms)    User     Sys Real   User   Sys Real   User Sys Real User       Sys Real
Baseline (cat)                    0 356    215      1  84     88    0 272  127          0 423  244
Low (bzip2)                  136184 896 136765 136728 160 137131 -545 736 -366         99 560   99
Medium (gzip)                 26564 788  26791  26332 108  26492  232 680  298        100 729  101
High (grep no_such_string)      264 392    483    216  84    304   48 308  179        122 466  158

Los resultados muestran que para un rendimiento bajo y medio, el costo es del orden del 1%. Esto está dentro de la incertidumbre de las mediciones, por lo que en la práctica no hay diferencia.

Para un alto rendimiento, la diferencia es mayor y existe una clara diferencia entre los dos.

Eso lleva a la conclusión: debe usar en <lugar de cat |si:

  • la complejidad del procesamiento es similar a un simple grep
  • el rendimiento importa más que la legibilidad.

De lo contrario, no importa si usa <o cat |.

Y, por lo tanto, solo debe otorgar un premio UUoC si y solo si:

  • puede medir una diferencia significativa en el rendimiento (publique sus mediciones cuando entregue el premio)
  • el rendimiento importa más que la legibilidad.
Ole Tange
fuente
-3

Creo que (la forma tradicional) usar pipe es un poco más rápido; en mi caja usé el stracecomando para ver qué está pasando:

Sin tubería:

toc@UnixServer:~$ strace wc -l < wrong_output.c
execve("/usr/bin/wc", ["wc", "-l"], [/* 18 vars */]) = 0
brk(0)                                  = 0x8b50000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ad000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=29107, ...}) = 0
mmap2(NULL, 29107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb77a5000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1552584, ...}) = 0
mmap2(NULL, 1563160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7627000
mmap2(0xb779f000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178) = 0xb779f000
mmap2(0xb77a2000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb77a2000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7626000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb76268d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb779f000, 8192, PROT_READ)   = 0
mprotect(0x804f000, 4096, PROT_READ)    = 0
mprotect(0xb77ce000, 4096, PROT_READ)   = 0
munmap(0xb77a5000, 29107)               = 0
brk(0)                                  = 0x8b50000
brk(0x8b71000)                          = 0x8b71000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=5540198, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7426000
mmap2(NULL, 1507328, PROT_READ, MAP_PRIVATE, 3, 0x2a8) = 0xb72b6000
close(3)                                = 0
open("/usr/share/locale/locale.alias", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=2570, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ac000
read(3, "# Locale name alias data base.\n#"..., 4096) = 2570
read(3, "", 4096)                       = 0
close(3)                                = 0
munmap(0xb77ac000, 4096)                = 0
open("/usr/share/locale/fr_FR.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr_FR.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr_FR/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr_FR.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr_FR.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr_FR/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr/LC_MESSAGES/coreutils.mo", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=316721, ...}) = 0
mmap2(NULL, 316721, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7268000
close(3)                                = 0
open("/usr/lib/i386-linux-gnu/gconv/gconv-modules.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=26064, ...}) = 0
mmap2(NULL, 26064, PROT_READ, MAP_SHARED, 3, 0) = 0xb7261000
close(3)                                = 0
read(0, "#include<stdio.h>\n\nint main(int "..., 16384) = 180
read(0, "", 16384)                      = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7260000
write(1, "13\n", 313
)                     = 3
close(0)                                = 0
close(1)                                = 0
munmap(0xb7260000, 4096)                = 0
close(2)                                = 0
exit_group(0)                           = ?

Y con pipa:

toc@UnixServer:~$ strace cat wrong_output.c | wc -l
execve("/bin/cat", ["cat", "wrong_output.c"], [/* 18 vars */]) = 0
brk(0)                                  = 0xa017000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb774b000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=29107, ...}) = 0
mmap2(NULL, 29107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7743000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1552584, ...}) = 0
mmap2(NULL, 1563160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb75c5000
mmap2(0xb773d000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178) = 0xb773d000
mmap2(0xb7740000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7740000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb75c4000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb75c48d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb773d000, 8192, PROT_READ)   = 0
mprotect(0x8051000, 4096, PROT_READ)    = 0
mprotect(0xb776c000, 4096, PROT_READ)   = 0
munmap(0xb7743000, 29107)               = 0
brk(0)                                  = 0xa017000
brk(0xa038000)                          = 0xa038000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=5540198, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb73c4000
mmap2(NULL, 1507328, PROT_READ, MAP_PRIVATE, 3, 0x2a8) = 0xb7254000
close(3)                                = 0
fstat64(1, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
open("wrong_output.c", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0664, st_size=180, ...}) = 0
read(3, "#include<stdio.h>\n\nint main(int "..., 32768) = 180
write(1, "#include<stdio.h>\n\nint main(int "..., 180) = 180
read(3, "", 32768)                      = 0
close(3)                                = 0
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
13

Puede hacer algunas pruebas con stracey timeordenar con más y más comandos para una buena evaluación comparativa.

TOC
fuente
9
No entiendo lo que quiere decir con (la forma tradicional) usar tubería , o por qué cree que esto stracemuestra que es más rápido; straceno se está rastreando la wc -lejecución en el segundo caso. Aquí solo rastrea el primer comando de la canalización.
kojiro
@kojiro: me refiero a la forma tradicional = la forma más utilizada (creo que usamos tubería más que indirección), no puedo confirmar que sea más rápido o no, en mi seguimiento vi más llamadas al sistema para indirección. Puede usar un programa de CA y un bucle para ver con uno consumir más tiempo. Si estás interesado, podemos ponerlo aquí :)
TOC
3
Una comparación de manzanas con manzanas pondría strace -f sh -c 'wc -l < wrong_output.c'al lado strace -f sh -c 'cat wrong_output.c | wc -l'.
Charles Duffy
5
Estos son los resultados de ideone.com, que actualmente están claramente a favor de sin cat: ideone.com/2w1W42#stderr
tripleee
1
@CharlesDuffy: mkfifocrea una tubería con nombre . Se configura una tubería anónima pipe(2)y luego se bifurca, y el padre y el niño cierran diferentes extremos de la tubería. Pero sí, esta respuesta es una tontería total, y ni siquiera trató de contar las llamadas al sistema o usarlo strace -Opara medir la sobrecarga, o -rmarcar la hora de cada llamada en relación con la última ...
Peter Cordes