risposta-alla-domanda-sullo-sviluppo-web-bd.com

Script Bash per eseguire il comando su tutti i file in una directory

Qualcuno potrebbe fornire il codice per fare quanto segue: Supponiamo che ci sia una directory di file, ognuno dei quali deve essere eseguito attraverso un programma. Il programma mostra i risultati come standard. Ho bisogno di uno script che vada in una directory, esegua il comando su ogni file e concatchi l'output in un unico grande file di output.

Ad esempio, per eseguire il comando su 1 file:

$ cmd [option] [filename] > results.out
225
themaestro

Il seguente codice bash passerà $ file al comando dove $ file rappresenterà ogni file in/dir

for file in /dir/*
do
  cmd [option] "$file" >> results.out
done

Esempio

[email protected] ~/foo $ touch foo.txt bar.txt baz.txt
[email protected] ~/foo $ for i in *.txt; do echo "hello $i"; done
hello bar.txt
hello baz.txt
hello foo.txt
343
Andrew Logvinov

Cosa ne pensi di questo:

find /some/directory -maxdepth 1 -type f -exec cmd option {} \; > results.out
  • L'argomento -maxdepth 1 impedisce la ricerca da discese ricorsiva in qualsiasi sottodirectory. (Se si desidera che tali directory annidate vengano elaborate, è possibile ometterle.)
  • -type -f specifica che verranno elaborati solo i file normali.
  • -exec cmd option {} dice di eseguire cmd con il option specificato per ogni file trovato, con il nome file sostituito {}
  • \; indica la fine del comando.
  • Infine, l'output di tutte le esecuzioni cmd individuali viene reindirizzato a results.out

Tuttavia, se ti interessa l'ordine in cui i file vengono elaborati, è meglio scrivere un ciclo. Penso che find elabori i file in ordine di inode (anche se potrei sbagliarmi), che potrebbe non essere quello che vuoi.

144
Jim Lewis

Sto facendo questo sul mio Raspberry Pi dalla riga di comando eseguendo:

for i in *;do omxplayer "$i";done
45
robgraves

Avevo bisogno di copiare tutti i file .md da una directory a un'altra, quindi ecco cosa ho fatto.

for i in **/*.md;do mkdir -p ../docs/"$i" && rm -r ../docs/"$i" && cp "$i" "../docs/$i" && echo "$i -> ../docs/$i"; done

Che è piuttosto difficile da leggere, quindi lasciala scomporre.

primo cd nella directory con i tuoi file,

