Cách thay thế khoảng trắng trong tên tệp bằng cách sử dụng tập lệnh bash


264

Bất cứ ai cũng có thể đề xuất một giải pháp an toàn để thay thế đệ quy các khoảng trắng bằng dấu gạch dưới trong tên tệp và thư mục bắt đầu từ một thư mục gốc nhất định? Ví dụ:

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

trở thành:

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

9
Bạn muốn điều gì xảy ra nếu có một tệp được gọi foo barvà một tệp khác được gọi foo_bartrong cùng thư mục?
Mark Byers

Câu hỏi hay. Tôi sẽ không muốn ghi đè lên các tệp hiện có hoặc mất bất kỳ dữ liệu nào. Nó sẽ không thay đổi .. lý tưởng là in một cảnh báo nhưng điều đó có thể yêu cầu quá nhiều.
armandino

Câu trả lời:


312

Sử dụng rename(aka prename) là tập lệnh Perl có thể có trên hệ thống của bạn. Làm điều đó theo hai bước:

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

Dựa trên câu trả lời của Jürgen và có thể xử lý nhiều lớp tệp và thư mục trong một ràng buộc duy nhất bằng cách sử dụng phiên bản "Phiên bản 1.5 1998/12/18 16:16:31 rmb1" của /usr/bin/rename(một tập lệnh Perl):

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

5
Không cần hai bước: Sử dụng tìm kiếm theo chiều sâu: tìm dir -depth
Jürgen Hötzel

3
Ồ, tôi vừa đọc renametrang này (tôi không biết công cụ này) và tôi nghĩ bạn có thể tối ưu hóa mã của mình bằng cách thay đổi s/ /_/gthành y/ /_/;-)
Michael Krelin - hacker

1
Tất nhiên, bạn sẽ không nhận được một hiệu suất tăng từ nó. Đó là nhiều hơn về việc sử dụng đúng công cụ. Và toàn bộ câu hỏi này là về tối ưu hóa vi mô nhiều hay ít. Rốt cuộc nó không vui sao? ;-)
Michael Krelin - hacker

14
Nếu bạn đang chạy cái này trên OS X, bạn sẽ cầnbrew install rename
loeschg

7
Điều này không hoạt động trên Centos 7, vì lệnh đổi tên hoàn toàn khác (đó là tệp nhị phân, không phải là tập lệnh perl) và nó không chấp nhận dữ liệu từ stdin.
CpnCrunch

357

Tôi sử dụng:

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

Mặc dù nó không đệ quy, nhưng nó khá nhanh và đơn giản. Tôi chắc rằng ai đó ở đây có thể cập nhật nó để được đệ quy.

