So durchlaufen Sie Dateien im Verzeichnis und ändern den Pfad und fügen dem Dateinamen ein Suffix hinzu

562

Ich muss ein Skript schreiben, das mein Programm mit verschiedenen Argumenten startet, aber ich bin neu in Bash. Ich beginne mein Programm mit:

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

Hier ist der Pseudocode für das, was ich tun möchte:

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

Ich bin wirklich verwirrt, wie man aus dem ersten ein zweites Argument erstellt. Es sieht also wie dataABCD_Log1.txt aus und startet mein Programm.

Dobrobobr
quelle

Antworten:

736

Ein paar Anmerkungen zuerst: Wenn Sie Data/data1.txtals Argument verwenden, sollte es wirklich sein /Data/data1.txt(mit einem führenden Schrägstrich)? Sollte die äußere Schleife auch nur nach TXT-Dateien oder nach allen Dateien in / Data suchen? Hier ist eine Antwort, vorausgesetzt /Data/data1.txt, nur .txt-Dateien:

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

Anmerkungen:

  • /Data/*.txtErweitert sich auf die Pfade der Textdateien in / Data ( einschließlich / Data / part).
  • $( ... ) führt einen Shell-Befehl aus und fügt seine Ausgabe an dieser Stelle in die Befehlszeile ein
  • basename somepath .txtgibt den Basisteil eines Pfads aus, wobei .txt am Ende entfernt wird (z. B. /Data/file.txt-> file)

Wenn Sie benötigen MyProgram mit laufen , Data/file.txtanstatt /Data/file.txt, Verwendung "${filename#/}"zu entfernen den führenden Schrägstrich. Wenn Sie jedoch wirklich Datanicht /Datascannen möchten, verwenden Sie einfach for filename in Data/*.txt.

Gordon Davisson
quelle
1
Anscheinend basename -shandelt es sich um eine nicht standardmäßige Erweiterung - ich werde meine Antwort bearbeiten, um die Standardsyntax zu verwenden.
Gordon Davisson
21
Wenn keine Dateien gefunden werden / mit dem Platzhalter übereinstimmen, wird der Ausführungsblock für for-Schleifen immer noch einmal mit dem Dateinamen = "/Data/*.txt" eingegeben. Wie kann ich das vermeiden?
Oliver Pearmain
20
@OliverPearmain Verwenden Sie diese Option entweder shopt -s nullglobvor der Schleife (und shopt -u nullglobdanach, um später Probleme zu vermeiden) oder fügen Sie sie if [[ ! -e "$filename ]]; then continue; fiam Anfang der Schleife hinzu, damit nicht vorhandene Dateien übersprungen werden.
Gordon Davisson
9
Dies funktioniert nicht, wenn Dateien vorhanden sind, deren Name Leerzeichen enthält.
Isa Hassen
4
@Isa Es sollte mit Leerzeichen funktionieren, solange alle doppelten Anführungszeichen vorhanden sind. Lassen Sie doppelte Anführungszeichen weg, und Sie haben Probleme mit Leerzeichen.
Gordon Davisson
345

Es tut uns leid, dass Sie den Thread nekromantiert haben, aber wenn Sie Dateien durch Globbing durchlaufen, sollten Sie den Eckfall vermeiden, in dem der Glob nicht übereinstimmt (wodurch die Schleifenvariable auf die (nicht übereinstimmende) Glob-Musterzeichenfolge selbst erweitert wird).

Zum Beispiel:

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

Referenz: Fallstricke

Cong Ma
quelle
8
Dies ist immer noch eine rechtzeitige Warnung. Ich dachte, ich hätte mein Skript falsch erstellt, aber ich hatte meine Dateierweiterung in Kleinbuchstaben anstelle von Großbuchstaben, fand keine Dateien und gab das Glob-Muster zurück. Pfui.
RufusVS
5
Da das Bash-Tag verwendet wird: Dafür shopt nullglobist es da! (oder shopt failglobkann auch verwendet werden, je nach gewünschtem Verhalten).
gniourf_gniourf
Außerdem wird auch nach Dateien gesucht, die während der Verarbeitung gelöscht wurden, bevor die Schleife sie erreicht.
Loxaxs
1
dir.txt
Behandelt
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

Durch die name=${file##*/}Ersetzung ( Shell-Parametererweiterung ) wird der führende Pfadname bis zum letzten entfernt /.

Die base=${name%.txt}Substitution entfernt das Nachlaufen .txt. Es ist etwas schwieriger, wenn die Erweiterungen variieren können.

Jonathan Leffler
quelle
3
Ich glaube, es gibt einen Fehler in Ihrem Code. Die eine Zeile sollte base=${name%.txt}anstelle von sein base=${base%.txt}.
Caseklim
4
@CaseyKlimkowsky: Ja; Wenn der Code und die Kommentare nicht übereinstimmen, ist mindestens einer von ihnen falsch. In diesem Fall denke ich, dass es nur der eine ist - der Code; oft sind tatsächlich beide falsch. Vielen Dank für den Hinweis; Ich habe es behoben.
Jonathan Leffler
8

Sie können die Option "Getrennte Null-getrennte Ausgabe" mit "Lesen" verwenden, um Verzeichnisstrukturen sicher zu durchlaufen.

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

Also für deinen Fall

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

zusätzlich

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

führt die while-Schleife im aktuellen Bereich des Skripts (Prozesses) aus und ermöglicht, dass die Ausgabe von find bei Bedarf zum Festlegen von Variablen verwendet wird

James
quelle
2
$'\0'ist eine seltsame Art zu schreiben ''. Sie fehlen IFS=und der -rWechsel zu read: Ihre gelesene Anweisung sollte lauten : IFS= read -rd '' file.
gniourf_gniourf
Ich denke, einige müssten gesucht $'\0'und einige Stapelpunkte verteilt werden. Nehmen Sie die Änderungen vor, auf die Sie hingewiesen haben. Was sind die negativen Auswirkungen, wenn IFS=man echo -e "ok \nok\0" | while read -d '' line; do echo -e "$line"; donees nicht versucht, scheint es keine zu geben. Auch -r, wie ich sehe, ist oft Standard, konnte aber kein Beispiel dafür finden, was dies verhindert.
James
4
IFS=wird benötigt, wenn ein Dateiname mit einem Leerzeichen endet: try is with touch 'Prospero '(beachten Sie das nachfolgende Leerzeichen). Außerdem benötigen Sie den -rSchalter, falls ein Dateiname einen Backslash aufweist: Versuchen Sie es mit touch 'Prospero\n'.
gniourf_gniourf
-1

Sieht so aus, als würden Sie versuchen, eine Windows-Datei (.exe) auszuführen. Sicherlich sollten Sie Powershell verwenden. Auf jeden Fall reicht auf einer Linux-Bash-Shell ein einfacher Einzeiler aus.

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

Oder in einer Bash

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