2012-05-29 6 views
31

ersetzen Wenn ich xargs manchmal verwende ich brauche nicht explizit den Ersatz-String verwenden:Ändern Zeichenfolge in xargs

find . -name "*.txt" | xargs rm -rf 

In anderen Fällen mag ich den Ersatz-String angeben, um Dinge zu tun:

find . -name "*.txt" | xargs -I '{}' mv '{}' /foo/'{}'.bar 

der vorherige Befehl würde alle Textdateien im aktuellen Verzeichnis in /foo bewegen und es wird die Erweiterung bar auf alle Dateien anhängen.

Wenn ich anstelle des Hinzufügens von Text zu der Ersetzungszeichenfolge diese Zeichenfolge so ändern möchte, dass ich Text zwischen den Namen und die Erweiterung der Dateien einfügen kann, wie kann ich das tun? Nehmen wir zum Beispiel an, ich möchte dasselbe wie im vorherigen Beispiel tun, aber die Dateien sollten von <name>.txt zu /foo/<name>.bar.txt (anstelle von /foo/<name>.txt.bar) umbenannt/verschoben werden.

UPDATE: Ich schaffe es, eine Lösung zu finden:

find . -name "*.txt" | xargs -I{} \ 
    sh -c 'base=$(basename $1) ; name=${base%.*} ; ext=${base##*.} ; \ 
      mv "$1" "foo/${name}.bar.${ext}"' -- {} 

Aber ich frage mich, ob es eine kürzere/bessere Lösung ist.

+1

