bash iterate file list, excepto cuando está vacío

33

Pensé que esto sería simple, pero está resultando más complejo de lo que esperaba.

Quiero iterar a través de todos los archivos de un tipo particular en un directorio, así que escribo esto:

#!/bin/bash

for fname in *.zip ; do
   echo current file is ${fname}
done

Esto funciona siempre que haya al menos un archivo coincidente en el directorio. Sin embargo, si no hay archivos coincidentes, obtengo esto:

current file is *.zip

Entonces intenté:

#!/bin/bash

FILES=`ls *.zip`
for fname in "${FILES}" ; do
    echo current file is ${fname}
done

Si bien el cuerpo del bucle no se ejecuta cuando no hay archivos, recibo un error de ls:

ls: *.zip: No such file or directory

¿Cómo escribo un bucle que no maneja limpiamente los archivos coincidentes?

symcbean
fuente
77
Agregue shopt -s nullglobantes de ejecutar el bucle for.
Cuonglm
@cuolnglm: esto da como resultado que ls devuelva el nombre del script de ejecución en lugar de una lista vacía en este cuadro RHEL5 (bash 3.2.25) si hago FILES=ls * .zip ; for fname in "${FILES}"...pero funciona como se esperaba confor fname in *.zip ; do....
symcbean
3
Uso for file in *.zip, no `ls ...`. La sugerencia de @ cuonglm es que se *.zipexpanda a nada cuando el patrón no coincide con ningún archivo. lssin argumentos enumera el directorio actual.
Stéphane Chazelas
1
Esta pregunta discute por qué lsgeneralmente se debe evitar analizar la salida de : ¿Por qué no analizar ls? ; También vea el enlace cerca de la parte superior de esa página al artículo ParsingLs de BashGuide .
PM 2Ring

Respuestas:

34

En bash, puede configurar la nullglobopción para que un patrón que no coincide con nada "desaparezca", en lugar de ser tratado como una cadena literal:

shopt -s nullglob
for fname in *.zip ; do
   echo "current file is ${fname}"
done

En el script de shell POSIX, solo verifica que fnameexiste (y al mismo tiempo [ -f ], verifica que sea un archivo normal (o enlace simbólico a un archivo normal) y no otros tipos como directorio / fifo / dispositivo ...):

for fname in *.zip; do
    [ -f "$fname" ] || continue
    printf '%s\n' "current file is $fname"
done

Reemplace [ -f "$fname" ]con [ -e "$fname" ] || [ -L "$fname ]si desea recorrer todos los archivos (no ocultos) cuyo nombre termina .zipindependientemente de su tipo.

Reemplace *.zipcon .*.zip .zip *.zipsi también desea considerar archivos ocultos cuyo nombre termina en .zip.

chepner
fuente
1
shopt -s nullglobNo funcionó para mí en Ubuntu 17.04, pero [ -f "$fname" ] || continuefuncionó bien.
koppor
@koppor Parece que en realidad no estás usando bash.
chepner
2
set ./*                               #set the arg array to glob results
${2+":"} [ -e "$1" ] &&               #if more than one result skip the stat "$1"
printf "current file is %s\n" "$@"    #print the whole array at once

###or###

${2+":"} [ -e "$1" ] &&               #same kind of test
for    fname                          #iterate singly on $fname var for array
do     printf "file is %s\n" "$fname" #print each $fname for each iteration
done                                  

En un comentario aquí mencionas invocar una función ...

file_fn()
    if     [ -e "$1" ] ||               #check if first argument exists
           [ -L "$1" ]                  #or else if it is at least a broken link
    then   for  f                       #if so iterate on "$f"
           do : something w/ "$f"
           done
    else   command <"${1-/dev/null}"    #only fail w/ error if at least one arg
    fi

 file_fn *
mikeserv
fuente
2

Use buscar

export -f myshellfunc
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec bash -c 'myshellfunc "$0"' {} \;

DEBE exportar su función de shell export -fpara que esto funcione. Ahora findejecuta lo bashque ejecuta su función de shell, y permanece solo en el nivel de directorio actual.

Dani_l
fuente
Lo cual se repite a través de subdirectorios, y quiero invocar una función bash (no script) para las coincidencias.
symcbean
@symcbean que he editado para limitar a un solo directorio y manejar funciones de bash
Dani_l
-3

En lugar de:

FILES=`ls *.zip`

Tratar:

FILES=`ls * | grep *.zip`

De esta manera, si ls falla (lo que hace en su caso), grep la salida fallida y regresará como una variable en blanco.

current file is      <---Blank Here

Puede agregar algo de lógica a esto para que devuelva "No se encontró ningún archivo"

#!/bin/bash

FILES=`ls * | grep *.zip`
if [[ $? == "0" ]]; then
    for fname in "$FILES" ; do
        echo current file is $fname
    done
else
    echo "No Files Found"
fi

De esta forma, si el comando anterior tuvo éxito (salió con un valor 0), imprimirá el archivo actual, de lo contrario, imprimirá "No se encontraron archivos"

Kip K
fuente
1
Creo que es una mala idea agregar un proceso más ( grep) en lugar de tratar de solucionar el problema utilizando una herramienta mejor ( find) o cambiando la configuración relevante para la solución actual (con shopt -s nullglob)
Thomas Baruchel
1
Según el comentario del OP en su publicación original shopt -s nullglob, no funciona. Intenté findmientras verificaba mi respuesta y seguía fallando. Creo que por lo de exportación dijo Dani.
Kip K