it-swarm.com.de

Benennen Sie Dateien mit find und sed rekursiv um

Ich möchte eine Reihe von Verzeichnissen durchgehen und alle Dateien, die in _test.rb enden, in _spec.rb umbenennen. Ich habe noch nie richtig verstanden, wie man mit Bash umgeht, und dieses Mal dachte ich, ich würde mich ein wenig anstrengen, um es in Ordnung zu bringen. Ich bin bis jetzt nicht weit gekommen, aber ich bemühe mich nach besten Kräften:

find spec -name "*_test.rb" -exec echo mv {} `echo {} | sed s/test/spec/` \;

NB: Es gibt ein zusätzliches Echo nach exec, sodass der Befehl gedruckt wird, anstatt ausgeführt zu werden, während ich ihn teste.

Wenn ich es ausführe, ist die Ausgabe für jeden übereinstimmenden Dateinamen:

mv original original

d.h. die Substitution durch sed ist verloren gegangen. Was ist der Trick?

77
opsb

Dies geschieht, weil sed die Zeichenfolge {} Als Eingabe empfängt, wie mit folgendem Befehl überprüft werden kann:

find . -exec echo `echo "{}" | sed 's/./foo/g'` \;

die rekursiv foofoo für jede Datei im Verzeichnis druckt. Der Grund für dieses Verhalten ist, dass die Pipeline von der Shell einmal ausgeführt wird, wenn sie den gesamten Befehl erweitert.

Es gibt keine Möglichkeit, die sed -Pipeline so zu zitieren, dass find sie für jede Datei ausführt, da find keine Befehle über die Shell ausführt und keine hat Vorstellung von Pipelines oder Backquotes. Das GNU findutils-Handbuch erklärt, wie man eine ähnliche Aufgabe ausführt, indem man die Pipeline in ein separates Shell-Skript schreibt:

#!/bin/sh
echo "$1" | sed 's/_test.rb$/_spec.rb/'

(Möglicherweise gibt es eine perverse Art und Weise, sh -c Und eine Menge Anführungszeichen zu verwenden, um all dies in einem Befehl zu tun, aber ich werde es nicht versuchen.)

32
Fred Foo

Um es auf eine Weise zu lösen, die dem ursprünglichen Problem am nächsten kommt, würde man wahrscheinlich die Option xargs "args per command line" verwenden:

find . -name *_test.rb | sed -e "p;s/test/spec/" | xargs -n2 mv

Es findet die Dateien im aktuellen Arbeitsverzeichnis rekursiv, gibt den ursprünglichen Dateinamen (p) und anschließend einen geänderten Namen (s/test/spec/) und füttert alles paarweise an mv (xargs -n2). Beachten Sie, dass in diesem Fall der Pfad selbst keine Zeichenfolge test enthalten darf.

113
ramtam

vielleicht möchten Sie auch anders überlegen

for file in $(find . -name "*_test.rb")
do 
  echo mv $file `echo $file | sed s/_test.rb$/_spec.rb/`
done
23
ajreal

Ich finde das kürzer

find . -name '*_test.rb' -exec bash -c 'echo mv $0 ${0/test.rb/spec.rb}' {} \;
17
csg

Sie erwähnen, dass Sie bash als Ihre Shell verwenden. In diesem Fall benötigen Sie find und sed nicht, um die gewünschte Batch-Umbenennung zu erreichen ...

Angenommen, Sie verwenden bash als Shell:

$ echo $Shell
/bin/bash
$ _

... und vorausgesetzt, Sie haben die sogenannte globstar Shell-Option aktiviert:

$ shopt -p globstar
shopt -s globstar
$ _

... und schließlich davon ausgehend, dass Sie das Hilfsprogramm rename installiert haben (im Paket util-linux-ng enthalten)

$ which rename
/usr/bin/rename
$ _

... dann können Sie die Batch-Umbenennung in einem bash one-liner wie folgt durchführen:

