Usunąć spacje, łączniki i podkreślenia w nazwach plików?

10

Jakie jest dobre polecenie do usuwania spacji, łączników i znaków podkreślenia ze wszystkich plików w katalogu lub wybranych plików?

Używam następującego polecenia z niestandardowymi działaniami Thunar, aby spowolnić nazwy plików:

for file in %N; do mv "$file" "$(echo "$file" | tr -s ' ' | tr ' A-Z' '-a-z' | tr -s '-' | tr -c '[:alnum:][:cntrl:].' '-')"; done

Ale to polecenie zastępuje spacje tylko myślnikami / myślnikami i małymi literami.

Użyłem następującego polecenia w terminalu, aby usunąć spacje z tysięcy nazw plików w folderze, i zadziałało to dość szybko:

 rename "s/ //g" *

Ponownie usuwa tylko spacje, a nie łączniki / myślniki i podkreślenia.

Idealnie nie chcę spacji, myślników / myślników i podkreślników w moich nazwach plików. Byłoby wspaniale, gdyby komenda mogła być używana z niestandardowymi akcjami Thunar dla wybranych plików.

użytkownik8547
źródło
2
Zwracam uwagę, że jednym z problemów wielu proponowanych rozwiązań jest niewłaściwe sprawdzenie istnienia „nowej” nazwy przed zmianą pliku. Nie zrobienie tego może być potencjalnym źródłem wielu problemów.
mdpc,
Czy można zmodyfikować polecenie John1024, aby to sprawdzić?
user8547,
@ user8547rename -i "s/[-_ ]//g" *
Sparhawk
Dziękuję Sparhawk. Nawiasem mówiąc, dla osób zainteresowanych użyciem tego jako niestandardowej akcji Thunar, polecenie dla Thunar brzmi: dla pliku w% N; do mv "$ file" echo $file | sed -e 's/[ _-]//g'; gotowe
user8547,

Odpowiedzi:

11

Ta wersja renamedołączona do perlpakietu obsługuje wyrażenia regularne:

rename "s/[-_ ]//g" *

Alternatywnie,

rename -i "s/[-_ ]//g" *

-iFlaga będzie make renameskorzystać z trybu interaktywnego, co skłoniło jeśli cel już istnieje, zamiast nadpisywania milczeniu.

Nazwa Perla jest czasem nazywana prename.

Zmiana nazwy Perla kontra zmiana nazwy util-linux

W systemach podobnych do Debiana zmiana nazwy perla wydaje się być domyślna i powyższe polecenia powinny po prostu działać.

W niektórych dystrybucjach renamenarzędzie z util-linux jest domyślne. To narzędzie jest całkowicie niezgodne z Perlem rename.

  • Wszystko: najpierw sprawdź, czy Perl renamejest dostępny pod tą nazwą prename.

  • Debian: Zmiana nazwy Perla powinna być domyślna. Jest również dostępny jako prename. Plik renamewykonywalny jest jednak pod kontrolą /etc/alternativesi dlatego mógł zostać zmieniony na coś innego.

  • archlinux: Uruchom, pacman -S perl-renamea polecenie jest dostępne jako perl-rename. Aby uzyskać wygodniejszą nazwę, utwórz alias. (Porada: ChiseledAbs)

  • Mac OSX Według tej odpowiedzi , renamemoże być zainstalowany na OSX używając homebrew poprzez:

    brew install rename 
  • Bezpośrednie pobieranie: rename jest również dostępne w Perl Monks:

     wget 'http://www.perlmonks.org/?displaytype=displaycode;node_id=303814' -O rename
John1024
źródło
Myślę, że to zależy od tego, o czym renamemówisz. Ten z util-linux -2.24.2-1.fc20.x86_64 nie obsługuje wyrażeń regularnych.
Cristian Ciupitu,
1
@CristianCiupitu Właśnie sprawdziłem stronę podręcznika man dla znalezionej wersji zmiany nazwy. Na podstawie argumentów, wersja rename, której używał PO, wygląda jak perlwersja, a nie util-linuxwersja.
John1024,
Dla przypomnienia jest to renamestrona podręcznika użytkownika dla wersji util-linux . W każdym razie, oprócz tej notatki, ważną rzeczą jest to, że OP otrzymał swoją odpowiedź (a ty głos ode mnie :-D).
Cristian Ciupitu,
@CristianCiupitu Dzięki za znalezienie tego. Z powrotem na ciebie z +1.
John1024,
1
@ John1024 archlinux, ale dowiedziałem się, jak to zrobić, po prostu idź, pacman -S perl-renamewięc myślę, że możesz alias.
ChiseledAbs
5

