x=$(find . -name "*.txt")
echo $x
si ejecuto el fragmento de código anterior en Bash shell, lo que obtengo es una cadena que contiene varios nombres de archivos separados por espacios en blanco, no una lista.
Por supuesto, puedo separarlos aún más en blanco para obtener una lista, pero estoy seguro de que hay una mejor manera de hacerlo.
Entonces, ¿cuál es la mejor manera de recorrer los resultados de un find
comando?
x=( $(find . -name "*.txt") ); echo "${x[@]}"
Entonces puede recorrerlofor item in "${x[@]}"; { echo "$item"; }
Respuestas:
TL; DR: Si solo está aquí para obtener la respuesta más correcta, probablemente quiera mi preferencia personal
find . -name '*.txt' -exec process {} \;
(consulte la parte inferior de esta publicación). Si tiene tiempo, lea el resto para ver varias formas diferentes y los problemas con la mayoría de ellas.La respuesta completa:
La mejor manera depende de lo que quieras hacer, pero aquí hay algunas opciones. Siempre que ningún archivo o carpeta en el subárbol tenga un espacio en blanco en su nombre, simplemente puede recorrer los archivos:
Marginalmente mejor, recorte la variable temporal
x
:Es mucho mejor pegarse cuando puedas. Espacio en blanco seguro, para archivos en el directorio actual:
Al habilitar la
globstar
opción, puede glob todos los archivos coincidentes en este directorio y todos los subdirectorios:En algunos casos, por ejemplo, si los nombres de archivo ya están en un archivo, es posible que deba usar
read
:read
se puede usar de forma segura en combinaciónfind
estableciendo el delimitador adecuadamente:Para búsquedas más complejas, es probable que desee utilizar
find
, ya sea con su-exec
opción o con-print0 | xargs -0
:find
también puede cd en el directorio de cada archivo antes de ejecutar un comando mediante el uso de en-execdir
lugar de-exec
, y se puede hacer interactivo (solicitar antes de ejecutar el comando para cada archivo) utilizando en-ok
lugar de-exec
(o en-okdir
lugar de-execdir
).*: Técnicamente, ambos
find
yxargs
(por defecto) ejecutarán el comando con tantos argumentos como puedan caber en la línea de comando, tantas veces como sea necesario para pasar por todos los archivos. En la práctica, a menos que tenga una gran cantidad de archivos, no importará, y si excede la longitud pero los necesita todos en la misma línea de comando,SOLencontrará una forma diferente.fuente
done < filename
y el siguiente con la tubería, el stdin ya no se puede usar (→ no más cosas interactivas dentro del bucle), pero en los casos en que es necesario, se puede usar en3<
lugar de<
y agregar<&3
o-u3
para laread
parte, básicamente usando un descriptor de archivo separado. Además, creo queread -d ''
es lo mismoread -d $'\0'
pero no puedo encontrar ninguna documentación oficial sobre eso en este momento.-exec process {} \;
y supongo que esa es otra pregunta: ¿qué significa eso y cómo lo manipulo? ¿Dónde hay un buen Q / A o doc. ¿en eso?man find
). En este caso,-exec
le indicafind
que ejecute el siguiente comando, terminado por;
(o+
), en donde{}
será reemplazado por el nombre del archivo que está procesando (o, si+
se usa, todos los archivos que han llegado a esa condición).-d ''
es mejor que-d $'\0'
. Este último no solo es más largo, sino que también sugiere que podría pasar argumentos que contienen bytes nulos, pero no puede. El primer byte nulo marca el final de la cadena. En bash$'a\0bc'
es lo mismo quea
y$'\0'
es lo mismo$'\0abc'
o solo la cadena vacía''
.help read
establece que " El primer carácter de delim se usa para terminar la entrada ", por lo que usarlo''
como delimitador es un poco hack. El primer carácter en la cadena vacía es el byte nulo que siempre marca el final de la cadena (incluso si no lo escribe explícitamente).Hagas lo que hagas, no uses un
for
bucle :Tres razones:
find
debe ejecutarse hasta su finalización.for
bucle devuelve 40 KB de texto. Los últimos 8 KB se eliminarán de tufor
ciclo y nunca lo sabrás.Siempre use una
while read
construcción:El bucle se ejecutará mientras se ejecuta el
find
comando. Además, este comando funcionará incluso si se devuelve un nombre de archivo con espacios en blanco. Y no desbordará el búfer de la línea de comandos.El
-print0
usará el NULL como un separador de archivo en lugar de una nueva línea y la-d $'\0'
va a usar NULL como el separador durante la lectura.fuente
-exec
en su lugar.-exec
es el más seguro ya que no usa el shell en absoluto. Sin embargo, NL en los nombres de archivo es bastante raro. Los espacios en los nombres de archivo son bastante comunes. El punto principal es no usar unfor
bucle que muchos carteles recomiendan.for file $(find)
debido a los problemas asociados con eso.-r
opción pararead
:-r raw input - disables interpretion of backslash escapes and line-continuation in the read data
Nota: este método y el (segundo) método mostrado por bmargulies son seguros para usar con espacios en blanco en los nombres de archivo / carpeta.
Para tener también el caso, algo exótico, de nuevas líneas en los nombres de archivo / carpeta cubiertos, tendrá que recurrir al
-exec
predicado defind
esta manera:El
{}
es el marcador de posición para el elemento encontrado y\;
se utiliza para terminar el-exec
predicado.Y en aras de la integridad, permítanme agregar otra variante: deben amar las formas * nix por su versatilidad:
Esto separaría los elementos impresos con un
\0
carácter que no está permitido en ninguno de los sistemas de archivos en los nombres de archivos o carpetas, que yo sepa, y por lo tanto debería cubrir todas las bases.xargs
los recoge uno por uno y luego ...fuente
find -print0
yxargs -0
son tanto extensiones GNU como argumentos no portátiles (POSIX). Sin embargo, ¡increíblemente útil en aquellos sistemas que los tienen!read -r
que solucionaría), o nombres de archivo que terminan en espacios en blanco (loIFS= read
que solucionaría). Por lo tanto, BashFAQ # 1 sugierewhile IFS= read -r filename; do ...
exit
, no funcionará como se esperaba y las variables establecidas en el cuerpo del bucle no estarán disponibles después del bucle.Los nombres de archivo pueden incluir espacios e incluso caracteres de control. Los espacios son delimitadores (predeterminados) para la expansión de shell en bash y, como resultado de eso,
x=$(find . -name "*.txt")
la pregunta no se recomienda en absoluto. Si find obtiene un nombre de archivo con espacios, por ejemplo"the file.txt"
, obtendrá 2 cadenas separadas para el procesamiento, si procesax
en un bucle. Puede mejorar esto cambiando el delimitador (IFS
variable bash ), por ejemplo\r\n
, a , pero los nombres de archivo pueden incluir caracteres de control, por lo que este no es un método (completamente) seguro.Desde mi punto de vista, hay 2 patrones recomendados (y seguros) para procesar archivos:
1. Use para la expansión de bucle y nombre de archivo:
2. Utilice find-read-while y sustitución de procesos
Observaciones
en el Patrón 1:
nullglob
se puede utilizar para evitar esta línea adicional.failglob
opción de shell y no se encuentran coincidencias, se imprime un mensaje de error y el comando no se ejecuta". (del Manual de Bash arriba)globstar
: "Si se establece, el patrón '**' usado en un contexto de expansión de nombre de archivo coincidirá con todos los archivos y cero o más directorios y subdirectorios. Si el patrón es seguido por un '/', solo los directorios y subdirectorios coinciden". ver Bash Manual, Shopt Builtinextglob
,nocaseglob
,dotglob
y variable de shellGLOBIGNORE
en el Patrón 2:
los nombres de archivo pueden contener espacios en blanco, tabulaciones, espacios, saltos de línea, ... a los nombres de archivo de proceso en una manera segura,
find
con-print0
se utiliza: nombre de archivo se imprime con todos los caracteres de control y termina con NUL. véase también Gnu Findutils Página de manual, Manejo inseguro Nombre de archivo , salvo Nombre de archivo Manipulación , caracteres inusuales en nombres de archivo . Ver David A. Wheeler a continuación para una discusión detallada de este tema.Hay algunos patrones posibles para procesar resultados de búsqueda en un ciclo while. Otros (kevin, David W.) han mostrado cómo hacer esto usando tuberías:
Cuando pruebe este fragmento de código, verá que no funciona:files_found
siempre es "verdadero" y el código siempre repetirá "no se encontraron archivos". La razón es: cada comando de una tubería se ejecuta en una subshell separada, por lo que la variable modificada dentro del bucle (subshell separado) no cambia la variable en el script de la shell principal. Es por eso que recomiendo usar la sustitución de procesos como el patrón "mejor", más útil y más general.Vea que configuro variables en un bucle que está en una tubería. ¿Por qué desaparecen ... (de las preguntas frecuentes de Greg's Bash) para una discusión detallada sobre este tema.
Referencias adicionales y fuentes:
Manual de Gnu Bash, coincidencia de patrones
Nombres de archivo y nombres de ruta en Shell: cómo hacerlo correctamente, David A. Wheeler
¿Por qué no lees líneas con "para", Greg's Wiki
Por qué no deberías analizar la salida de ls (1), Greg's Wiki
Manual de Gnu Bash, sustitución de procesos
fuente
(Actualizado para incluir la excelente mejora de velocidad de @Scowcowi)
Con cualquiera
$SHELL
que lo admita (dash / zsh / bash ...):Hecho.
Respuesta original (más corta, pero más lenta):
fuente
\;
usar, puede+
pasar tantos archivos como sea posible a un soloexec
. Luego use"$@"
dentro del script de shell para procesar todos estos parámetros.$@
omite, ya que generalmente es el nombre del script. Sólo tenemos que añadirdummy
en el medio'
y{}
por lo que puede tomar el lugar del nombre del script, asegurando que todos los partidos son procesados por el bucle.OTHERVAR=foo find . -na.....
debería permitirle acceder$OTHERVAR
desde ese shell recién creado.fuente
for x in $(find ...)
se romperá para cualquier nombre de archivo con espacios en blanco. Lo mismo con afind ... | xargs
menos que use-print0
y-0
find . -name "*.txt -exec process_one {} ";"
lugar. ¿Por qué deberíamos usar xargs para recopilar resultados, que ya tenemos?process_one
sea. Si es un marcador de posición para un comando real , asegúrese de que funcionaría (si corrige el error tipográfico y agrega comillas de cierre después"*.txt
). Pero siprocess_one
es una función definida por el usuario, su código no funcionará.Puede almacenar su
find
salida en una matriz si desea usar la salida más tarde como:Ahora, para imprimir cada elemento en una nueva línea, puede usar
for
iteración de bucle para todos los elementos de la matriz, o puede usar la instrucción printf.o
También puedes usar:
Esto imprimirá cada nombre de archivo en nueva línea
Para imprimir solo el
find
resultado en forma de lista, puede usar cualquiera de los siguientes:o
Esto eliminará los mensajes de error y solo dará el nombre del archivo como resultado en una nueva línea.
Si desea hacer algo con los nombres de archivo, almacenarlo en una matriz es bueno, de lo contrario no hay necesidad de consumir ese espacio y puede imprimir directamente la salida desde
find
.fuente
Si puede suponer que los nombres de los archivos no contienen líneas nuevas, puede leer la salida
find
en una matriz Bash con el siguiente comando:Nota:
-t
hacereadarray
que se eliminen las nuevas líneas.readarray
está en una tubería, de ahí la sustitución del proceso.readarray
está disponible desde Bash 4.Bash 4.4 y versiones posteriores también admiten el
-d
parámetro para especificar el delimitador. El uso del carácter nulo, en lugar de nueva línea, para delimitar los nombres de archivo funciona también en el raro caso de que los nombres de archivo contengan nuevas líneas:readarray
También se puede invocar comomapfile
con las mismas opciones.Referencia: https://mywiki.wooledge.org/BashFAQ/005#Loading_lines_from_a_file_or_stream
fuente
exit
alreadarray -d '' x < <(find . -name '*.txt' -print0)
Me gusta usar find, que primero se asigna a la variable e IFS cambió a una nueva línea de la siguiente manera:
En caso de que desee repetir más acciones en el mismo conjunto de DATOS y la búsqueda es muy lenta en su servidor (I / 0 de alta utilización)
fuente
Puede poner los nombres de archivo devueltos por
find
en una matriz como esta:Ahora puede recorrer la matriz para acceder a elementos individuales y hacer lo que quiera con ellos.
Nota: es un espacio en blanco seguro.
fuente
mapfile -t -d '' array < <(find ...)
. La configuraciónIFS
no es necesaria paramapfile
.basado en otras respuestas y comentarios de @phk, usando fd # 3:
(que todavía permite usar stdin dentro del bucle)
fuente
find <path> -xdev -type f -name *.txt -exec ls -l {} \;
Esto enumerará los archivos y dará detalles sobre los atributos.
fuente
¿Qué tal si usas grep en lugar de find?
Ahora puede leer este archivo y los nombres de los archivos tienen la forma de una lista.
fuente