$ rename _test _spec **/*_test.rb

(Die Shell-Option globstar stellt sicher, dass bash alle passenden *_test.rb - Dateien findet, egal wie tief sie in der Verzeichnishierarchie verschachtelt sind. Verwenden Sie help shopt, um herauszufinden, wie um die Option zu setzen)

9
pvandenberk

Sie können es ohne sed tun, wenn Sie wollen:

for i in `find -name '*_test.rb'` ; do mv $i ${i%%_test.rb}_spec.rb ; done

${var%%suffix} entfernt suffix vom Wert von var.

oder, um es mit sed zu tun:

for i in `find -name '*_test.rb'` ; do mv $i `echo $i | sed 's/test/spec/'` ; done
9
Wayne Conrad

Der einfachste Weg:

find . -name "*_test.rb" | xargs rename s/_test/_spec/

Der schnellste Weg (vorausgesetzt, Sie haben 4 Prozessoren):

find . -name "*_test.rb" | xargs -P 4 rename s/_test/_spec/

Wenn Sie eine große Anzahl von Dateien verarbeiten müssen, kann die Liste der an xargs weitergeleiteten Dateinamen dazu führen, dass die resultierende Befehlszeile die maximal zulässige Länge überschreitet.

Sie können das Limit Ihres Systems mit getconf ARG_MAX Überprüfen.

Auf den meisten Linux-Systemen können Sie mit free -b Oder cat /proc/meminfo Herausfinden, mit wie viel RAM Sie arbeiten müssen; andernfalls verwenden Sie top oder Ihre Systemaktivitätsmonitor App.

Sicherer (vorausgesetzt, Sie haben 1000000 Byte RAM zum Arbeiten):

find . -name "*_test.rb" | xargs -s 1000000 rename s/_test/_spec/
6
l3x

Dafür brauchen Sie sed nicht. Sie können mit einer while -Schleife, die mit dem Ergebnis von find durch ein Prozessersetzung gespeist wird, perfekt alleine sein.

Wenn Sie also einen find -Ausdruck haben, der die benötigten Dateien auswählt, verwenden Sie die folgende Syntax:

while IFS= read -r file; do
     echo "mv $file ${file%_test.rb}_spec.rb"  # remove "echo" when OK!
done < <(find -name "*_test.rb")

Dies wird find Dateien und benennt alle von ihnen Striping der Zeichenfolge _test.rb vom Ende und anhängend _spec.rb.

Für diesen Schritt verwenden wir Shell Parameter Expansion wobei ${var%string} entfernt das kürzeste passende Muster "string" aus $var.

$ file="HELLOa_test.rbBYE_test.rb"
$ echo "${file%_test.rb}"          # remove _test.rb from the end
HELLOa_test.rbBYE
$ echo "${file%_test.rb}_spec.rb"  # remove _test.rb and append _spec.rb
HELLOa_test.rbBYE_spec.rb

Sehen Sie sich ein Beispiel an:

$ tree
.
├── ab_testArb
├── a_test.rb
├── a_test.rb_test.rb
├── b_test.rb
├── c_test.hello
├── c_test.rb
└── mydir
    └── d_test.rb

$ while IFS= read -r file; do echo "mv $file ${file/_test.rb/_spec.rb}"; done < <(find -name "*_test.rb")
mv ./b_test.rb ./b_spec.rb
mv ./mydir/d_test.rb ./mydir/d_spec.rb
mv ./a_test.rb ./a_spec.rb
mv ./c_test.rb ./c_spec.rb
2
fedorqui

Folgendes hat bei mir funktioniert, als die Dateinamen Leerzeichen enthielten. Im folgenden Beispiel werden alle .dar-Dateien rekursiv in .Zip-Dateien umbenannt:

find . -name "*.dar" -exec bash -c 'mv "$0" "`echo \"$0\" | sed s/.dar/.Zip/`"' {} \;
2
rskengineer

Dies ist ein Beispiel, das in allen Fällen funktionieren sollte. Funktioniert rekursiv, braucht nur Shell und unterstützt Dateinamen mit Leerzeichen.

find spec -name "*_test.rb" -print0 | while read -d $'\0' file; do mv "$file" "`echo $file | sed s/test/spec/`"; done
1
eldy

Ich habe nicht das Herz, es noch einmal zu tun, aber ich schrieb dies als Antwort auf Commandline Find Sed Exec . Dort wollte der Fragesteller wissen, wie er sich bewegen soll einen ganzen Baum, möglicherweise ein oder zwei Verzeichnisse ausgenommen, und benennen Sie alle Dateien und Verzeichnisse, die den String "OLD" enthalten, in "NEW" um.

Neben der Beschreibung des wie mit akribischer Ausführlichkeit kann diese Methode auch insofern einzigartig sein, als sie ein eingebautes Debugging beinhaltet . Grundsätzlich werden nur die Befehle kompiliert und in einer Variablen gespeichert, die es für erforderlich hält, um die angeforderte Arbeit auszuführen.

Es werden auch ausdrücklich Schleifen so weit wie möglich vermieden. Außer der sed rekursiven Suche nach mehr als einer Übereinstimmung des Musters gibt es meines Wissens keine andere Rekursion.

Und zuletzt ist dies vollständig null begrenzt - es wird kein Zeichen in einem Dateinamen außer null ausgelöst. Ich denke nicht, dass du das haben solltest.

Das ist übrigens [~ # ~] wirklich [~ # ~] schnell. Aussehen:

% _mvnfind() { mv -n "${1}" "${2}" && cd "${2}"
> read -r SED <<SED
> :;s|${3}\(.*/[^/]*${5}\)|${4}\1|;t;:;s|\(${5}.*\)${3}|\1${4}|;t;s|^[0-9]*[\t]\(mv.*\)${5}|\1|p
> SED
> find . -name "*${3}*" -printf "%d\tmv %P ${5} %P\000" |
> sort -zg | sed -nz ${SED} | read -r ${6}
> echo <<EOF
> Prepared commands saved in variable: ${6}
> To view do: printf ${6} | tr "\000" "\n"
> To run do: sh <<EORUN
> $(printf ${6} | tr "\000" "\n")
> EORUN
> EOF
> }
% rm -rf "${UNNECESSARY:=/any/dirs/you/dont/want/moved}"
% time ( _mvnfind ${SRC=./test_tree} ${TGT=./mv_tree} \
> ${OLD=google} ${NEW=replacement_Word} ${sed_sep=SsEeDd} \
> ${sh_io:=sh_io} ; printf %b\\000 "${sh_io}" | tr "\000" "\n" \
> | wc - ; echo ${sh_io} | tr "\000" "\n" |  tail -n 2 )

   <actual process time used:>
    0.06s user 0.03s system 106% cpu 0.090 total

   <output from wc:>

    Lines  Words  Bytes
    115     362   20691 -

    <output from tail:>

    mv .config/replacement_Word-chrome-beta/Default/.../googlestars \
    .config/replacement_Word-chrome-beta/Default/.../replacement_wordstars        