Zastąpiłbym wszystkie te trpolecenia poleceniem sedpodstawienia, np .:

for file in %N; do 
    mv "$file" "$(echo "$file" | sed 's/[ _-]//g')"
done
Cristian Ciupitu
źródło
4

Nie licząc mv, tak naprawdę wcale nie potrzebujesz do tego zewnętrznego procesu - możesz po prostu poofać .

ifsqz() ( LC_ALL=C sqz=$1
    isf() { [ -e "$1" ] || [ -L "$1" ] ; }  
    set -- * ; set -f
    for f do isf "$f" || break
    IFS=$sqz; set -- $f; IFS=
    isf "$*" || mv -- "$f" "$*"
    done
)

Mimo to oznacza to mvwywołanie na plik, a więc prawdopodobnie renamelepiej. Chociaż powinno działać podane tylko POSIX mvw $PATHi POSIX powłoki.

Wymyśliłem do tego coś w rodzaju szalonego dema. Zestaw testowy jest generowany jak:

tee - - - - <<CGEN |\
dd cbs=90 conv=unblock |\
sed 'G;$!N'";s/^/touch -- '/;s/$/'/" |sh
$( #BEGIN CGEN
   LC_ALL=C
   i= n='"$((i=((i=i+1)==10||i==39||i==47)>0?(i+1):i))"'
   printf '%b -_   ---___'  $(
   IFS=0; eval \
       printf '"\\\\%04o\\\\%04o "' "$(
       printf "$n"' "$i" '%s $(
       printf %.252d
#END
))"))
CGEN

Po pierwsze, jako pierwszy potwierdzę, że powyższe polecenie daje wyniki, które można łatwiej uzyskać innymi sposobami. Ale inne środki prawdopodobnie nie pokazałyby równie dobrze, co można by zrobić z $IFSodrobiną (chorej) wyobraźni.

Pierwszy bit jest więc dość prosty:

  • tee usuwa 5 kopii danych wejściowych - heredocument o nazwie CGEN

  • dd blokuje wprowadzanie nowego wiersza po 90 bajtów na blok i potoki, które ...

  • sedłączy 2 z tych bloków na dwóch \nznakach ewline, 'pojedynczo cytuje wyniki i przygotowuje ciąg znaków touch --dla każdego cyklu wiersza przed odpływem do ...

  • sh który następnie wykonuje wszystkie dane wejściowe jako polecenia powłoki

#CGENNieco chociaż ... no, krótko ...

  • na dole printfwydrukowano 252 0s

  • następny od ostatniego otrzymuje 252 ''argumentów o pustym łańcuchu i dla każdego wypisuje zawartość znaku $nnastępującego po nim" $i "

  • evalinterpretuje argumenty następnego printfprzed wydrukowaniem wyników tej interpretacji jako liczby ósemkowe poprzedzone 2 ukośnikami odwrotnymi

  • ostatni printfwypisuje wartości bajtów dla tych liczb ósemkowych naraz, po których następuje łańcuch -_ ---___dla każdej pary

  • $njest inicjowany do równania, które będzie zwiększane $io jeden dla każdej oceny, z wyjątkiem tego, że pomija wartości 10, 39 lub 47 - (które są odpowiednio \newline, 'pojedynczym cudzysłowem i /ukośnikiem dziesiętnym ASCII)

Rezultatem końcowym jest katalog zawierający wiele naprawdę brzydkich nazw plików zawierających każdy bajt w moim zestawie znaków od 1 do 255, z wyjątkiem pojedynczego cudzysłowu (pomijanego tylko w celu uniknięcia jeszcze jednej sed s///instrukcji) i /ukośnika. Te nazwy plików wyglądają tak:

(set -- *; printf '%s\n\n##############\n\n%s\n' "${9}" "${34}")  | cat -A

   ---___ww -_   ---___xx -_   ---___yy -_   ---___zz -_   ---___{{ -_   ---___|| -_   ---$
$
___}} -_   ---___~~ -_   ---___^?^? -_   ---___M-^@M-^@ -_   ---___M-^AM-^A -_   ---___M-^BM-^B -_   ---___M-^CM-^C$
$
##############$
$
 -_   ---___M-ZM-Z -_   ---___M-[M-[ -_   ---___M-\M-\ -_   ---___M-]M-] -_   ---___M-^M-^ -_   ---___M-_M-_ -_$
$
---___M-`M-` -_   ---___M-aM-a -_   ---___M-bM-b -_   ---___M-cM-c -_   ---___M-dM-d -_   ---___M-eM-e -_   ---___$

Teraz uzyskam trochę danych na temat tych plików:

chksqz() ( LC_ALL=C sqz=$1
    set -- * ; set -f ; IFS= ; tc="$*"
    printf '#%s\n' \
        "There are $# files in this test directory." \
        "All filenames combined contain a total of ${#tc} bytes."
    IFS=$sqz ; set -- $* ; IFS= ; sc="$*"  
    printf "%s '$sqz'" \
        "#Of which ${#sc} bytes are not"\
        " and $((${#tc}-${#sc})) bytes are"
    set +f ; unset IFS
    printf ".\n#%s\n#Total:\t%d\n#Other:\t%d\n#'$sqz':\t%d\n" \
        "And to confirm these figures:" \
        $(  printf %s * | wc -c 
            printf %s * | tr -d "$sqz" | wc -c
            printf %s * | tr -dc "$sqz" | wc -c
))
chksqz '_ -'

WYNIK

#There are 101 files in this test directory.
#All filenames combined contain a total of 17744 bytes.
#Of which 2692 bytes are not '_ -' and 15052 bytes are '_ -'.
#And to confirm these figures:
#Total: 17744
#Other: 2692
#'_ -': 15052

Ok. Teraz wreszcie do działania:

ifsqz '_ -'
chksqz '_ -'

WYNIK

#There are 101 files in this test directory.
#All filenames combined contain a total of 2692 bytes.
#Of which 2692 bytes are not '_ -' and 0 bytes are '_ -'.
#And to confirm these figures:
#Total: 2692
#Other: 2692
#'_ -': 0

Sukces! Możesz przekonać się sam:

ls

????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
???????????????????????????
???????????????????????????
???????????????????????????
????????????????????????????
????????????????????????????
????????????????
??????????????????????
????????????????????????
??????????????????????????
??????????????????????????
??????????????????????????
??????????????????????????
???????????????????????????
???????????????????????????
???????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
??????????????????????????
????????????????????????
????????????????????
??????????????????
????????????????????????????
??
????????????????????????????
??????????????????????????
????????????????????????????
????????????????????????????
????????????????????!!""##
??????????????????!!""##$$
????????????????!!""##$$%%
????????????!!""##$$%%&&((
????????!!""??##$$%%&&(())
$$%%&&(())**??++,,..0011
%%&&(())**++??,,..00112233
&&(())**++,,??..0011223344
))**++,,..??0011223344556
**++,,..00??11223344556677
22334455667788??99::;;<<==>>
445566778899??::;;<<==>>??@@
5566778899::;;??<<==>>??@@AA
6778899::;;<<??==>>??@@AABB
8899::;;<<==??>>??@@AABBCCDD
\\]]^^``aa??bbccddeeffgghh
]]^^``aabbc??cddeeffgghhii
^^``aabbccdd??eeffgghhiijj
??@@AABBCCDDEE??FFGGHHIIJJKK
AABBCCDDEEFF??GGHHIIJJKKLLM
BBCCDDEEFFGG??HHIIJJKKLLMMNN
CCDDEEFFGGHHII??JJKKLLMMNNOO
EEFFGGHHIIJJ??KKLLMMNNOOPPQQ
ffgghhiijjkk??llmmnnooppqqrr
gghhiijjkkllmm??nnooppqqrrss
iijjkkllmmnn??ooppqqrrsstt
jjkkllmmnnoo??ppqqrrssttuuvv
kkllmmnnooppqq??rrssttuuvvww
LLMMNNOOPPQQRR??SSTTUUVVWWXX
MNNOOPPQQRRSS??TTUUVVWWXXYY
OOPPQQRRSSTT??UUVVWWXXYYZZ[[
PPQQRRSSTTUUVV??WWXXYYZZ[[\\
RRSSTTUUVVWW??XXYYZZ[[\\]]
ssttuuvvwwxx??yyzz{{||}}~~??
ttuuvvwwxxyyz??z{{||}}~~????
uuvvwwxxyyzz{{??||}}~~??????
wwxxyyzz{{||??}}~~??????????
xxyyzz{{||}}~~??????????????
YYZZ[[\\]]^^??``aabbccddee
ZZ[[\\]]^^``??aabbccddeeff
mikeserv
źródło
2
+1 za twórcze użycie IFS+printf
John1024,
@ John1024 - co jest naprawdę zabawne:set -- 'some arbitrary' args; eval printf '"%s\n"' "$(IFS=0; printf ' "$@" %s' $(printf %025d))"
mikeserv
1
new="$(IFS=" -_"; printf %s $1)"rozwidla podpowłokę (z wyjątkiem ksh93) i ma problemy z tailingiem nowych linii. Inną opcją jest użycie IFS=' -_'; set -- $1; IFS=; new="$*"(i zmiana pętli while na pętlę for)
Stéphane Chazelas
1
[ -e x ]zwróci false, jeśli xjest dowiązaniem symbolicznym do nieistniejącego lub niedostępnego pliku.
Stéphane Chazelas,
1
Niezła skorupa Kung-Fu!
kontr-
2

jeśli masz perla, zwykle masz nazwę. możesz to zrobić:

> type rename
rename is /usr/bin/rename

i pokaż, jak napisany jest ten skrypt:

> cat /usr/bin/rename | head -n 5 #firt 5 lines for example
#!/usr/bin/perl -w
#
#  This script was developed by Robin Barker ([email protected]),
#  from Larry Wall's original script eg/rename from the perl source.
#

Ten skrypt nie obsługuje flagi -i (jest to wersja w moim systemie), ale może twój obsługuje. Co z argumentami. Pierwszy to wyrażenia regularne w formacie PCRE, działa jak filtr, modyfikuje nazwę wejściową do nazwy wyjściowej. Lista nazw wejściowych podanych przez gwiazdkę „*”. na przykład:

> cd /tmp
> rename 's/ //g' *

w rzeczywistości „*” można rozwinąć do:

> rename 's/ //g' file1 file2 file3 othe files found in current directory

Gdy masz naprawdę duże pliki, jesteś w pułapce. shell rozszerzy twoją linię dłużej niż system akceptuje. możesz obejść to za pomocą funkcji find lub xargs. używanie „znajdź” jest problemem, ponieważ zmiana nazwy będzie wywoływana wiele razy równa liczbie plików w katalogu. lepiej użyj xargs z opcją -r. jedna zmiana nazwy połączenia modyfikuje wiele plików. na przykład:

> ls | xargs -r rename 's/ //g'   #thats all, names will be appended at the end of this command.

ostatni problem, co to znaczy:

's/ //g'

jest to wyrażenie regularne do modyfikowania nazw. po pierwszym „/” jest spacją. jest to wykrywane i zastępowane ciągiem znaków po drugim „/”. Ale jest pusty ciąg zakończony trzecim '/', następnie spacja jest zastępowana przez nic. Opcja „g” sprawia, że ​​wyrażenie to się powtarza. wyrażenie przejdzie do wszystkich nazw od początku do końca i wykryje wszystkie spacje.

Ale co, jeśli masz znak tabulacji lub inny „biały” znak? istnieje zamiennik tych „\ s”. jakie inne niepotrzebne postacie? po prostu dodaj go do wyrażenia. Wszystkie zamykane za pomocą nawiasów, na przykład:

's/[\s_-]//g'

to wszystko. widzisz podobieństwo? Myślę, że powinieneś przeczytać Man Perlrequick i Man Perlretut, to wyjaśnia ci (mam nadzieję), jak działa wyrażenie regularne. w razie potrzeby możesz użyć polecenia zmiany nazwy we własnym skrypcie.

Znik
źródło
1

Następująca shpętla powłoki usunie wszystkie spacje, podkreślenia i myślniki z nazw plików w bieżącym katalogu, uważając, aby nie zastąpić istniejących plików:

for f in *; do
    test -f "$f" || continue
    nf=$( echo "$f" | tr -d ' _-' )
    ! test -e "$nf" && echo mv "$f" "$nf"
done

Za bashi kshbędąc nieco bardziej gadatliwym z logiką:

for f in *; do
    if [[ -f "$f" ]]; then
        nf=$( tr -d ' _-' <<<"$f" )
        if [[ ! -e "$nf" ]]; then
            echo mv "$f" "$nf"
        fi
    fi
done

Usuń, echogdy masz pewność, że robi to, co chcesz.

trKomenda delete ( -d) dowolny znak w danym zestawie znaków ( ' _-'). Ważne jest, aby mieć myślnik na samym początku lub na końcu zestawu, inaczej będzie interpretowany jako zakres znaków.

Kusalananda
źródło