Phần này ${f// /_}sử dụng cơ chế mở rộng tham số của bash để thay thế một mẫu trong một tham số bằng chuỗi được cung cấp. Cú pháp có liên quan là ${parameter/pattern/string}. Xem: https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html hoặc http://wiki.bash-hackers.org/syntax/pe .


9
Đơn giản và làm việc trong mac. (mac không có rename, và quá khó để cài đặt cái này với brew ..)
JonnieJS

6
câu trả lời tuyệt vời. tôi đã sử dụng for d in *\ *; do mv "$d" "${d// /}"; donekhông dưới điểm số.
Yoon Lee

1
Không giống như câu trả lời 'find -name', cái này hoạt động trên OS X của tôi! Cảm ơn ngài!
Julio Faerman

1
Để tham khảo, điều này có thể dễ dàng trở thành đệ quy trong bash để sử dụng shopt -s globstarfor f in **/*\ *; do .... Các globstartùy chọn là nội bộ để bash, trong khi renamelệnh là một Linux công cụ phổ biến và không nằm trong bash.
ghoti

2
${f// /_}là một mở rộng biến Bash để tìm kiếm và thay thế . - Là fbiến từ forvòng lặp cho mỗi tệp có chứa một khoảng trắng. - Đầu tiên //có nghĩa là "thay thế tất cả" (không dừng lại ở lần xuất hiện đầu tiên). - Sau đó, `/ _` có nghĩa là" thay thế không gian bằng dấu gạch dưới "
Ari

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

ban đầu không thành công vì tôi không nghĩ đến các thư mục.


1
Hoạt động như một lá bùa. Cảm ơn Michael.
armandino

Dennis, bắt tốt, dễ dàng sửa chữa bằng cách đặt IFS=''trước mặt read. Ngoài ra, đối với những gì tôi có thể nói bằng các bình luận khác, sortbước có thể được bỏ qua để ủng hộ -depthtùy chọn find.
Michael Krelin - hacker

1
Không hoạt động nếu tên tệp chứa \ (dấu gạch chéo ngược). Có thể được sửa bằng cách thêm một -rtùy chọn để đọc.
jfg956

8
Đây phải là lần thứ 50 tôi truy cập trang này để sao chép và sử dụng giải pháp của bạn. Cảm ơn bạn rất nhiều . Tôi thích câu trả lời của bạn, vì tôi đang dùng Mac và không có renamelệnh do Dennis đề xuất.
Alex Constantin

@AlexConstantin, không macportsrename? Tôi chưa bao giờ bận tâm để tìm hiểu bởi vì tôi không nghĩ rằng nhiệm vụ biện minh cho tiện ích. Và nếu bạn không có macports, bạn nên xem xét việc cài đặt chúng;)
Michael Krelin - hacker


12

Một giải pháp tìm / đổi tên . đổi tên là một phần của linux-linux.

Bạn cần phải xuống độ sâu trước, vì tên tệp khoảng trắng có thể là một phần của thư mục khoảng trắng:

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

Tôi không nhận được bất kỳ thay đổi nào khi tôi chạy của bạn.
Tạm dừng cho đến khi có thông báo mới.

Kiểm tra thiết lập của linux-linux: đổi tên $ đổi tên - đổi tên (produc-linux-ng 2.17.2)
Jürgen Hötzel

Grepping / usr / bin / đổi tên (tập lệnh Perl) cho thấy "Bản sửa đổi 1.5 1998/12/18 16:16:31 rmb1"
Tạm dừng cho đến khi có thông báo mới.

Hmm ... nhị phân linux-linux của bạn đã đi đâu? Đường dẫn tệp này phải được sở hữu bởi linux-linux. Bạn không cho chúng tôi một hệ thống GNU-Linux?
Jürgen Hötzel

1
chỉ thay đổi một không gian trong quá trình chạy của tôi, vì vậy "hãy nói với ngọn lửa trên núi" trở thành "ngọn lửa trên ngọn núi".
brokkr

6

bash 4.0

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

Có vẻ như điều này sẽ tự thực hiện một mv nếu tên tệp hoặc thư mục không có khoảng trống trong đó (mv: không thể di chuyển a' to a subdirectory of itself, a / a ')
armandino

không quan trọng chỉ cần loại bỏ thông báo lỗi bằng cách chuyển hướng đến /dev/null.
ghostdog74

ghostdog, sinh ra mvnăm mươi năm ngàn lần chỉ để đổi tên bốn tệp có thể là một chút chi phí ngay cả khi bạn không làm ngập người dùng bằng tin nhắn.
Michael Krelin - hacker

krelin, thậm chí find sẽ đi qua 55000 tệp mà bạn đã đề cập để tìm những tệp có khoảng trắng và sau đó thực hiện đổi tên. Ở phía sau, nó vẫn đi qua tất cả. Nếu bạn muốn, kiểm tra ban đầu cho khoảng trắng trước khi đổi tên sẽ thực hiện.
ghostdog74

Tôi đã nói về sinh sản mv, không đi qua. Sẽ không for file in *' '*hoặc một số như vậy làm một công việc tốt hơn?
Michael Krelin - hacker

6

bạn có thể sử dụng cái này:

    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

Phiên bản đệ quy của Câu trả lời của 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

2

Đây là một giải pháp find -exec (khá dài dòng) ghi cảnh báo "tập tin đã tồn tại" vào 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

Cái này làm nhiều hơn một chút. Tôi sử dụng nó để đổi tên các torrent đã tải xuống của mình (không có ký tự đặc biệt (không phải ASCII), dấu cách, nhiều dấu chấm, v.v.).

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

Tôi tìm thấy xung quanh kịch bản này, nó có thể thú vị :)

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

Thất bại trên các tập tin với dòng mới trong tên của họ.
ghoti


1

Đối với những người gặp khó khăn khi sử dụng macOS, trước tiên hãy cài đặt tất cả các công cụ:

 brew install tree findutils rename

Sau đó, khi cần đổi tên, tạo bí danh cho GNU find (gfind) là find. Sau đó chạy mã của @Michel Krelin:

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

0

Điều này chỉ tìm thấy các tập tin trong thư mục hiện tại và đổi tên chúng . Tôi có bí danh này.

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


0

Tôi chỉ làm một cái cho mục đích riêng của tôi. Bạn có thể sử dụng nó làm tài liệu tham khảo.

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

Đối với các tệp trong thư mục có tên / tệp

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

Giải pháp cho vấn đề của tôi là một tập lệnh 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

chỉ cần đặt tên thư mục, trên đó bạn muốn áp dụng tập lệnh, làm đối số sau khi thực thi tập lệnh.


0

Trong macOS

Cũng giống như câu trả lời được chọn.

brew install rename

# 
cd <your dir>
find . -name "* *" -type d | rename 's/ /_/g'    # do the directories first
find . -name "* *" -type f | rename 's/ /_/g'
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.