la cabeza come personajes extra

15

Se esperaba que el siguiente comando de shell imprimiera solo líneas impares de la secuencia de entrada:

echo -e "aaa\nbbb\nccc\nddd\n" | (while true; do head -n 1; head -n 1 >/dev/null; done)

Pero en vez de eso sólo imprime la primera línea: aaa.

No ocurre lo mismo cuando se usa con la opción -c( --bytes):

echo 12345678901234567890 | (while true; do head -c 5; head -c 5 >/dev/null; done)

Este comando sale 1234512345como se esperaba. Pero esto solo funciona en la implementación de coreutils de la headutilidad. La implementación de busybox todavía consume caracteres adicionales, por lo que la salida es justa 12345.

Supongo que esta forma específica de implementación se realiza con fines de optimización. No puede saber dónde termina la línea, por lo que no sabe cuántos caracteres necesita leer. La única forma de no consumir caracteres adicionales de la secuencia de entrada es leer la secuencia byte por byte. Pero leer de la transmisión un byte a la vez puede ser lento. Supongo que headlee el flujo de entrada en un búfer lo suficientemente grande y luego cuenta las líneas en ese búfer.

No se puede decir lo mismo del caso cuando --bytesse usa la opción. En este caso, usted sabe cuántos bytes necesita leer. Entonces puede leer exactamente este número de bytes y no más que eso. La implementación de corelibs usa esta oportunidad, pero la busybox no, todavía lee más bytes de los necesarios en un búfer. Probablemente se haga para simplificar la implementación.

Entonces la pregunta. ¿Es correcto que la headutilidad consuma más caracteres de la secuencia de entrada de los que se le pidió? ¿Existe algún tipo de estándar para las utilidades de Unix? Y si lo hay, ¿especifica este comportamiento?

PD

Debe presionar Ctrl+Cpara detener los comandos anteriores. Las utilidades de Unix no fallan al leer más allá EOF. Si no desea presionar, puede usar un comando más complejo:

echo 12345678901234567890 | (while true; do head -c 5; head -c 5 | [ `wc -c` -eq 0 ] && break >/dev/null; done)

que no usé por simplicidad.

anton_rh
fuente
2
Neardupe unix.stackexchange.com/questions/48777/… y unix.stackexchange.com/questions/84011/… . Además, si este título hubiera estado en películas.SX, mi respuesta sería Zardoz :)
dave_thompson_085

Respuestas:

30

¿Es correcto que la utilidad head consuma más caracteres de la secuencia de entrada de los que se le pidió?

Sí, está permitido (ver más abajo).

¿Existe algún tipo de estándar para las utilidades de Unix?

Sí, POSIX volumen 3, Shell y utilidades .

Y si lo hay, ¿especifica este comportamiento?

Lo hace, en su introducción:

Cuando una utilidad estándar lee un archivo de entrada que se puede buscar y finaliza sin un error antes de llegar al final del archivo, la utilidad se asegurará de que el desplazamiento del archivo en la descripción del archivo abierto se coloque correctamente justo después del último byte procesado por la utilidad. Para los archivos que no son buscables, el estado del desplazamiento del archivo en la descripción del archivo abierto para ese archivo no está especificado.

heades una de las utilidades estándar , por lo que una implementación conforme con POSIX debe implementar el comportamiento descrito anteriormente.

GNU head no tratan de dejar el descriptor de archivo en la posición correcta, pero es imposible que buscar en las tuberías, por lo que en su prueba de que no puede restaurar la posición. Puedes ver esto usando strace:

$ echo -e "aaa\nbbb\nccc\nddd\n" | strace head -n 1
...
read(0, "aaa\nbbb\nccc\nddd\n\n", 8192) = 17
lseek(0, -13, SEEK_CUR)                 = -1 ESPIPE (Illegal seek)
...

Los readretornos 17 bytes (todos la entrada disponible), headprocesa cuatro de esos y luego trata de moverse hacia atrás 13 bytes, pero no puede. (También puede ver aquí que GNU headusa un búfer de 8 KiB).

Cuando le dice headque cuente bytes (que no es estándar), sabe cuántos bytes leer, por lo que puede (si se implementa de esa manera) limitar su lectura en consecuencia. Es por eso que su head -c 5prueba funciona: GNU headsolo lee cinco bytes y, por lo tanto, no necesita buscar restaurar la posición del descriptor de archivo.