HINWEIS: Für die obigen function sind wahrscheinlich GNU Versionen von sed und find, um die Aufrufe find printf und sed -z -e und :;recursive regex test;t richtig zu behandeln. Wenn Ihnen diese nicht zur Verfügung stehen, kann die Funktionalität wahrscheinlich mit ein paar geringfügigen Anpassungen dupliziert werden.

Dies sollte alles tun, was Sie von Anfang bis Ende mit sehr wenig Aufwand wollten. Ich habe fork mit sed gemacht, aber ich habe auch einige rekursive Verzweigungstechniken sed geübt, deshalb bin ich hier. Ich glaube, es ist so, als würde man sich in einer Friseurschule einen ermäßigten Haarschnitt zulegen. Hier ist der Workflow:

  • rm -rf ${UNNECESSARY}
    • Ich habe absichtlich jeden Funktionsaufruf ausgelassen, der Daten jeglicher Art löschen oder zerstören könnte. Sie erwähnen, dass ./app Möglicherweise unerwünscht ist. Löschen Sie es oder verschieben Sie es vorher an einen anderen Ort. Alternativ können Sie eine \( -path PATTERN -exec rm -rf \{\} \) -Routine erstellen, um find programmgesteuert auszuführen, aber das gehört Ihnen.
  • _mvnfind "${@}"
    • Deklarieren Sie die Argumente und rufen Sie die Worker-Funktion auf. ${sh_io} Ist besonders wichtig, da es die Rückkehr von der Funktion sichert. ${sed_sep} Folgt in Kürze. Dies ist eine beliebige Zeichenfolge, mit der auf die Rekursion von sed in der Funktion verwiesen wird. Wenn ${sed_sep} Auf einen Wert gesetzt ist, der möglicherweise in einem Ihrer Pfad- oder Dateinamen enthalten ist, auf den Sie angewendet haben, lassen Sie es einfach nicht zu.
  • mv -n $1 $2
    • Der ganze Baum wird von Anfang an verschoben. Das erspart viel Kopfschmerzen. glaube mir. Der Rest dessen, was Sie tun möchten - das Umbenennen - ist einfach eine Frage der Metadaten des Dateisystems. Wenn Sie dies zum Beispiel von einem Laufwerk auf ein anderes oder über Dateisystemgrenzen hinweg tun, ist es besser, dies mit einem Befehl auf einmal zu tun. Es ist auch sicherer. Beachten Sie die Option -noclobber Für mv; Wie geschrieben, setzt diese Funktion nicht ${SRC_DIR}, wenn bereits ein ${TGT_DIR} existiert.
  • read -R SED <<HEREDOC
    • Ich habe alle Befehle von sed hier gefunden, um Ärger zu vermeiden und sie in eine Variable einzulesen, die Sed unten zugeführt werden soll. Erklärung unten.
  • find . -name ${OLD} -printf
    • Wir beginnen den Prozess find. Mit find suchen wir nur nach Elementen, die umbenannt werden müssen, da wir bereits mit dem ersten Befehl der Funktion alle Vorgänge von Ort zu Ort mv ausgeführt haben. Anstatt irgendeine direkte Aktion mit find auszuführen, wie zum Beispiel einen exec - Aufruf, verwenden wir ihn stattdessen, um die Befehlszeile mit -printf Dynamisch aufzubauen.
  • %dir-depth :tab: 'mv '%path-to-${SRC}' '${sed_sep}'%path-again :null delimiter:'
    • Nachdem find die Dateien gefunden hat, die wir benötigen, erstellt und druckt es direkt ( most) des Befehls, den wir benötigen, um Ihr Umbenennen zu verarbeiten. Das %dir-depth Am Anfang jeder Zeile stellt sicher, dass wir nicht versuchen, eine Datei oder ein Verzeichnis in der Struktur mit einem übergeordneten Objekt umzubenennen, das noch nicht umbenannt wurde. find verwendet alle Arten von Optimierungstechniken, um Ihren Dateisystembaum zu durchsuchen, und es ist nicht sicher, ob die benötigten Daten in betriebssicherer Reihenfolge zurückgegeben werden. Dies ist, warum wir als nächstes ...
  • sort -general-numerical -zero-delimited
    • Wir sortieren alle Ausgaben von find basierend auf %directory-depth, Sodass die Pfade, die $ {SRC} am nächsten liegen, zuerst bearbeitet werden. Dies vermeidet mögliche Fehler, bei denen mv Dateien an nicht vorhandenen Speicherorten abgelegt werden, und minimiert den Bedarf an rekursiven Schleifen. ( Tatsächlich könnte es schwierig sein, überhaupt eine Schleife zu finden)
  • sed -ex :rcrs;srch|(save${sep}*til)${OLD}|\saved${SUBSTNEW}|;til ${OLD=0}
    • Ich denke, dies ist die einzige Schleife im gesamten Skript und sie durchläuft nur die zweite %Path - Schleife, die für jede Zeichenfolge gedruckt wird, falls sie mehr als einen $ {OLD} -Wert enthält, der möglicherweise ersetzt werden muss. Alle anderen Lösungen, die ich mir vorgestellt hatte, beinhalteten einen zweiten sed -Prozess, und obwohl eine kurze Schleife nicht wünschenswert sein mag, übertrifft sie sicherlich das Laichen und Verzweigen eines gesamten Prozesses.
    • Im Grunde genommen sucht sed hier nach $ {sed_sep}. Nachdem es gefunden wurde, werden es und alle Zeichen, auf die es trifft, gespeichert, bis es $ {OLD} findet, und es wird dann durch $ {NEW} ersetzt. Anschließend kehrt es zu $ ​​{sed_sep} zurück und sucht erneut nach $ {OLD}, falls es in der Zeichenfolge mehrmals vorkommt. Wenn es nicht gefunden wird, gibt es den geänderten String in stdout aus (den es dann als nächstes wieder fängt) und beendet die Schleife.
    • Dies vermeidet das Parsen der gesamten Zeichenfolge und stellt sicher, dass die erste Hälfte der Befehlszeichenfolge mv, die natürlich $ {OLD} enthalten muss, diese enthält und die zweite Hälfte so oft geändert wird wie erforderlich, um den Namen $ {OLD} aus dem Zielpfad von mv zu löschen.
  • sed -ex...-ex search|%dir_depth(save*)${sed_sep}|(only_saved)|out
    • Die beiden Aufrufe -exec Erfolgen hier ohne einen zweiten fork. Wie wir gesehen haben, ändern wir im ersten Schritt den Befehl mv, wie er vom Funktionsbefehl find-printf Bereitgestellt wird, nach Bedarf, um alle Referenzen von $ {OLD ordnungsgemäß zu ändern } zu $ ​​{NEW}, aber um dies zu tun, mussten wir einige willkürliche Referenzpunkte verwenden, die nicht in der endgültigen Ausgabe enthalten sein sollten. Nachdem sed alle erforderlichen Schritte ausgeführt hat, weisen wir es an, seine Referenzpunkte aus dem Haltepuffer zu entfernen, bevor es weitergeleitet wird.

