Wsadowa zmiana nazw plików za pomocą Bash

101

Jak Bash może zmienić nazwę serii pakietów, aby usunąć ich numery wersji? Bawiłem się obydwoma expri %%bezskutecznie.

Przykłady:

Xft2-2.1.13.pkg staje się Xft2.pkg

jasper-1.900.1.pkg staje się jasper.pkg

xorg-libXrandr-1.2.3.pkg staje się xorg-libXrandr.pkg

Jeremy L.
źródło
1
Zamierzam używać tego regularnie, jako skryptu do jednorazowego użytku. Każdy system, którego będę używał, będzie miał na to bash, więc nie boję się bashizmów, które są całkiem przydatne.
Jeremy L
Bardziej ogólnie zobacz także stackoverflow.com/questions/28725333/ ...
tripleee

Odpowiedzi:

191

Możesz użyć funkcji rozszerzania parametrów basha

for i in ./*.pkg ; do mv "$i" "${i/-[0-9.]*.pkg/.pkg}" ; done

W przypadku nazw plików ze spacjami potrzebne są cudzysłowy.

richq
źródło
1
Też mi się podoba, ale używa funkcji specyficznych dla basha ... Zwykle nie używam ich na rzecz "standardowych" sh.
Diego Sevilla
2
Do jednorazowych, jednowierszowych, interaktywnych rzeczy zawsze używam tego zamiast sed-fu. Gdyby to był scenariusz, tak, unikaj bashizmów.
Richq
Jeden problem, który znalazłem w kodzie: Co się stanie, jeśli nazwy plików zostały już zmienione i uruchomię je ponownie? Otrzymuję ostrzeżenia za próbę przejścia do podkatalogu samego siebie.
Jeremy L
1
Aby uniknąć błędów ponownego uruchamiania, możesz użyć tego samego wzorca w części „ .pkg”, tj.For i in * - [0-9.] .Pkg; do mv $ i $ {i / - [0-9 .] *. pkg / .pkg}; gotowe ". Ale błędy są wystarczająco nieszkodliwe (przenoszenie do tego samego pliku).
Richq
3
Nie jest to rozwiązanie bash, ale jeśli masz zainstalowany perl, zapewnia poręczną komendę zmiany nazwy do zmiany nazwy partii, po prostu zróbrename "s/-[0-9.]*//" *.pkg
Rufus
33

Jeśli wszystkie pliki znajdują się w tym samym katalogu, sekwencja

ls | 
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' | 
sh

wykona twoją pracę. Polecenie sed utworzy sekwencję poleceń mv , którą możesz następnie przesłać do powłoki. Najlepiej jest najpierw uruchomić potok bez końcowego | sh, aby sprawdzić, czy polecenie robi to, co chcesz.

Aby korzystać z wielu katalogów, użyj czegoś takiego jak

find . -type f |
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' |
sh

Zauważ, że w sed sekwencja grupowania wyrażeń regularnych jest poprzedzona nawiasami odwrotnymi, \(a \)zamiast pojedynczych nawiasów (i ).

Diomidis Spinellis
źródło
Wybacz mi moją naiwność, ale czy ucieczka od nawiasów odwrotnym ukośnikiem nie sprawi, że będą traktowane jak znaki dosłowne?
devios1
2
W sed sekwencja grupująca wyrażenia regularne to (zamiast pojedynczego nawiasu.
Diomidis Spinellis
nie zadziałało dla mnie, byłbym zadowolony, gdybyś uzyskał pomoc ... Sufiks pliku FROM jest dołączany do pliku TO: $ find. -typ d | sed -n 's /(.*)\/(.*) anysoftkeyboard (. *) / mv "\ 1 \ / \ 2anysoftkeyboard \ 3" "\ 1 \ / \ 2effectedkeyboard \ 3" / p' | sh >> >>>>> OUTPUT >>>> mv: rename ./base/src/main/java/com/anysoftkeyboard/base/dictionaries to ./base/src/main/java/com/effectedkeyboard/base/dictionaries/dictionaries : Nie ma takiego pliku lub katalogu
Vitali Pom
Wygląda na to, że w nawiasach brakuje ukośnika odwrotnego.
Diomidis Spinellis
1
Nie używaj lsw skryptach. Pierwszą odmianę można osiągnąć przy printf '%s\n' * | sed ...odrobinie kreatywności i prawdopodobnie można się jej pozbyć sed. Bash printfoferuje '%q'kod formatu, który może się przydać, jeśli masz nazwy plików, które zawierają metaznaki powłoki dosłownie.
tripleee
10

Zrobię coś takiego:

for file in *.pkg ; do
    mv $file $(echo $file | rev | cut -f2- -d- | rev).pkg
done

przypuszczalnie cały twój plik znajduje się w bieżącym katalogu. Jeśli nie, spróbuj użyć funkcji find zgodnie z powyższymi zaleceniami Javiera.

EDYCJA : Ponadto ta wersja nie używa żadnych funkcji specyficznych dla basha, jak inne powyżej, co prowadzi do większej przenośności.

Diego Sevilla
źródło
Wszystkie znajdują się w tym samym katalogu. Co myślisz o odpowiedzi rq?
Jeremy L
Nie lubię używać funkcji specyficznych dla basha, ale zamiast tego bardziej standardowe sh.
Diego Sevilla
1
Założyłem,
1
Haha, w porządku ... Tak po prostu od człowieka myślącego o przenośności, takiego jak ja :)
Diego Sevilla
Ten nie wyjaśnia trzeciego przykładu: xorg-libXrandr (łącznik używany w nazwie).
Jeremy L
5

