Uso inútil de gato?

Esto es probablemente en muchas preguntas frecuentes, en lugar de usar:

cat file | command 

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

 command < file 

En la segunda, forma “correcta”: el sistema operativo no tiene que generar un proceso adicional.
A pesar de saber eso, continué usando un gato inútil por dos razones.

  1. más estético: me gusta cuando los datos se mueven uniformemente de izquierda a derecha. Y es más fácil reemplazar cat con otra cosa ( gzcat , echo , …), agregar un segundo archivo o insertar un 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, primero ( cat ) hace la lectura y el segundo hace lo que sea. Y pueden ejecutarse en paralelo, lo que significa a veces una ejecución más rápida.

¿Mi lógica es correcta (por segunda razón)?

Hasta el día de hoy no conocía el premio cuando un novato intentó ponerme el UUOC por una de mis respuestas. Era un cat file.txt | grep foo | cut ... | cut ... cat file.txt | grep foo | cut ... | cut ... cat file.txt | grep foo | cut ... | cut ... Le di una idea y solo después de hacerlo visité el enlace que me dio en referencia a los orígenes del premio y la práctica de hacerlo. La búsqueda adicional me llevó a esta pregunta. Desafortunadamente, a pesar de la consideración consciente, ninguna de las respuestas incluía mi razonamiento.

No quise ponerme a la defensiva al responderle. Después de todo, en mis años más jóvenes habría escrito el comando como grep foo file.txt | cut ... | cut ... grep foo file.txt | cut ... | cut ... grep foo file.txt | cut ... | cut ... porque cada vez que haces grep común con frecuencia aprendes la ubicación del argumento del archivo y está listo para saber que el primero es el patrón y los últimos son nombres de archivo.

Cuando respondí a la pregunta, fue una elección consciente utilizar el cat , en parte por una razón de “buen gusto” (en palabras de Linus Torvalds) pero principalmente por una razón convincente de la función.

La última razón es más importante, así que la sacaré primero. Cuando ofrezco una tubería como solución, espero que sea reutilizable. Es bastante 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 estropea la reutilización, y posiblemente lo haga en silencio sin un mensaje de error si el argumento del archivo existe. I. e. grep foo xyz | grep bar xyz | wc grep foo xyz | grep bar xyz | wc indicará cuántas líneas en xyz contienen bar mientras espera el número de líneas que contienen tanto foo como bar . Tener que cambiar los argumentos a un comando en una canalización antes de usarlo es propenso a errores. Agregue a esto la posibilidad de fallas silenciosas y se convierte en una práctica particularmente insidiosa.

La razón anterior no carece de importancia, ya que mucho “buen gusto” no es más que un fundamento subconsciente intuitivo para cosas como las fallas silenciosas anteriores que no se puede pensar justo en el momento en que alguien con necesidades educativas dice “pero no ese gato inútil “.

Sin embargo, intentaré también hacer consciente el antiguo motivo de “buen gusto” que mencioné. Esa razón tiene que ver con el espíritu de diseño ortogonal de Unix. grep no cut y ls no grep . Por lo tanto, al menos grep foo file1 file2 file3 va en contra del espíritu de diseño. La forma ortogonal de hacerlo es cat file1 file2 file3 | grep foo cat file1 file2 file3 | grep foo . Ahora, grep foo file1 es simplemente un caso especial de grep foo file1 file2 file3 , y si no lo tratas igual, al menos estarás usando ciclos de reloj cerebrales para evitar el galardón inútil.

Esto nos lleva al argumento de que grep foo file1 file2 file3 está concatenándose, y cat concatena para que sea apropiado para cat file1 file2 file3 pero porque cat no concatena en cat file1 | grep foo cat file1 | grep foo por lo tanto, estamos violando el espíritu del cat y el todopoderoso Unix. Bueno, si ese fuera el caso, entonces Unix necesitaría un comando diferente para leer la salida de un archivo y escupirlo a stdout (no a paginarlo ni a nada más que a spit puro a stdout). Entonces tendría la situación en la que diga cat file1 file2 o diga dog file1 y recuerde cat file1 evitar el archivo cat file1 para evitar recibir el premio, mientras también evita dog file1 file2 ya que con suerte el diseño del dog arrojaría un error si se encuentran varios archivos especificado.