for i in **/*.md; per ogni file nel tuo pattern

mkdir -p ../docs/"$i"make quella directory in una cartella docs al di fuori della cartella contenente i tuoi file. Che crea una cartella aggiuntiva con lo stesso nome di quel file.

rm -r ../docs/"$i" rimuove la cartella extra creata come risultato di mkdir -p

cp "$i" "../docs/$i" Copia il file attuale

echo "$i -> ../docs/$i" Echo quello che hai fatto

; done Vivi per sempre felici e contenti

2
Eric Wooley

Un modo veloce e sporco che fa il lavoro a volte è:

find directory/ | xargs  Command 

Ad esempio per trovare il numero di linee in tutti i file nella directory corrente, puoi fare:

find . | xargs wc -l
2
Rahul

Basato sull'approccio di @Jim Lewis:

Ecco una soluzione rapida con find e anche l'ordinamento dei file in base alla loro data di modifica:

$ find  directory/ -maxdepth 1 -type f -print0 | \
  xargs -r0 stat -c "%y %n" | \
  sort | cut -d' ' -f4- | \
  xargs -d "\n" -I{} cmd -op1 {} 

Per ordinare vedi:

http://www.commandlinefu.com/commands/view/5720/find-files-and-list-them-sorted-by-modification-time

1
tuxdna

penso che la soluzione semplice sia:

sh /dir/* > ./result.txt
1
yovie

Le risposte accettate/votate in alto sono grandiose, ma mancano alcuni dettagli nitidi. Questo post copre i casi su come gestire meglio quando l'espansione nome-percorso (glob) di Shell fallisce, quando i nomi dei file contengono simboli newline/dash incorporati e spostano la direzione di uscita del comando dal ciclo for.

Quando si esegue l'espansione di Shell glob usando *, esiste la possibilità che l'espansione fallisca se ci sono no file presenti nella directory e un glob non espanso stringa passata al comando da eseguire sul file che potrebbe avere risultati indesiderati. La shell bash fornisce un'opzione estesa Shell per questo utilizzando nullglob. Quindi il ciclo diventa fondamentalmente come segue all'interno della directory contenente i tuoi file

 shopt -s nullglob

 for file in ./*; do
     cmdToRun [option] -- "$file"
 done

Questo ti permette di uscire in sicurezza dal ciclo for quando l'espressione ./* restituisce qualsiasi file (se la directory è vuota)

o in un modo conforme a POSIX (nullglob è bash specifico)

 for file in ./*; do
     [ -f "$file" ] || continue
     cmdToRun [option] -- "$file"
 done

Questo ti permette di andare all'interno del ciclo quando l'espressione fallisce per una volta e la condizione [ -f "$file" ] controlla se la stringa non espansa ./* è un nome file valido in quella directory, che non sarebbe. Quindi, in questa condizione di errore, usando continue riprendiamo il ciclo for che non verrà eseguito successivamente.

Si noti inoltre l'utilizzo di -- appena prima di passare l'argomento del nome del file. Questo è necessario perché, come notato in precedenza, i nomi di file Shell contengono trattini ovunque nel nome del file. Alcuni dei comandi di Shell interpretano ciò e li trattano come un'opzione di comando ed esegue il comando pensando se il flag è fornito.

-- segnala la fine delle opzioni della riga di comando in quel caso, il che significa che il comando non deve analizzare le stringhe oltre questo punto come flag di comando ma solo come nomi di file.


La doppia citazione dei nomi di file risolve correttamente i casi in cui i nomi contengono caratteri glob o spazi bianchi. Ma i nomi di file * nix possono contenere anche nuove righe in essi. Quindi limitiamo i nomi dei file con l'unico carattere che non può essere parte di un nome file valido: il byte null (\0). Poiché bash utilizza internamente le stringhe di stile C in cui i byte null vengono utilizzati per indicare la fine della stringa, è il candidato giusto per questo.

Quindi, usando l'opzione printf di Shell per delimitare i file con questo byte NULL usando l'opzione -d del comando read, possiamo fare sotto

( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
    cmdToRun [option] -- "$file"
done

nullglob e printf sono racchiusi tra (..) che significa che sono fondamentalmente eseguiti in una sub-shell (shell figlio), perché per evitare l'opzione nullglob per riflettere sulla shell genitore, una volta che il comando è terminato. L'opzione -d '' del comando read è non conforme a POSIX, quindi ha bisogno di una shell bash per fare ciò. Usando il comando find questo può essere fatto come

while IFS= read -r -d '' file; do
    cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0)

Per le implementazioni find che non supportano -print0 (oltre alle GNU e alle implementazioni di FreeBSD), questo può essere emulato usando printf

find . -maxdepth 1 -type f -exec printf '%s\0' {} \; | xargs -0 cmdToRun [option] --

Un'altra importante soluzione è spostare la ri-direzione dal ciclo for per ridurre un numero elevato di I/O di file. Se utilizzato all'interno del ciclo, Shell deve eseguire chiamate di sistema due volte per ogni iterazione del ciclo for, una volta per l'apertura e una volta per la chiusura del descrittore di file associato al file. Questo diventerà un ostacolo alle tue prestazioni per eseguire grandi iterazioni. Suggerimento suggerito sarebbe quello di spostarlo al di fuori del ciclo.

Estendendo il codice precedente con queste correzioni, si potrebbe fare

( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
    cmdToRun [option] -- "$file"
done > results.out

che fondamentalmente metterà il contenuto del tuo comando per ogni iterazione del tuo file in input su stdout e quando il ciclo termina, apri il file di destinazione una volta per scrivere il contenuto dello stdout e salvarlo. La versione equivalente find dello stesso sarebbe

while IFS= read -r -d '' file; do
    cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out
0
Inian

Profondità massima

Ho trovato che funziona bene con La risposta di Jim Lewis basta aggiungere un po 'come questo:

$ export DIR=/path/dir && cd $DIR && chmod -R +x *
$ find . -maxdepth 1 -type f -name '*.sh' -exec {} \; > results.out

Ordinamento

Se si desidera eseguire in ordine, modificarlo in questo modo:

$ export DIR=/path/dir && cd $DIR && chmod -R +x *
find . -maxdepth 2 -type f -name '*.sh' | sort | bash > results.out

Solo per un esempio, questo verrà eseguito con il seguente ordine:

bash: 1: ./assets/main.sh
bash: 2: ./builder/clean.sh
bash: 3: ./builder/concept/compose.sh
bash: 4: ./builder/concept/market.sh
bash: 5: ./builder/concept/services.sh
bash: 6: ./builder/curl.sh
bash: 7: ./builder/identity.sh
bash: 8: ./concept/compose.sh
bash: 9: ./concept/market.sh
bash: 10: ./concept/services.sh
bash: 11: ./product/compose.sh
bash: 12: ./product/market.sh
bash: 13: ./product/services.sh
bash: 14: ./xferlog.sh

Profondità illimitata

Se vuoi eseguire in profondità illimitata determinate condizioni, puoi usare questo:

export DIR=/path/dir && cd $DIR && chmod -R +x *
find . -type f -name '*.sh' | sort | bash > results.out

quindi metti sopra ogni file nelle directory secondarie come questo:

#!/bin/bash
[[ "$(dirname `pwd`)" == $DIR ]] && echo "Executing `realpath $0`.." || return

e da qualche parte nel corpo del file principale:

if <a condition is matched>
then
    #execute child files
    export DIR=`pwd`
fi
0
Chetabahana