Zmiana nazwy dużej liczby plików graficznych za pomocą bash

16

Muszę zmienić nazwę ok. 70 000 plików. Na przykład: Od sb_606_HBO_DPM_0089000do sb_606_dpm_0089000itp.

Zakres liczb wynosi od 0089000do 0163022. To tylko pierwsza część nazwy, która musi się zmienić. wszystkie pliki znajdują się w jednym katalogu i są kolejno ponumerowane (sekwencja obrazów). Liczby muszą pozostać niezmienione.

Kiedy próbuję tego w bash, dręczy mnie, że „lista argumentów jest za długa”.

Edytować:

Najpierw próbowałem zmienić nazwę pojedynczego pliku za pomocą mv:

mv sb_606_HBO_DPM_0089000.dpx sb_606_dpm_0089000.dpx

Potem próbowałem zmienić nazwę zakresu (w zeszłym tygodniu nauczyłem się tutaj, jak przenosić ładunek plików, więc pomyślałem, że ta sama składnia może działać do zmiany nazw plików ...). Myślę , że próbowałem następujące (lub coś podobnego):

mv sb_606_HBO_DPM_0{089000..163023}.dpx sb_606_dpm_0{089000..163023}.dpx
bogaty
źródło
4
Do recenzentów : nie sądzę, że jest to duplikat; większość odpowiedzi CLI na inne pytanie nie zadziała tutaj z powodu dużej liczby plików kolidujących z ARG_MAXlimitem powłoki . Ponieważ pytanie to wyraźnie wymaga rozwiązania z wiersza poleceń, (prawdopodobnie równe) rozwiązania GUI, tak jak w drugim pytaniu, również się nie zgadzają.
deser
1
Nie sądzę, że jest to duplikat, ponieważ można zadawać więcej niż jedno pytanie dotyczące zmiany nazwy plików. Proszę nie zamykać szczegółowych pytań w stosunku do ogólnych zasobów, które tak naprawdę na nie nie odpowiadają ...
Zanna
1
@rich Jeśli możesz bezpośrednio edytować komendę, której próbowałeś, byłoby bardziej zrozumiałe, że nie jest to duplikat. (To pokazuje nam, że znasz to podejście.) Pozdrawiam.
Sparhawk
2
bogate, twoje pytanie nie jest duplikatem, ponieważ jest to konkretne pytanie. Nie martw się o to. Co ważniejsze, po otrzymaniu wielu pozytywnych odpowiedzi na pytanie edycja prawdopodobnie nie jest dobrym pomysłem, ponieważ wprowadzone zmiany mogą sprawić, że istniejące odpowiedzi będą mniej ważne. Teraz czuję, że moja odpowiedź powinna wyjaśniać, dlaczego mv {1..2} {3..4}nie działa, co jest zupełnie innym problemem niż ARG_MAX... Każdy, kto odpowiedział, prawdopodobnie poczuje to samo! Tak więc, z mojego punktu widzenia, chciałbym, żebyś cofnął swoją ostatnią edycję i, jeśli chcesz, zadałby zupełnie nowe pytanie dotyczące mving z zakresami
Zanna
1
@Sparhawk OP napisał dość wyraźnie, z pierwszej wersji pytania, że ​​problemem jest argument list too longbłąd. Nie ma potrzeby dalszego wyjaśniania, to oczywiście nie jest duplikat, ponieważ potrzebujemy obejścia problemu z ARG_MAX, a odpowiedzi w proponowanym duplikacie tego nie robią.
terdon

Odpowiedzi:

25

Jednym ze sposobów jest skorzystanie findz -execoraz +opcji. Tworzy to listę argumentów, ale dzieli listę na tyle wywołań, ile potrzeba do działania na wszystkich plikach bez przekraczania maksymalnej listy argumentów. Jest odpowiedni, gdy wszystkie argumenty będą traktowane tak samo. Tak jest w przypadku rename, choć nie w przypadku mv.

