2 votos

¿Cómo controlar el almacenamiento en búfer para los procesos en tuberías de terminal? `stdbuf` parece que no funciona

Estoy tratando de usar stdbuf para controlar el buffering de un proceso en la terminal, específicamente usando stdbuf -o0 para evitar el buffering al redirigir la salida, más o menos como se describe en esta respuesta, pero no parece funcionar como se espera, o de hecho tener algún efecto. Compare la salida de los siguientes comandos:

{ echo 'foo' ; sleep 2 ; echo 'boo'} | grep 'oo'
{ echo 'foo' ; sleep 2 ; echo 'boo'} | grep 'oo' | cat
{ echo 'foo' ; sleep 2 ; echo 'boo'} | stdbuf -o0 grep 'oo' | cat
{ echo 'foo' ; sleep 2 ; echo 'boo'} | stdbuf -o1M grep 'oo'

En el primero, grep procesa y muestra cada línea según se recibe. En el segundo, la salida aparece toda junta después de 2 segundos, como se explica aquí, grep hace buffering de su salida cuando no la envía a una terminal. Ambos son como se espera. El tercero es la sorpresa: según la primera respuesta enlazada y toda otra documentación/discusión que he encontrado, stdbuf -o0 debería evitar que grep haga buffering de su salida, por lo que esto debería actuar como el primer comando. Sin embargo, en mis Macs actúa como el segundo comando, la salida llega toda junta después de 2 segundos. De manera similar, el cuarto comando debería (según entiendo) hacer que grep haga buffering de su salida, y por lo tanto actuar como el segundo, pero no lo hace, da una salida incremental como el primero.

¿Por qué stdbuf no tiene ningún efecto aquí? ¿Los demás usuarios de Mac obtienen los mismos resultados? ¿Y hay alguna forma alternativa de prevenir el buffering de manera general en las tuberías? He probado con stdbuf -oL (buffering de línea), y esto tampoco tiene ningún efecto. También lo he intentado con gstdbuf, es decir, la versión instalada por coreutils de Homebrew, y tampoco tiene efecto; tanto stdbuf como gstdbuf son de la versión 9.5. Lo he intentado en tres Macs, corriendo Sonoma 14.4.1 y Ventura 13.6.6, y Monterey 12.7.4, con resultados idénticos. He probado con unbuffer del paquete expect; eso tampoco tiene ningún efecto aquí. Y lo he probado con otros objetivos de la cadena de tuberías además de cat; todos parecen mostrar el mismo comportamiento.

La opción específica de grep --line-buffered funciona, al igual que reemplazar grep por ag, que no hace buffering por defecto en las tuberías. Sin embargo, mi caso de uso real es con ripgrep-all (rga) que hace buffering en las tuberías como grep pero no ofrece una opción de buffering de línea o sin buffering, y parece ser un problema general con stdbuf y unbuffer (no encuentro ningún caso en el que parezcan tener algún efecto), así que realmente me gustaría encontrar una respuesta general si es posible.

3voto

David Anderson Puntos 2189

El problema es causado por System Integrity Protect (SIP). A continuación se presentan posibles soluciones.

Nota: Algunas de las soluciones presuponen que /usr/local/bin ocurre antes que /usr/bin en la variable PATH.

A continuación se muestra el PATH predeterminado para macOS Monterey.

% echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

Nota: Cuando sea apropiado, es posible que necesites ingresar el comando hash -r para vaciar la tabla hash después de ingresar ciertos comandos.

  • Utiliza el comando a continuación para copiar grep a una carpeta no cubierta por SIP. En este caso, sería el directorio /usr/local/bin.

    rm -f /usr/local/bin/grep
    cp /usr/bin/grep /usr/local/bin
  • Utiliza el comando a continuación para instalar ggrep (GNU grep) 3.11.

    brew install grep

    Luego, puedes usar el siguiente comando para cambiar la variable PATH.

    PATH="/usr/local/opt/grep/libexec/gnubin:$PATH"

    O, en su lugar, deja la ruta en paz y copia el enlace simbólico ggrep a grep en el directorio /usr/local/bin, como se muestra a continuación.

    rm -f /usr/bin/local/grep
    ln -s "$(readlink /usr/local/bin/ggrep)" /usr/local/bin/grep

    O, nuevamente, deja la ruta en paz y utiliza solo ggrep en lugar de grep.