Es de esperar que en este momento simpatice con los diseñadores de Unix por no incluir un comando separado para escupir un archivo a stdout, y al mismo tiempo nombrar cat para concatenar en lugar de darle otro nombre. eliminó los comentarios incorrectos en < , de hecho < es una instalación eficiente sin copia para escupir un archivo a stdout que puede ubicar al principio de una tubería para que los diseñadores de Unix incluyan algo específicamente para este

La siguiente pregunta es por qué es importante tener comandos que simplemente escupir un archivo o la concatenación de varios archivos a stdout, sin ningún procesamiento posterior? Una razón es evitar que cada comando de Unix que opera en la entrada estándar sepa 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 del archivo; y (b) evite el error silencioso en la tubería como se mencionó anteriormente.

Eso nos lleva a por qué grep tiene la lógica extra. El motivo es permitir la fluidez del usuario para los comandos que se utilizan con frecuencia y de manera independiente (en lugar de como una interconexión). Es un ligero compromiso de ortogonalidad para una ganancia significativa en la usabilidad. No todos los comandos deberían diseñarse de esta manera y los comandos que no se usan con frecuencia deberían evitar por completo la lógica extra 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 archivos como en el caso de grep . (Por cierto, tenga en cuenta que ls tiene una razón completamente diferente para no solo aceptar, sino que más bien requiere 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. Esto es razonable porque los comandos incluyen lógica que viola el espíritu ortogonal de Unix para la comodidad del usuario. Para mayor comodidad del usuario, es decir, para evitar el sufrimiento causado por una falla silenciosa, dichos comandos no deben dudar en violar su propia violación al tener una lógica adicional para alertar al usuario si existe la posibilidad de un error silencioso.

¡No!

En primer lugar, no importa en qué parte de un comando se produzca la redirección. Entonces, si te gusta la redirección a la izquierda de tu comando, está bien:

 < somefile command 

es lo mismo que

 command < somefile 

En segundo lugar, hay n + 1 procesos y una subcadena que ocurre cuando usa una tubería. Es decididamente más lento. En algunos casos, n hubiera sido cero (por ejemplo, cuando está redireccionando a un shell incorporado), por lo que al usar cat está agregando un nuevo proceso completamente innecesario.

Como generalización, cada vez 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). Estos son algunos ejemplos en los que las tuberías y los procesos se usan con frecuencia innecesariamente:

 for word in $(cat somefile); … # for word in $( 

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

