¿Cómo redirigir stdout a un archivo y stdout + stderr a otro?

32

¿Cómo puedo lograr

cmd >> file1 2>&1 1>>file2

Es decir, stdout y stderr deberían redirigir a un archivo (archivo1) y solo stdout (archivo2) debería redirigir a otro (ambos en modo de agregado).

Swarna Gowri
fuente

Respuestas:

41

El problema es que cuando redirige su salida, ya no está disponible para la próxima redirección. Puede canalizar teeen una subshell para mantener la salida para la segunda redirección:

( cmd | tee -a file2 ) >> file1 2>&1

o si desea ver la salida en la terminal:

( cmd | tee -a file2 ) 2>&1 | tee -a file1

Para evitar agregar el stderr del primero teea file1, debe redirigir el stderr de su comando a algún descriptor de archivo (por ejemplo, 3), y luego agregar esto a stdout nuevamente:

( 2>&3 cmd | tee -a file2 ) >> file1 3>&1
# or
( 2>&3 cmd | tee -a file2 ) 3>&1 | tee -a file1

(gracias @ fra-san)

pLumo
fuente
16

Con zsh:

cmd >& out+err.log > out.log

En modo agregar:

cmd >>& out+err.log >> out.log

En zsh, y siempre que la mult_iosopción no se haya deshabilitado, cuando un descriptor de archivo (aquí 1) se redirige varias veces para escribir, el shell implementa una función integrada teepara duplicar la salida a todos los destinos.

Stéphane Chazelas
fuente
No puedo entender qué out+erry outdecir aquí. Nombres de archivo? ¿Flujos para ser redirigidos?
gronostaj
@gronostaj Piensa que el comando dicecmd >& file1 > file2
Isaac
Esta solución preservará el orden en que se generó la salida. Para almacenar realmente el stdout y el stderr (en ese orden) necesita un enfoque diferente.
Isaac
1
@Isaac, el orden no se conservará necesariamente ya que la salida stdout pasará por una tubería (a un proceso que lo reenvía a cada archivo) mientras que la salida stderr irá directamente al archivo. En cualquier caso, no parece que el OP haya pedido que la salida stderr venga después de la salida estándar.
Stéphane Chazelas
3

Podrías: etiquetar stdout (usando un sed UNBUFFERED, es decir:) sed -u ..., hacer que stderr también vaya a stdout (sin etiquetar, ya que no pasó por ese etiquetado sed), y así poder diferenciar los 2 en el archivo de registro resultante.

Lo siguiente: es lento (puede optimizarse seriamente, usando por ejemplo un script perl en lugar de while ...; do ...; hecho, por ejemplo, ¡eso generará subshells y comandos en cada línea!), Extraño (parece que necesito las 2 {} etapas para cambiar el nombre de una stdout y luego en la otra agregar el stderr "fallado"), etc. Pero es: una " prueba de concepto " que intentará mantener la salida ordena lo más posible stdout & stderr tanto como sea posible:

#basic principle (some un-necessary "{}" to visually help see the layers):
# { { complex command ;} | sed -e "s/^/TAGstdout/" ;} 2>&1 | read_stdin_and_redispatch

#exemple:
# complex command = a (slowed) ls of several things (some existing, others not)
#  to see if the order of stdout&stderr is kept

#preparation, not needed for the "proof of concept", but needed for our specific exemple setup:
\rm out.file out_AND_err.file unknown unknown2 
touch existing existing2 existing3

#and the (slow, too many execs, etc) "proof of concept":
uniquetag="_stdout_" # change this to something unique, that will NOT appear in all the commands outputs... 
                     # avoid regexp characters ("+" "?" "*" etc) to make it easy to remove with another sed later on.

{
   { for f in existing unknown existing2 unknown2 existing3 ; do ls -l "$f" ; sleep 1; done ;
   } | sed -u -e "s/^/${uniquetag}/" ;
} 2>&1 | while IFS="" read -r line ; do
    case "$line" in
       ${uniquetag}*) printf "%s\n" "$line" | tee -a out_AND_err.file | sed -e "s/^${uniquetag}//" >> out.file ;; 
        *)            printf "%s\n" "$line"       >> out_AND_err.file ;;   
    esac; 
done;

