Come sostituire gli spazi nei nomi dei file usando uno script bash


264

Qualcuno può raccomandare una soluzione sicura per sostituire ricorsivamente spazi con caratteri di sottolineatura nei nomi di file e directory a partire da una determinata directory principale? Per esempio:

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

diventa:

$ tree
.
|-- a_dir
|   `-- file_with_spaces.txt
`-- b_dir
    |-- another_file_with_spaces.txt
    `-- yet_another_file_with_spaces.pdf

9
Cosa vuoi che accada se c'è un file chiamato foo bare un altro file chiamato foo_barnella stessa directory?
Mark Byers,

Buona domanda. Non vorrei sovrascrivere i file esistenti o perdere alcun dato. Dovrebbe lasciarlo invariato ... idealmente stampando un avvertimento, ma probabilmente è chiedere troppo.
Armandino,

Risposte:


312

Usa rename(aka prename) che è uno script Perl che potrebbe essere già sul tuo sistema. Fallo in due passaggi:

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

Basato sulla risposta di Jürgen e in grado di gestire più livelli di file e directory in un unico limite utilizzando la versione "Revision 1.5 1998/12/18 16:16:31 rmb1" di /usr/bin/rename(uno script Perl):

find /tmp/ -depth -name "* *" -execdir rename 's/ /_/g' "{}" \;

5
Non sono necessari due passaggi: utilizzare la ricerca della profondità prima: trova dir-profondità
Jürgen Hötzel

3
Oh, ho appena letto la renamemanpage (non conoscevo lo strumento) e penso che puoi ottimizzare il tuo codice cambiando s/ /_/gin y/ /_/;-)
Michael Krelin - hacker

1
Ovviamente non otterrai un aumento delle prestazioni da esso. Si tratta più di usare lo strumento giusto. E tutta questa domanda riguarda la micro-ottimizzazione più o meno. Non è divertente, dopo tutto? ;-)
Michael Krelin - hacker

15
Se lo stai eseguendo su OS X, dovraibrew install rename
loeschg

7
Questo non funziona su Centos 7, poiché il comando rename è completamente diverso (è uno script binario, non uno perl) e non accetta dati dallo stdin.
CpnCrunch del

357

Io uso:

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

Sebbene non sia ricorsivo, è abbastanza veloce e semplice. Sono sicuro che qualcuno qui potrebbe aggiornarlo per essere ricorsivo.

La ${f// /_}parte utilizza il meccanismo di espansione dei parametri di bash per sostituire un modello all'interno di un parametro con la stringa fornita. La sintassi rilevante è ${parameter/pattern/string}. Vedi: https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html o http://wiki.bash-hackers.org/syntax/pe .


9
Semplice e funziona in mac. (Mac non ha rename, ed è troppo difficile installarlo con brew ..)
JonnieJS

6
risposta fantastica. ho usato for d in *\ *; do mv "$d" "${d// /}"; donenon sotto il punteggio.
Yoon Lee,

1
A differenza della risposta "find -name", questa ha funzionato sul mio OS X! Grazie Signore!
Julio Faerman,

1
Per riferimento, questo può facilmente diventare ricorsivo in bash per l'utilizzo di shopt -s globstare for f in **/*\ *; do .... L' globstaropzione è interna a bash, mentre il renamecomando è uno strumento Linux comune e non fa parte di bash.
ghoti,

2
${f// /_}è un'espansione variabile di Bash per la ricerca e la sostituzione . - La fè la variabile dal forciclo per ogni file che contiene uno spazio. - Il primo //significa "sostituisci tutto" (non fermarti alla prima occorrenza). - Quindi `/ _` significa" sostituisci lo spazio con il trattino basso "
Ari,

101
find . -depth -name '* *' \
| while IFS= read -r f ; do mv -i "$f" "$(dirname "$f")/$(basename "$f"|tr ' ' _)" ; done

all'inizio non sono riuscito a farlo bene, perché non pensavo alle directory.


1
Funziona come un fascino. Grazie Michael.
armandino,

Dennis, buona cattura, facilmente risolvibile mettendo IFS=''di fronte read. Inoltre, per quello che posso dire da altri commenti, il sortpassaggio può essere abbandonato a favore -depthdell'opzione find.
Michael Krelin - hacker

1
Non funziona se un nome file contiene un \ (barra rovesciata). Può essere risolto aggiungendo -run'opzione da leggere.
jfg956,

8
Questa deve essere la cinquantesima volta che visito questa pagina per copiare e utilizzare la tua soluzione. Grazie mille . Preferisco la tua risposta, visto che sono su un Mac e non ho il renamecomando suggerito da Dennis.
Alex Constantin,

@AlexConstantin, non macportshai rename? Non mi sono mai preso la briga di scoprirlo perché non credo che l'attività giustifichi l'utilità. E se non lo hai macports, dovresti considerare di installarli;)
Michael Krelin - hacker il


12

Una soluzione trova / rinomina . rename fa parte di util-linux.

Devi prima scendere in profondità, perché un nome di file di spazi bianchi può far parte di una directory di spazi bianchi:

find /tmp/ -depth -name "* *" -execdir rename " " "_" "{}" ";"

Non ottengo alcun cambiamento quando corro il tuo.
In pausa fino a nuovo avviso.

Controlla la configurazione di util-linux: $ rename --version rename (util-linux-ng 2.17.2)
Jürgen Hötzel

Grepping / usr / bin / rename (uno script Perl) rivela "Revisione 1.5 1998/12/18 16:16:31 rmb1"
pausa fino a nuovo avviso.

Hmm ... dove è finito il tuo binario util-linux? Questo percorso di file dovrebbe essere di proprietà di util-linux. Non sei un sistema GNU-Linux?
Jürgen Hötzel,

1
che cambia solo uno spazio nella mia corsa, quindi "vai a dire fuoco sulla montagna" diventa "fuoco go_tell sulla montagna".
brokkr,

6

bash 4.0

#!/bin/bash
shopt -s globstar
for file in **/*\ *
do 
    mv "$file" "${file// /_}"       
done

Sembra che questo farà un mv a se stesso se un nome di file o directory non ha spazio (mv: impossibile spostare a' to a subdirectory of itself, a / a ')
armandino

non importa. basta rimuovere il messaggio di errore reindirizzando a /dev/null.
ghostdog74

ghostdog, che si genera mvcinquantacinque volte solo per rinominare quattro file può essere un po 'sovraccarico anche se non invadete l'utente con i messaggi.
Michael Krelin - hacker

krelin, anche find andrà attraverso quei 55000 file che hai citato per trovare quelli con spazi e quindi fare il rinominare. Sul retro, sta ancora attraversando tutto. Se vuoi, lo farà un controllo iniziale per gli spazi prima di rinominare.
ghostdog74,

Stavo parlando di spawning mv, non di passare. Non sarebbe for file in *' '*o qualcosa del genere fare un lavoro migliore?
Michael Krelin - hacker

6

puoi usare questo:

    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

3

Versione ricorsiva di Naidim's Answers.

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

2

Ecco una soluzione find -exec (abbastanza dettagliata) che scrive avvertimenti "file già esistenti" su 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 _

2

Questo fa un po 'di più. Lo uso per rinominare i miei torrent scaricati (senza caratteri speciali (non ASCII), spazi, punti multipli, ecc.).

#!/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,$_);
    }
}

1

Ho trovato questo script, potrebbe essere interessante :)

 IFS=$'\n';for f in `find .`; do file=$(echo $f | tr [:blank:] '_'); [ -e $f ] && [ ! -e $file ] && mv "$f" $file;done;unset IFS

Errore nei file con le nuove righe nel loro nome.
ghoti,


1

Per coloro che lottano per questo usando macOS, installa prima tutti gli strumenti:

 brew install tree findutils rename

Quindi, quando necessario per rinominare, creare un alias per GNU find (gfind) come find. Quindi esegui il codice di @Michel Krelin:

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

0

Questo trova solo i file nella directory corrente e li rinomina . Ho questo alias.

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


0

Ne faccio solo uno per i miei scopi. Puoi usarlo come riferimento.

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

0

Per i file nella cartella denominata / 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

0

La mia soluzione al problema è uno script 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

basta inserire il nome della directory, su cui si desidera applicare lo script, come argomento dopo aver eseguito lo script.


0

In macOS

Proprio come la risposta scelta.

brew install rename

# 
cd <your dir>
find . -name "* *" -type d | rename 's/ /_/g'    # do the directories first
find . -name "* *" -type f | rename 's/ /_/g'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.