Jak zmienić nazwę wielu plików za pomocą funkcji znajdź

37

Chcę zmienić nazwę wielu plików (file1 ... filen na file1_renamed ... filen_renamed) za pomocą findpolecenia:

find . -type f -name 'file*' -exec mv filename='{}' $(basename $filename)_renamed ';'

Ale uzyskanie tego błędu:

mv: cannot stat filename=./file1’: No such file or directory

To nie działa, ponieważ nazwa pliku nie jest interpretowana jako zmienna powłoki.

użytkownik164014
źródło

Odpowiedzi:

46

Oto bezpośrednia poprawka twojego podejścia:

find . -type f -name 'file*' -exec sh -c 'x="{}"; mv "$x" "${x}_renamed"' \;

Jest to jednak bardzo kosztowne, jeśli masz wiele pasujących plików, ponieważ uruchamiasz nową powłokę (która wykonuje a mv) dla każdego dopasowania. A jeśli masz zabawne postacie w dowolnej nazwie pliku, to wybuchnie. Bardziej wydajne i bezpieczne podejście jest następujące:

find . -type f -name 'file*' -print0 | xargs --null -I{} mv {} {}_renamed

Ma także tę zaletę, że pracuje z dziwnie nazwanymi plikami. Jeśli findobsługuje, można to zmniejszyć do

find . -type f -name 'file*' -exec mv {} {}_renamed \;

xargsWersja jest przydatna, gdy nie jest używany {}, tak jak w

find .... -print0 | xargs --null rm

Tutaj rmjest wywoływany raz (lub wiele plików wiele razy), ale nie dla każdego pliku.

Usunąłem basenamew tobie pytanie, ponieważ prawdopodobnie jest złe: przeniósłbyś foo/bar/file8się file8_renamed, nie foo/bar/file8_renamed.

Edycje (zgodnie z sugestiami w komentarzach):

  • Dodano skrócone findbezxargs
  • Dodano naklejkę bezpieczeństwa
Thomas Erker
źródło
W przypadku, gdy xjest to bezużyteczne: find . -type f -name 'file*' -exec mv {} "{}_renamed" \; xargswersja ma taką samą wydajność jak pierwszy przykład /
Costas
xJest tam tylko bezpośrednio naprawić podejście Pytający za.
Thomas Erker,
2
(1) Używanie {}bezpośrednio w sh -c "…"poleceniu shell ( ) jest bardzo niebezpieczne - zawsze powinieneś przekazać ją jako argument. (2) Nie wszystkie wersje findobsługują {}_renamedkonstrukcję. (3) Nie rozumiem twojego stwierdzenia, które xargsjest przydatne do usuwania plików (w przeciwieństwie do zmiany ich nazw).
G-Man mówi „Przywróć Monikę”
@ G-Man: Różnica z xargsnie jest mvvs. rm, ale użycie {}vs. bez. Pierwsza jest podobna do mv file1 file1_renamed; mv file2 file2_renameddrugiej rm file1 file2.
Thomas Erker,
1
a jeśli chciałbyś wtedy zmienić _nazwane pliki z powrotem na ich oryginalne nazwy, jak byś to zrobił?
mcmillab,
17

Po wypróbowaniu pierwszej odpowiedzi i odrobinie z nią zabawy okazało się, że można to zrobić nieco krócej i mniej skomplikowane, używając -execdir:

find . -type f -name 'file*' -execdir mv {} {}_renamed ';'

Wygląda na to, że powinien zrobić dokładnie to, czego potrzebujesz.

Matijs
źródło
2
Dzięki findimplementacjom, które obsługują, -execdira {}nie jako całość, jest również najbezpieczniejszy. Możesz jednak dodać coś -ido mvtego (i -Tjeśli mvgo wspierasz)
Stéphane Chazelas
1
@ StéphaneChazelas nawet lepiej, zamiast polegać na mvpytaniu, lub oprócz tego, możesz (bez wątpienia w zależności od tego, czy twoja implementacja findgo obsługuje) również użyć, -okdirktóre wypisze polecenie do wykonania przed jego wykonaniem.
Matijs,
Warto wspomnieć, że -depthto także dobry pomysł, jeśli zamierzasz także dotykać nazw katalogów.
Ciro Santilli 12 改造 中心 法轮功 六四 事件
Argh, właśnie odkryłem, że -execdirma jeden bardzo irytujący minus, findodmawia robienia czegokolwiek, jeśli PATHzawiera jakieś ścieżki względne ... askubuntu.com/questions/621132/ ...find: The relative path XXX is included in the PATH environment
Ciro Santilli 13 改造 中心 法轮功 六四 事件
6

Innym podejściem jest użycie while readpętli nad findwyjściem. Pozwala to na dostęp do każdej nazwy pliku jako zmienna, że można manipulować bez konieczności martwienia się o dodatkowe koszty / potencjalnych problemów bezpieczeństwa tarła oddzielny sh -cproces przy użyciu find„s -execopcję.

find . -type f -name 'file*' |
    while IFS= read file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done

A jeśli używana powłoka obsługuje -dopcję określania readseparatora, możesz obsługiwać pliki o dziwnie nazwanych nazwach (np. Z nową linią), używając następujących poleceń :

find . -type f -name 'file*' -print0 |
    while IFS= read -d '' file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done
John B.
źródło
3

Chcę rozwinąć pierwszą odpowiedź i zauważyć, że to nie zadziała, aby dołączyć do nazwy pliku, ponieważ ./przedrostek ścieżki znajduje się w argumencie nazwy pliku.

Modyfikując odpowiedź Thomasa Erkera, uważam, że jest to bardziej ogólne podejście

find . -name PATTERN -printf "%f\0" | xargs --null -I{} mv {} "prefix {} suffix"

Opcje xargs:

--nullWskazuje, że każdy przekazany argument stdinkończy się znakiem null ( \0). W ten sposób nazwa pliku może zawierać spacje, w przeciwnym razie każde słowo będzie traktowane jako inny parametr mvpolecenia.

-I replace-strKażde wystąpienie replace-strzostanie zastąpione argumentem odczytanym z stdin. Możesz więc zmienić go na inny ciąg, jeśli jest to potrzebne.

Sdlion
źródło
Dlaczego pringf "%f\0"zamiast zamiast print0?
slm
1
@sim, ponieważ -print0spowoduje utworzenie ./prefiksów, jeśli PATTERNzawiera metaznaki powłoki, które przeszkadzają w zmianie nazwy na coś, co poprzedza oryginalne nazwy. (np. zmień nazwę 0 - foo.txtna 00 - foo.txt, 1 - bar.txtna 01 - bar.txtitp.)
das-g
2

Udało mi się zrobić coś podobnego z for, find, i mv.

for i in $(find . -name 'config.yml'); do mv $i $i.bak; done

Znajduje wszystkie config.ymlpliki i zmienia ich nazwy naconfig.yml.bak

Varun Risbud
źródło
ma to tę zaletę, że można zastosować zmienną ekspansję np. $ {i: r}. Niezła odpowiedź.
Salami,
Tak, możesz umieścić dowolne wyrażenie fori wykonać operację zbiorczą.
Varun Risbud