Con la versión de UUoC, cat tiene que leer el archivo en la memoria, luego escribirlo en la tubería, y el comando debe leer los datos de la tubería, por lo que el kernel debe copiar el archivo completo tres veces, mientras que en el caso redirigido , el núcleo 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 del cat . Todavía 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 sucesivamente. Considere el comando tr : es un filtro puro que ignora o rechaza los argumentos del nombre de archivo. Para alimentar varios archivos, debe usar cat como se muestra. (Por supuesto, hay una discusión por separado de que el diseño de tr no es muy bueno, no hay una razón real por la que no podría 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 único archivo en lugar de múltiples archivos separados, incluso si el comando acepta múltiples archivos separados: por ejemplo, wc es un comando de este tipo.

Es el caso de un cat single-file que es incondicionalmente inútil.

No estoy de acuerdo con la mayoría de las instancias del Premio UUOC excesivamente presumido porque, al enseñar a otra persona, cat es un lugar conveniente para cualquier comando o línea complicada crujiente de comandos que producen resultados adecuados para el problema o tarea que se discute.

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 tiene ganas de agregar información adicional al respecto, genial, hable acerca de cómo usar el gato es ineficiente. ¡Pero no reprenda a las personas porque eligieron apostar por la simplicidad y la facilidad de comprensión en sus ejemplos en lugar de mirarme a mí cómo fresco! complejidad.

En resumen, porque el gato no siempre es un gato.

También porque la mayoría de las personas que disfrutan yendo por la concesión de UUOC lo hacen porque están más preocupados por alardear de cuán “inteligentes” son que de ayudar o enseñar a las personas. En realidad, demuestran que probablemente sean solo otro novato que ha encontrado un pequeño palo para vencer 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 pedagogos de UUOC dirían que eso es un UUOC porque es muy posible hacer $filter defecto a la cadena vacía y tener la instrucción if hacer filter='| grep -v "^$"' filter='| grep -v "^$"' pero IMO, al no incrustar el carácter de la tubería en $filter , este cat “inútil” cumple el propósito extremadamente útil de auto-documentar el hecho de que $filter en la línea printf no es más que otro argumento a sqlplus , es un filtro de salida opcional seleccionable por el usuario.

Si hay alguna necesidad de tener múltiples filtros de salida opcionales, el proceso de opciones podría simplemente anexar | whatever | whatever que | whatever que se $filter veces como sea necesario, un cat adicional en la tubería no va a doler nada ni provocará una pérdida notable de rendimiento.

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

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

Puede esperar que x contenga foo , pero no es así. La x que estableciste estaba en una subshell generada para ejecutar el ciclo while. x en el shell que inició la tubería 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 una canalización se ejecute en el mismo shell que el que inicia la canalización, pero entonces puede intentar esto

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

y x es una vez más local para la subshell del tiempo.

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

La secuencia de comandos de Shell es en gran medida un idioma de copiar / pegar. Para la mayoría de las personas que escriben scripts de shell, no están ahí para aprender el idioma; es solo un obstáculo que tienen que superar para continuar haciendo las cosas en el / los idioma (s) con el (los) que de hecho están familiarizados.

En ese contexto, lo veo como disruptivo y potencialmente incluso destructivo para propagar varios anti patrones de scripts 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 scripting de shell en la red, Stack Overflow es inusual ya que los usuarios pueden ayudar a dar forma a la calidad del sitio al editar 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 el autor del código no pretendía. Por lo tanto, tendemos a dejar comentarios para sugerir cambios en el código.

El UUCA y los comentarios antipatrón relacionados no son solo para los autores del código que comentamos; son tanto una advertencia para ayudar a los lectores del sitio a tomar conciencia 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 inútiles (o variables sin cita, o chmod 777 , o una gran variedad de otras plagas antipatrón), pero al menos podemos ayudar a educar al usuario que está a punto para copiar / pegar este código en el bucle cerrado más interno de su script que se ejecuta millones de veces.

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

En defensa del gato:

Sí,

  < input process > output 

o

  process < input > output 

es más eficiente, pero muchas invocaciones no tienen problemas de rendimiento, por lo que no te 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 el proceso1, y luego leer de izquierda a derecha. Esto puede ser curado por:

  < infile process1 | process2 > outfile 

parece de alguna manera, como si hubiera una flecha apuntando hacia la izquierda, donde nada está. Más confuso y que parece citar de lujo es:

  process1 > outfile < infile 

y generar scripts a menudo es un proceso iterativo,

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

donde ves tu progreso paso a paso, mientras

  < file 

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

Otro tema es que la mayoría de las personas estuvieron expuestas a> y

Y la comparación de dos operandos con es contra conmutativa, lo que significa

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

Recuerdo la primera vez que usé

 a.sh < file 

podría significar lo mismo que

 file > a.sh 

y de alguna manera sobrescribir mi script a.sh Tal vez este es un problema para muchos principiantes.

diferencias raras

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

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

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

Por supuesto, el

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

pero a quién le importa - 15k?

Si tuviera problemas ocasionalmente, seguramente cambiaría mi hábito de invocar a un gato.

Al usar archivos muy grandes o muchos, evitar el gato está bien. Para la mayoría de las preguntas, el uso de cat es ortogonal, fuera del tema, no es un problema.

Comenzar con este inútil e inútil uso de la discusión de gatos sobre cada segundo tema de shell es molesto y aburrido. Consiga una vida y espere su minuto de fama cuando trate con preguntas sobre el rendimiento.

Creo que (la forma tradicional) usar tubería es un poco más rápido; en mi caja strace comando strace 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\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 tubería:

 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\n\nint main(int "..., 32768) = 180 write(1, "#include\n\nint main(int "..., 180) = 180 read(3, "", 32768) = 0 close(3) = 0 close(1) = 0 close(2) = 0 exit_group(0) = ? 13 

You can do some testing with strace and time command with more and longer commands for good benchmarking.