Nein, außer, dass ich mehr verwenden würde zitieren 'mv "$ 1" "foo/$ {name} .bar $ {ext}"' und Sie tun können, '. Basisname wie folgt: 'base = $ {1 ## * /}'. Sie sollten Ihre Lösung als Antwort veröffentlichen und akzeptieren. –

+0

@DennisWilliamson Danke für Ihren Kommentar! Ich werde noch etwas warten, nur um zu sehen, ob irgendjemand etwas ausgefallenes macht, sonst werde ich die Frage selbst beantworten. – betabandido

+1

Ich denke, wenn Ihr Dateiname das letzte Ding in der Zeile ist, brauchen Sie nicht die -I {} noch {} in der Befehlszeile. (Beachten Sie, dass der Hauptzweck von xargs darin besteht, Gruppen zu gruppieren. Wenn Sie also am Ende des Argumentaufrufs von xargs NICHT mehrere Dinge wollen, brauchen Sie "-l 1" (oder -L1 für einige Versionen von xargs). -I {} impliziert -l 1, deshalb funktioniert das auch hier.) –

Antwort

12

In Fällen wie diesen, eine while Schleife wäre besser lesbar:

find . -name "*.txt" | while IFS= read -r pathname; do 
    base=$(basename "$pathname"); name=${base%.*}; ext=${base##*.} 
    mv "$pathname" "foo/${name}.bar.${ext}" 
done 

Beachten Sie, dass Dateien mit dem gleichen Namen in verschiedenen Unterverzeichnissen finden. Sind Sie in Ordnung mit Duplikaten, die von mv überschrieben werden?

+0

Ja, ich weiß, dass es dieses "Problem" hat. Eigentlich ist das Beispiel, das ich gepostet habe, eher ein dummer. Ich habe es mir einfach ausgedacht, um ein einfaches Beispiel in meiner Frage zu zeigen, also kein Problem :) – betabandido

+1

Um mit Dateinamen zu arbeiten, die eine neue Zeile haben, benutze diese: 'find. -name "* .txt" -print0 | während read -r -d '' Pfadname; do ... ' – Cem

+2

Beachten Sie, dass diese (serielle) Vorgehensweise nicht funktioniert, wenn Sie versuchen, xargs mit' --max-procs' parallel zu gestalten. – g33kz0r

2

Wenn Sie etwas anderes als bash/sh verwenden dürfen, UND das ist nur für ein schickes "mv" ... können Sie das ehrwürdige Skript "rename.pl" ausprobieren. Ich benutze es unter Linux und Cygwin unter Windows die ganze Zeit.

http://people.sc.fsu.edu/~jburkardt/pl_src/rename/rename.html

rename.pl 's/^(.*?)\.(.*)$/\1-new_stuff_here.\2/' list_of_files_or_glob 

Sie auch eine „-p“ Parameter, um ihn zu rename.pl verwenden können haben Sie sagen, was es, getan hätte, ohne es wirklich zu tun.

Ich habe gerade Folgendes in meinem c:/bin (Cygwin/Windows-Umgebung) versucht. Ich benutzte das "-p", damit es ausspuckte, was es getan hätte. In diesem Beispiel werden nur die Basis und die Erweiterung geteilt und zwischen ihnen eine Zeichenfolge hinzugefügt.

perl c:/bin/rename.pl -p 's/^(.*?)\.(.*)$/\1-new_stuff_here.\2/' *.bat 

rename "here.bat" => "here-new_stuff_here.bat" 
rename "htmldecode.bat" => "htmldecode-new_stuff_here.bat" 
rename "htmlencode.bat" => "htmlencode-new_stuff_here.bat" 
rename "sdiff.bat" => "sdiff-new_stuff_here.bat" 
rename "widvars.bat" => "widvars-new_stuff_here.bat" 
+0

Danke für deine Antwort! 'mv' Beispiel war nur ein Beispiel. Ich stelle mich der Notwendigkeit, etwas Ähnliches in vielen anderen Situationen zu tun, also denke ich, dass 'rename.pl' in diesen Fällen nicht funktionieren würde. – betabandido

+0

Nun, ja und nein; Sie können Rename.pl leicht ändern, um * NOT * eine Datei tatsächlich umzubenennen, aber tun Sie etwas anderes damit. Wie Sie sehen, wird der Parameter "-p" nicht umbenannt, sondern nur das Ergebnis der Transformation ausgegeben. Sie können dieses Skript als Grundlage für viele andere Dinge verwenden, und das Argument, das es braucht, ist willkürlicher Perl-Code, der immens mächtig ist. Aber Sie sind sicherlich dennoch willkommen. –

9

Wenn Sie GNU haben Parallel http://www.gnu.org/software/parallel/ installiert, dies zu tun:

find . -name "*.txt" | parallel 'ext="{/}" ; mv -- {} foo/{/.}.bar.${ext##*.}' 

Sie können GNU installieren Parallel einfach durch:

wget http://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel 
chmod 755 parallel 
cp parallel sem 

Beobachten Sie die Intro-Videos für GNU Parallel mehr erfahren: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

+2

Ich empfehle auch GNU Parallel über 'xargs', es ist viel mächtiger und flexibler. Für die Installation empfehle ich dringend, den Paketmanager Ihrer Distribution zu verwenden (wie zum Beispiel 'sudo apt-get install parallel ') oder das empfohlene Installationsverfahren (siehe http://git.savannah.gnu.org/cgit/parallel). git/tree/README): '(wget -O - pi.dk/3 || locken pi.dk/3/ | fetch -o - http://pi.dk/3) | bash' – MestreLion

+1

@MetreLion Bitte empfehlen Sie nicht das Cull-Pipe-to-Shell Anti-Pattern. Was ist 'pi.dk'? Warum sollte ich dieser Domain vertrauen? 'gnu.org' ist zumindest vertrauenswürdiger. Aber keine URL verwendet TLS, und noch wichtiger, keiner überprüft eine kryptografische Signatur dessen, was heruntergeladen wurde. Die einzige Lösung, die zu empfehlen ist, ist "apt-get". – blujay

+0

Wie Sie sehen, führt pi.dk/3 eine Überprüfung der kryptografischen Signatur durch und stoppt, wenn die Signatur nicht übereinstimmt. Wenn es Ihnen unangenehm ist, es direkt an eine Shell weiterzuleiten, können Sie es in einer Datei speichern, den Code überprüfen und dann ausführen. –

18

Der folgende Befehl erstellt den Befehl move mit xargs und ersetzt das zweite Vorkommen von '.' mit '.bar.', führt dann die Befehle mit bash aus und arbeitet unter Mac OSX.

ls *.txt | xargs -I {} echo mv {} foo/{} | sed 's/\./.bar./2' | bash 
+1

Dirtay! Ich mag es – g33kz0r

+0

Das ist ein netter Trick – Zerodf

7

Es ist möglich, dies in einem Durchgang (in GNU getestet) zu tun

find . -name "*.txt" | xargs -I{} sh -c 'mv "$1" "foo/$(basename ${1%.*}).new.${1##*.}"' -- {} 
+1

Ich liebe die Art, wie Sie die Ersetzung Zeichenfolge als Positionsparameter für die Shell verwenden, so dass Sie Substitutionen und Erweiterungen durchführen können. Sehr cool. – GL2014

0

Inspirierten die Verwendung der temporären Variablenzuweisungen zu vermeiden, indem eine Antwort von @justaname oben, dieser Befehl, das Perl enthält Einzeiler wird es tun:

find ./ -name \*.txt | perl -p -e 's/^(.*\/(.*)\.txt)$/mv $1 .\/foo\/$2.bar.txt/' | bash