¿Debería preocuparme por los gatos innecesarios?

50

Muchas utilidades de línea de comandos pueden tomar su entrada de una tubería o como un argumento de nombre de archivo. Para scripts de shell largos, encuentro que comenzar la cadena con un cathace que sea más legible, especialmente si el primer comando necesitaría argumentos de varias líneas.

Comparar

sed s/bla/blaha/ data \
| grep blah \
| grep -n babla

y

cat data \
| sed s/bla/blaha/ \
| grep blah \
| grep -n babla

¿Es este último método menos eficiente? Si es así, ¿la diferencia es suficiente para preocuparse si el script se ejecuta, por ejemplo, una vez por segundo? La diferencia en la legibilidad no es enorme.

tshepang
fuente
30
Paso mucho más tiempo observando a las personas atacarse entre sí por el uso inútil del gato en este sitio de lo que mi sistema realmente inicia los procesos del gato
Michael Mrozek
44
@Michael: 100% de acuerdo. Diablos, me tomó más tiempo vincularme al viejo premio de Usenet una vez de lo que mi computadora perderá la creación de instancias cat. Sin embargo, creo que la pregunta más importante aquí es la legibilidad del código, que a menudo es una prioridad sobre el rendimiento. Cuando más rápido se puede escribir más bonito , ¿por qué no? Señalar el problema catgeneralmente lleva al usuario a tener una mejor comprensión de las tuberías y los procesos en general. Vale la pena el esfuerzo para que escriban un código comprensible la próxima vez.
Caleb
3
De hecho, tengo otra razón por la que no me gusta la primera forma: si desea agregar otro comando al comienzo de la tubería, también debe mover el argumento, por lo que la edición es más molesta. (Por supuesto, esto no significa que tenga que usar cat; el punto de Caleb sobre el uso de funciones y la redirección también lo resuelve.)
Cascabel
Relacionado: ¿ Eliminar los usos inútiles del gato o no?   (Meta)
G-Man dice 'Reinstate Monica'
1
Es tarde en el trabajo, mi trabajo se niega a trabajar. Abro stackoverflow y encuentro una pregunta titulada "¿Debería preocuparme por los gatos innecesarios?" y ver algunos animales sin hogar y un programador, reflexionando sobre alimentarlos o no ...
Boris Burkov

Respuestas:

46

La respuesta "definitiva" es presentada por The Useless of catAward .

El propósito de cat es concatenar (o "catenate") archivos. Si es solo un archivo, concatenarlo sin nada es una pérdida de tiempo y le cuesta un proceso.

La creación de instancias de cat solo para que su código se lea de manera diferente solo genera un proceso más y un conjunto más de flujos de entrada / salida que no son necesarios. Normalmente, el retraso real en sus scripts será bucles ineficientes y procesamiento real. En la mayoría de los sistemas modernos, un extra catno va a matar su rendimiento, pero casi siempre hay otra forma de escribir su código.

La mayoría de los programas, como puede observar, pueden aceptar un argumento para el archivo de entrada. Sin embargo, siempre existe el shell incorporado <que se puede usar donde sea que se espere una secuencia STDIN que le ahorrará un proceso al hacer el trabajo en el proceso de shell que ya se está ejecutando.

Incluso puedes ser creativo con DONDE lo escribes. Normalmente se colocaría al final de un comando antes de especificar cualquier redirección de salida o canalización como esta:

sed s/blah/blaha/ < data | pipe

Pero no tiene por qué ser así. Incluso puede venir primero. Por ejemplo, su código de ejemplo podría escribirse así:

< data \
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla

Si le preocupa la legibilidad de los scripts y su código es lo suficientemente desordenado como para agregar una línea para catque sea más fácil de seguir, existen otras formas de limpiar su código. Uno que uso mucho y que ayuda a que los scripts sean fáciles de descifrar más tarde es dividir las tuberías en conjuntos lógicos y guardarlos en funciones. El código del script se vuelve muy natural y cualquier parte de la línea de canalización es más fácil de depurar.

function fix_blahs () {
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla
}

fix_blahs < data

Entonces podrías continuar con fix_blahs < data | fix_frogs | reorder | format_for_sql. Una pipleline que se lee así es realmente fácil de seguir, y los componentes individuales se pueden depurar fácilmente en sus respectivas funciones.

