recorrer los resultados de `ls` en el script de shell bash

94

¿Alguien tiene un script de shell de plantilla para hacer algo con lsuna lista de nombres de directorio y recorrer cada uno y hacer algo?

Estoy planeando hacer ls -1d */para obtener la lista de nombres de directorio.

Daniel A. White
fuente

Respuestas:

111

Editado para no usar ls donde lo haría un globo, como sugirieron @ shawn-j-goff y otros.

Solo usa un for..do..donebucle:

for f in *; do
  echo "File -> $f"
done

Puede reemplazar el *con *.txto cualquier otro pegote que devuelve una lista (de los archivos, directorios o lo que fuera), un comando que genera una lista, por ejemplo, $(cat filelist.txt)o en realidad sustituirla por una lista.

Dentro del dobucle, solo se refiere a la variable del bucle con el prefijo del signo de dólar ( $fen el ejemplo anterior). Puedes echohacerlo o hacer cualquier otra cosa que quieras.

Por ejemplo, para cambiar el nombre de todos los .xmlarchivos en el directorio actual a .txt:

for x in *.xml; do 
  t=$(echo $x | sed 's/\.xml$/.txt/'); 
  mv $x $t && echo "moved $x -> $t"
done

O incluso mejor, si está usando Bash, puede usar expansiones de parámetros de Bash en lugar de generar una subshell:

for x in *.xml; do 
  t=${x%.xml}.txt
  mv $x $t && echo "moved $x -> $t"
done
CoverosGene
fuente
22
¿Qué pasa si el nombre del archivo tiene un espacio?
Daniel A. White
44
Desafortunadamente, como dijo Daniel, el código en esta respuesta se romperá si alguno de los archivos o carpetas contiene un espacio o una nueva línea en su nombre. Muestra un mal uso muy común de los forbucles, y un error típico de tratar de analizar la salida de ls. @ DanielA.White, podría considerar no aceptar esta respuesta si no fue útil (o es potencialmente engañosa), ya que, como dijo, está actuando en los directorios. La respuesta de Shawn J. Goff debería proporcionar una solución más sólida y funcional a su problema.
slhck
44
-∞ No debe analizar la lssalida , no debe leer la salida en un forbucle , debe usar en $()lugar de `` y Usar más comillas ™ . Estoy seguro de que @CoverosGene tenía buenas intenciones, pero esto es simplemente terrible.
l0b0
1
Una mejor alternativa si realmente quieres usar lses ls -1 | while read line; do stuff; done. Al menos ese no se romperá por espacios en blanco.
ejoubaud
1
con mv -vusted no necesitaecho "moved $x -> $t"
DmitrySandalov
69

Usar la salida de lspara obtener nombres de archivos es una mala idea . Puede provocar un mal funcionamiento e incluso scripts peligrosos. Esto se debe a un nombre de archivo puede contener cualquier carácter excepto /y el nullcarácter, y lsno utilizar cualquiera de esos caracteres como delimitadores, por lo que si un nombre de archivo tiene un espacio o una nueva línea, que va a obtener resultados inesperados.

Hay dos formas muy buenas de iterar sobre archivos. Aquí, he usado simplemente echopara demostrar que estoy haciendo algo con el nombre del archivo; Sin embargo, puedes usar cualquier cosa.

El primero es usar las características de globbing nativas del shell.

for dir in */; do
  echo "$dir"
done

El shell se expande */en argumentos separados que forlee el bucle; incluso si hay un espacio, una nueva línea o cualquier otro carácter extraño en el nombre del archivo, forverá cada nombre completo como una unidad atómica; no está analizando la lista de ninguna manera.

