Jak zamienić spacje w nazwach plików za pomocą skryptu bash

264

Czy ktokolwiek może zalecić bezpieczne rozwiązanie rekurencyjnego zastępowania spacji znakami podkreślenia w nazwach plików i katalogów zaczynającymi się od danego katalogu głównego? Na przykład:

$ tree
.
|-- a dir
|   `-- file with spaces.txt
`-- b dir
    |-- another file with spaces.txt
    `-- yet another file with spaces.pdf

staje się:

$ tree
.
|-- a_dir
|   `-- file_with_spaces.txt
`-- b_dir
    |-- another_file_with_spaces.txt
    `-- yet_another_file_with_spaces.pdf
armandino
źródło
9
Co chcesz zrobić, jeśli w tym samym katalogu jest plik o nazwie foo bari inny plik foo_bar?
Mark Byers,
Dobre pytanie. Nie chciałbym nadpisywać istniejących plików ani tracić żadnych danych. Powinno to pozostać niezmienione. Najlepiej wydrukować ostrzeżenie, ale to prawdopodobnie wymaga zbyt wiele.
armandino,

Odpowiedzi:

311

Użyj rename(aka prename), który jest skryptem Perla, który może już być w twoim systemie. Zrób to w dwóch krokach:

find -name "* *" -type d | rename 's/ /_/g'    # do the directories first
find -name "* *" -type f | rename 's/ /_/g'

Oparty na odpowiedzi Jürgena i zdolny do obsługi wielu warstw plików i katalogów w jednym powiązaniu za pomocą wersji „Revision 1.5 1998/12/18 16:16:31 rmb1” /usr/bin/rename(skrypt Perla):

find /tmp/ -depth -name "* *" -execdir rename 's/ /_/g' "{}" \;
Wstrzymano do odwołania.
źródło
5
Nie trzeba wykonywać dwóch kroków: Użyj wyszukiwania Głębokość pierwsze: znajdź katalog
Jürgen
3
Och, właśnie przeczytałem stronę renamepodręcznika (nie znałem narzędzia) i myślę, że możesz zoptymalizować kod, zmieniając s/ /_/gna y/ /_/;-)
Michael Krelin - haker
1
Oczywiście nie dostaniesz z tego wzrostu wydajności. Chodzi raczej o korzystanie z odpowiedniego narzędzia. Całe to pytanie dotyczy mniej więcej optymalizacji mikro. W końcu to nie jest zabawne? ;-)
Michael Krelin - haker
14
Jeśli używasz tego w systemie OS X, musiszbrew install rename
loeschg
7
Nie działa to w Centos 7, ponieważ polecenie zmiany nazwy jest zupełnie inne (jest to skrypt binarny, a nie perlowy) i nie przyjmuje danych ze standardowego wejścia.
CpnCrunch
357

Używam:

for f in *\ *; do mv "$f" "${f// /_}"; done

Chociaż nie jest rekurencyjny, jest dość szybki i prosty. Jestem pewien, że ktoś tutaj mógłby go zaktualizować, by był rekurencyjny.