Może być konieczne zainstalowanie zmiany nazwy Perla:

sudo apt install rename

Następnie możesz użyć na przykład:

find . -maxdepth 1 -exec rename -n 's/_HBO_DPM_/_dpm_/' {} +

Usuń -npo przetestowaniu, aby faktycznie zmienić nazwę plików.

Zanna
źródło
11

Mam zamiar zasugerować trzy alternatywy. Każde z nich jest prostym poleceniem w jednym wierszu, ale podam warianty dla bardziej skomplikowanych przypadków, głównie na wypadek, gdyby pliki do przetworzenia zostały zmieszane z innymi plikami w tym samym katalogu.

mmv

Chciałbym użyć polecenia MMV z pakietu o tej samej nazwie :

mmv '*HBO_DPM*' '#1dpm#2'

Zauważ, że argumenty są przekazywane jako ciągi, więc ekspansja globu nie zachodzi w powłoce. Polecenie otrzymuje dokładnie dwa argumenty, a następnie wyszukuje odpowiednie pliki wewnętrznie, bez ścisłych ograniczeń liczby plików. Zauważ też, że powyższe polecenie zakłada, że ​​wszystkie pliki pasujące do pierwszego globu zostaną przemianowane. Oczywiście możesz być bardziej szczegółowy:

mmv 'sb_606_HBO_DPM_*' 'sb_606_dpm_#1'

Jeśli pliki znajdują się poza żądanym zakresem numerów w tym samym katalogu, lepiej byłoby, gdyby pętla nad liczbami była podana w dalszej części tej odpowiedzi. Można jednak użyć sekwencji wywołań mmv z odpowiednimi wzorami:

mmv 'sb_606_HBO_DPM_0089*'       'sb_606_dpm_0089#1'    # 0089000-0089999
mmv 'sb_606_HBO_DPM_009*'        'sb_606_dpm_009#1'     # 0090000-0099999
mmv 'sb_606_HBO_DPM_01[0-5]*'    'sb_606_dpm_01#1#2'    # 0100000-0159999
mmv 'sb_606_HBO_DPM_016[0-2]*'   'sb_606_dpm_016#1#2'   # 0160000-0162999
mmv 'sb_606_HBO_DPM_01630[01]?'  'sb_606_dpm_01630#1#2' # 0163000-0163019
mmv 'sb_606_HBO_DPM_016302[0-2]' 'sb_606_dpm_016302#1'  # 0163020-0163022

zapętlić liczby

Jeśli chcesz uniknąć instalowania czegokolwiek lub musisz wybrać według zakresu numerów, unikając dopasowań poza tym zakresem, i jesteś gotowy czekać na 74.023 wywołania komend, możesz użyć zwykłej pętli bash:

for i in {0089000..0163022}; do mv sb_606_HBO_DPM_$i sb_606_dpm_$i; done

Działa to szczególnie dobrze, ponieważ nie ma żadnych przerw w sekwencji. W przeciwnym razie możesz sprawdzić, czy plik źródłowy faktycznie istnieje.

for i in {0089000..0163022}; do
  test -e sb_606_HBO_DPM_$i && mv sb_606_HBO_DPM_$i sb_606_dpm_$i
done

Zauważ, że w przeciwieństwie do for ((i=89000; i<=163022; ++i))rozszerzenia nawiasów obsługuje zer wiodących, ponieważ kilka wersji Bash kilka lat temu. Właściwie o zmianę, o którą prosiłem, więc cieszę się, że widzę przypadki jej użycia.

Dalsza lektura: Brace Expansion na stronach informacyjnych Bash, szczególnie w części o {x..y[..incr]}.

przeglądaj pliki

Inną opcją byłoby zapętlenie odpowiedniego globu zamiast zapętlenia danego zakresu liczb całkowitych. Coś takiego:

for i in *HBO_DPM*; do mv "$i" "${i/HBO_DPM/dpm}"; done

