Cara mengganti spasi dalam nama file menggunakan skrip bash

264

Adakah yang bisa merekomendasikan solusi yang aman untuk secara rekursif mengganti ruang dengan garis bawah pada nama file dan direktori mulai dari direktori root yang diberikan? Sebagai contoh:

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

menjadi:

$ tree
.
|-- a_dir
|   `-- file_with_spaces.txt
`-- b_dir
    |-- another_file_with_spaces.txt
    `-- yet_another_file_with_spaces.pdf
armandino
sumber
9
Apa yang Anda inginkan terjadi jika ada file yang dipanggil foo bardan file lain yang disebut foo_bardi direktori yang sama?
Mark Byers
Pertanyaan bagus. Saya tidak ingin menimpa file yang ada atau kehilangan data apa pun. Seharusnya tidak berubah .. idealnya mencetak peringatan tetapi itu mungkin meminta terlalu banyak.
armandino

Jawaban:

312

Gunakan rename(alias prename) yang merupakan skrip Perl yang mungkin sudah ada di sistem Anda. Lakukan dalam dua langkah:

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

Berdasarkan jawaban Jürgen dan mampu menangani beberapa lapisan file dan direktori dalam satu ikatan menggunakan versi "Revisi 1.5 1998/12/18 16:16:31 rmb1" dari /usr/bin/rename(skrip Perl):

find /tmp/ -depth -name "* *" -execdir rename 's/ /_/g' "{}" \;
Dijeda sampai pemberitahuan lebih lanjut.
sumber
5
Tidak perlu untuk dua langkah: Gunakan pencarian Kedalaman-pertama: temukan dir
-depth
3
Oh, saya baru saja membaca halaman renamemanual (saya tidak tahu alatnya) dan saya pikir Anda dapat mengoptimalkan kode Anda dengan mengubah s/ /_/gmenjadi y/ /_/;-)
Michael Krelin - hacker
1
Tentu saja Anda tidak akan mendapatkan peningkatan kinerja darinya. Ini lebih tentang menggunakan alat yang tepat. Dan seluruh pertanyaan ini adalah tentang mengoptimalkan mikro kurang lebih. Bukankah itu menyenangkan? ;-)
Michael Krelin - hacker
15
Jika Anda menjalankan ini pada OS X, Anda harusbrew install rename
loeschg
7
Ini tidak berfungsi pada Centos 7, karena perintah ganti nama sama sekali berbeda (ini adalah biner, bukan skrip perl), dan tidak menerima data dari stdin.
CpnCrunch
357

Saya menggunakan:

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

Meskipun tidak rekursif, ini cukup cepat dan sederhana. Saya yakin seseorang di sini dapat memperbaruinya untuk menjadi rekursif.

Bagian ini ${f// /_}menggunakan mekanisme ekspansi parameter bash untuk mengganti pola dalam parameter dengan string yang disediakan. Sintaks yang relevan adalah ${parameter/pattern/string}. Lihat: https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html atau http://wiki.bash-hackers.org/syntax/pe .

Naidim
sumber
9
Sederhana dan bekerja di mac. (mac tidak punya rename, dan itu terlalu sulit untuk menginstal ini dengan minuman ..)
JonnieJS
6
jawaban yang luar biasa. saya menggunakan for d in *\ *; do mv "$d" "${d// /}"; donenon di bawah skor.
Yoon Lee
1
Tidak seperti jawaban 'find -name', jawaban ini berfungsi pada OS X saya! Terima kasih Pak!
Julio Faerman
1
Sebagai referensi, ini dapat dengan mudah menjadi rekursif dalam bash untuk menggunakan shopt -s globstardan for f in **/*\ *; do .... The globstarpilihan adalah internal untuk bash, sedangkan renameperintah adalah alat Linux umum dan bukan bagian dari bash.
ghoti
2
${f// /_}adalah ekspansi variabel Bash untuk pencarian dan penggantian . - Ini fadalah variabel dari forloop untuk setiap file yang berisi spasi. - Yang pertama //berarti "ganti semua" (jangan berhenti pada kejadian pertama). - Kemudian `/ _` berarti" ganti spasi dengan garis bawah "
Ari
101
find . -depth -name '* *' \
| while IFS= read -r f ; do mv -i "$f" "$(dirname "$f")/$(basename "$f"|tr ' ' _)" ; done

gagal melakukannya dengan benar pada awalnya, karena saya tidak memikirkan direktori.

Michael Krelin - hacker
sumber
1
Bekerja seperti pesona. Terima kasih Michael.
armandino
Dennis, tangkapan bagus, mudah diperbaiki dengan meletakkan IFS=''di depan read. Juga, untuk apa yang dapat saya katakan dengan komentar lain, sortlangkah dapat dibatalkan demi -depthopsi find.
Michael Krelin - hacker
1
Tidak berfungsi jika nama file mengandung \ (backslash). Dapat diperbaiki dengan menambahkan -ropsi untuk membaca.
jfg956
8
Ini harus menjadi yang ke 50 kalinya saya mengunjungi halaman ini untuk menyalin dan menggunakan solusi Anda. Terima kasih banyak . Saya lebih suka jawaban Anda, karena saya menggunakan Mac dan tidak memiliki renameperintah yang disarankan oleh Dennis.
Alex Constantin
@AlexConstantin, tidak macportspunya rename? Saya tidak pernah repot-repot mencari tahu karena saya pikir tugas itu tidak membenarkan utilitas. Dan jika Anda belum memilikinya macports, Anda harus mempertimbangkan untuk menginstalnya;)
Michael Krelin - hacker
30

Anda bisa menggunakannya detoxoleh Doug Harple

detox -r <folder>
pengguna78274
sumber
12

Solusi find / rename . rename adalah bagian dari util-linux.

Anda harus menuruni kedalaman terlebih dahulu, karena nama file spasi putih dapat menjadi bagian dari direktori spasi putih:

find /tmp/ -depth -name "* *" -execdir rename " " "_" "{}" ";"
Jürgen Hötzel
sumber
Saya tidak mendapat perubahan sama sekali saat menjalankan milik Anda.
Dijeda sampai pemberitahuan lebih lanjut.
Periksa setup util-linux: $ rename --version rename (util-linux-ng 2.17.2)
Jürgen Hötzel
Grepping / usr / bin / rename (skrip Perl) mengungkapkan "Revisi 1.5 1998/12/18 16:16:31 rmb1"
Dijeda hingga pemberitahuan lebih lanjut.
Hmm ... kemana biner util-linux Anda hilang? Jalur file ini harus dimiliki oleh util-linux. Anda tidak menggunakan sistem GNU-Linux?
Jürgen Hötzel
1
yang hanya mengubah satu ruang dalam menjalankan saya, jadi "pergi kirim api di gunung" menjadi "go_tell api di gunung"
brokkr
6

bash 4.0

#!/bin/bash
shopt -s globstar
for file in **/*\ *
do 
    mv "$file" "${file// /_}"       
done
ghostdog74
sumber
Sepertinya ini akan melakukan mv untuk dirinya sendiri jika file atau nama direktori tidak memiliki ruang di dalamnya (mv: tidak dapat memindahkan a' to a subdirectory of itself, a / a ')
armandino
tidak masalah. hapus saja pesan kesalahan dengan mengarahkan ulang ke /dev/null.
ghostdog74
ghostdog, menelurkan mvlima puluh lima ribu kali hanya untuk mengganti nama empat file mungkin sedikit overhead bahkan jika Anda tidak membanjiri pengguna dengan pesan.
Michael Krelin - hacker
Krelin, bahkan menemukan akan melalui 55.000 file yang Anda sebutkan untuk menemukan orang-orang dengan spasi dan kemudian melakukan penggantian nama. Di bagian belakang, masih melewati semua. Jika Anda mau, pemeriksaan awal untuk spasi sebelum mengganti nama akan melakukannya.
ghostdog74
Saya berbicara tentang pemijahan mv, tidak melalui. Bukankah for file in *' '*atau semacamnya melakukan pekerjaan yang lebih baik?
Michael Krelin - hacker
6

Anda bisa menggunakan ini:

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

Versi rekursif dari Jawaban Naidim.

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

Berikut ini adalah (cukup verbose) solusi -exec yang menulis peringatan "file sudah ada" untuk 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
sumber
2

Yang ini sedikit lebih. Saya menggunakannya untuk mengganti nama torrents yang saya unduh (tidak ada karakter khusus (non-ASCII), spasi, banyak titik, dll.).

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

Saya menemukan skrip ini, mungkin menarik :)

 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
sumber
Gagal pada file dengan baris baru di namanya.
ghoti
1

Berikut solusi bash script berukuran cukup

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

Bagi mereka yang berjuang melalui ini menggunakan macOS, instal terlebih dahulu semua alat:

 brew install tree findutils rename

Kemudian ketika diperlukan untuk mengganti nama, buat alias untuk GNU find (gfind) sebagai find. Kemudian jalankan kode @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
sumber
0

Ini hanya menemukan file di dalam direktori saat ini dan mengganti nama mereka . Saya memiliki ini alias.

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

pengguna1060059
sumber
0

Saya hanya membuat satu untuk tujuan saya sendiri. Anda dapat menggunakannya sebagai referensi.

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

Untuk file dalam folder bernama / file

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
sumber
0

Solusi saya untuk masalah ini adalah skrip 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

cukup masukkan nama direktori, di mana Anda ingin menerapkan skrip, sebagai argumen setelah menjalankan skrip.

Ostongoston Volcz
sumber
0

Di macOS

Sama seperti jawaban yang dipilih.

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
sumber