2012-05-29 14 views
31

Kiedy używam xargs czasami nie trzeba jawnie użyć ciągu wymień:Modyfikacja zastąpić ciąg w xargs

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

w innych przypadkach, chcę, aby określić ciąg zastępujący w tym celu rzeczy jak:

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

poprzedniego polecenia byłoby przenieść wszystkie pliki tekstowe w ramach bieżącego katalogu do /foo i będzie dołączyć rozszerzenie bar do wszystkich plików.

Jeśli zamiast dołączać jakiś tekst do ciągu zastępującego, chciałem zmodyfikować ten ciąg tak, żebym mógł wstawić jakiś tekst między nazwą i rozszerzeniem plików, jak mogłem to zrobić? Na przykład, powiedzmy, że chcę zrobić to samo, co w poprzednim przykładzie, ale nazwy plików powinny zostać zmienione/przeniesione z <name>.txt na /foo/<name>.bar.txt (zamiast /foo/<name>.txt.bar).

UPDATE: udało mi się znaleźć rozwiązanie:

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

Ale zastanawiam się, czy jest krótszy/lepszym rozwiązaniem.

+1

No, chyba, że ​​będę używać więcej powołując 'mv "$ 1" "foo/$ {nazwa} .bar $ {eXT}"' i można zrobić '. basename 'like this:' base = $ {1 ## * /} '. Powinieneś opublikować swoje rozwiązanie jako odpowiedź i zaakceptować je. –

+0

@DennisWilliamson Dzięki za komentarz! Poczekam jeszcze trochę, żeby sprawdzić, czy ktoś nie wymyśli sobie jakiejś wymyślnej rzeczy, inaczej sam odpowiem na to pytanie. – betabandido

+1

Myślę, że jeśli nazwa pliku jest ostatnią rzeczą w linii, nie potrzebujesz -I {} ani {} w linii poleceń. (Zauważ, że głównym celem xargs jest grupowanie, więc jeśli NIE chcesz wielu rzeczy na końcu 1 wywołania argumentu xargs potrzebujesz "-l 1" (lub -L 1 dla niektórych wersji xargs). -I {} oznacza -l 1, więc to też działa tutaj.) –

Odpowiedz

12

W takich przypadkach tego, while pętla byłaby bardziej czytelna:

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

Zauważ, że można znaleźć pliki o tej samej nazwie w różnych podkatalogów. Czy masz problemy z duplikatami, które zostały nadpisane przez mv?

+0

Tak, wiem, że ma ten "problem". W rzeczywistości przykład, który zamieściłem, jest raczej głupi. Właśnie to wymyśliłem, aby pokazać prosty przykład w moim pytaniu, więc nie ma problemu :) – betabandido

+1

Aby obsłużyć nazwy plików, które zawierają znak nowej linii, użyj: 'find. -name "* .txt" -print0 | while read -r -d '' ścieżka do nazwy; do ... ' – Cem

+2

Zauważ, że to (seryjne) podejście nie zadziała, jeśli próbujesz równoległe łączyć xargs za pomocą' --max-procs'. – g33kz0r

2

Jeśli możesz używać czegoś innego niż bash/sh, A to tylko dla wyszukanego "mv" ... możesz spróbować czcigodnego skryptu "rename.pl". Używam go w systemie Linux i cygwin w oknach przez cały czas.

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

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

Można również użyć parametru „-p” aby rename.pl mieć to powiedzieć, co uczyniłby to bez faktycznie robi.

Właśnie próbowałem następujących w moim c:/bin (Cygwin/Windows środowiska). Użyłem "-p", więc wypluło to, co by zrobił. Ten przykład dzieli tylko bazę i rozszerzenie i dodaje między nimi ciąg znaków.

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

Dziękuję za odpowiedź! Przykład "mv" był jednak tylko przykładem. Stawiam czoła potrzebie zrobienia czegoś podobnego w wielu innych sytuacjach, więc domyślam się, że "rename.pl" nie działałby w takich przypadkach. – betabandido

+0

Cóż, tak i nie; możesz łatwo zmienić rename.pl na * NOT * faktycznie zmienia nazwę pliku, ale zrób coś innego. Jak widać, z parametrem "-p", w ogóle nie zmienia nazwy, a jedynie drukuje wynik transformacji. Możesz użyć tego skryptu jako podstawy do robienia wielu innych rzeczy, a argumentem, który bierze, jest arbitralny kod perla, który jest niezwykle potężny. Ale mimo wszystko z pewnością jesteś mile widziany. –

9

Jeśli masz GNU Parallel http://www.gnu.org/software/parallel/ zainstalowany można to zrobić:

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

Można zainstalować GNU Parallel prostu przez:

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

Watch intro filmy GNU Parallel, aby dowiedzieć się więcej: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

+2

Polecam także GNU Parallel over 'xargs', jest o wiele bardziej wydajny i elastyczny. Jeśli chodzi o jego instalację, zdecydowanie sugeruję użycie menedżera pakietów twojej dystrybucji (takiego jak 'sudo apt-get install parallel') lub zalecanej procedury instalacji równoległej (zgodnie z http://git.savannah.gnu.org/cgit/parallel. git/tree/README): '(wget -O - pi.dk/3 || curl pi.dk/3/ || fetch -o - http://pi.dk/3) | bash' – MestreLion

+1

@MestreLion Prosimy nie polecać antystopu z zawijasami. Co to jest 'pi.dk'? Dlaczego powinienem ufać tej domenie? 'gnu.org' jest co najmniej bardziej godny zaufania. Ale żaden adres URL nie używa protokołu TLS, a co ważniejsze, żaden z nich nie sprawdza podpisu kryptograficznego pobranego. Jedynym rozwiązaniem, które można polecić, jest 'apt-get'. – blujay

+0

Jak widać pi.dk/3 sprawdza weryfikację podpisu kryptograficznego i zatrzymuje się, jeśli podpis nie pasuje. Jeśli czujesz się niekomfortowo, przesyłając go bezpośrednio do powłoki, możesz zapisać go w pliku, sprawdzić kod, a następnie go uruchomić. –

18

Następujące polecenie konstruuje polecenie move za pomocą xargs, zastępuje drugie wystąpienie "." z ".bar.", a następnie wykonuje polecenia z bash, pracując na Mac OSX.

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

Dirtay! Podoba mi się – g33kz0r

+0

To jest fajna sztuczka – Zerodf

7

Jest możliwe, aby to zrobić w jednym przebiegu (sprawdzone GNU) unikanie stosowania tymczasowych zmiennych zadań

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

Uwielbiam sposób, w jaki używasz ciągu zastępującego jako parametru pozycyjnego do powłoki, abyś mógł dokonywać podstawień i rozszerzeń. Bardzo fajny. – GL2014

0

zainspirowany Odpowiedź @justaname powyżej, to polecenie, które zawiera Perl jedno-liner zrobi:

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