bash itera l'elenco dei file, tranne quando è vuoto


33

Ho pensato che sarebbe stato semplice, ma si sta dimostrando più complesso di quanto mi aspettassi.

Voglio scorrere tutti i file di un determinato tipo in una directory, quindi scrivo questo:

#!/bin/bash

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

Funziona finché esiste almeno un file corrispondente nella directory. Tuttavia, se non ci sono file corrispondenti, ottengo questo:

current file is *.zip

Ho quindi provato:

#!/bin/bash

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

Mentre il corpo del ciclo non viene eseguito quando non ci sono file, ricevo un errore da ls:

ls: *.zip: No such file or directory

Come faccio a scrivere un ciclo che non gestisca in modo pulito nessun file corrispondente?


7
Aggiungi shopt -s nullglobprima di eseguire il ciclo for.
cuonglm,

@cuolnglm: spettralmente questo porta a restituire il nome dello script in esecuzione piuttosto che un elenco vuoto su questo riquadro RHEL5 (bash 3.2.25) se faccio FILES=ls * .zip ; for fname in "${FILES}"...ma funziona come previsto confor fname in *.zip ; do....
symcbean

3
Usa for file in *.zip, no `ls ...`. Il suggerimento di @ cuonglm è in modo che non si *.zipespanda a nulla quando il modello non corrisponde a nessun file. lssenza argomenti elenca la directory corrente.
Stéphane Chazelas,

1
Questa domanda discute il motivo per cui l'analisi dell'output di lsdeve generalmente essere evitata: perché non analizzare ls? ; vedi anche il link nella parte superiore di quella pagina dell'articolo ParsingLs di BashGuide .
PM 2Ring

Risposte:


34

In bash, puoi impostare l' nullglobopzione in modo tale che uno schema che non corrisponda a nulla "scompaia", anziché essere trattato come una stringa letterale:

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

Nello script shell POSIX, basta verificare che fnameesista (e allo stesso tempo [ -f ], verificare che sia un file normale (o un collegamento simbolico a un file normale) e non altri tipi come directory / fifo / dispositivo ...):

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

Sostituisci [ -f "$fname" ]con [ -e "$fname" ] || [ -L "$fname ]se desideri eseguire il ciclo su tutti i file (non nascosti) il cui nome termina .zipindipendentemente dal tipo.

Sostituisci *.zipcon .*.zip .zip *.zipse vuoi considerare anche i file nascosti il ​​cui nome finisce .zip.


1
shopt -s nullglobnon ha funzionato per me su Ubuntu 17.04, ma ha [ -f "$fname" ] || continuefunzionato bene.
koppor,

@koppor Sembra che tu non stia effettivamente 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                                  

In un commento qui menzioni il richiamo di una funzione ...

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 *

2

Usa trova

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

È NECESSARIO esportare la funzione di shell con export -faffinché funzioni. Ora findesegue quello bashche esegue la funzione della shell e rimane solo al livello dir corrente.


Che ricorre attraverso le sottodirectory e voglio invocare una funzione bash (non script) per le partite.
symcbean,

@symcbean Ho modificato per limitare alla singola
directory

-3

Invece di:

FILES=`ls *.zip`

Provare:

FILES=`ls * | grep *.zip`

In questo modo se ls fallisce (cosa che fa nel tuo caso), eseguirà grep sull'output fallito e tornerà come variabile vuota.

current file is      <---Blank Here

È possibile aggiungere un po 'di logica a questo per farlo restituire "Nessun file trovato"

#!/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

In questo modo se il comando precedente ha avuto esito positivo (è uscito con un valore 0), stampa il file corrente, altrimenti stampa "Nessun file trovato"


1
Penso che sia una cattiva idea aggiungere un altro processo ( grep) piuttosto che cercare di risolvere il problema utilizzando uno strumento migliore ( find) o modificando l'impostazione pertinente per la soluzione corrente (con shopt -s nullglob)
Thomas Baruchel,

1
Secondo il commento del PO sul loro post originale il shopt -s nullglobnon funziona. Ho provato a findverificare la mia risposta e ha continuato a fallire. Penso a causa dell'esportazione che Dani ha detto.
Kip K
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.