¿Buscar recursivamente un patrón / texto solo en el nombre de archivo especificado de un directorio?

16

Tengo un directorio (por ejemplo, abc/def/efg) con muchos subdirectorios (por ejemplo ,:) abc/def/efg/(1..300). Todos estos subdirectorios tienen un archivo común (por ejemplo, file.txt). Quiero buscar una cadena solo en esto, file.txtexcluyendo otros archivos. ¿Cómo puedo hacer esto?

Solía grep -arin "pattern" *, pero es muy lento si tenemos muchos subdirectorios y archivos.

Rajesh Keladimath
fuente

Respuestas:

21

En el directorio principal, puede usar findy luego ejecutar grepsolo esos archivos:

find . -type f -iname "file.txt" -exec grep -Hi "pattern" '{}' +
Zanna
fuente
2
Sugiero también pasar -Ha grepfin de que, en los casos en un solo camino se pasa a ella, ese camino todavía se imprime (en lugar de sólo las líneas coincidentes desde el archivo).
Elías Kagan
24

También es posible usar Globstar.

La construcción de grepcomandos con find, al igual que en la respuesta de Zanna , es un versátil, y de manera muy robusta y portátil para hacer esto (véase también la respuesta de sudodus ). Y Muru ha publicado un excelente enfoque de usar grep's --includeopción . Pero si desea utilizar sólo el grepmando y su concha, hay otra manera de hacerlo - usted puede hacer el depósito propiamente realizar la recursividad es necesario :

shopt -s globstar   # you can skip this if you already have globstar turned on
grep -H 'pattern' **/file.txt

Las -Hmarcas de bandera grepmuestran el nombre del archivo, incluso si sólo se encuentra un archivo coincidente. Puede pasar el -a, -iy -nbanderas (de su ejemplo) para grep, así, si eso es lo que necesita. Pero no deje pasar -ro -Rcuando se utiliza este método. Es la cáscara que recursivamente directorios en la expansión del patrón global que contiene **, y nogrep .

Estas instrucciones son específicas del shell Bash. Bash es el shell de usuario predeterminado en Ubuntu (y la mayoría de los otros sistemas operativos GNU / Linux), por lo que si está en Ubuntu y no sabe cuál es su shell, es casi seguro que sea Bash. Aunque los shells populares generalmente admiten **globos de desplazamiento de directorio , no siempre funcionan de la misma manera. Para obtener más información, consulte la excelente respuesta de Stéphane Chazelas a El resultado de ls *, ls ** y ls *** en Unix.SE .

Cómo funciona

Encendido de la Globstar golpe opción shell hace **caminos de los partidos que contienen el separador de directorio ( /). Por lo tanto, es un globo recurrente de directorio. Específicamente, como man bashexplica:

Cuando la opción de shell globstar está habilitada y * se usa en un contexto de expansión de nombre de ruta, dos * s adyacentes utilizados como un solo patrón coincidirán con todos los archivos y cero o más directorios y subdirectorios. Si seguido de a /, dos * s adyacentes coincidirán solo con directorios y subdirectorios.

Debes tener cuidado con esto, ya que puedes ejecutar comandos que modifiquen o eliminen muchos más archivos de los que pretendes, especialmente si escribes **cuando querías escribir *. (Es seguro en este comando, que no cambia ningún archivo).shopt -u globstar Desactiva la opción de shell globstar.

Hay algunas diferencias prácticas entre globstar y find .

findEs mucho más versátil que Globstar. Cualquier cosa que puedas hacer con globstar, también puedes hacerlo con el findcomando. Me gusta globstar, y a veces es más conveniente, pero globstar no es una alternativa generalfind .

El método anterior no busca dentro de los directorios cuyos nombres comienzan con a .. A veces no desea repetir estas carpetas, pero a veces sí.

Al igual que con un globo ordinario, el shell construye una lista de todas las rutas coincidentes y las pasa como argumentos a su comando ( grep) en lugar del globo mismo. Si tiene tantos archivos llamados file.txtque el comando resultante sería demasiado largo para que el sistema se ejecute, entonces el método anterior fallará. En la práctica, necesitaría (al menos) miles de esos archivos, pero podría suceder.

Los métodos que utilizan findno están sujetos a esta restricción, porque:

  • La manera de Zanna construye y ejecuta un grepcomando con potencialmente muchos argumentos de ruta. Pero si se encuentran más archivos de los que se pueden enumerar en una sola ruta, la acción +terminada -execejecuta el comando con algunas de las rutas, luego lo ejecuta nuevamente con algunas rutas más, y así sucesivamente. En el caso de grepuna cadena en varios archivos, esto produce el comportamiento correcto.

    Al igual que el método globstar cubierto aquí, imprime todas las líneas coincidentes, con rutas antepuestas a cada una.

  • el camino de sudodus se ejecuta greppor separado para cadafile.txt encontrado. Si hay muchos archivos, puede ser más lento que otros métodos, pero funciona.

    Ese método encuentra archivos e imprime sus rutas, seguido de líneas coincidentes, si las hay. Este es un formato de salida diferente del formato producido por mi método, el de Zanna y el de muru .