A continuación se ofrece una explicación más detallada de cómo funciona setbuf.

El comando setbuf establece una o más variables ambientales en un intento de hacer que el comando especificado como un parámetro posicional utilice una biblioteca diferente. Las funciones en esta biblioteca luego actúan sobre una o más de estas variables ambientales para cambiar el almacenamiento en búfer. SIP evita que los comandos almacenados en directorios protegidos puedan usar bibliotecas distintas a las especificadas al crear el comando.

El comando setbuf no está incluido en macOS. Instalé setbuf utilizando el siguiente comando.

brew install coreutils

Se crean enlaces simbólicos tanto para setbuf como para gsetbuf hacia el comando que se muestra a continuación.

/usr/local/Cellar/coreutils/9.5/bin/gstdbuf

La biblioteca asociada con setbuf se muestra a continuación.

/usr/local/Cellar/coreutils/9.5/libexec/coreutils/libstdbuf.so

Es posible realizar las mismas operaciones que con setbuf sin utilizar realmente setbuf o gsetbuf. Por ejemplo, los comandos a continuación pueden ejecutarse sin utilizar setbuf.

{ echo 'foo' ; sleep 2 ; echo 'boo'} | stdbuf -o0 grep 'oo' | cat

Para demostrar esto, primero utilizaré setbuf para obtener la variable ambiental deseada y su valor, como se muestra a continuación.

% diff <(env) <(stdbuf -o0 env)              
0a1
> _STDBUF_O=0
24c25
< _=/usr/bin/env
---
> _=/usr/local/bin/stdbuf

Luego, grep se copia a un directorio no cubierto por SIP, como se muestra a continuación.

rm -f /usr/local/bin/grep
cp /usr/bin/grep /usr/local/bin

Finalmente, los comandos a continuación hacen lo mismo sin setbuf.

Nota: Puedes encontrar la descripción de DYLD_INSERT_LIBRARIES ingresando man dyld.

{ echo 'foo' ; sleep 2 ; echo 'boo'} | DYLD_INSERT_LIBRARIES=/usr/local/Cellar/coreutils/9.5/libexec/coreutils/libstdbuf.so _STDBUF_O=0 grep 'oo' | cat

O utiliza los comandos a continuación.

export DYLD_INSERT_LIBRARIES=/usr/local/Cellar/coreutils/9.5/libexec/coreutils/libstdbuf.so
{ echo 'foo' ; sleep 2 ; echo 'boo'} | _STDBUF_O=0 grep 'oo' | cat

Lo que está sucediendo aquí es que grep está utilizando las funciones en libstdbuf.so. Estas funciones utilizan la variable _STDBUF_O para determinar que no se debe utilizar ningún búfer.


Para ver qué variables ambientales adicionales se están pasando a grep en el ejemplo anterior, creé el archivo llamado myenv.c que contiene el siguiente código fuente.

#include 
int main(int argc, char *argv[], char *envp[]) {
    while (*envp) printf("%s\n",*envp++);
    return 0;
}

Utilicé el comando gcc mostrado a continuación para crear el comando ejecutable myenv. Este comando myenv imprime las variables ambientales.

gcc myenv.c -o myenv

Para ver qué variables ambientales adicionales se están pasando, ingresé el siguiente comando. La salida también se muestra a continuación.

% diff <(./myenv) <(stdbuf -o0 ./myenv)
0a1,2
> DYLD_INSERT_LIBRARIES=/usr/local/Cellar/coreutils/9.5/libexec/coreutils/libstdbuf.so
> _STDBUF_O=0
24c26,27
< _=/Users/davidanderson/myenv/./myenv
---
> _=/usr/local/bin/stdbuf
> DYLD_FORCE_FLAT_NAMESPACE=y

Esta salida muestra DYLD_FORCE_FLAT_NAMESPACE como una variable ambiental adicional, que no incluí en el ejemplo anterior. No creo que esta variable sea necesaria para macOS Monterey y versiones más nuevas de macOS, ya que la variable no está definida en la salida del comando man dyld. Sin embargo, encontré la variable DYLD_FORCE_FLAT_NAMESPACE definida en la salida del comando man dyld para macOS Catalina y macOS High Sierra. Por lo tanto, es posible que versiones más antiguas de macOS y OS X necesiten esta variable definida.


Referencias

AppleAyuda.com

AppleAyuda es una comunidad de usuarios de los productos de Apple en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X