Verifica se ci sono file corrispondenti a un modello per eseguire uno script


29

Sto cercando di scrivere una ifdichiarazione per verificare se ci sono file corrispondenti a un determinato modello. Se esiste un file di testo in una directory, dovrebbe eseguire un determinato script.

Il mio codice attualmente:

if [ -f /*.txt ]; then ./script fi

Per favore, date alcune idee; Voglio solo eseguire lo script se c'è un .txtnella directory.


3
Sei sicuro che "la directory" dovrebbe essere /? Inoltre, ti manca un punto e virgola prima fi.
depquid

La soluzione più pulita e robusta che abbia mai incontrato è usare findcome spiegato qui su StackOverflow .
Joshua Goldberg,

Risposte:


39
[ -f /*.txt ]

restituire true solo se esiste un (e solo uno) file non nascosto nel /cui nome termina.txt e se quel file è un file normale o un collegamento simbolico a un file normale.

Questo perché i caratteri jolly vengono espansi dalla shell prima di essere passati al comando (qui [).

Quindi, se c'è una /a.txte /b.txt, [verrà passato 5 argomenti: [, -f, /a.txt, /b.txte ]. [si lamenterebbe quindi che -fvengono dati troppi argomenti.

Se si desidera verificare che il *.txtmodello si espanda in almeno un file non nascosto (normale o meno):

shopt -s nullglob
set -- *.txt
if [ "$#" -gt 0 ]; then
  ./script "$@" # call script with that list of files.
fi
# Or with bash arrays so you can keep the arguments:
files=( *.txt )
# apply C-style boolean on member count
(( ${#files[@]} )) && ./script "${files[@]}"

shopt -s nullglobè bashspecifico, ma gusci come ksh93, zsh, yash, tcshavere istruzioni equivalenti.

Nota che trova quei file leggendo il contenuto della directory, non prova ad accedere a quei file, il che lo rende più efficiente delle soluzioni che chiamano comandi come lsostat su quell'elenco di file calcolati dalla shell.

L' shequivalente standard sarebbe:

set -- [*].txt *.txt
case "$1$2" in
  ('[*].txt*.txt') ;;
  (*) shift; script "$@"
esac

Il problema è che con le shell Bourne o POSIX, se un modello non corrisponde, si espande a se stesso. Quindi, se si *.txtespande in *.txt, non sai se è perché non ci sono .txtfile nella directory o perché c'è un file chiamato *.txt. L'utilizzo [*].txt *.txtconsente di discriminare tra i due.


[ -f /*.txt ]è abbastanza veloce rispetto a compgen.
Daniel Böhmer,

@ DanielBöhmer [ -f /*.txt ]sarebbe sbagliato, ma nel mio test su una directory che contiene 3425file, 94di cui sono file txt non nascosti, compgen -G "*.txt" > /dev/null 2>&1sembrano essere veloci set -- *.txt; [ "$#" -gt 0 ](20,5 secondi per entrambi se ripetuti 10000 volte nel mio caso).
Stéphane Chazelas,

11

Puoi sempre usare find:

find . -maxdepth 1 -type f -name "*.txt" 2>/dev/null | grep -q . && ./script

Spiegazione:

  • find . : cerca nella directory corrente
  • -maxdepth 1: non cercare nelle sottodirectory
  • -type f : cerca solo file regolari
  • name "*.txt" : cerca i file che terminano con .txt
  • 2>/dev/null : reindirizza i messaggi di errore a /dev/null
  • | grep -q . : grep per qualsiasi personaggio, restituirà false se non viene trovato alcun carattere.
  • && ./script: Eseguire ./scriptsolo se il comando precedente ha avuto esito positivo ( &&)

2
findrestituisce false solo se ha problemi a cercare file, non se non trova alcun file. Si desidera reindirizzare l'output grep -q .per verificare se trova qualcosa.
Stéphane Chazelas,

@StephaneChazelas hai perfettamente ragione ovviamente. Strano però, l'avevo provato e sembrava funzionare. Deve aver fatto qualcosa di strano perché non lo fa più. Quando troverà "problemi nella ricerca dei file"?
terdon

@terdon, come quando alcune directory sono inaccessibili, o errori I / O o qualsiasi errore restituito da qualsiasi chiamata di sistema che fa. In tal caso, prova dopo chmod a-x ..
Stéphane Chazelas,

8

Una possibile soluzione è anche incorporata in Bash compgen. Tale comando restituisce tutte le possibili corrispondenze per un modello globbing e ha un codice di uscita che indica se i file corrispondono.

compgen -G "/*.text" > /dev/null && ./script

Ho trovato questa domanda mentre cercavo soluzioni più veloci.


1
Buona scoperta! Se ti trovi in ​​una locale multi-byte, puoi migliorarla leggermente con LC_ALL=C compgen -G "*.txt" > /dev/null.
Stéphane Chazelas,

7

Ecco una fodera per farlo:

$ ls
file1.pl  file2.pl

i file esistono

$ stat -t *.pl >/dev/null 2>&1 && echo "file exists" || echo "file doesn't exist"
file exists

i file non esistono

$ stat -t -- *.txt >/dev/null 2>&1 && echo "file exists" || echo "file don't exist"
file don't exist

Questo approccio utilizza gli operatori ||e &&in bash. Questi sono gli operatori "o" e "e".

Quindi, se il comando stat restituisce un $?uguale a 0, echoviene chiamato il primo , se restituisce 1, quindi il secondoecho viene chiamato .

restituisce risultati da stat

# a failure
$ stat -t -- *.txt >/dev/null 2>&1
$ echo "$?"
1

# a success
$ stat -t -- *.pl >/dev/null 2>&1
$ echo "$?"
0

Questa domanda è ampiamente trattata su StackOverflow:


1
Perché usare il non standard statquando si ls -dpuò fare lo stesso?
Stéphane Chazelas,

Ho pensato ls -delenca una directory? Sembrava non funzionare quando ho appena provato a elencare una directory con file in essa ls -d *.plad esempio.
slm

È possibile sostituire l'istruzione a sinistra &&da ls *.txte funzionerà pure. Assicurati di inviare stdout e stderr a /dev/nullcome suggerito da @slm.
unxnut

1
Se usi ls *.txte non ci sono file presenti nella directory questo restituirà a $? = 2, che funzionerà comunque con if, ma questo è stato uno dei miei motivi per scegliere statover ls. Volevo uno 0 per il successo e un 1 per un fallimento.
slm

ls -dè elencare le directory invece del loro contenuto. Quindi ls -dfa solo lstatil file, proprio come statfa GNU . Quali comandi di stato di uscita diversi da zero ritornano in caso di errore è specifico del sistema, ha poco senso fare ipotesi su di essi.
Stéphane Chazelas,

4

Come sottolinea Chazelas, il tuo script fallirebbe se l'espansione dei caratteri jolly corrisponde a più di un file.

Tuttavia, c'è un trucco che uso ( anche se non mi piace molto ) per aggirare:

PATTERN=(/*.txt)
if [ -f ${PATTERN[0]} ]; then
...
fi

Come funziona?

L'espansione dei caratteri jolly corrisponderà a una matrice di nomi di file, otteniamo il primo se ce ne sono alcuni, altrimenti null se non corrisponde.


IMO questa è la risposta meno negativa qui. Sembrano tutti piuttosto orribili, come se nella lingua mancasse una funzionalità di base.
lavaggio:

@plugwash è intenzionale ... * Gli script della shell nix hanno un controllo di flusso di base e qualche altra probabilità e fine, ma alla fine della giornata il compito è incollare insieme altri comandi. Se bash fa schifo ... è perché i comandi che usi da esso fanno schifo
cb88

2
Questa è la logica sbagliata (e ti mancano le virgolette). Che controlla se il primo file corrispondente è un file normale. Potrebbe trattarsi di file non regolari ma potrebbero esserci molti altri .txtfile di tipo regolare . Prova ad esempio dopo mkdir a.txt; mkfifo b.txt; echo regular > c.txt.
Stéphane Chazelas,


0

Mi piace la precedente soluzione di array, ma ciò potrebbe diventare dispendioso con un gran numero di file: la shell userebbe molta memoria per costruire l'array e solo il primo elemento verrebbe mai testato.

Ecco una struttura alternativa che ho testato ieri:

$ cd /etc; if [[ $(echo * | grep passwd) ]];then echo yes;else echo no;fi yes $ cd /etc; if [[ $(echo * | grep password) ]];then echo yes;else echo no;fi no

Il valore di uscita dal grep sembra determinare il percorso attraverso la struttura di controllo. Ciò verifica anche con espressioni regolari anziché con modelli di shell. Alcuni dei miei sistemi hanno il comando "pcregrep" che consente partite regex molto più sofisticate.

(Ho modificato questa risposta per rimuovere un "ls" nella sostituzione del comando dopo aver letto le critiche di cui sopra per averlo analizzato.)


-2

se si desidera utilizzare una clausola if, valutare il conteggio:

if (( `ls *.txt 2> /dev/null|wc -l` ));then...
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.