# see the results:
grep "^" out.file out_AND_err.file
Olivier Dulac
fuente
Esto es realmente difícil de entender. (1) ¿Por qué utiliza un caso de uso tan complicado ( ls unknown) para imprimir algo en stderr? >&2 echo "error"estaría bien. (2) teepuede agregar a múltiples archivos a la vez. (3) ¿Por qué no solo en catlugar de grep "^"? (4) su script fallará cuando comience la salida stderr _stdout_. (5) ¿Por qué?
pLumo
@RoVo: las primeras 2 líneas comentadas muestran el algoritmo, más simple que el ejemplo de prueba de concepto. 1): esto ls loopsaldrá en stdout y stderr, mezclado (alternativamente), en un orden controlado, para que podamos verificar que mantuvimos ese orden stderr / stdout a pesar del etiquetado de stdout 2): cola de gnu, tal vez, pero no cola regular (ex, en aix.). 3): grep "^" también muestra ambos nombres de archivo. 4): esto puede ser cambiado por la variable. 5): el ejemplo complicado funciona en viejos oses (ej., Viejo aix) donde lo probé (no hay perl disponible).
Olivier Dulac
(1) el eco múltiple para stderr y stout estaría bien, pero está bien, no es importante, sería más fácil de leer. (3) de acuerdo, (4) seguro, pero fallará si comienza con lo que contenga la variable. (5) Ya veo.
pLumo
@RoVo Estoy de acuerdo con tu 1). para 4), la variable podría ser tan compleja como sea necesaria para hacer desaparecer el pb (ej: uniquetag="banaNa11F453355B28E1158D4E516A2D3EDF96B3450406...)
Olivier Dulac
1
Seguro, no es muy probable, pero de todos modos podría presentar un problema de seguridad más adelante. Y luego es posible que desee eliminar esa cadena antes de imprimir en el archivo ;-)
pLumo
2

Si el orden de salida debe ser: stdout, entonces stderr ; no hay solución solo con redireccionamiento.
El stderr debe almacenarse en un archivo temporal

cmd 2>>file-err | tee -a file1 >>file2
cat file-err >> file1
rm file-err

Descripción:

La única forma de redirigir una salida (una fd como stdout o stderr) a dos archivos es reproducirla. El comando teees la herramienta correcta para reproducir el contenido de un descriptor de archivo. Entonces, una idea inicial para tener una salida en dos archivos sería usar:

... |  tee file1 file2

Eso reproduce el stdin de tee en ambos archivos (1 y 2) dejando la salida de tee aún sin usar. Pero necesitamos agregar (usar -a) y solo necesitamos una copia. Esto resuelve ambos problemas:

... | tee -a file1 >>file2

Para suministrar teestdout (el que hay que repetir) necesitamos consumir stderr directamente fuera del comando. Una forma, si el orden no es importante (el orden de salida se conservará (muy probablemente) como generado, el que se envíe primero se almacenará primero). Ya sea:

  1. cmd 2>>file1 | tee -a file2 >>file1
  2. cmd 2>>file1 > >( tee -a file2 >>file1 )
  3. ( cmd | tee -a file2 ) >> file1 2>&1

La opción 2 solo funciona en algunos depósitos. La opción 3 usa una subshell adicional (más lenta) pero usa los nombres de archivo solo una vez.

Pero si stdout debe ser el primero (cualquiera que sea la salida del pedido que se genere), debemos almacenar stderr para agregarlo al archivo al final (primera solución publicada).

Isaac
fuente
1
O almacene en la memoria como lo spongehace:(cmd | tee -a out >> out+err) 2>&1 | sponge >> out+err
Stéphane Chazelas
1

En interés de la diversidad:

Si su sistema es compatible /dev/stderr, entonces

(cmd | tee -a /dev/stderr) 2>> file1 >> file2

trabajará. La salida estándar de cmd se envía tanto al stdout como al stderr de la tubería. El error estándar de la cmdderivación tee y sale del stderr de la tubería.

Asi que

  • el stdout de la tubería es solo el stdout del cmd, y
  • El stderr de la tubería es el stdout y el stderr del cmd, entremezclado.

Entonces se trata simplemente de enviar esas transmisiones a los archivos correctos.

Como ocurre con casi cualquier enfoque como este (incluida la respuesta de Stéphane ), es file1posible que las líneas se salgan de orden.

Scott
fuente