Caleb
fuente
26
No sabía que eso <filepodría venir antes de la orden. ¡Esto resuelve todos mis problemas!
3
@Tim: Bash y Zsh son compatibles con eso, aunque creo que es feo. Cuando me preocupa que mi código sea bonito y mantenible, generalmente uso funciones para limpiarlo. Ver mi última edición.
Caleb
8
@Tim <filepuede venir a cualquier parte de la línea de comando: <file grep needleo grep <file needleo grep needle <file. La excepción son comandos complejos como bucles y agrupaciones; allí la redirección debe venir después del cierre done/ }/ )/ etc. @Caleb Esto se mantiene en todos los shells Bourne / POSIX. Y no estoy de acuerdo con que sea feo.
Gilles 'SO- deja de ser malvado'
99
@Gilles, en bash se pueden reemplazar $(cat /some/file)con $(< /some/file), que hace lo mismo, pero no ofrece un proceso de desove.
cjm
3
Solo para confirmar que $(< /some/file)es de portabilidad limitada. Funciona en bash, pero no en Ash de BusyBox, por ejemplo, o en FreeBSD sh. Probablemente tampoco funcione en el tablero, ya que esos tres últimos proyectiles son primos cercanos.
dubiousjim
22

Aquí hay un resumen de algunos de los inconvenientes de:

cat $file | cmd

terminado

< $file cmd
  • Primero, una nota: faltan (intencionalmente para el propósito de la discusión) comillas dobles $filearriba. En el caso de cat, eso siempre es un problema, excepto zsh; en el caso de la redirección, eso es solo un problema para basho ksh88y, para algunos otros shells, solo cuando es interactivo (no en scripts).
  • El inconveniente más citado es el proceso adicional que se genera. Tenga en cuenta que si cmdestá integrado, eso es incluso 2 procesos en algunos shells como bash.
  • Todavía en el frente del rendimiento, excepto en shells donde catestá integrado, también se ejecuta un comando adicional (y, por supuesto, se carga e inicializa (y las bibliotecas a las que también está vinculado)).
  • Todavía en la parte frontal de rendimiento, para archivos de gran tamaño, que significa que el sistema tiene que programar la forma alterna caty cmdprocesos y constantemente llenar y vaciar el búfer de canalización. Incluso si el sistema cmdrealiza 1GBgrandes read()llamadas a la vez, el control tendrá que ir y venir entre caty cmdporque una tubería no puede contener más de unos pocos kilobytes de datos a la vez.
  • Algunos cmds (como wc -c) pueden hacer algunas optimizaciones cuando su stdin es un archivo normal con el que no pueden hacerlo, cat | cmdya que su stdin es solo una tubería. Con caty una tubería, también significa que no pueden seek()dentro del archivo. Para comandos como taco tail, eso hace una gran diferencia en el rendimiento, ya que eso significa que catnecesitan almacenar toda la entrada en la memoria.
  • El cat $file, e incluso su versión más correcta cat -- "$file"no funcionará correctamente para algunos nombres de archivo específicos como -( --helpo cualquier cosa que comience -si olvida el --). Si uno insiste en usarlo cat, probablemente debería usarlo en cat < "$file" | cmdlugar de confiabilidad.
  • Si $fileno puede abrirse para lectura (acceso denegado, no existe ...), < "$file" cmdinformará un mensaje de error coherente (por parte del shell) y no se ejecutará cmd, mientras cat $file | cmdque todavía se ejecutará cmdpero con su stdin como si fuera un archivo vacío. Eso también significa que en cosas como < file cmd > file2, file2no se golpea si fileno se puede abrir.
Stéphane Chazelas
fuente
2
Respecto al rendimiento: esta prueba muestra que la diferencia es del orden de 1 PCT a menos que esté procesando muy poco en la secuencia oletange.blogspot.dk/2013/10/useless-use-of-cat.html
Ole Tange
2
@OleTange. He aquí otra prueba: truncate -s10G a; time wc -c < a; time cat a | wc -c; time cat a | cat | wc -c. Hay muchos parámetros que entran en escena. La penalización de rendimiento puede ir de 0 a 100%. En cualquier caso, no creo que la penalización pueda ser negativa.
Stéphane Chazelas
2
wc -cEs un caso bastante único, porque tiene un atajo. Si lo hace, wc -wentonces es comparable a grepen mi ejemplo (es decir, muy poco procesamiento, que es la situación en la que '<' puede marcar la diferencia).
Ole Tange
@OleTange, incluso ( wc -wen un archivo escaso de 1 GB en la configuración regional de C en linux 4.9 amd64), encuentro que el enfoque de gato tarda un 23% más de tiempo en un sistema multinúcleo y un 5% cuando los une a un núcleo. Mostrar la sobrecarga adicional incurrida al tener acceso a los datos por más de un núcleo. Posiblemente obtendrá resultados diferentes si cambia el tamaño de la tubería, usa datos diferentes, involucra E / S real, usa una implementación cat que usa empalme () ... Todo confirmando que hay muchos parámetros en la imagen y eso en cualquier caso catno ayudará.
Stéphane Chazelas
1
Para mí, con un archivo de 1GB wc -w, es una diferencia de aproximadamente 2% ... 15% de diferencia si está en un grep simple y directo. Entonces, extrañamente, si está en un recurso compartido de archivos NFS, en realidad es un 20% más rápido leerlo si se envía desde cat( gist.github.com/rdp/7162414833becbee5919cda855f1cb86 ) Extraño ...
rogerdpack
16