Obtener color con find

Uno de los beneficios inmediatos de usar globstar es que, por defecto en Ubuntu, grepproducirá resultados coloreados. Pero se puede conseguir fácilmente con este find, también .

Las cuentas de usuario en Ubuntu se crean con un alias que hace que greprealmente se ejecute grep --color=auto(ejecutar alias greppara ver). Es bueno que los alias se expandan prácticamente solo cuando los emite de forma interactiva , pero significa que si desea findinvocar grepcon la --colorbandera, tendrá que escribirla explícitamente. Por ejemplo:

find . -name file.txt -exec grep --color=auto -H 'pattern' {} +
Eliah Kagan
fuente
Es posible que desee indicar más claramente que necesita usar el bashshell para que esto funcione. Usted no lo dice implícitamente en "la opción del shell Globstar fiesta" pero se puede pasar por alto fácilmente por la gente que lee demasiado rápido.
Stig Hemmer
Eliminé mi respuesta porque causó muchos comentarios críticos. Por lo tanto, debe eliminar la referencia en su respuesta.
sudodus
@StigHemmer Gracias: he aclarado que no todos los shells tienen esta característica. Aunque muchos shells (no solo bash) son compatibles con los globos que atraviesan el directorio **, su crítica principal es correcta: la presentación de **esta respuesta es específica de bash, siendo shopt solo bash y el término "globstar" es (creo) bash y solo tcsh. Había pasado por alto esto originalmente debido a esas complejidades, pero tienes razón en que es algo confuso. En lugar de discutirlo detenidamente en esta respuesta, me he vinculado a otra publicación (bastante exhaustiva) que hace el trabajo pesado.
Eliah Kagan
@sudodus Lo he hecho, pero espero que esto sea temporal. Yo y otros hemos encontrado valiosa tu respuesta. Es cierto -eque no debe aplicarse a las rutas, pero esto se soluciona fácilmente. Para el primer comando, simplemente omita -e. Para el segundo, use find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;o find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;. Los usuarios a veces preferirán su camino (con -euso fijo) a los demás, que imprimen una ruta por línea coincidente ; el suyo imprime una ruta por archivo encontrado seguido de grepresultados.
Eliah Kagan
@sudodus Entonces, en grepsí mismo , no hará lo que estás haciendo. Algunas otras críticas también estaban equivocadas. grep -Hejecutado por -execno se coloreará sin --color(o GREP_COLOR). IEEE 1003.1-2008 no garantiza la {}expansión ##### {}:, pero Ubuntu tiene GNU find, que lo hace . Si está bien con usted , editaré su publicación para corregir el -eerror (y aclarar su caso de uso) y podrá ver si desea recuperarla. (Tengo el representante para ver / editar publicaciones eliminadas).
Eliah Kagan
18

No necesitas findpara esto; greppuede manejar esto perfectamente bien por sí solo:

grep "pattern" . -airn --include="file.txt"

De man grep:

--exclude=GLOB
      Skip  files  whose  base  name  matches  GLOB  (using   wildcard
      matching).   A  file-name  glob  can  use  *,  ?,  and [...]  as
      wildcards, and \ to quote  a  wildcard  or  backslash  character
      literally.

--exclude-from=FILE
      Skip  files  whose  base name matches any of the file-name globs
      read from FILE  (using  wildcard  matching  as  described  under
      --exclude).

--exclude-dir=DIR
      Exclude  directories  matching  the  pattern  DIR from recursive
      searches.

--include=GLOB
      Search  only  files whose base name matches GLOB (using wildcard
      matching as described under --exclude).
muru
fuente
Bien, esta parece ser la mejor manera. Simple y eficiente. Desearía haber sabido (o haber pensado en revisar la página de manual) este método. ¡Gracias!
Eliah Kagan
@EliahKagan Estoy más sorprendida de que Zanna no haya publicado esto: había mostrado un ejemplo de esta opción para otra respuesta hace algún tiempo. :)
muru
2
aprendiz lento, por desgracia, pero finalmente llego allí, tus enseñanzas no se desperdician por completo en mí;)
Zanna
Esto es muy simple y fácil de recordar. Gracias.
Rajesh Keladimath
Estoy de acuerdo, que esta es la mejor respuesta. ¿Debo eliminar mi respuesta para disminuir la confusión o dejar que se quede para mostrar que hay alternativas y qué se puede hacer?find?
sudodus
8

