2017-01-04 1 views
1

aus dem aktuellen Verzeichnis Strippe ich mehrere Unterverzeichnisse habe:jedes Unterverzeichnis Gehen Sie in und Masse Dateien umbenennen, indem führende Zeichen

subdir1/ 
001myfile001A.txt 
002myfile002A.txt 
subdir2/ 
001myfile001B.txt 
002myfile002B.txt 

, wo ich jedes Zeichen aus den Dateinamen entfernen lassen möchte, bevor myfile so dass ich mit

am Ende
subdir1/ 
myfile001A.txt 
myfile002A.txt 
subdir2/ 
myfile001B.txt 
myfile002B.txt 

ich einen Code haben, dies zu tun ...

#!/bin/bash 
for d in `find . -type d -maxdepth 1`; do 
    cd "$d" 
    for f in `find . "*.txt"`; do 
     mv "$f" "$(echo "$f" | sed -r 's/^.*myfile/myfile/')" 
    done 

done 

jedoch die neu umbenannten Dateien enden im Stammverzeichnis nach oben

heißt

myfile001A.txt 
myfile002A.txt 
myfile001B.txt 
myfile002B.txt 
subdir1/ 
subdir2/ 

, in dem die Unterverzeichnisse sind nun leer.

Wie ändere ich mein Skript, um die Dateien umzubenennen und sie in ihren jeweiligen Unterverzeichnissen zu behalten? Wie Sie die erste Schleife ändert Verzeichnis in das Unterverzeichnis sehen können so nicht sicher, warum die Dateien ein Verzeichnis immer geschickt am Ende ...

Antwort

1

Ihr Skript hat mehrere Probleme. An erster Stelle erfüllt Ihr äußerer find Befehl nicht ganz, was Sie erwarten: es gibt nicht nur jedes der Unterverzeichnisse aus, sondern auch den Suchstamm ., der selbst ein Verzeichnis ist. Sie könnten dies entdeckt haben, indem Sie den Befehl unter anderem manuell ausführen. Sie brauchen nicht wirklich find dafür verwenden, aber die Annahme, dass Sie es tun verwenden, dies wäre besser:

for d in $(find * -maxdepth 0 -type d); do 

Außerdem . ist das erste Ergebnis Ihres ursprünglichen find Befehl, und Deine Probleme gehen dort weiter. Ihre ursprüngliche cd ist ohne sinnvollen Effekt, weil Sie nur zu dem gleichen Verzeichnis wechseln, in dem Sie bereits sind. Der find Befehl in der inneren Schleife ist dort verwurzelt und steigt in beide Unterverzeichnisse. Die Pfadinformationen für jede Datei, die Sie umbenennen möchten, werden daher durch sed entfernt, weshalb die Ergebnisse im ursprünglichen Arbeitsverzeichnis enden (./subdir1/001myfile001A.txt ->myfile001A.txt). Zu dem Zeitpunkt, an dem Sie die Unterverzeichnisse bearbeiten, sind keine Dateien zum Umbenennen mehr vorhanden.

Aber das ist nicht alles: Der find Befehl in Ihrer inneren Schleife ist falsch. Da Sie keine Option davor angeben, interpretiert find"*.txt" als Bezeichnung eines zweiten Suchstamms zusätzlich zu .. Sie wollten vermutlich -name "*.txt" verwenden, um die Suchergebnisse zu filtern. Ohne es gibt find den Namen jeder Datei in der Struktur aus. Vermutlich unterdrücken oder ignorieren Sie die resultierenden Fehlermeldungen.

Aber angenommen, dass Ihre Verzeichnisse keine Unterverzeichnisse ihrer eigenen, wie gezeigt, und dass Sie mit dotfiles nicht betroffen sind, auch diese korrigierte Version ...

for f in `find . -name "*.txt"`; 

... ein schrecklich Schwergewicht Weg, dies zu sagen ...

for f in *.txt; 

... oder auch die ...

for f in *?myfile*.txt; 

... Letzteres vermeidet Versuche, Dateien umzubenennen, deren Namen sich nicht ändern.

Des Weiteren ist ein sed Prozess für jeden Dateinamen starten ziemlich verschwenderisch und teuer, wenn man nur bash's Einbau-Substitution Feature verwenden:

mv "$f" "${f/#*myfile/myfile}" 

Und Sie werden auch feststellen, dass Ihr Arbeitsverzeichnis verkorkste wird . Das Arbeitsverzeichnis ist ein Merkmal der gesamten Shell-Umgebung und wird daher nicht bei jeder Schleifeniteration automatisch zurückgesetzt. Sie müssen das manuell in irgendeiner Weise behandeln. pushd/popd würde dies tun, als würde der äußere Schleifenkörper in einer Subshell laufen.

