Come eseguire il loop dei file nella directory e modificare il percorso e aggiungere il suffisso al nome file


563

Devo scrivere una sceneggiatura che avvii il mio programma con diversi argomenti, ma sono nuovo di Bash. Inizio il mio programma con:

./MyProgram.exe Data/data1.txt [Logs/data1_Log.txt].

Ecco lo pseudocodice per quello che voglio fare:

for each filename in /Data do
  for int i = 0, i = 3, i++
    ./MyProgram.exe Data/filename.txt Logs/filename_Log{i}.txt
  end for
end for

Quindi sono davvero perplesso su come creare il secondo argomento dal primo, quindi sembra dataABCD_Log1.txt e avviare il mio programma.

Risposte:


739

Prima un paio di note: quando usate Data/data1.txtcome argomento, dovrebbe essere /Data/data1.txt(con una barra)? Inoltre, il ciclo esterno dovrebbe cercare solo i file .txt o tutti i file in / Data? Ecco una risposta, assumendo /Data/data1.txtsolo i file .txt:

#!/bin/bash
for filename in /Data/*.txt; do
    for ((i=0; i<=3; i++)); do
        ./MyProgram.exe "$filename" "Logs/$(basename "$filename" .txt)_Log$i.txt"
    done
done

Appunti:

  • /Data/*.txtsi espande nei percorsi dei file di testo in / Data ( inclusa la / Data / parte)
  • $( ... ) esegue un comando shell e inserisce il suo output in quel punto nella riga di comando
  • basename somepath .txtgenera la parte base di somepath, con .txt rimosso dalla fine (es. /Data/file.txt-> file)

Se è necessario eseguire MyProgram con Data/file.txtinvece di /Data/file.txt, utilizzare "${filename#/}"per rimuovere la barra iniziale. D'altra parte, se davvero Datanon /Datavuoi scansionare, basta usare for filename in Data/*.txt.


1
Apparentemente basename -sè un'estensione non standard - Modificherò la mia risposta per usare la sintassi standard.
Gordon Davisson,

21
Se non viene trovato alcun file / corrisponde al carattere jolly, trovo che il blocco per i cicli di esecuzione sia ancora inserito una volta con nomefile = "/Data/*.txt". Come posso evitarlo?
Oliver Pearmain,

20
@OliverPearmain Utilizzare shopt -s nullglobprima del ciclo (e shopt -u nullglobdopo per evitare problemi in un secondo momento), oppure aggiungere if [[ ! -e "$filename ]]; then continue; fiall'inizio del ciclo, in modo da saltare i file inesistenti.
Gordon Davisson,

9
Questo non funziona quando ci sono file che contengono spazi bianchi nel loro nome.
Isa Hassen,

4
@Isa Dovrebbe funzionare con spazi bianchi, purché siano presenti tutte le virgolette. Lascia una delle virgolette doppie e avrai problemi con gli spazi bianchi.
Gordon Davisson il

345

Ci scusiamo per il necromanismo del thread, ma ogni volta che esegui l'iterazione dei file con i globbing, è buona norma evitare il caso angolare in cui il glob non corrisponde (il che rende la variabile loop espandersi nella stringa del pattern glob (non corrispondente) stessa).

Per esempio:

for filename in Data/*.txt; do
    [ -e "$filename" ] || continue
    # ... rest of the loop body
done

Riferimento: insidie ​​di Bash


8
Questo è ancora un avvertimento tempestivo. Pensavo di aver creato il mio script in modo errato, ma avevo l'estensione del file in minuscolo anziché maiuscola, non trovava alcun file e restituiva il modello glob. ugh.
RufusVS,

5
Poiché viene utilizzato il tag Bash: ecco a cosa shopt nullglobserve! (o shopt failglobpuò essere utilizzato anche, a seconda del comportamento desiderato).
gniourf_gniourf,

Inoltre, questo controlla anche i file eliminati durante l'elaborazione, prima che il ciclo li raggiunga.
Loxaxs

1
Gestisce anche il caso in cui hai una directory chiamatadir.txt
vidstige

75
for file in Data/*.txt
do
    for ((i = 0; i < 3; i++))
    do
        name=${file##*/}
        base=${name%.txt}
        ./MyProgram.exe "$file" Logs/"${base}_Log$i.txt"
    done
done

La name=${file##*/}sostituzione ( espansione dei parametri della shell ) rimuove il percorso principale fino all'ultimo /.

La base=${name%.txt}sostituzione rimuove il finale .txt. È un po 'più complicato se le estensioni possono variare.


3
Credo che ci sia un errore nel tuo codice. L'unica riga dovrebbe essere base=${name%.txt}, invece di base=${base%.txt}.
caseklim,

4
@CaseyKlimkowsky: Sì; quando il codice e i commenti non sono d'accordo, almeno uno di essi è sbagliato. In questo caso, penso che sia solo quello: il codice; spesso, in realtà entrambi sono sbagliati. Grazie per la segnalazione; L'ho risolto.
Jonathan Leffler,

8

È possibile utilizzare l'opzione Trova output separato null con lettura per scorrere in sicurezza le strutture di directory.

#!/bin/bash
find . -type f -print0 | while IFS= read -r -d $'\0' file; 
  do echo "$file" ;
done

Quindi per il tuo caso

#!/bin/bash
find . -maxdepth 1 -type f  -print0 | while IFS= read -r -d $'\0' file; do
  for ((i=0; i<=3; i++)); do
    ./MyProgram.exe "$file" 'Logs/'"`basename "$file"`""$i"'.txt'
  done
done

Inoltre

#!/bin/bash
while IFS= read -r -d $'\0' file; do
  for ((i=0; i<=3; i++)); do
    ./MyProgram.exe "$file" 'Logs/'"`basename "$file"`""$i"'.txt'
  done
done < <(find . -maxdepth 1 -type f  -print0)

eseguirà il ciclo while nell'ambito corrente dello script (processo) e consentirà di utilizzare l'output di find nell'impostazione delle variabili, se necessario


2
$'\0'è uno strano modo di scrivere ''. Ti stai perdendo IFS=e il -rpassaggio a read: la vostra dichiarazione di lettura dovrebbe essere: IFS= read -rd '' file.
gniourf_gniourf

Immagino che alcuni avrebbero bisogno di ricerca $'\0'e diffondere alcuni punti dello stack in giro. Andando a fare le modifiche che hai sottolineato. Quali sono gli effetti negativi del non IFS=provare echo -e "ok \nok\0" | while read -d '' line; do echo -e "$line"; donenon sembrano essercene. Inoltre -r vedo che è spesso predefinito, ma non sono riuscito a trovare un esempio di ciò che impedisce di accadere.
James,

4
IFS=è necessario nel caso in cui un nome file termini con uno spazio: provare è con touch 'Prospero '(notare lo spazio finale). Inoltre è necessario l' -rinterruttore nel caso in cui il nome di un file abbia una barra rovesciata: provalo con touch 'Prospero\n'.
gniourf_gniourf,

-1

Sembra che tu stia cercando di eseguire un file Windows (.exe) Sicuramente dovresti usare PowerShell. Comunque su una shell bash Linux basterà un semplice one-liner.

[/home/$] for filename in /Data/*.txt; do for i in {0..3}; do ./MyProgam.exe  Data/filenameLogs/$filename_log$i.txt; done done

O in un colpo

#!/bin/bash

for filename in /Data/*.txt; 
   do
     for i in {0..3}; 
       do ./MyProgam.exe Data/filename.txt Logs/$filename_log$i.txt; 
     done 
 done
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.