Oto niemal odpowiednik obecnie akceptowanej odpowiedzi POSIX . To zamienia ${variable/substring/replacement}rozszerzenie parametrów tylko do Bash na takie, które jest dostępne w dowolnej powłoce kompatybilnej z Bourne.

for i in ./*.pkg; do
    mv "$i" "${i%-[0-9.]*.pkg}.pkg"
done

Interpretacja parametrów ${variable%pattern}daje wartość variablez dowolnym sufiksem, który pasuje, patternusunięty. (Należy również ${variable#pattern}usunąć przedrostek).

Zachowałem wzór podrzędny -[0-9.]*z zaakceptowanej odpowiedzi, chociaż może jest to mylące. To nie jest wyrażenie regularne, ale wzorzec glob; więc nie oznacza „myślnika, po którym następuje zero lub więcej cyfr lub kropek”. Zamiast tego oznacza „myślnik, po którym następuje liczba lub kropka i cokolwiek”. „Cokolwiek” będzie najkrótszym możliwym dopasowaniem, a nie najdłuższym. (Bash oferuje ##i %%przycinanie najdłuższego możliwego przedrostka lub sufiksu zamiast najkrótszego).

tripleee
źródło
5

Możemy założyć, że sedjest dostępny na dowolnym * nix, ale nie możemy być pewni, że będzie obsługiwał sed -ngenerowanie poleceń mv. ( UWAGA:sed robi to tylko GNU ).

Mimo to, bash builtins i sed, możemy szybko uruchomić funkcję powłoki, aby to zrobić.

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      mv -v "$file" "$(sed $sed_pattern <<< $file)"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

Stosowanie

sedrename 's|\(.*\)\(-[0-9.]*\.pkg\)|\1\2|' *.pkg

before:

./Xft2-2.1.13.pkg
./jasper-1.900.1.pkg
./xorg-libXrandr-1.2.3.pkg

after:

./Xft2.pkg
./jasper.pkg
./xorg-libXrandr.pkg

Tworzenie folderów docelowych:

Ponieważ mvnie tworzy automatycznie folderów docelowych, nie możemy używać naszej początkowej wersji sedrename.

To dość mała zmiana, więc fajnie byłoby uwzględnić tę funkcję:

Będziemy potrzebować funkcji narzędzia abspath(lub ścieżki bezwzględnej), ponieważ bash nie ma tej wbudowanej .

abspath () { case "$1" in
               /*)printf "%s\n" "$1";;
               *)printf "%s\n" "$PWD/$1";;
             esac; }

Gdy już to zrobimy, możemy wygenerować folder docelowy (i) dla wzorca sed / rename, który zawiera nową strukturę folderów.

Dzięki temu będziemy znać nazwy naszych folderów docelowych. Kiedy zmienimy nazwę, będziemy musieli użyć jej na docelowej nazwie pliku.

# generate the rename target
target="$(sed $sed_pattern <<< $file)"

# Use absolute path of the rename target to make target folder structure
mkdir -p "$(dirname $(abspath $target))"

# finally move the file to the target name/folders
mv -v "$file" "$target"

Oto skrypt obsługujący pełny folder ...

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      target="$(sed $sed_pattern <<< $file)"
      mkdir -p "$(dirname $(abspath $target))"
      mv -v "$file" "$target"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

Oczywiście nadal działa, gdy nie mamy również określonych folderów docelowych.

Gdybyśmy chcieli umieścić wszystkie utwory w folderze, ./Beethoven/możemy to zrobić:

Stosowanie

sedrename 's|Beethoven - |Beethoven/|g' *.mp3

before:

./Beethoven - Fur Elise.mp3
./Beethoven - Moonlight Sonata.mp3
./Beethoven - Ode to Joy.mp3
./Beethoven - Rage Over the Lost Penny.mp3

after:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

Runda bonusowa ...

Użycie tego skryptu do przenoszenia plików z folderów do jednego folderu:

Zakładając, że chcieliśmy zebrać wszystkie pasujące pliki i umieścić je w bieżącym folderze, możemy to zrobić:

sedrename 's|.*/||' **/*.mp3

before:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

after:

./Beethoven/ # (now empty)
./Fur Elise.mp3
./Moonlight Sonata.mp3
./Ode to Joy.mp3
./Rage Over the Lost Penny.mp3

Uwaga na temat wzorców sed regex

W tym skrypcie obowiązują regularne reguły wzorca sed, te wzorce nie są PCRE (wyrażenia regularne zgodne z Perlem). Możesz mieć rozszerzoną składnię wyrażeń regularnych sed, używając jednej z nich sed -rlub w sed -E zależności od platformy.

man re_formatPełny opis podstawowych i rozszerzonych wzorców regexp sed można znaleźć w dokumencie zgodnym z POSIX .

ocodo
źródło
4

Uważam, że zmiana nazwy jest znacznie prostszym narzędziem do tego typu rzeczy. Znalazłem to na Homebrew dla OSX

Dla twojego przykładu zrobiłbym:

rename 's/\d*?\.\d*?\.\d*?//' *.pkg

„S” oznacza substytut. Formularz to s / searchPattern / replace / files_to_apply. W tym celu musisz użyć wyrażenia regularnego, co wymaga trochę przestudiowania, ale jest warte wysiłku.

Simon Katan
źródło
Zgadzam się. Od czasu do czasu coś, używając fori seditd. Jest w porządku, ale jest to znacznie szybciej i proste wystarczy użyć rename, jeśli znajdziesz się robić to częściej.
argentum2f
Czy to, że uciekasz przed okresami?
Big Money
Problem polega na tym, że nie jest to przenośne; nie tylko nie wszystkie platformy mają renamepolecenie; dodatkowo, niektóre będą miały inne rename polecenie z różnymi funkcjami, składnią i / lub opcjami. To wygląda jak Perl, renamektóry jest dość popularny; ale odpowiedź powinna naprawdę to wyjaśnić.
tripleee
3

lepsze użycie seddo tego, coś takiego:

find . -type f -name "*.pkg" |
 sed -e 's/((.*)-[0-9.]*\.pkg)/\1 \2.pkg/g' |
 while read nameA nameB; do
    mv $nameA $nameB;
 done

ustalenie wyrażenia regularnego pozostawia się jako ćwiczenie (podobnie jak w przypadku nazw plików zawierających spacje)

Javier
źródło
Dzięki: spacje nie są używane w nazwach.
Jeremy L
1

Wydaje się, że to działa, zakładając, że

  • wszystko kończy się na $ pkg
  • numer twojej wersji zawsze zaczyna się od „-”

zdejmij .pkg, a następnie - ..

for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done
Steve B.
źródło
Istnieje kilka pakietów, które mają w nazwie myślnik (zobacz trzeci przykład). Czy ma to wpływ na Twój kod?
Jeremy L
1
Możesz uruchomić wiele wyrażeń sed za pomocą -e. Np sed -e 's/\.pkg//g' -e 's/-.*//g'.
wtorek
Nie analizuj lswyników i nie cytuj swoich zmiennych. Zobacz także shellcheck.net, aby zdiagnozować typowe problemy i antywzory w skryptach powłoki.
tripleee
1

Miałem wiele *.txtplików do zmiany nazwy, tak jak .sqlw tym samym folderze. poniżej pracował dla mnie:

for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done
Kumar Mashalkar
źródło
2
Możesz ulepszyć swoją odpowiedź, abstrahując ją od rzeczywistego przypadku użycia PO.
Dakab
Ma to wiele problemów, a także pozorny błąd składniowy. Zobacz shellcheck.net na początek.
tripleee
0

Dziękuję za te odpowiedzi. Miałem też jakiś problem. Przenoszenie plików .nzb.queued do plików .nzb. W nazwach plików były spacje i inne nierówności, a to rozwiązało mój problem:

find . -type f -name "*.nzb.queued" |
sed -ne "s/^\(\(.*\).nzb.queued\)$/mv -v \"\1\" \"\2.nzb\"/p" |
sh

Opiera się na odpowiedzi Diomidisa Spinellisa.

Wyrażenie regularne tworzy jedną grupę dla całej nazwy pliku i jedną grupę dla części przed .nzb.queued, a następnie tworzy polecenie przesunięcia powłoki. Z cytowanymi łańcuchami. Pozwala to również uniknąć tworzenia pętli w skrypcie powłoki, ponieważ jest to już zrobione przez sed.

Jerry Jacobs
źródło
Należy zauważyć, że dotyczy to tylko seda GNU. Na BSD sed nie zinterpretuje tej -nopcji w ten sam sposób.
ocodo