${f// /_}Część wykorzystuje mechanizm ekspansji parametr bash, by zastąpić wzór w parametrze z dostarczonego łańcucha. Odpowiednia składnia to ${parameter/pattern/string}. Zobacz: https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html lub http://wiki.bash-hackers.org/syntax/pe .

Naidim
źródło
9
Prosty i działa w Mac. (Mac nie ma rename, a zainstalowanie go za pomocą naparu jest zbyt trudne)
JonnieJS
6
niesamowita odpowiedź. użyłem for d in *\ *; do mv "$d" "${d// /}"; donenon under score.
Yoon Lee
1
W przeciwieństwie do odpowiedzi „znajdź-nazwę”, ta działała na moim OS X! Dziękuję Panu!
Julio Faerman,
1
Dla porównania, może to łatwo stać się rekurencyjne w bash za pomocą shopt -s globstari for f in **/*\ *; do .... globstarOpcja jest wewnętrzny do bash, podczas gdy renamepolecenie jest powszechnym narzędziem Linux i nie jest częścią bash.
ghoti
2
${f// /_}jest rozszerzeniem zmiennej Bash do wyszukiwania i zamiany . - Jest fto zmienna z forpętli dla każdego pliku zawierającego spację. - Pierwszy //oznacza „zamień wszystko” (nie przestawaj przy pierwszym wystąpieniu). - Zatem `/ _` oznacza„ zamień spację na podkreślnik ”
Ari
101
find . -depth -name '* *' \
| while IFS= read -r f ; do mv -i "$f" "$(dirname "$f")/$(basename "$f"|tr ' ' _)" ; done

na początku nie udało mi się tego zrobić, ponieważ nie myślałem o katalogach.

Michael Krelin - haker
źródło
1
Działa jak marzenie. Dzięki Michael.
armandino,
Dennis, dobry haczyk, łatwo naprawić, stawiając IFS=''przed read. Ponadto, co mogę powiedzieć w innych komentarzach, sortkrok można pominąć na korzyść -depthopcji find.
Michael Krelin - haker
1
Nie działa, jeśli nazwa pliku zawiera \ (ukośnik odwrotny). Można to naprawić, dodając -ropcję czytania.
jfg956
8
Odwiedzam tę stronę po raz 50, aby skopiować i użyć rozwiązania. Dziękuję bardzo . Wolę twoją odpowiedź, ponieważ jestem na komputerze Mac i nie mam renamepolecenia sugerowanego przez Dennisa.
Alex Constantin,
@AlexConstantin, nie macportsmasz rename? Nigdy nie zadałem sobie trudu, aby się tego dowiedzieć, ponieważ nie sądzę, aby zadanie to uzasadniało użyteczność. A jeśli nie masz macports, powinieneś rozważyć ich zainstalowanie;)
Michael Krelin - haker
30

możesz użyć detoxprzez Douga Harple

detox -r <folder>
użytkownik78274
źródło
12

Find / rename rozwiązanie. zmiana nazwy jest częścią util-linux.

Najpierw musisz zejść głębią, ponieważ nazwa pliku białych znaków może być częścią katalogu białych znaków:

find /tmp/ -depth -name "* *" -execdir rename " " "_" "{}" ";"
Jürgen Hötzel
źródło
Nie dostaję żadnej zmiany, kiedy prowadzę twoją.
Wstrzymano do odwołania.
Sprawdź konfigurację util-linux: $ rename --version rename (util-linux-ng 2.17.2)
Jürgen Hötzel
Grepping / usr / bin / rename (skrypt Perla) ujawnia „Wersja 1.5 1998/12/18 16:16:31 rmb1”
Wstrzymano do odwołania.
Hmm ... gdzie jest twój plik binarny util-linux? Ta ścieżka do pliku powinna należeć do użytkownika util-linux. Nie jesteś systemem GNU-Linux?
Jürgen Hötzel,
1
co zmienia tylko jedno pole w moim biegu, więc „idź powiedzieć, ogień na górze” staje się „go_tell ogień na górze”.
brokkr
6

bash 4.0

#!/bin/bash
shopt -s globstar
for file in **/*\ *
do 
    mv "$file" "${file// /_}"       
done
ghostdog74
źródło
Wygląda na to, że zrobi to mv, jeśli w nazwie pliku lub katalogu nie ma spacji (mv: nie można przenieść a' to a subdirectory of itself, a / a ')
armandino
nie ważne po prostu usuń komunikat o błędzie przekierowując na /dev/null.
ghostdog74
ghostdog, pojawienie się mvpięćdziesięciu pięciu tysięcy razy tylko w celu zmiany nazwy czterech plików może być trochę narzutem, nawet jeśli nie zaleją użytkownika wiadomościami.
Michael Krelin - haker
krelin, nawet find przejdzie przez te 55000 plików, o których wspomniałeś, aby znaleźć te ze spacjami, a następnie dokonać zmiany nazwy. Z tyłu wciąż przechodzi przez wszystko. Jeśli chcesz, zrobi to wstępne sprawdzenie spacji przed zmianą nazwy.
ghostdog74
Mówiłem o spawnowaniu mv, a nie przechodzeniu. Czy for file in *' '*lub niektórzy z nich nie wykonaliby lepszej pracy?
Michael Krelin - haker
6

możesz użyć tego:

    find . -name '* *' | while read fname 

do
        new_fname=`echo $fname | tr " " "_"`

        if [ -e $new_fname ]
        then
                echo "File $new_fname already exists. Not replacing $fname"
        else
                echo "Creating new file $new_fname to replace $fname"
                mv "$fname" $new_fname
        fi
done
Itamar
źródło
3

Rekurencyjna wersja Odpowiedzi Naidima.

find . -name "* *" | awk '{ print length, $0 }' | sort -nr -s | cut -d" " -f2- | while read f; do base=$(basename "$f"); newbase="${base// /_}"; mv "$(dirname "$f")/$(basename "$f")" "$(dirname "$f")/$newbase"; done
Junyeop Lee
źródło
2

Oto (dość pełne) rozwiązanie find -exec, które zapisuje ostrzeżenia „plik już istnieje” do stderr:

function trspace() {
   declare dir name bname dname newname replace_char
   [ $# -lt 1 -o $# -gt 2 ] && { echo "usage: trspace dir char"; return 1; }
   dir="${1}"
   replace_char="${2:-_}"
   find "${dir}" -xdev -depth -name $'*[ \t\r\n\v\f]*' -exec bash -c '
      for ((i=1; i<=$#; i++)); do
         name="${@:i:1}"
         dname="${name%/*}"
         bname="${name##*/}"
         newname="${dname}/${bname//[[:space:]]/${0}}"
         if [[ -e "${newname}" ]]; then
            echo "Warning: file already exists: ${newname}" 1>&2
         else
            mv "${name}" "${newname}"
         fi
      done
  ' "${replace_char}" '{}' +
}

trspace rootdir _
yabt
źródło
2

Ten robi trochę więcej. Używam go do zmiany nazwy pobranych torrentów (bez znaków specjalnych (spoza ASCII), spacji, wielu kropek itp.).

#!/usr/bin/perl

&rena(`find . -type d`);
&rena(`find . -type f`);

sub rena
{
    ($elems)=@_;
    @t=split /\n/,$elems;

    for $e (@t)
    {
    $_=$e;
    # remove ./ of find
    s/^\.\///;
    # non ascii transliterate
    tr [\200-\377][_];
    tr [\000-\40][_];
    # special characters we do not want in paths
    s/[ \-\,\;\?\+\'\"\!\[\]\(\)\@\#]/_/g;
    # multiple dots except for extension
    while (/\..*\./)
    {
        s/\./_/;
    }
    # only one _ consecutive
    s/_+/_/g;
    next if ($_ eq $e ) or ("./$_" eq $e);
    print "$e -> $_\n";
    rename ($e,$_);
    }
}
degi
źródło
1

Znalazłem wokół tego skryptu, może być ciekawie :)

 IFS=$'\n';for f in `find .`; do file=$(echo $f | tr [:blank:] '_'); [ -e $f ] && [ ! -e $file ] && mv "$f" $file;done;unset IFS
Juan Sebastian Totero
źródło
Nie działa w przypadku plików z nową linią w nazwie.
ghoti
1

Oto rozsądne rozwiązanie skryptu bash

#!/bin/bash
(
IFS=$'\n'
    for y in $(ls $1)
      do
         mv $1/`echo $y | sed 's/ /\\ /g'` $1/`echo "$y" | sed 's/ /_/g'`
      done
)
jojohtf
źródło
1

Dla tych, którzy zmagają się z tym za pomocą macOS, najpierw zainstaluj wszystkie narzędzia:

 brew install tree findutils rename

Następnie, gdy trzeba zmienić nazwę, utwórz alias dla GNU find (gfind) jako find. Następnie uruchom kod @Michel Krelin:

alias find=gfind 
find . -depth -name '* *' \
| while IFS= read -r f ; do mv -i "$f" "$(dirname "$f")/$(basename "$f"|tr ' ' _)" ; done   
Mohamed El-Nakib
źródło
0

To tylko wyszukuje pliki w bieżącym katalogu i zmienia ich nazwy . Mam to aliasy.

find ./ -name "* *" -type f -d 1 | perl -ple '$file = $_; $file =~ s/\s+/_/g; rename($_, $file);

użytkownik1060059
źródło
0

Po prostu robię jeden do własnych celów. Możesz użyć go jako odniesienia.

#!/bin/bash
cd /vzwhome/c0cheh1/dev_source/UB_14_8
for file in *
do
    echo $file
    cd "/vzwhome/c0cheh1/dev_source/UB_14_8/$file/Configuration/$file"
    echo "==> `pwd`"
    for subfile in *\ *; do [ -d "$subfile" ] && ( mv "$subfile" "$(echo $subfile | sed -e 's/ /_/g')" ); done
    ls
    cd /vzwhome/c0cheh1/dev_source/UB_14_8
done
Hongtao
źródło
0

Dla plików w folderze o nazwie / files

for i in `IFS="";find /files -name *\ *`
do
   echo $i
done > /tmp/list


while read line
do
   mv "$line" `echo $line | sed 's/ /_/g'`
done < /tmp/list

rm /tmp/list
Marcos Jean Sampaio
źródło
0

Moim rozwiązaniem tego problemu jest skrypt bash:

#!/bin/bash
directory=$1
cd "$directory"
while [ "$(find ./ -regex '.* .*' | wc -l)" -gt 0 ];
do filename="$(find ./ -regex '.* .*' | head -n 1)"
mv "$filename" "$(echo "$filename" | sed 's|'" "'|_|g')"
done

po prostu wpisz nazwę katalogu, do którego chcesz zastosować skrypt, jako argument po wykonaniu skryptu.

Ágoston Volcz
źródło
0

W systemie macOS

Tak jak wybrana odpowiedź.

brew install rename

# 
cd <your dir>
find . -name "* *" -type d | rename 's/ /_/g'    # do the directories first
find . -name "* *" -type f | rename 's/ /_/g'
CK
źródło