en zsh
rm foo.bar
impresionesrm: foo.bar: No such file or directory
.rm foo.bar 2>/dev/null
no imprime nada, como esperaba.
Pero si el comando contiene coincidencias de patrones, el error no se suprime mediante 2>/dev/null
:
rm *.bar
impresioneszsh: no matches found: *.bar
.rm *.bar 2>/dev/null
imprime lo mismo.
¿Existe una forma general de suprimir los mensajes de error en zsh? Unosimplemétodo para todo tipo de mensajes de error.
Respuesta1
Cuando intentas ejecutar rm *.bar
y no hay ningún archivo que coincida *.bar
, básicamente pueden pasar dos cosas:
El comportamiento POSIX es que el shell se ejecuta
rm
con literal*.bar
como argumento. La herramienta intenta eliminar un archivo llamado literalmente*.bar
(como si ejecutararm '*.bar'
), falla e imprime algo comorm: *.bar: No such file or directory
. Así es comosh
se comportan el shell POSIX ( ) y los shells compatibles.El comportamiento que no es POSIX es que el shell detecta que no hay coincidencia, imprime algo así como
no matches found: *.bar
y no se ejecutarm
en absoluto. Así es como se comporta Zsh por defecto. (A modo de comparación: en Bash puedes cambiar a este comportamiento medianteshopt -s failglob
.)
Un error del rm
(caso anterior) se imprime en el stderr de rm
. Un error del shell (el último caso) se imprime en el stderr del shell.
La redirección rm *.bar 2>/dev/null
afecta al stderr de rm
solo + . En tu ejemplo rm
ni siquiera se ejecuta.
Para redirigir el stderr del shell principal necesita exec
. Por "el shell principal" me refiero al shell interactivo donde escribes; o, en caso de ejecutar un script, el shell que interpreta el script. Ejemplo:
exec 2>/dev/null
En Zsh incluso puedes cerrarlo:
exec 2>&-
Un enfoque razonable es duplicar el stderr original de antemano, en caso de que necesite usarlo (o restaurarlo) más adelante. Ejemplo (tenga en cuenta que si desea pegar este código en un Zsh interactivo, debe invocarsetopt interactive_comments
primero):
exec 7>&2 # "save" stderr
exec 2>/dev/null # redirect
rm *.bar
whatever
exec 2>&7 # restore
exec 7>&- # close descriptor which is no longer needed
Aquí 7
hay un número arbitrario de un solo dígito, que de otro modo no se utilizaría como descriptor de archivo. Las dos primeras líneas se pueden escribir como una: exec 7>&2 2>/dev/null
; De manera similar, las dos últimas líneas pueden hacerlo.
Tenga en cuenta rm
que cualquier comando (como whatever
) invocado sin redirección stderr hereda stderr del shell. Esto significa que en el ejemplo anterior rm
(si alguna vez se ejecuta) whatever
imprimirá sus mensajes de error (si los hay) en /dev/null
. Después exec 2>/dev/null
podrás invocar cualquier número o comando y todos tendrán /dev/null
como stderr. Si realmente desea suprimir todo tipo de mensajes de error, esta es la manera.
La solución funciona en muchos shells, no solo en Zsh. Si desea redirigir stderr de un shell interactivo, tenga en cuenta que algunos shells menos sofisticados usan stderr para imprimir su mensaje.
Tenga en cuenta que un proceso puede redirigir su propio stderr (como lo hace nuestro shell con exec 2>…
); o puede imprimir mensajes de error en stdout o /dev/tty
(no debería, pero técnicamente puede hacerlo). Por lo exec 2>/dev/null
tantonoLe garantizamos que no verá mensajes que parezcan mensajes de error.
Después exec 2>/dev/null
aún puede redirigir stderr de cualquier comando a pedido. Por ejemplo, si en lugar de eso whatever
tuviéramos:
whatever 2>&7
entonces sus mensajes de error irían al stderr original que guardamos deliberadamente como descriptor de archivo 7
.
exec
no es la única forma de redirigir stderr (u otro descriptor de archivo) de un shell. Puede tratar un caparazón (aunque no todo el caparazón principal) como lo hace rm
en rm … 2>/dev/null
. Puedes tratar una subcapa de esta manera:
( rm *.bar ) 2>/dev/null
o simplemente algún código interpretado por el shell principal:
{ rm *.bar; } 2>/dev/null
( ;
aquí es opcional en Zsh, obligatorio en algunos otros shells; solo quería que el código funcionara también fuera de Zsh).
Cualquiera de estas líneas puede solucionar tu problema. En el contexto de la supresión de mensajes de error provenientes de un shell, las dos líneas son equivalentes ++ .
Tenga en cuenta que la redirección comienza en (
/ {
y termina en )
/ }
, su alcance es limitado. Aún así, puede colocar muchos comandos entre paréntesis, por lo que el alcance "limitado" puede ser en realidad todo el script. O puede ser solo una parte del guión (incluido whatever
lo que se presentó anteriormente en esta respuesta, lo que quiera). El hecho de que la redirección deje de funcionar después de )
/ }
hace que este enfoque sea una excelente alternativa a guardar y restaurar stderr con exec
.
+ Estrictamente: afecta " rm
" antes y después de convertirse (o intentar convertirse) rm
. Tengan paciencia conmigo. La redirección rm … 2>/dev/null
comienza como una redirección realizada por unshell bifurcado que está a punto de reemplazarse con rm
un ejecutable. Este shell redirige su propio stderr antes de intentar reemplazarse con rm
. Cualquier error que informe después de realizar la redirección irá a /dev/null
. Por ejemplo, si rm
no se encuentra, la redirección ya realizada suprimirá el command not found
error.
++ Técnicamente ( … )
crea una subcapa, { … }
no lo hace. Un subshell se comporta como un proceso de shell separado que hereda las variables y el directorio de trabajo actual, pero no puede cambiar nada (como las variables o el directorio de trabajo actual) en su shell principal. Puede que se realice o no como un proceso verdaderamente separado; lo que importa es el comportamiento. OTOH { … }
solo agrupa comandos para algún propósito (en nuestro caso el propósito es la redirección). Esto no significa que nunca haya una subcapa cuando usas { … }
; por ejemplo, en una tubería, todas las partes excepto la última son subcapas de todos modos (en algunas capas: todas las partes, incluida la última). Estos detalles técnicos son irrelevantes en el contexto de nuestro problema únicamente, pero en general pueden marcar la diferencia.