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.
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
cat
con otra cosa (gzcat
,echo
, ...), añadir un segundo archivo o insertar el nuevo filtro (pv
,mbuffer
,grep
...)."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)?
fuente
cat
es 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 pasacat
(o de la entrada estándar, si no pasa ningún argumento), entoncescat
es absolutamente inútil y solo resulta en un proceso adicional que se bifurca y una tubería adicional creado.-
, 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.<file command1 | command2
, aunque habría desacuerdo sobre la estética.Respuestas:
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 frecuentesgrep
, 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
cat
cuando 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 | wc
le dará cuántas líneasxyz
contienebar
mientras espera la cantidad de líneas que contienen tantofoo
ybar
. 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.
grep
no lo hacecut
yls
no lo hacegrep
. Por lo tanto, al menosgrep foo file1 file2 file3
va en contra del espíritu de diseño. La forma ortogonal de hacerlo escat file1 file2 file3 | grep foo
. Ahora,grep foo file1
es simplemente un caso especial degrep 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 file3
está concatenando, ycat
concatena por lo que es apropiadocat file1 file2 file3
pero porquecat
no está concatenando en,cat file1 | grep foo
por lo tanto, estamos violando el espíritu tanto delcat
Unix 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 dicecat file1 file2
o dicedog file1
y recuerda concienzudamente evitarcat file1
para evitar obtener el premio, al mismo tiempo que evita,dog file1 file2
ya que con suerte el diseñodog
arrojarí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
cat
para 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é
grep
tiene 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 degrep
. (Por cierto, tenga en cuenta quels
tiene 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 necesariamentels
) generan un error si la entrada estándar también está disponible cuando se especifican los argumentos del archivo.fuente
grep
se 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 usacat
para alimentargrep
, 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 quegrep
manejar varios archivos por sí mismo quecat
no puede manejar. Los casos de archivo único y archivo cero son simplemente casos especiales del uso general de archivos múltiples degrep
.< 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>output
y<input
redirecciones:<input command1 -opt 1 | command2 -o | command3 >output
.cat
sea inútil. No es quecat
sea inútil; es que una construcción particular no necesita el uso decat
. Si lo desea, tenga en cuenta que es UUoC (Uso inútil decat
) y no UoUC (Uso de inútilcat
). Hay muchas ocasiones en las quecat
es 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).cat
en 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 debash
que, en términos de rendimiento, es como una rueda de forma rectangular (en comparación con deksh
todos 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.¡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:
es lo mismo que
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
cat
está 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:
Siéntase libre de editar para agregar más ejemplos.
fuente
< cat grep dog
es 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.stdout=$(foo bar -exec baz <qux | ENV=VAR quux)
. P. ¿Se<qux
aplica afoo
, o parabaz
, que es-exec
'd byfoo
? R. Se aplica afoo
, pero puede parecer ambiguo. Poner<qux
antesfoo
en este caso es más claro, aunque menos común, y es análogo al finalENV=VAR quux
.<"cat" grep dog
es más fácil de leer, ahí. (Normalmente soy pro-espacios en blanco, pero este caso en particular es una excepción).No estoy de acuerdo con la mayoría de los casos del Premio UUOC excesivamente presumido porque, cuando se enseña a otra persona,
cat
es 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 :
Los pedantes de UUOC dirían que eso es un UUOC porque es fácilmente posible establecer
$filter
la cadena vacía por defecto y hacer que laif
declaració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"cat
tiene el propósito extremadamente útil de autodocumentar el hecho que$filter
en laprintf
línea no es solo otro argumentosqlplus
, 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
| whatever
a$filter
tan a menudo como sea necesario - una adicionalcat
en la tubería no se va a hacer daño a nada ni causa ninguna pérdida notable de rendimiento.fuente
==
interior[ ]
y no todas las implementaciones lo aceptan. El operador estandarizado es solo=
.Con la versión UUoC,
cat
tiene 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:
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 eltr
comando: es un filtro puro que ignora o rechaza los argumentos del nombre de archivo. Para alimentarlo con varios archivos, debe usarcat
como se muestra. (Por supuesto, hay una discusión separada de que el diseño detr
no 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,wc
es un comando de este tipo.Es el
cat single-file
caso que es incondicionalmente inútil.fuente
En defensa del gato:
Si,
o
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
es trivial de entender.
tiene que saltar sobre el proceso1 y luego leer de izquierda a derecha. Esto se puede curar con:
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:
y la generación de scripts suele ser un proceso iterativo,
donde ve su progreso paso a paso, mientras
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
Recuerdo la primera vez que usé <para la redirección de entrada, temí
podría significar lo mismo que
y de alguna manera sobrescribir mi script a.sh. Quizás este sea un problema para muchos principiantes.
diferencias raras
Este último se puede utilizar en los cálculos directamente.
Por supuesto, el <también se puede usar aquí, en lugar de un parámetro de archivo:
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.
fuente
file > a.sh
sí sola vale la pena el tiempo de leer esto :) ¡Gracias por compartir!cat file | wc -c
,wc
necesita leer stdin hasta EOF, contando bytes. Pero en esto,wc -c < file
solo 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.Un problema adicional es que la tubería puede enmascarar silenciosamente una subcapa. Para este ejemplo, lo reemplazaré
cat
porecho
, pero existe el mismo problema.Puede esperar
x
contenerfoo
, pero no es así. Elx
que estableciste se generó en una subcapa para ejecutar elwhile
bucle.x
en 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
y
x
es una vez más local para elwhile
subshell de.fuente
shopt -s lastpipe
que evitar la creación de la subcapa.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
cat
s inútiles (o variables sin comillaschmod 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.
fuente
cat
es 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 encat
el 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.bzip2
y lagzip
compresión son muy lentas en comparación con la cantidad de gastos generales que secat
agregan a eso solo (con la máquina inactiva). Es difícil leer sus tablas (¿ajuste de línea en medio de un número?).sys
el tiempo aumenta mucho, pero sigue siendo pequeño en comparación con el usuario o real?A menudo lo uso
cat file | myprogram
en 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
es más fácil de leer que
Si mueve la redirección al principio, está confundiendo a las personas que no están acostumbradas a esta sintaxis:
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 < foo
funcionacat 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: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
).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 )
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 decat |
si:De lo contrario, no importa si usa
<
ocat |
.Y, por lo tanto, solo debe otorgar un premio UUoC si y solo si:
fuente
Creo que (la forma tradicional) usar pipe es un poco más rápido; en mi caja usé el
strace
comando para ver qué está pasando:Sin tubería:
Y con pipa:
Puede hacer algunas pruebas con
strace
ytime
ordenar con más y más comandos para una buena evaluación comparativa.fuente
strace
muestra que es más rápido;strace
no se está rastreando lawc -l
ejecución en el segundo caso. Aquí solo rastrea el primer comando de la canalización.strace -f sh -c 'wc -l < wrong_output.c'
al ladostrace -f sh -c 'cat wrong_output.c | wc -l'
.cat
: ideone.com/2w1W42#stderrmkfifo
crea una tubería con nombre . Se configura una tubería anónimapipe(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 usarlostrace -O
para medir la sobrecarga, o-r
marcar la hora de cada llamada en relación con la última ...