Si desea ir recursivamente a subdirectorios, entonces esto no funcionará a menos que su shell tenga algunas características de globbing extendidas (como bash's globstar. Si su shell no tiene estas características, o si quiere asegurarse de que su script funcionará en una variedad de sistemas, entonces la siguiente opción es usar find.

find . -type d -exec echo '{}' \;

Aquí, el findcomando llamará echoy le pasará un argumento del nombre del archivo. Hace esto una vez por cada archivo que encuentra. Al igual que con el ejemplo anterior, no se analiza una lista de nombres de archivo; en cambio, un fileneame se envía completamente como argumento.

La sintaxis del -execargumento parece un poco divertida. findtoma el primer argumento después -execy lo trata como el programa a ejecutar, y cada argumento posterior, toma como un argumento para pasar a ese programa. Hay dos argumentos especiales que -exechay que ver. El primero es {}; este argumento se reemplaza con un nombre de archivo que findgenera las partes anteriores . El segundo es ;, que permite findsaber que este es el final de la lista de argumentos para pasar al programa; findnecesita esto porque puede continuar con más argumentos destinados findy no destinados al programa ejecutado. La razón de esto \es que la cáscara también trata;especialmente: representa el final de un comando, por lo que debemos escapar para que el shell lo ofrezca como argumento en findlugar de consumirlo por sí mismo; Otra forma de hacer que el shell no lo trate especialmente es ponerlo entre comillas: ';'funciona tan bien como \;para este propósito.

Shawn J. Goff
fuente
2
+1 este es definitivamente el camino a seguir cuando necesita generar una lista de archivos y usarla en un comando. find -exec está limitado al solo poder ejecutar un solo comando. Con el bucle, puede canalizar el contenido de su corazón.
MaQleod
+1. Desearía que más personas lo usaran find. Hay magia que se puede hacer, e incluso las limitaciones percibidas -execse pueden solucionar. La -print0opción también es valiosa para usar con xargs.
ghoti
La opción de bucle no mostrará directorios ocultos. La opción de búsqueda no mostrará enlaces simbólicos.
jinawee
16

Para los archivos con espacios, deberá asegurarse de citar la variable como:

 for i in $(ls); do echo "$i"; done; 

o puede cambiar la variable de entorno del separador de campo de entrada (IFS):

 IFS=$'\n';for file in $(ls); do echo $i; done

Finalmente, dependiendo de lo que esté haciendo, es posible que ni siquiera necesite el ls:

 for i in *; do echo "$i"; done;
John
fuente
buen uso de una subshell en el primer ejemplo
Jeremy L
¿Por qué IFS necesita un $ después de la asignación y antes del nuevo personaje?
Andy Ibáñez
3
Los nombres de archivo también pueden contener nuevas líneas. Romper \nes insuficiente. Nunca es una buena idea recomendar analizar la salida de ls.
ghoti
4

Solo para agregar a la respuesta de CoverosGene, aquí hay una manera de enumerar solo los nombres de directorio:

for f in */; do
  echo "Directory -> $f"
done
n3o
fuente
1

¿Por qué no establecer IFS en una nueva línea y luego capturar la salida de lsen una matriz? Establecer IFS en nueva línea debería resolver problemas con caracteres divertidos en los nombres de archivo; usar lspuede ser bueno porque tiene una función de clasificación incorporada.

(En las pruebas, tuve problemas para configurar IFS \npero funciona como backspace de nueva línea, como se sugiere en otro lugar aquí):

Por ejemplo (suponiendo lsque se pasa el patrón de búsqueda deseado $1):

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")

FILES=($(/bin/ls "$1"))

for AFILE in ${FILES[@]}
do
    ... do something with a file ...
done

IFS=$SAVEIFS

Esto es especialmente útil en OS X, por ejemplo, para capturar una lista de archivos ordenados por fecha de creación (más antigua a más reciente), el lscomando es ls -t -U -r.

Jet set
fuente
2
Los nombres de archivo también pueden contener nuevas líneas, y a menudo lo hacen cuando los usuarios pueden nombrar sus propios archivos. Romper \nes insuficiente. Las únicas soluciones válidas utilizan un bucle for con expansión de nombre de ruta, o find.
ghoti
La única forma confiable de transferir una lista de nombres de archivos es separarlos con un carácter NUL, ya que este es el único definitivamente no contenido en una ruta de archivo.
glglgl
-3

Así es como lo hago, pero probablemente hay formas más eficientes.

ls > filelist.txt

while read filename; do echo filename: "$filename"; done < filelist.txt
ÁRBOL
fuente
66
Se adhieren a las tuberías en lugar del archivo:>ls | while read i; do echo filename: $i; done
Jeremy L
Bueno. Debo decir que también puede usar $ EDITOR filelist.txt entre los dos comandos. Muchas cosas que puedes hacer en un editor que es más fácil que en la línea de comandos. Sin embargo, no es relevante para esta pregunta.
ÁRBOL
Su solución no aborda en absoluto el problema con los nombres de archivo que contienen líneas nuevas y otras cosas elegantes.
glglgl