¿Cómo un shell (bash, por ejemplo) expande los patrones comodín?

9

Suponga que un directorio tiene 100 archivos que comienzan con la letra 'a'.

Si hago una grep <some string> a*desde la terminal, ¿cómo manejará esto el shell?

¿Expandirá la expresión regular, obtendrá una lista de todos los archivos comenzando con a y grep en cada uno de ellos secuencialmente? ¿O hay alguna otra manera?

Suponga que tengo una matriz de los nombres de archivo anteriores que comienzan con 'a'. ¿Tomará más / menos tiempo si escribo un bucle for y hago la iteración yo mismo en un script de shell o un programa ac?

harithski
fuente
77
Por cierto, no es una globexpresión regular. Gran diferencia.
Aaron D. Marasco el

Respuestas:

8

Primero, un nitpick: una cadena como a*en la sintaxis de shell normal es un glob, que funciona de manera diferente a las expresiones regulares.

En una descripción general de alto nivel, el intérprete de shell (es decir, bash) expande la cadena a*a una lista de cada nombre de archivo que coincida con el patrón a*. Estos luego se convierten en parte de los parámetros de la línea de comandos para una sola instancia de grep(para los programadores, todas las palabras expandidas van como cadenas separadas en el argvargumento de main). Ese grepcomando único analiza los argumentos de la forma que elija, y depende de grepinterpretar esos argumentos como nombres de archivo, opciones, argumentos de opción, expresiones regulares, etc., y tomar las acciones apropiadas. Todo ocurre secuencialmente (AFAIK ninguna grepimplementación usa múltiples hilos).

Si implementa un bucle en un script de shell para hacer lo mismo, es casi seguro que será más lento que el proceso anterior, por las siguientes razones. Si genera un nuevo proceso grep para cada archivo, seguramente será más lento debido a la sobrecarga de la creación del proceso que se multiplica innecesariamente. Si construyó la lista de argumentos usted mismo en el script de shell y utilizó una sola instancia de grep, todo lo que haga en shell seguirá siendo más lento porque los comandos de shell deben interpretarse (por bash), lo que agrega una capa adicional de código, y obtendrá simplemente vuelva a implementar lo que bash ya hacía más rápido internamente en el código compilado.

En cuanto a escribirlo usted mismo en C, probablemente pueda obtener fácilmente un rendimiento comparable al proceso descrito en el primer párrafo, pero es poco probable que pueda lograr una ganancia de rendimiento suficiente sobre las implementaciones grep / bash actuales para justificar el tiempo gastado sin profundizar en optimizaciones de rendimiento específicas de la máquina o sacrificar la portabilidad. Tal vez podría intentar crear una versión paralelizable arbitrariamente grep, pero incluso eso puede no ayudar, ya que es más probable que esté vinculado a E / S que a CPU. La expansión global y el grep ya son "lo suficientemente rápidos" para la mayoría de los propósitos "normales".

jw013
fuente
Gracias por la respuesta muy detallada. En realidad, necesito grep archivos comprimidos (pocos GB cada uno). Tengo una lista de esos archivos. Ahora tengo la opción de construir una expresión regular (complicada) para que coincida con esos archivos o iterar sobre la lista conocida y ejecutar grep en cada uno de ellos (fácil). De ahí la preocupación por el rendimiento.
harithski
intente zcaty zgrep; no es necesario descomprimirlos uno por uno
jw013
Sí, por supuesto. Estoy usando zgrep.
harithski
6

Sí, se expandirá a una lista de archivos y alimentará la lista resultante al grepprograma. Al menos eso es lo que man bashdice en la subsección Pathname Expansion .

Hay otra forma de usar la expansión en casos simples como usted menciona: escriba grep <some_string> ay antes de presionar* , presione ESC. Esto expandirá la lista de archivos coincidentes directamente en la línea de comando, para que pueda verificar que la lista esté bien antes de presionar Enter.

En cuanto a la segunda parte de su pregunta, depende. Si quiere escribir un bucle for que ejecute grep en cada uno de los archivos, entonces definitivamente sería más lento, porque el programa grep se ejecutará no una vez, sino una vez por archivo. Sin embargo, lo que es importante tener en cuenta es que hay un cierto límite en la longitud expandida de los argumentos de la línea de comandos que puede usar, aunque generalmente es bastante alto. Para ver eso, puedes intentarlo grep adasdsadf /usr/*/*/* >/dev/null.

rozcietrzewiacz
fuente
2
ESC+*no es exactamente lo mismo que dejar que bash se expanda * porque ESC+*insertará archivos de puntos (nombres que comienzan con a .) mientras que la expansión de *depende de la dotglob shoptconfiguración. La secuencia de teclas para expandir e insertar globos es C-x *por defecto y se asigna al comando readline glob-expand-word.
jw013
1
@ jw013 ¡Gracias por la información! No parece cambiar el caso de a*expansión, pero ciertamente es importante en un ámbito más amplio.
rozcietrzewiacz
2
zshnota: simplemente presionando la tecla de tabulación en los parámetros expandibles (patrones globales, expansión de llaves, sustitución de comandos, ...) los expandirá.
Stéphane Gimenez
@ jw013 En realidad, acabo de probar el C-xacceso directo y no expande la lista de archivos en mi sistema (usando bash).
rozcietrzewiacz
1
@roz Correcto: de todos modos, casi nunca lo uso, solo quería señalar la diferencia (bastante quisquillosa) :). C-x *solo hace globos que solo hacen nombres de archivo, pero en Esc *realidad hace mucho más, ya que es insert-completions, como en todas las terminaciones posibles. Esto significa que el uso Esc *en una línea de comando vacía insertará el nombre de cada archivo ejecutable en su $PATH, por ejemplo.
jw013