Si escribe el documento en un archivo y lo usa en su lugar, obtendrá el comportamiento que busca:

$ echo -e "aaa\nbbb\nccc\nddd\n" > file
$ < file (while true; do head -n 1; head -n 1 >/dev/null; done)
aaa
ccc
Stephen Kitt
fuente
2
Uno puede usar las utilidades line(ahora eliminadas de POSIX / XPG pero aún disponibles en muchos sistemas) o read( IFS= read -r line) que leen un byte a la vez para evitar el problema.
Stéphane Chazelas
3
Tenga en cuenta que si head -c 5leerá 5 bytes o un búfer completo depende de la implementación (también tenga en cuenta que head -cno es estándar), no puede confiar en eso. Debería dd bs=1 count=5tener una garantía de que no se leerán más de 5 bytes.
Stéphane Chazelas
Gracias @ Stéphane, he actualizado la -c 5descripción.
Stephen Kitt
Tenga en cuenta que la headconstrucción de ksh93lecturas de un byte a la vez con head -n 1cuando la entrada no es buscable.
Stéphane Chazelas
1
@anton_rh, ddsolo funciona correctamente con las tuberías con bs=1si usa un countcomo las lecturas en las tuberías pueden devolver menos de lo solicitado (pero al menos un byte a menos que se alcance eof). Sin embargo, GNU ddtiene iflag=fullblockeso puede aliviar eso.
Stéphane Chazelas
6

de POSIX

La utilidad head copiará sus archivos de entrada a la salida estándar, finalizando la salida de cada archivo en un punto designado.

No dice nada sobre cuánto head debe leerse de la entrada. Exigirle que lea byte a byte sería una tontería, ya que sería extremadamente lento en la mayoría de los casos.

Sin embargo, esto se aborda en el readbuiltin / utility: todos los shells que puedo encontrar readen las tuberías de un byte a la vez y el texto estándar puede interpretarse en el sentido de que esto debe hacerse, para poder leer solo esa línea:

La lectura de utilidad será leído una sola línea lógica de la entrada estándar en una o más variables de shell.

En el caso de read, que se usa en scripts de shell, un caso de uso común sería algo como esto:

read someline
if something ; then 
    someprogram ...
fi

Aquí, la entrada estándar de someprogrames la misma que la del shell, pero se puede esperar que someprogramlea todo lo que viene después de la primera línea de entrada consumida por ready no lo que queda después de una lectura almacenada read. Por otro lado, usar headcomo en su ejemplo es mucho más infrecuente.


Si realmente desea eliminar cualquier otra línea, sería mejor (y más rápido) usar alguna herramienta que pueda manejar toda la entrada de una sola vez, por ej.

$ seq 1 10 | sed -ne '1~2p'   # GNU sed
$ seq 1 10 | sed -e 'n;d'     # works in GNU sed and the BSD sed on macOS

$ seq 1 10 | awk 'NR % 2' 
$ seq 1 10 | perl -ne 'print if $. % 2'
ilkkachu
fuente
Pero vea la sección "INPUT ARCHIVOS" de la introducción de POSIX al volumen 3 ...
Stephen Kitt
1
POSIX dice: "Cuando una utilidad estándar lee un archivo de entrada buscable y finaliza sin un error antes de que llegue al final del archivo, la utilidad se asegurará de que el desplazamiento del archivo en la descripción del archivo abierto se coloque correctamente justo después del último byte procesado por la utilidad. Para archivos que no son buscables, el estado del desplazamiento del archivo en la descripción del archivo abierto para ese archivo no está especificado. "
AlexP
2
Tenga en cuenta que, a menos que lo use -r, readpuede leer más de una línea (sin IFS=ella también se eliminarían los espacios y las pestañas iniciales y finales (con el valor predeterminado de $IFS)).
Stéphane Chazelas
@AlexP, sí, Stephen acaba de vincular esa parte.
ilkkachu
Tenga en cuenta que la headconstrucción de ksh93lecturas de un byte a la vez con head -n 1cuando la entrada no es buscable.
Stéphane Chazelas
1
awk '{if (NR%2) == 1) print;}'
ijbalazs
fuente
Hellóka :-) y bienvenido al sitio! Tenga en cuenta que preferimos las respuestas más elaboradas. Deberían ser útiles para los googlers del futuro.
peterh - Restablece a Mónica el