So ersetzen Sie Leerzeichen in Dateinamen mithilfe eines Bash-Skripts

264

Kann jemand eine sichere Lösung empfehlen, um Leerzeichen rekursiv durch Unterstriche in Datei- und Verzeichnisnamen ab einem bestimmten Stammverzeichnis zu ersetzen? Beispielsweise:

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

wird:

$ tree
.
|-- a_dir
|   `-- file_with_spaces.txt
`-- b_dir
    |-- another_file_with_spaces.txt
    `-- yet_another_file_with_spaces.pdf
Armandino
quelle
9
Was möchten Sie tun, wenn sich eine Datei mit dem Namen foo barund eine andere Datei foo_barim selben Verzeichnis befinden?
Mark Byers
Gute Frage. Ich möchte keine vorhandenen Dateien überschreiben oder Daten verlieren. Es sollte unverändert bleiben. Idealerweise wird eine Warnung gedruckt, aber das verlangt wahrscheinlich zu viel.
Armandino

Antworten:

311

Verwenden Sie rename(aka prename), ein Perl-Skript, das sich möglicherweise bereits auf Ihrem System befindet. Machen Sie es in zwei Schritten:

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

Basierend auf Jürgens Antwort und in der Lage mehr Schichten von Dateien und Verzeichnissen in einem einzigen Satz zu handhaben mit der „Revision 1.5 1998.12.18 16.16.31 RMB1“ -Version /usr/bin/rename(Perl - Skript):

find /tmp/ -depth -name "* *" -execdir rename 's/ /_/g' "{}" \;
Bis auf weiteres angehalten.
quelle
5
Zwei Schritte sind nicht erforderlich: Verwenden Sie die Tiefensuche: Finden Sie die Tiefe
Jürgen Hötzel
3
Oh, ich habe gerade die renameManpage gelesen (ich kannte das Tool nicht) und ich denke, Sie können Ihren Code optimieren, indem Sie s/ /_/gzu y/ /_/;-)
Michael Krelin - Hacker
1
Natürlich werden Sie dadurch keinen Leistungsschub erhalten. Es geht mehr darum, das richtige Werkzeug zu verwenden. Und bei dieser ganzen Frage geht es mehr oder weniger um Mikrooptimierung. Macht es schließlich keinen Spaß? ;-)
Michael Krelin - Hacker
14
Wenn Sie dies unter OS X brew install rename
ausführen
7
Dies funktioniert unter Centos 7 nicht, da der Befehl zum Umbenennen völlig anders ist (es ist ein Binärskript, kein Perl-Skript) und keine Daten von stdin akzeptiert.
CpnCrunch
357

Ich benutze:

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

Obwohl es nicht rekursiv ist, ist es ziemlich schnell und einfach. Ich bin sicher, jemand hier könnte es so aktualisieren, dass es rekursiv ist.

Der ${f// /_}Teil verwendet den Parametererweiterungsmechanismus von bash, um ein Muster innerhalb eines Parameters durch die angegebene Zeichenfolge zu ersetzen. Die relevante Syntax lautet ${parameter/pattern/string}. Siehe: https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html oder http://wiki.bash-hackers.org/syntax/pe .

Naidim
quelle
9
Einfach und arbeiten in Mac. (Mac hat nicht rename, und es ist zu schwer, dies mit
Brew
6
tolle Antwort. Ich habe for d in *\ *; do mv "$d" "${d// /}"; donenicht unter Punktzahl verwendet.
Yoon Lee
1
Im Gegensatz zur Antwort 'find -name' funktionierte diese auf meinem OS X! Danke mein Herr!
Julio Faerman
1
Als Referenz kann dies leicht wird in bash rekursiv für die Verwendung shopt -s globstarund for f in **/*\ *; do .... Die globstarOption ist intern für bash, während der renameBefehl ein allgemeines Linux-Tool ist und nicht Teil von bash.
Ghoti
2
${f// /_}ist eine Bash-Variablenerweiterung zum Suchen und Ersetzen . - Dies fist die Variable aus der forSchleife für jede Datei, die ein Leerzeichen enthält. - Das erste //bedeutet "alle ersetzen" (nicht beim ersten Auftreten aufhören). - Dann bedeutet das "/ _" "Leerzeichen durch Unterstrich ersetzen"
Ari
101
find . -depth -name '* *' \
| while IFS= read -r f ; do mv -i "$f" "$(dirname "$f")/$(basename "$f"|tr ' ' _)" ; done

Ich habe es zuerst nicht richtig verstanden, weil ich nicht an Verzeichnisse gedacht habe.

Michael Krelin - Hacker
quelle
1
Klappt wunderbar. Danke Michael.
Armandino
Dennis, guter Fang, leicht zu reparieren, indem man IFS=''vor read. Auch für das, was ich durch andere Kommentare sagen kann, sortkann der Schritt zugunsten der -depthOption auf fallengelassen werden find.
Michael Krelin - Hacker
1
Funktioniert nicht, wenn ein Dateiname einen \ (Backslash) enthält. Kann durch Hinzufügen einer -rLeseoption behoben werden .
jfg956
8
Dies muss das 50. Mal sein, dass ich diese Seite besuche, um Ihre Lösung zu kopieren und zu verwenden. Vielen Dank sehr viel . Ich bevorzuge Ihre Antwort, da ich auf einem Mac bin und nicht den renamevon Dennis vorgeschlagenen Befehl habe.
Alex Constantin
@AlexConstantin, hast macportsdu das nicht rename? Ich habe mich nie darum gekümmert, es herauszufinden, weil ich nicht denke, dass die Aufgabe den Nutzen rechtfertigt. Und wenn Sie nicht haben macports, sollten Sie in Betracht ziehen, sie zu installieren;)
Michael Krelin - Hacker
30

Sie können detoxvon Doug Harple verwenden

detox -r <folder>
user78274
quelle
12

Eine Lösung zum Suchen / Umbenennen . Das Umbenennen ist Teil von util-linux.

Sie müssen zuerst die Tiefe verringern, da ein Whitespace-Dateiname Teil eines Whitespace-Verzeichnisses sein kann:

find /tmp/ -depth -name "* *" -execdir rename " " "_" "{}" ";"
Jürgen Hötzel
quelle
Ich bekomme überhaupt keine Änderung, wenn ich deine leite.
Bis auf weiteres angehalten.
Überprüfen Sie das Setup von util-linux: $ rename --version rename (util-linux-ng 2.17.2)
Jürgen Hötzel
Grepping / usr / bin / rename (ein Perl-Skript) enthüllt "Revision 1.5 1998/12/18 16:16:31 rmb1"
Bis auf weiteres angehalten.
Hmm ... wo ist deine Util-Linux-Binärdatei geblieben? Dieser Dateipfad sollte util-linux gehören. Sie sind kein GNU-Linux-System?
Jürgen Hötzel
1
Das ändert nur ein Feld in meinem Lauf. "Go Tell Fire on the Mountain" wird zu "Go_tell Fire on the Mountain".
Brokkr
6

Bash 4.0

#!/bin/bash
shopt -s globstar
for file in **/*\ *
do 
    mv "$file" "${file// /_}"       
done
Ghostdog74
quelle
Sieht so aus, als würde dies eine MV für sich selbst tun, wenn ein Datei- oder Verzeichnisname keinen Speicherplatz enthält (MV: Kann a' to a subdirectory of itself, ein / a ' nicht verschieben )
armandino
egal. Entfernen Sie einfach die Fehlermeldung, indem Sie zu umleiten /dev/null.
Ghostdog74
Ghostdog, das mvfünfundfünfzigtausend Mal nur zum Umbenennen von vier Dateien erzeugt wird, kann ein wenig Aufwand bedeuten, selbst wenn Sie den Benutzer nicht mit Nachrichten überfluten.
Michael Krelin - Hacker
krelin, sogar find durchsucht die von Ihnen erwähnten 55000 Dateien nach Leerzeichen und führt dann die Umbenennung durch. Am hinteren Ende geht es immer noch durch alles. Wenn Sie möchten, wird dies vor dem Umbenennen auf Leerzeichen überprüft.
Ghostdog74
Ich habe davon gesprochen, mv zu laichen, nicht durchzugehen. Würden for file in *' '*oder einige solche keinen besseren Job machen?
Michael Krelin - Hacker
6

Sie können dies verwenden:

    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
quelle
3

Rekursive Version von Naidims Antworten.

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
quelle
2

Hier ist eine (ziemlich ausführliche) find -exec-Lösung, die Warnungen "Datei existiert bereits" an stderr schreibt:

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
quelle
2

Dieser macht ein bisschen mehr. Ich benutze es, um meine heruntergeladenen Torrents umzubenennen (keine Sonderzeichen (Nicht-ASCII), Leerzeichen, mehrere Punkte usw.).

#!/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
quelle
1

Ich fand um dieses Skript herum, es kann interessant sein :)

 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
quelle
Schlägt bei Dateien mit Zeilenumbrüchen im Namen fehl.
Ghoti
1

Hier ist eine Bash-Skriptlösung mit angemessener Größe

#!/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
quelle
1

Installieren Sie zunächst alle Tools, wenn Sie mit macOS Probleme haben:

 brew install tree findutils rename

Erstellen Sie dann bei Bedarf zum Umbenennen einen Alias ​​für GNU find (gfind) als find. Führen Sie dann den Code von @Michel Krelin aus:

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

Dadurch werden nur Dateien im aktuellen Verzeichnis gefunden und umbenannt . Ich habe diesen Alias.

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

user1060059
quelle
0

Ich mache nur eine für meinen eigenen Zweck. Sie können es als Referenz verwenden.

#!/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
quelle
0

Für Dateien im Ordner / 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
quelle
0

Meine Lösung für das Problem ist ein Bash-Skript:

#!/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

Geben Sie einfach den Verzeichnisnamen, auf den Sie das Skript anwenden möchten, als Argument an, nachdem Sie das Skript ausgeführt haben.

Ágoston Volcz
quelle
0

In macOS

Genau wie die gewählte Antwort.

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
quelle