Insgesamt ist dies den Trick:

#!/bin/bash 

for d in $(find * -maxdepth 0 -type d); do 
    pushd "$d" 
    for f in *.txt; do 
     mv "$f" "${f/#*myfile/myfile}" 
    done 
    popd 
done 
+1

ich für die Auswahl der richtigen Antworten verdorben wurde, aber dieses war so gründlich, und wies darauf hin, was ich da einige Best Practices falsch als auch tat. Vielen Dank! – brucezepplin

1

können Sie es tun, ohne find und sed:

$ for f in */*.txt; do echo mv "$f" "${f/\/*myfile/\/myfile}"; done 
mv subdir1/001myfile001A.txt subdir1/myfile001A.txt 
mv subdir1/002myfile002A.txt subdir1/myfile002A.txt 
mv subdir2/001myfile001B.txt subdir2/myfile001B.txt 
mv subdir2/002myfile002B.txt subdir2/myfile002B.txt 

Wenn Sie entfernen die echo, es wird tatsächlich die Dateien umbenennen.

Dies verwendet shell parameter expansion, um einen Schrägstrich und alles bis myfile mit nur einem Schrägstrich und myfile zu ersetzen.

Beachten Sie, dass dies bricht, wenn mehr als eine Ebene von Unterverzeichnissen vorhanden ist.

$ for f in **/*.txt; do echo mv "$f" "${f/\/*([!\/])myfile/\/myfile}"; done 
mv subdir1/001myfile001A.txt subdir1/myfile001A.txt 
mv subdir1/002myfile002A.txt subdir1/myfile002A.txt 
mv subdir1/subdir3/001myfile001A.txt subdir1/subdir3/myfile001A.txt 
mv subdir1/subdir3/002myfile002A.txt subdir1/subdir3/myfile002A.txt 
mv subdir2/001myfile001B.txt subdir2/myfile001B.txt 
mv subdir2/002myfile002B.txt subdir2/myfile002B.txt 

Dies verwendet die *([!\/]) Muster („null oder mehr Zeichen, die kein Schrägstrich sind“): In diesem Fall könnten Sie pattern matching (aktiviert mit shopt -s extglob) und der globstar Shell Option (shopt -s globstar), verwenden Sie erweitert. Der Schrägstrich muss im Klammerausdruck maskiert werden, da wir immer noch innerhalb des Teils der ${parameter/pattern/string} Erweiterung sind.

1

eine leichte Modifikation sollte Ihr Problem beheben:

#!/bin/bash 
for f in `find . -maxdepth 2 -name "*.txt"`; do 
    mv "$f" "$(echo "$f" | sed -r 's,[^/]+(myfile),\1,')" 
done 

Hinweis: Diese sed verwendet , statt / als Trennzeichen.

jedoch gibt es viel schnellere Möglichkeiten.

hier ist mit dem Umbenennungs Dienstprogramm verfügbar oder einfach installiert, wo immer es bash und perl:

find . -maxdepth 2 -name "*.txt" | rename 's,[^/]+(myfile),/$1,' 

hier sind Tests auf 1000 Dateien:

for `find`; do mv  9.176s 
rename     0.099s 

, die 100-fach so schnell ist.

John Bollinger akzeptierte Antwort ist doppelt so schnell wie die OPs, aber 50x so langsam wie diese rename Lösung:

for|for|mv "$f" "${f//}" 4.316s 

auch, es wird nicht funktionieren, wenn es ein Verzeichnis mit zu vielen Elementen für a Shell-Glob. ebenso irgendwelche Antworten, die for f in *.txt oder for f in */*.txt oder find * oder rename ... subdir*/* verwenden. Antworten, die mit find . beginnen, funktionieren auch auf Verzeichnissen mit einer beliebigen Anzahl von Elementen.

1

Vielleicht möchten Sie den folgenden Befehl verwenden, anstatt:

rename 's#(.*/).*(myfile.*)#$1$2#' subdir*/* 

Sie rename -n verwenden können ... um das Ergebnis zu überprüfen, ohne tatsächlich etwas umbenennen.

In Bezug auf Ihre eigentliche Frage:
Der Fund Befehl von der äußeren Schleife zurück 3 Verzeichnisse (!):

. 
./subdir1 
./subdir2 

Die unerwünschte . ist der Grund, warum alle Dateien im Stammverzeichnis am Ende (das ist .). Sie können . mit der Option -mindepth 1 ausschließen. Leider war dies der Grund dafür, dass die Dateien am falschen Ort landeten, aber nicht das einzige Problem. Da Sie bereits eine der Antworten akzeptiert haben, müssen Sie sie nicht alle auflisten.

Verwandte Themen