El método dado en la respuesta de muru , de correr grepcon la --includebandera para especificar un nombre de archivo, es a menudo la mejor opción. Sin embargo, esto también se puede hacer con find.

El enfoque de esta respuesta se findejecuta greppor separado para cada archivo encontrado e imprime la ruta a cada archivo exactamente una vez , por encima de las líneas coincidentes encontradas en cada archivo. (Los métodos que imprimen la ruta delante de cada línea coincidente están cubiertos en otras respuestas).


Puede cambiar el directorio a la parte superior del árbol de directorios donde tiene esos archivos. Entonces corre:

find . -name "file.txt" -type f -exec echo "##### {}:" \; -exec grep -i "pattern" {} \;

Eso imprime la ruta (en relación con el directorio actual ., e incluye el nombre del archivo) de cada archivo nombrado file.txt, seguido de todas las líneas coincidentes en el archivo. Esto funciona porque {}es un marcador de posición para el archivo encontrado. La ruta de cada archivo se separa de su contenido con un prefijo #####y se imprime solo una vez, antes de las líneas coincidentes de ese archivo. (Los archivos llamados file.txtque no contienen coincidencias aún tienen sus rutas impresas). Puede encontrar este resultado menos abarrotado que el que obtiene de los métodos que imprimen una ruta al comienzo de cada línea coincidente.

Usarlo de findesta manera casi siempre será más rápido que ejecutarlo grepen cada archivo ( grep -arin "pattern" *), porque findbusca los archivos con el nombre correcto y omite todos los demás.

Ubuntu usa GNU find , que siempre se expande {}incluso cuando aparece en una cadena más grande , como ##### {}:. Si necesita su comando para trabajar finden sistemas que pueden no admitir esto , o prefiere usar la -execacción solo cuando sea absolutamente necesario, puede usar:

find . -name "file.txt" -type f -printf '##### %p:\n' -exec grep -i "pattern" {} \;

Para que la salida sea más fácil de leer , puede usar secuencias de escape ANSI para obtener nombres de archivos coloreados. Esto hace que el encabezado de la ruta de cada archivo se destaque mejor de las líneas coincidentes que se imprimen debajo:

find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;

Eso hace que su shell convierta el código de escape para el verde en la secuencia de escape real que produce el verde en un terminal, y haga lo mismo con el código de escape para el color normal. Se pasan estos escapes find, que los usan cuando imprime un nombre de archivo. (la $' 'cita es necesaria aquí porque findla -printfacción no reconoce \epara interpretar códigos de escape ANSI).

Si lo prefiere, puede utilizarlo -execcon el printfcomando del sistema (que sí es compatible \e). Entonces, otra forma de hacer lo mismo es:

find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;
sudodus
fuente
Iba a hacer un "bucle for" con una matriz y no pensé en la opción nativa exec de find. Bueno uno! Pero creo que usar dot te ubicará en el directorio donde ya estás. Corrígeme si estoy equivocado. ¿No sería mejor especificar directamente para analizar en el orden de búsqueda? find abc/def/efg -name "file.txt" -type f -exec echo -e "##### {}:" \; -exec grep -i "pattern" {} \;
kcdtv
Claro, eso eliminará el cd abc/def/efgcomando 'cambiar directorio' :-)
sudodus
(1) ¿Por qué especificas la -eopción echo? Eso hará que destruya cualquier nombre de archivo que contenga barras invertidas. (2) No se garantiza que el uso {}como parte de un argumento funcione. Sería mejor decir -exec echo "#####" {} \;o -exec printf "##### %s:\n" {} \;. (3) ¿Por qué no solo usar -printo -printf? (4) Considere también grep -H.
G-Man dice 'Reincorporar a Monica' el
@ G-man, 1) Porque originalmente usé el color ANSI: find . -name "file.txt" -type f -exec echo -e "\0033[32m{}:\0033[0m" \; -exec grep -i "pattern" {} \;2) Puede que tengas razón, pero hasta ahora esto está funcionando para mí. 3) -print y -printf también son alternativas. 4) Esto ya está allí en la respuesta principal. - De todos modos, eres bienvenido con tu propia respuesta :-)
sudodus
No necesitas las dos -execllamadas. Simplemente use grep -Hy eso imprimirá el nombre del archivo (en color), así como el texto coincidente.
terdon
0

Solo para señalar que si las condiciones de la pregunta pueden tomarse como literarias, puede usar grep directo:

grep 'pattern' abc/def/efg/*/file.txt

o

grep 'pattern' abc/def/efg/{1..300}/file.txt

fuente