Según tengo entendido, el shell interpreta un comodín glob, que luego ejecuta el comando dado para cada nombre de archivo coincidente. Supongamos que tengo archivos:abc1, abc2, and abc3
en mi directorio actual. Entonces, por ejemplo, echo abc*
se repetirá una vez por cada nombre de archivo que comience con 'abc'.
Sin embargo, si ejecuto grep 'foo' abc*
, me imagino que esto debería ejecutarse:
grep 'foo' abc1
grep 'foo' abc2
grep 'foo' abc3
Lo que significa que debería obtener el siguiente resultado (suponiendo que todos los archivos contengan una línea que diga "foo"):
foo
foo
foo
Sin embargo, en su lugar obtengo:
abc1:foo
abc2:foo
abc3:foo
Así que me imagino que hay 2 posibles explicaciones para esto. Primero, de alguna manera grep puede detectar que se usó con expresiones globales y responde mostrando los nombres de archivo antes de las coincidencias. En segundo lugar, dado que puede pasar varios archivos a grep, el shell en realidad ejecuta solo 1 comando:
grep 'foo' abc1 abc2 abc3
Sin embargo, esto solo funciona porque grep acepta múltiples archivos al final. Es posible que otro comando solo permita pasar 1 archivo. Por lo tanto, si quisiera ejecutar el comando para varios archivos que coincidan con el glob, no funcionaría si globbing funcionara a través del segundo método descrito anteriormente.
De todos modos, ¿alguien puede arrojar algo de luz sobre esto?
¡Gracias!
Respuesta aceptada:
Ese es el truco:el comando no sabe, es el shell el que hace el trabajo
Considere, por ejemplo, grep 'abc' *.txt
. Si ejecutamos un seguimiento de las llamadas al sistema, verá algo como esto:
bash-4.3$ strace -e trace=execve grep "abc" *.txt > /dev/null
execve("/bin/grep", ["grep", "abc", "ADDA_converters.txt", "after.txt", "altera_license.txt", "altera.txt", "ANALOG_DIGITAL_NOTES.txt", "androiddev.txt", "answer2.txt", "answer.txt", "ANSWER.txt", "ascii.txt", "askubuntu-profile.txt", "AskUbuntu_Translators.txt", "a.txt", "bash_result.txt", ...], [/* 80 vars */]) = 0
+++ exited with 0 +++
El shell expandió *.txt
en todos los nombres de archivo en el directorio actual que terminan con .txt
extensión. De manera tan efectiva, su shell traduce el grep 'abc' *.txt
comando en grep 'abc' file1.txt file2.txt file3.txt . . .
. Por lo tanto, su segunda suposición es correcta.
La primera suposición no es correcta:los programas no tienen forma de detectar glob. Es posible pasar *
como argumento de cadena para el comando, pero es el trabajo del comando decidir qué hacer con él en ese momento. La expansión del nombre de archivo, sin embargo, es propiedad de su shell respectivo como ya he mencionado.
Sin embargo, esto solo funciona porque grep acepta múltiples archivos al final. Es posible que otro comando solo permita pasar 1 archivo.
Exactamente correcto ! Los programas no limitan la cantidad de argumentos de línea de comandos aceptables (por ejemplo, en C, esa es una matriz de cadenas const char *args[]
y en python sys.argv[]
), pero pueden detectar la longitud de esa matriz o si algo inesperado está o no en la posición incorrecta de la matriz. grep
no hace eso y acepta múltiples archivos, lo cual es por diseño.
En la nota al margen, las citas incorrectas junto con el globbing con grep a veces pueden ser un problema. Considere esto:
bash-4.3$ echo "one two" | strace -e trace=execve grep *est*
execve("/bin/grep", ["grep", "self_test.sh", "test.wxg"], [/* 80 vars */]) = 0
+++ exited with 1 +++
El usuario no preparado esperaría que grep coincida con cualquier línea con est
las letras en él provienen de la tubería, pero en cambio, la expansión del nombre de archivo de Shell lo torció todo. He visto que esto sucede mucho con personas que hacen ps aux | grep shell_script_name.sh
, y esperan encontrar su proceso ejecutándose, pero porque ejecutaron el comando desde mismo directorio donde estaba el script , la expansión del nombre de archivo de shell se hizo grep
comando para lucir detrás de escena completamente diferente de lo que el usuario esperaba.
La forma correcta sería usar comillas simples:
bash-4.3$ echo "one two" | strace -e trace=execve grep '*est*'
execve("/bin/grep", ["grep", "*est*"], [/* 80 vars */]) = 0
+++ exited with 1 +++