UND JETZT SIND WIR ZURÜCK

read erhält einen Befehl, der so aussieht:

% mv /path2/$SRC/$OLD_DIR/$OLD_FILE /same/path_w/$NEW_DIR/$NEW_FILE \000

Es wird read in ${msg} Als ${sh_io} Geschrieben, das nach Belieben außerhalb der Funktion geprüft werden kann.

Cool.

-Mike

1
mikeserv

In der Antwort von ramtam, die mir gefällt, funktioniert der Find-Teil in Ordnung, der Rest jedoch nicht, wenn der Pfad Leerzeichen enthält. Ich bin nicht sehr vertraut mit sed, aber ich konnte diese Antwort ändern auf:

find . -name "*_test.rb" | Perl -pe 's/^((.*_)test.rb)$/"\1" "\2spec.rb"/' | xargs -n2 mv

Ich brauchte wirklich eine solche Änderung, weil in meinem Anwendungsfall der letzte Befehl eher so aussieht

find . -name "olddir" | Perl -pe 's/^((.*)olddir)$/"\1" "\2new directory"/' | xargs -n2 mv
1
dzs0000

Ich konnte mit Dateinamen mit Leerzeichen umgehen, indem ich den Beispielen folgte, die von onitake vorgeschlagen wurden.

Dieses unterbricht nicht , wenn der Pfad Leerzeichen oder die Zeichenfolge test enthält:

