วิธีแทนที่ช่องว่างในชื่อไฟล์โดยใช้สคริปต์ทุบตี


264

ใครสามารถแนะนำวิธีแก้ปัญหาที่ปลอดภัยเพื่อแทนที่ช่องว่างด้วยการขีดเส้นใต้ในชื่อไฟล์และไดเรกทอรีเริ่มต้นจากไดเรกทอรีรากที่กำหนด? ตัวอย่างเช่น:

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

กลายเป็น:

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

9
คุณต้องการเกิดอะไรขึ้นถ้ามีไฟล์ชื่อfoo barหนึ่งและอีกไฟล์หนึ่งชื่อfoo_barในไดเรกทอรีเดียวกัน
Mark Byers

คำถามที่ดี. ฉันไม่ต้องการเขียนทับไฟล์ที่มีอยู่หรือสูญเสียข้อมูลใด ๆ ควรปล่อยไว้โดยไม่มีการเปลี่ยนแปลง .. ควรพิมพ์คำเตือน แต่อาจจะถามมากเกินไป
armandino

คำตอบ:


311

ใช้rename(aka prename) ซึ่งเป็นสคริปต์ Perl ซึ่งอาจอยู่ในระบบของคุณแล้ว ทำได้สองขั้นตอน:

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

ตามคำตอบของJürgenและสามารถจัดการไฟล์และไดเรกทอรีหลายเลเยอร์ในขอบเขตเดียวโดยใช้ "Revision 1.5 1998/12/18 16:16:31 rmb1" เวอร์ชั่นของ/usr/bin/rename(สคริปต์ Perl):

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

5
ไม่จำเป็นต้องมีสองขั้นตอน: ใช้การค้นหาความลึกก่อนอื่น: ค้นหา dir
-depth

3
โอ้ฉันเพิ่งอ่านrenamemanpage (ฉันไม่รู้เครื่องมือ) และฉันคิดว่าคุณสามารถเพิ่มประสิทธิภาพรหัสของคุณโดยเปลี่ยนs/ /_/gเป็นy/ /_/;-)
Michael Krelin - แฮกเกอร์

1
แน่นอนคุณจะไม่ได้รับการเพิ่มประสิทธิภาพจากมัน มันเกี่ยวกับการใช้เครื่องมือที่เหมาะสม และคำถามทั้งหมดนี้เกี่ยวกับการเพิ่มประสิทธิภาพแบบไมโครให้มากขึ้นหรือน้อยลง มันไม่สนุกหรอกเหรอ? ;-)
Michael Krelin - แฮกเกอร์

14
หากคุณใช้งานบน OS X คุณจะต้องbrew install rename
loeschg

7
สิ่งนี้ใช้ไม่ได้กับ Centos 7 เนื่องจากคำสั่งเปลี่ยนชื่อแตกต่างอย่างสิ้นเชิง (เป็นไบนารีไม่ใช่สคริปต์ Perl) และไม่ยอมรับข้อมูลจาก stdin
CpnCrunch

357

ฉันใช้:

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

แม้ว่ามันจะไม่เวียนเกิดซ้ำ แต่ก็ค่อนข้างเร็วและง่าย ฉันแน่ใจว่ามีบางคนที่นี่สามารถอัปเดตให้เป็นแบบเรียกซ้ำ

