¿Por qué `watch` hace que` ls / tmp` enumere los contenidos de $ HOME?

13

Estoy tratando de ver el número de archivos en mi /tmp/directorio. Para esto pensé que este comando funcionaría:

watch sh -c 'ls /tmp/|wc -l'

Pero parece funcionar como si lsno tuviera argumentos. A saber, estoy dentro ~, y obtengo el número de archivos allí en lugar de /tmp/. Encontré una solución, que parece funcionar:

watch sh -c 'ls\ /tmp/|wc -l'

Pero, ¿por qué necesito escapar del espacio entre lsy /tmp/? ¿Cómo se transforma el comando watchpara que la lssalida se alimente wc, pero /tmp/no se pase como argumento ls?

Ruslan
fuente
1
watch "sh -c 'ls /tmp | wc -l'"hacer este comando debería obtener el efecto deseado. Esto no es culpa de los relojes, intente sh -c ls /tmpy obtendrá su directorio de inicio (pero no tengo idea de por qué ...)
Jacob Minshall
8
No es una respuesta, sino que está utilizando watchde forma incorrecta .El comando que se pasa a watchsu vez es alimentado por watcha sh -c, por lo que está haciendo en efecto sh -cdos veces.
iruvar
Si tiene curiosidad, también puede echar un vistazo a la fuente .
michas
1
@JacobMinshall, el por qué es sencillo: el /tmpes un argumento para sh, en ese caso, no un argumento para ls.
Charles Duffy

Respuestas:

17

La diferencia se puede ver a través de strace:

$ strace -ff -o bq watch sh -c 'ls\ /tmp/|wc -l'
^C
$ strace -ff -o nobq watch sh -c 'ls /tmp/|wc -l'
^C
$ grep exec bq* | grep sh
bq.29218:execve("/usr/bin/watch", ["watch", "sh", "-c", "ls\\ /tmp/|wc -l"], [/* 54 vars */]) = 0
bq.29219:execve("/bin/sh", ["sh", "-c", "sh -c ls\\ /tmp/|wc -l"], [/* 56 vars */]) = 0
bq.29220:execve("/bin/sh", ["sh", "-c", "ls /tmp/"], [/* 56 vars */]) = 0
$ grep exec nobq* | grep sh
nobq.29227:execve("/usr/bin/watch", ["watch", "sh", "-c", "ls /tmp/|wc -l"], [/* 54 vars */]) = 0
nobq.29228:execve("/bin/sh", ["sh", "-c", "sh -c ls /tmp/|wc -l"], [/* 56 vars */]) = 0
nobq.29229:execve("/bin/sh", ["sh", "-c", "ls", "/tmp/"], [/* 56 vars */]) = 0

En el caso de la comilla inversa, ls /tmpse pasa como un argumento único a -cto sh, que se ejecuta como se esperaba. Sin esta comilla inversa, el comando se divide en palabras cuando se watchejecuta, shque a su vez ejecuta el suministrado sh, de modo que solo lsse pasa como argumento a -c, lo que significa que el sub-sub shsolo ejecutará un lscomando simple y enumera el contenido del trabajo actual directorio.

Entonces, ¿por qué la complicación de sh -c ...? ¿Por qué no simplemente correr watch 'ls /tmp|wc -l'?

thrig
fuente
Oh, de hecho, no pensé en intentarlo strace.
Ruslan
1
En realidad, `es una comilla inversa (o marca de retroceso). Esta pregunta es sobre \, que es la barra invertida.
G-Man dice 'reinstalar a Monica' el
@Ruslan: publiqué este comentario en esta respuesta porque es un comentario sobre esta respuesta . thrig dice "En el caso de la comilla invertida , ls /tmpes ..." y "Sin esta comilla invertida , el comando es ...", y usa bqy nobqcomo nombres de archivo, cuando todo se refiere a la barra invertida en su ls\ /tmpcomando.
G-Man dice 'Reincorporar a Monica' el
8

Hay dos categorías principales de watchcomandos (de los que se van a ejecutar comandos periódicamente, watchno es un comando estándar, incluso hay sistemas en los que watchhace algo completamente diferente como espiar en otra línea tty en FreeBSD).

Uno que ya pasa la concatenación de sus argumentos con espacios a un shell (en efecto llama sh -c <concatenation-of-arguments>) y otro que simplemente ejecuta el comando especificado con los argumentos especificados sin invocar un shell.

Estás en la primera situación, así que solo necesitas:

watch 'ls /tmp/|wc -l'

Cuando tu lo hagas:

watch sh -c 'ls /tmp/|wc -l'

tu watchrealmente corre:

sh -c 'sh -c ls /tmp/|wc -l'

Y sh -c ls /tmp/ejecuta el lsscript en línea donde $0está /tmp/(por lo que lsse ejecuta sin argumentos y enumera el directorio actual).

Algunas de las watchimplementaciones en la primera categoría (como la de procps-ng en Linux) aceptan una -xopción para que se comporten como las watchde la segunda categoría. Entonces, allí, puedes hacer:

watch -x sh -c 'ls /tmp/|wc -l'
Stéphane Chazelas
fuente