Poner <fileen el extremo de una tubería es menos legible que tener cat fileal principio. El inglés natural se lee de izquierda a derecha.

Diría que poner <fileel comienzo de la tubería también es menos legible que el gato. Una palabra es más legible que un símbolo, especialmente un símbolo que parece indicar el camino equivocado.

El uso catconserva el command | command | commandformato.

Jim
fuente
Estoy de acuerdo, usar <una vez hace que el código sea menos legible, ya que destruye la consistencia de sintaxis de una multiplínea.
A.Danischewski
@Jim Puede resolver la legibilidad mediante la creación de un alias que <desea: alias load='<'y luego utiliza por ejemplo load file | sed .... Los alias pueden usarse en scripts después de ejecutarse shopt -s expand_aliases.
niieani
1
Sí, sé sobre alias. Sin embargo, aunque este alias reemplaza el símbolo con una palabra, requiere que el lector conozca su configuración de alias personal, por lo que no es muy portátil.
Jim
8

Una cosa que las otras respuestas aquí no parecen haber abordado directamente es que el uso de cateste tipo no es "inútil" en el sentido de que "se genera un proceso de gato extraño que no funciona"; es inútil en el sentido de que "se genera un proceso cat que solo hace un trabajo innecesario".

En el caso de estos dos:

sed 's/foo/bar/' somefile
<somefile sed 's/foo/bar/'

el shell inicia un proceso de sed que se lee desde algún archivo o stdin (respectivamente) y luego realiza un procesamiento: se lee hasta que llega a una nueva línea, reemplaza el primer 'foo' (si lo hay) en esa línea con 'bar', luego imprime esa línea a stdout y bucles.

En el caso de:

cat somefile | sed 's/foo/bar/'

El caparazón genera un proceso de gato y un proceso de sed, y conecta el stdout del gato al stdin de sed. El proceso cat lee un trozo de varios kilobytes o quizás megabytes del archivo, luego lo escribe en su stdout, donde el sommand sed se recoge desde allí como en el segundo ejemplo anterior. Mientras sed está procesando ese fragmento, cat está leyendo otro fragmento y lo escribe en su stdout para que sed trabaje a continuación.

En otras palabras, el trabajo adicional necesario al agregar el catcomando no es solo el trabajo adicional de generar un catproceso adicional , sino también el trabajo adicional de leer y escribir los bytes del archivo dos veces en lugar de una vez. Ahora, prácticamente hablando y en sistemas modernos, eso no hace una gran diferencia: puede hacer que su sistema realice unos microsegundos de trabajo innecesario. Pero si se trata de un script que planea distribuir, potencialmente para las personas que lo usan en máquinas que ya tienen poca potencia, unos pocos microsegundos pueden acumularse en muchas iteraciones.

Godlygeek
fuente
2
Ver oletange.blogspot.dk/2013/10/useless-use-of-cat.html para una prueba de los gastos generales de usar el adicional cat.
Ole Tange
@OleTange: Me encontré con esto y visité tu blog. (1) Mientras veo el contenido (principalmente) en inglés, veo un montón de palabras en (supongo) danés: "Klassisk", "Flipcard", "Magasin", "Mosaik", "Sidebjælke", "Øjebliksbillede" , "Tidsskyder", "Blog-arkiv", "Om mig", "Skrevet" y "Vis kommentarer" (pero "Tweet", "Me gusta" y el banner de cookies están en inglés). ¿Sabías esto y está bajo tu control? (2) Tengo problemas para leer sus tablas (2a) porque las líneas de la cuadrícula están incompletas y (2b) No entiendo lo que quiere decir con "Diferencia (pct)".
G-Man dice 'Restablece a Mónica' el
blogspot.dk es administrado por Google. Intente reemplazar con blogspot.com. El "Diff (PCT)" es el ms con catdividido por el ms sin caten porcentaje (por ejemplo, 264 ms / 216 ms = 1.22 = 122% = 22% más lento con cat)
Ole Tange