find . -name "*_test.rb" -print0 | while read -d $'\0' file
do
    echo mv "$file" "$(echo $file | sed s/test/spec/)"
done
1
James

wenn Sie Ruby (1.9+)

Ruby -e 'Dir["**/*._test.rb"].each{|x|test(?f,x) and File.rename(x,x.gsub(/_test/,"_spec") ) }'
1
kurumi

Hier ist ein netter Oneliner, der den Trick macht. Sed kann mit diesem Recht nicht umgehen, besonders wenn mehrere Variablen von xargs mit -n 2 übergeben werden. Eine Bash-Substition würde dies leicht handhaben wie:

find ./spec -type f -name "*_test.rb" -print0 | xargs -0 -I {} sh -c 'export file={}; mv $file ${file/_test.rb/_spec.rb}'

Das Hinzufügen von -type -f begrenzt die Verschiebevorgänge nur auf Dateien, -print 0 behandelt leere Leerzeichen in Pfaden.

0
Orsiris de Jong
$ find spec -name "*_test.rb"
spec/dir2/a_test.rb
spec/dir1/a_test.rb

$ find spec -name "*_test.rb" | xargs -n 1 /usr/bin/Perl -e '($new=$ARGV[0]) =~ s/test/spec/; system(qq(mv),qq(-v), $ARGV[0], $new);'
`spec/dir2/a_test.rb' -> `spec/dir2/a_spec.rb'
`spec/dir1/a_test.rb' -> `spec/dir1/a_spec.rb'

$ find spec -name "*_spec.rb"
spec/dir2/b_spec.rb
spec/dir2/a_spec.rb
spec/dir1/a_spec.rb
spec/dir1/c_spec.rb
0
Damodharan R

Sicherere Methode zum Umbenennen mit find-Utils und sed-regulären Ausdrücken:

  mkdir ~/practice

  cd ~/practice

  touch classic.txt.txt

  touch folk.txt.txt

Entfernen Sie die Erweiterung ".txt.txt" wie folgt:

  cd ~/practice

  find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} \;

Wenn Sie das + anstelle von; Um im Batch-Modus zu arbeiten, benennt der obige Befehl nur die erste übereinstimmende Datei um, aber nicht die gesamte Liste der Datei-Übereinstimmungen mit 'find'.

  find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} +
0
Sathish

Ihre Frage scheint sich um sed zu handeln, aber um Ihr Ziel der rekursiven Umbenennung zu erreichen, schlage ich Folgendes vor, das ich schamlos aus einer anderen Antwort hier herausgerissen habe: rekursive Umbenennung in Bash

#!/bin/bash
IFS=$'\n'
function RecurseDirs
{
for f in "[email protected]"
do
  newf=echo "${f}" | sed -e 's/^(.*_)test.rb$/\1spec.rb/g'
    echo "${f}" "${newf}"
    mv "${f}" "${newf}"
    f="${newf}"
  if [[ -d "${f}" ]]; then
    cd "${f}"
    RecurseDirs $(ls -1 ".")
  fi
done
cd ..
}
RecurseDirs .
0
dreynold

Ich teile diesen Beitrag, da er etwas mit Fragen zu tun hat. Entschuldigung, dass Sie keine weiteren Details angegeben haben. Hoffe es hilft jemand anderem. http://www.peteryu.ca/tutorials/shellscripting/batch_rename

0
Breton F.