${f// /_}ส่วนใช้กลไกการขยายตัวพารามิเตอร์ทุบตีเพื่อเปลี่ยนรูปแบบภายในพารามิเตอร์ด้วยเชือกให้มา ${parameter/pattern/string}ไวยากรณ์ที่เกี่ยวข้องคือ ดู: https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.htmlหรือhttp://wiki.bash-hackers.org/syntax/pe


9
เรียบง่ายและทำงานใน mac (mac ไม่มีrenameและมันยากเกินกว่าจะติดตั้งด้วย brew .. )
JonnieJS

6
คำตอบที่น่ากลัว ฉันใช้for d in *\ *; do mv "$d" "${d// /}"; doneไม่ใช่คะแนนต่ำกว่า
Yoon Lee

1
ต่างจากคำตอบ 'find -name' ตัวนี้ทำงานบน OS X ของฉัน! ขอบคุณมาก!
Julio Faerman

1
สำหรับการอ้างอิงนี้ได้อย่างง่ายดายสามารถกลายเป็น recursive ในทุบตีสำหรับการใช้และshopt -s globstar ตัวเลือกที่อยู่ภายในทุบตีในขณะที่คำสั่งเป็นเครื่องมือลินุกซ์ที่พบบ่อยและไม่ได้เป็นส่วนหนึ่งของการทุบตี for f in **/*\ *; do ...globstarrename
ghoti

2
${f// /_}คือการขยายตัวของตัวแปรทุบตีสำหรับการค้นหาและแทนที่ - fเป็นตัวแปรจากการforวนซ้ำสำหรับแต่ละไฟล์ที่มีช่องว่าง - วิธีแรก//หมายถึง "แทนที่ทั้งหมด" (อย่าหยุดที่เกิดขึ้นครั้งแรก) - จากนั้น `/ _` หมายถึง" แทนที่พื้นที่ด้วยเครื่องหมายขีดล่าง "
Ari

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

ไม่สามารถทำให้ถูกต้องตั้งแต่แรกเพราะฉันไม่ได้นึกถึงไดเรกทอรี


1
ทำงานเหมือนจับใจ ขอบคุณไมเคิล
armandino

เดนนิสจับดีแก้ไขได้อย่างง่ายดายโดยการวางในด้านหน้าของIFS='' readนอกจากนี้สำหรับสิ่งที่ผมสามารถบอกได้โดยความเห็นอื่น ๆsortขั้นตอนที่สามารถลดลงในความโปรดปรานของตัวเลือกในการ-depth find
Michael Krelin - แฮ็กเกอร์

1
ไม่ทำงานหากชื่อไฟล์มี\ (เครื่องหมายแบ็กสแลช) สามารถแก้ไขได้โดยการเพิ่ม-rตัวเลือกในการอ่าน
jfg956

8
นี่เป็นครั้งที่ 50 ที่ฉันเยี่ยมชมหน้านี้เพื่อคัดลอกและใช้โซลูชันของคุณ ขอบคุณมากๆ ฉันชอบคำตอบของคุณเพราะฉันใช้ Mac และไม่มีrenameคำสั่งที่เดนนิสแนะนำ
Alex Constantin

@AlexConstantin ไม่macportsได้rename? ฉันไม่เคยสนใจที่จะค้นหาเพราะฉันไม่คิดว่างานจะให้ประโยชน์กับยูทิลิตี้ และถ้าคุณไม่มีmacportsคุณควรพิจารณาทำการติดตั้ง;)
Michael Krelin - แฮกเกอร์


12

หา / เปลี่ยนชื่อแก้ปัญหา เปลี่ยนชื่อเป็นส่วนหนึ่งของ util-linux

คุณต้องสืบหาความลึกก่อนเพราะชื่อไฟล์ช่องว่างสามารถเป็นส่วนหนึ่งของไดเรกทอรีช่องว่าง:

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

ฉันไม่ได้รับการเปลี่ยนแปลงเลยเมื่อฉันบริหารงานของคุณ
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

ตรวจสอบการตั้งค่า util-linux: $ rename --version rename (util-linux-ng 2.17.2)
JürgenHötzel

Grepping / usr / bin / rename (สคริปต์ Perl) เผย "การแก้ไข 1.5 1998/12/18 16:16:31 rmb1"
หยุดชั่วคราวจนกว่าจะมีประกาศเพิ่มเติม

อืม ... ไบนารี util-linux ของคุณหายไปไหน? พา ธ ไฟล์นี้ควรเป็นเจ้าของโดย util-linux คุณไม่ใช้ระบบ GNU-Linux ใช่ไหม
JürgenHötzel

1
ซึ่งเปลี่ยนเพียงหนึ่งช่องว่างในระยะของฉันดังนั้น "ไปบอกไฟบนภูเขา" จะกลายเป็น "go_tell ไฟบนภูเขา"
brokkr

6

ทุบตี 4.0

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

ดูเหมือนว่าจะทำ mv ให้ตัวเองหากไฟล์หรือชื่อไดเรกทอรีไม่มีที่ว่างอยู่ (mv: ไม่สามารถย้ายa' to a subdirectory of itself, / a ')
armandino

ไม่สำคัญ เพียง /dev/nullแต่เอาข้อผิดพลาดโดยการเปลี่ยนเส้นทางไปยัง
ghostdog74

ghostdog, วางไข่mvห้าหมื่นห้าพันครั้งเท่านั้นที่จะเปลี่ยนชื่อสี่ไฟล์อาจเป็นค่าใช้จ่ายเล็กน้อยแม้ว่าคุณจะไม่ส่งข้อความถึงผู้ใช้
Michael Krelin - แฮ็กเกอร์

อย่างไรก็ตาม, แม้จะค้นหาจะผ่านไฟล์ 55000 ที่คุณกล่าวถึงเพื่อค้นหาผู้ที่มีช่องว่าง, แล้วทำการเปลี่ยนชื่อ. ที่ปลายด้านหลังมันยังคงผ่านมาทั้งหมด หากคุณต้องการการตรวจสอบเบื้องต้นสำหรับช่องว่างก่อนที่จะเปลี่ยนชื่อจะทำ
ghostdog74

ฉันกำลังพูดถึงการวางไข่ mv ไม่ผ่าน จะfor file in *' '*หรือไม่เช่นนั้นจะทำงานได้ดีขึ้นหรือไม่
Michael Krelin - แฮ็กเกอร์

6

คุณสามารถใช้สิ่งนี้:

    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

เวอร์ชันคำตอบของ 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

ต่อไปนี้เป็นโซลูชัน verexose (ค่อนข้างละเอียด) ซึ่งเขียนว่า "file มีอยู่แล้ว" คำเตือนไปยัง 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

อันนี้ทำอะไรได้มากกว่า ฉันใช้มันเพื่อเปลี่ยนชื่อเพลงที่ดาวน์โหลดมา (ไม่มีตัวอักษรพิเศษ (ไม่ใช่ ASCII) เว้นวรรคหลายจุด ฯลฯ )

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

ฉันพบสคริปต์นี้มันน่าสนใจ :)

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

ล้มเหลวในไฟล์ที่มีบรรทัดใหม่ในชื่อของพวกเขา
ghoti


1

สำหรับผู้ที่ประสบปัญหาในการใช้ macOS ขั้นแรกให้ติดตั้งเครื่องมือทั้งหมด:

 brew install tree findutils rename

จากนั้นเมื่อต้องการเปลี่ยนชื่อให้สร้างนามแฝงสำหรับ GNU find (gfind) เป็น find จากนั้นเรียกใช้รหัสของ @Michel Krelin:

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

0

แบบนี้เท่านั้น ค้นหาไฟล์ภายในไดเรกทอรีปัจจุบันและเปลี่ยนชื่อไฟล์เท่านั้น ฉันมีนามแฝงนี้แล้ว

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


0

ฉันเพิ่งสร้างขึ้นมาเพื่อวัตถุประสงค์ของฉันเอง คุณสามารถใช้เป็นข้อมูลอ้างอิงได้

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

สำหรับไฟล์ในโฟลเดอร์ชื่อ / ไฟล์

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

วิธีแก้ปัญหาของฉันคือสคริปต์ทุบตี:

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

เพียงแค่ใส่ชื่อไดเรกทอรีที่คุณต้องการใช้สคริปต์เป็นอาร์กิวเมนต์หลังจากรันสคริปต์


0

ใน macOS

เช่นเดียวกับคำตอบที่เลือก

brew install rename

# 
cd <your dir>
find . -name "* *" -type d | rename 's/ /_/g'    # do the directories first
find . -name "* *" -type f | rename 's/ /_/g'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.