Ponownie jest to jedno mvwywołanie na plik. I znowu pętla ma długą listę elementów, ale cała lista nie jest przekazywana jako argument do podprocesu, ale obsługiwana wewnętrznie przez bash, więc limit nie spowoduje problemów.

Dalsza lektura: Rozszerzanie parametrów powłoki na stronach informacyjnych Bash, dokumentowanie ${parameter/pattern/string}między innymi.

Jeśli chcesz ograniczyć zakres numerów do tego, który podałeś, możesz dodać opcję:

for i in sb_606_HBO_DPM_+([0-9]); do
  if [[ "${i##*_*(0)}" -ge 89000 ]] && [[ "${i##*_*(0)}" -le 163022 ]]; then
    mv "$i" "${i/HBO_DPM/dpm}"
  fi
done

Tutaj ${i##pattern}usuwa najdłuższe dopasowanie prefiksu patternz $i. Ten najdłuższy prefiks jest zdefiniowany jako dowolny, następnie znak podkreślenia, a następnie zero lub więcej zer. Ten ostatni jest zapisywany jako taki, *(0)który jest rozszerzonym wzorcem globu, który zależy od ustawionej extglobopcji . Usuwanie zer wiodących jest ważne, aby traktować liczbę jako bazę 10, a nie 8. Argument +([0-9])w pętli to kolejny rozszerzony glob, dopasowujący jedną lub więcej cyfr, na wypadek, gdyby pliki tam, które zaczynają się tak samo, ale nie kończą się na numer.

MvG
źródło
Dziękuję Ci! To działało jak sen: dla i w {0089000..0163022}; do mv sb_606_HBO_DPM_ $ i sb_606_dpm_ $ i; gotowe - musiałem dodać rozszerzenie nazwy pliku, aby działało, ale zrobiłem to, co chciałem, a nawet rozumiem składnię. Dziękuję @MvG
bogaty
@rich: Cieszę się, że mogłem pomóc - również tobie i mam nadzieję, że przyszli goście. Nie zapomnij zaakceptować najbardziej użytecznej odpowiedzi. Zawsze możesz zmienić ten znacznik wyboru w przyszłości, jeśli pojawi się coś lepszego.
MvG
10

Jednym ze sposobów obejścia tego ARG_MAXograniczenia jest użycie wbudowanej powłoki bash printf:

printf '%s\0' sb_* | xargs -0 rename -n 's/HBO_DPM/dpm/'

Dawny.

rename -n 's/HBO_DPM/dpm/' sb_*
bash: /usr/bin/rename: Argument list too long

ale

printf '%s\0' sb_* | xargs -0 rename -n 's/HBO_DPM/dpm/'
rename(sb_606_HBO_DPM_0089000, sb_606_dpm_0089000)
.
.
.
rename(sb_606_HBO_DPM_0163022, sb_606_dpm_0163022)
steeldriver
źródło
7
find . -type f -exec bash -c 'echo $1 ${1/HBO_DPM/dpm}' _ {} \;
./sb_606_HBO_DPM_0089000 ./sb_606_dpm_0089000

findw bieżącym katalogu .dla wszystkich plików -type fi zmień nazwę znalezionego pliku, $1zastępując HBO_DPMgo dmp jeden po drugim-exec ... \;

wymienić echoze mvwykonać polecenie Zmień nazwę.

αғsнιη
źródło
6

Możesz napisać mały skrypt Pythona, taki jak:

import os
for file in os.listdir("."):
    os.rename(file, file.replace("HBO_DPM", "dpm"))

Zapisz to jako plik tekstowy, tak jak rename.pyw folderze, w którym znajdują się pliki, a następnie z terminalem w tym folderze przejdź:

python rename.py
Kamienna Czaszka
źródło
6

Możesz to zrobić plik po pliku (może to zająć trochę czasu)

sudo apt install util-linux  # if you don't have it already
for i in *; do rename.ul HBO_DPM dpm "$i"; done

Podobnie jak Perl renameużywany w innych odpowiedziach, rename.ulma również opcję -nlub --no-actdo testowania.

śluz
źródło
Zredagowałem Twój komentarz na temat odpowiedzi Zanny, edytuj odpowiedź Zanny lub zostaw komentarz.
fosslinux
@ubashu, który nie był komentarzem do mojej odpowiedzi - odnosił się do -nflagi, której użyłem do testowania i sugerował, że można jej również użyć rename.ul.
Zanna
3

Widzę, że nikt nie zaprosił mojego najlepszego przyjaciela sedna imprezę :). Następującefor pętla osiągnie twój cel:

for i in sb_606_HBO_DPM*; do
  mv "$i" "$(echo $i | sed 's/HBO_DPM/dpm/')";
done

Istnieje wiele narzędzi do takiej pracy, wybierz najbardziej zrozumiałe dla Ciebie. Ten jest prosty i łatwo go zmienić, aby pasował do tego lub innych celów ...

andrew.46
źródło
To prawda, niezbyt istotne w tym konkretnym przypadku, ale zakończy się niepowodzeniem, jeśli którakolwiek z nazw plików zawiera znaki nowej linii. Wspominam o tym, ponieważ większość (wszystkich?) Innych odpowiedzi jest solidna i może radzić sobie z dowolnymi nazwami plików lub działać tylko na schemacie nazewnictwa plików OP.
terdon
... znaki nowej linii, spacje, symbole wieloznaczne ... z których niektórych można uniknąć poprzez cytowanie $iw podstawieniu polecenia, ale nie jest to łatwy sposób na obsługę końcowego znaku nowej linii w nazwie pliku.
muru
3

Ponieważ dajemy opcje, oto podejście Perla. cddo katalogu docelowego i uruchom:

perl -e 'foreach(glob("sb_*")){rename $_, s/_HBO_DPM_/_dpm_/r}'

Wyjaśnienie

  • perl -e: uruchom skrypt podany przez -e.
  • foreach(glob){}: uruchom cokolwiek { }na każdym wyniku globu.
  • glob("sb_*"): zwraca listę wszystkich plików i katalogów w bieżącym katalogu, których nazwy pasują do globu powłoki sb*.
  • rename $_, s/_HBO_DPM_/_dpm_/r: Perl Magic. $_jest specjalną zmienną, która przechowuje każdy element, nad którym iterujemy (w foreach). Więc tutaj będzie każdy znaleziony plik. s/_HBO_DPM_/_dpm_/zastępuje pierwsze wystąpienie _HBO_DPM_z _dpm_. Działa $_domyślnie, więc będzie działać na każdej nazwie pliku. Te /rśrodki „zastosować tę wymianę na kopię napisu docelowej (nazwa pliku) i powrócić zmodyfikowany łańcuch. renameRobi to, czego można się spodziewać: to zmienia nazwy plików Więc cała sprawa będzie przemianować nazwę bieżącego pliku (. $_), Aby sobie z _HBO_DPM_zastąpiony przez _dpm_.

Możesz napisać to samo, co rozwinięty (i bardziej czytelny skrypt):

#! /usr/bin/env perl
use strict;
use warnings;

foreach my $fileName (glob("sb_*")){
  ## Copy the name to a new variable
  my $newName = $fileName;
  ## change the copy. $newName is now the changed version
  $newName =~ s/_HBO_DPM_/_dpm_/;
  ## rename
  rename $fileName, $newName;
}
terdon
źródło
1

W zależności od rodzaju planowanej zmiany nazwy, użycie vidir z edycją wielu linii może być satysfakcjonujące.
W konkretnym przypadku możesz zaznaczyć wszystkie wiersze w edytorze tekstu i usunąć część nazw plików „ HBO” w kilku naciśnięciach klawiszy.

kraymer
źródło
tak, vi ma gloable znaleźć i wymienić.
Jasen
2
Czy możesz rozszerzyć swoją odpowiedź i podać przykład, jak osiągnąć cel PO vidir?
deser