เปลี่ยนชื่อไฟล์ซ้ำ ๆ โดยใช้ find และ sed


87

ฉันต้องการอ่านไดเรกทอรีจำนวนมากและเปลี่ยนชื่อไฟล์ทั้งหมดที่ลงท้ายด้วย _test.rb เพื่อลงท้ายด้วย _spec.rb แทน มันเป็นสิ่งที่ฉันไม่เคยคิดมาก่อนว่าจะทำอย่างไรกับการทุบตีดังนั้นคราวนี้ฉันคิดว่าฉันจะพยายามอย่างเต็มที่เพื่อให้มันถูกตอก จนถึงตอนนี้ฉันคิดได้สั้น ๆ แต่ความพยายามที่ดีที่สุดของฉันคือ:

find spec -name "*_test.rb" -exec echo mv {} `echo {} | sed s/test/spec/` \;

หมายเหตุ: มีเสียงสะท้อนเพิ่มเติมหลังจาก exec เพื่อให้พิมพ์คำสั่งแทนการรันในขณะที่ฉันกำลังทดสอบ

เมื่อฉันเรียกใช้ผลลัพธ์สำหรับชื่อไฟล์ที่ตรงกันคือ:

mv original original

กล่าวคือการแทนที่โดย sed ได้สูญหายไป เคล็ดลับคืออะไร?


BTW ฉันทราบว่ามีคำสั่งเปลี่ยนชื่อ แต่ฉันต้องการหาวิธีใช้ sed เพื่อที่ฉันจะได้ทำคำสั่งที่มีประสิทธิภาพมากขึ้นในอนาคต
opsb

2
กรุณาอย่าข้ามโพสต์
Dennis Williamson

คำตอบ:


32

สิ่งนี้เกิดขึ้นเนื่องจากsedได้รับสตริง{}เป็นอินพุตซึ่งสามารถตรวจสอบได้ด้วย:

find . -exec echo `echo "{}" | sed 's/./foo/g'` \;

ซึ่งพิมพ์foofooสำหรับแต่ละไฟล์ในไดเร็กทอรีแบบวนซ้ำ สาเหตุของพฤติกรรมนี้คือไปป์ไลน์ถูกดำเนินการครั้งเดียวโดยเชลล์เมื่อขยายคำสั่งทั้งหมด

ไม่มีวิธีใดในการอ้างsedไปป์ไลน์ในลักษณะที่findจะดำเนินการกับทุกไฟล์เนื่องจากfindจะไม่ดำเนินการคำสั่งผ่านเชลล์และไม่มีความคิดเกี่ยวกับไปป์ไลน์หรือแบ็คควอต คู่มือการค้นหา GNU อธิบายถึงวิธีดำเนินงานที่คล้ายกันโดยการใส่ไปป์ไลน์ในเชลล์สคริปต์ที่แยกต่างหาก:

#!/bin/sh
echo "$1" | sed 's/_test.rb$/_spec.rb/'

(อาจมีวิธีใช้ที่ไม่เหมาะสมsh -cและมีเครื่องหมายคำพูดมากมายในการทำทั้งหมดนี้ในคำสั่งเดียว แต่ฉันจะไม่ลอง)


27
สำหรับผู้ที่สงสัยเกี่ยวกับการใช้งานในทางที่ผิดของ sh -c โปรดค้นหา spec -name "* _test.rb" -exec sh -c 'echo mv "$ 1" "$ (echo" $ 1 "| sed s / test.rb \ $ / spec.rb /) "'_ {} \;
opsb

1
@opsb ห่านั่น _ เพื่ออะไร? ทางออกที่ดี - แต่ฉันชอบ ramtam ตอบมากกว่า :)
iRaS

ไชโย! ช่วยฉันปวดหัวมาก เพื่อความสมบูรณ์นี่คือวิธีที่ฉันไพพ์มันไปยังสคริปต์: find. - ชื่อ "ไฟล์" -exec sh /path/to/script.sh {} \;
Sven M.

131

ในการแก้ปัญหาในลักษณะที่ใกล้เคียงกับปัญหาเดิมมากที่สุดอาจเป็นการใช้ xargs "args per command line" option

find . -name "*_test.rb" | sed -e "p;s/test/spec/" | xargs -n2 mv

พบไฟล์ในไดเร็กทอรีการทำงานปัจจุบันแบบวนซ้ำสะท้อนชื่อไฟล์เดิม ( p) จากนั้นชื่อที่แก้ไข ( s/test/spec/) และฟีดทั้งหมดเป็นmvคู่ ( xargs -n2) ระวังว่าในกรณีนี้เส้นทางไม่ควรมีสตริงtestระวังว่าในกรณีนี้เส้นทางของตัวเองไม่ควรมีสตริง


9
ขออภัยมีปัญหาเรื่องพื้นที่สีขาว ดังนั้นการใช้กับโฟลเดอร์ที่มีช่องว่างในชื่อจะแตกที่ xargs (ยืนยันด้วย -p สำหรับโหมด verbose / โต้ตอบ)
cde

1
นั่นคือสิ่งที่ฉันกำลังมองหา เสียดายสำหรับปัญหาพื้นที่สีขาว (ฉันไม่ได้ทดสอบ) แต่สำหรับความต้องการปัจจุบันของฉันมันสมบูรณ์แบบ ฉันขอแนะนำให้ทดสอบก่อนโดยใช้ "echo" แทน "mv" เป็นพารามิเตอร์ใน "xargs"
Michele Dall'Agata

5
หากคุณต้องการจัดการกับช่องว่างในเส้นทางและคุณใช้ GNU sed> = 4.2.2 คุณสามารถใช้-zตัวเลือกร่วมกับการค้นหา-print0และ xargs -0:find -name '*._test.rb' -print0 | sed -ze "p;s/test/spec/" | xargs -0 -n2 mv
Evan Purkhiser

ทางออกที่ดีที่สุด เร็วกว่าหา -exec มาก ขอบคุณ
Miguel A. Baldi Hörlle

สิ่งนี้จะไม่ได้ผลหากมีหลายtestโฟลเดอร์ในเส้นทางเดียว sedจะเปลี่ยนชื่อคนแรกเท่านั้นและmvคำสั่งจะล้มเหลวเนื่องจากNo such file or directoryข้อผิดพลาด
Casey

22

คุณอาจต้องการพิจารณาวิธีอื่นเช่น

for file in $(find . -name "*_test.rb")
do 
  echo mv $file `echo $file | sed s/_test.rb$/_spec.rb/`
done

ดูเหมือนเป็นวิธีที่ดีที่จะทำ ฉันอยากจะแตกไลน์เนอร์จริงๆเพื่อพัฒนาความรู้มากกว่าสิ่งอื่นใด
opsb

2
สำหรับไฟล์ใน $ (ค้นหา. -name "* _test.rb"); ทำไฟล์ echo mv $ echo $file | sed s/_test.rb$/_spec.rb/; doneเป็นซับเดียวไม่ใช่เหรอ
Bretticus

5
สิ่งนี้จะใช้ไม่ได้หากคุณมีชื่อไฟล์ที่มีช่องว่าง forจะแยกออกเป็นคำแยกกัน คุณสามารถทำให้มันทำงานได้โดยสั่งให้ลูปแบ่งเฉพาะในบรรทัดใหม่ ดูcyberciti.biz/tips/handling-filenames-with-spaces-in-bash.htmlสำหรับตัวอย่าง
onitake

ฉันเห็นด้วยกับ @onitake แม้ว่าฉันต้องการใช้-execตัวเลือกจากการค้นหา
ShellFish

18

ฉันพบว่าอันนี้สั้นกว่า

find . -name '*_test.rb' -exec bash -c 'echo mv $0 ${0/test.rb/spec.rb}' {} \;

สวัสดีฉันคิดว่า ' _test.rb "ควรเป็น' _test.rb '(เครื่องหมายคำพูดคู่เป็นเครื่องหมายคำพูดเดี่ยว) ฉันขอถามได้ไหมว่าทำไมคุณถึงใช้ขีดล่างเพื่อผลักอาร์กิวเมนต์ที่คุณต้องการวางตำแหน่ง $ 1 เมื่อดูเหมือนว่าฉันได้find . -name '*_test.rb' -exec bash -c 'echo mv $0 ${0/test.rb/spec.rb}' {} \;ผล ? ตามจะfind . -name '*_test.rb' -exec bash -c 'echo mv $1 ${1/test.rb/spec.rb}' iAmArgumentZero {} \;
agtb

ขอบคุณสำหรับคำแนะนำแก้ไข
csg

ขอบคุณสำหรับการล้างข้อมูลนั้น - ฉันแสดงความคิดเห็นเพียงเพราะฉันใช้เวลาในการไตร่ตรองความหมายของ _ คิดว่าอาจเป็นการหลอกใช้ $ _ ('_' ค่อนข้างยากที่จะค้นหาในเอกสาร!)
agtb

9

คุณสามารถทำได้โดยไม่ต้องใจเย็นหากคุณต้องการ:

for i in `find -name '*_test.rb'` ; do mv $i ${i%%_test.rb}_spec.rb ; done

${var%%suffix}แถบsuffixจากค่าของvar .

หรือจะทำโดยใช้ sed:

for i in `find -name '*_test.rb'` ; do mv $i `echo $i | sed 's/test/spec/'` ; done

สิ่งนี้ไม่ได้ผล ( sedอันเดียว) ตามที่อธิบายโดยคำตอบที่ยอมรับ
อาลี

@ อาลีมันใช้งานได้ - ฉันทดสอบด้วยตัวเองเมื่อฉันเขียนคำตอบ @ คำอธิบาย larsman ไม่ได้นำไปใช้for i in... ; do ... ; doneซึ่งรันคำสั่งผ่านเปลือกและไม่เข้าใจ backtick
Wayne Conrad

9

คุณระบุว่าคุณใช้bashเป็นเชลล์ซึ่งในกรณีนี้คุณไม่จำเป็นต้องใช้findและsedเพื่อให้บรรลุการเปลี่ยนชื่อแบตช์ที่คุณต้องการ ...

สมมติว่าคุณใช้bashเป็นเชลล์ของคุณ:

$ echo $SHELL
/bin/bash
$ _

... และสมมติว่าคุณได้เปิดใช้งานglobstarตัวเลือกเชลล์ที่เรียกว่า:

$ shopt -p globstar
shopt -s globstar
$ _

... และในที่สุดสมมติว่าคุณได้ติดตั้งrenameยูทิลิตี้ (พบในutil-linux-ngแพ็คเกจ)

$ which rename
/usr/bin/rename
$ _

... จากนั้นคุณจะสามารถเปลี่ยนชื่อแบทช์ในbash one-liner ได้ดังนี้:

$ rename _test _spec **/*_test.rb

( globstarตัวเลือกเชลล์จะตรวจสอบให้แน่ใจว่า bash พบ*_test.rbไฟล์ที่ตรงกันทั้งหมดไม่ว่าไฟล์เหล่านั้นจะซ้อนกันลึกแค่ไหนในลำดับชั้นของไดเร็กทอรี ... ใช้help shoptเพื่อค้นหาวิธีตั้งค่าตัวเลือก)


7

วิธีที่ง่ายที่สุด :

find . -name "*_test.rb" | xargs rename s/_test/_spec/

วิธีที่เร็วที่สุด (สมมติว่าคุณมีโปรเซสเซอร์ 4 ตัว):

find . -name "*_test.rb" | xargs -P 4 rename s/_test/_spec/

หากคุณมีไฟล์จำนวนมากที่ต้องประมวลผลอาจเป็นไปได้ว่ารายการชื่อไฟล์ที่ไพพ์ไปยัง xargs จะทำให้บรรทัดคำสั่งที่ได้ผลลัพธ์เกินความยาวสูงสุดที่อนุญาต

คุณสามารถตรวจสอบขีด จำกัด ของระบบได้โดยใช้ getconf ARG_MAX

ในระบบลินุกซ์ส่วนใหญ่คุณสามารถใช้free -bหรือcat /proc/meminfoค้นหาว่าคุณต้องใช้ RAM เท่าใด มิฉะนั้นให้ใช้topหรือแอปตรวจสอบกิจกรรมระบบของคุณ

วิธีที่ปลอดภัยกว่า (สมมติว่าคุณมี ram 1000000 ไบต์ที่จะใช้งานได้):

find . -name "*_test.rb" | xargs -s 1000000 rename s/_test/_spec/

2

นี่คือสิ่งที่ใช้ได้ผลสำหรับฉันเมื่อชื่อไฟล์มีช่องว่างอยู่ ตัวอย่างด้านล่างเปลี่ยนชื่อไฟล์. dar ทั้งหมดเป็นไฟล์. zip ซ้ำ ๆ :

find . -name "*.dar" -exec bash -c 'mv "$0" "`echo \"$0\" | sed s/.dar/.zip/`"' {} \;

2

สำหรับสิ่งนี้คุณไม่จำเป็นต้องsedใช้ คุณสมบูรณ์แบบสามารถได้รับเพียงอย่างเดียวกับwhileวงที่เลี้ยงด้วยผลมาจากการfindผ่านการเปลี่ยนตัวกระบวนการ

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

while IFS= read -r file; do
     echo "mv $file ${file%_test.rb}_spec.rb"  # remove "echo" when OK!
done < <(find -name "*_test.rb")

การดำเนินการนี้จะfindเปลี่ยนชื่อไฟล์และเปลี่ยนชื่อทั้งหมดโดยดึงสตริง_test.rbออกจากท้ายและต่อท้าย_spec.rbจากท้ายและท้าย

สำหรับขั้นตอนนี้เราจะใช้ขยายเชลล์พารามิเตอร์ที่${var%string}เอารูปแบบการจับคู่ที่สั้นที่สุด "สตริง" $varจาก

$ file="HELLOa_test.rbBYE_test.rb"
$ echo "${file%_test.rb}"          # remove _test.rb from the end
HELLOa_test.rbBYE
$ echo "${file%_test.rb}_spec.rb"  # remove _test.rb and append _spec.rb
HELLOa_test.rbBYE_spec.rb

ดูตัวอย่าง:

$ tree
.
├── ab_testArb
├── a_test.rb
├── a_test.rb_test.rb
├── b_test.rb
├── c_test.hello
├── c_test.rb
└── mydir
    └── d_test.rb

$ while IFS= read -r file; do echo "mv $file ${file/_test.rb/_spec.rb}"; done < <(find -name "*_test.rb")
mv ./b_test.rb ./b_spec.rb
mv ./mydir/d_test.rb ./mydir/d_spec.rb
mv ./a_test.rb ./a_spec.rb
mv ./c_test.rb ./c_spec.rb

ขอบคุณมาก! มันช่วยฉันในการลบ. gz ต่อท้ายจากชื่อไฟล์ทั้งหมดแบบวนซ้ำ while IFS= read -r file; do mv $file ${file%.gz}; done < <(find -type f -name "*.gz")
Vinay Vissh

1
@CasualCoder ที่ดีที่จะอ่านว่า :) find .... -exec mv ...หมายเหตุคุณโดยตรงสามารถพูดได้ นอกจากนี้โปรดระวังด้วย$fileเนื่องจากจะล้มเหลวหากมีช่องว่าง "$file"คำพูดที่ใช้ดีกว่า
fedorqui 'SO หยุดทำร้าย'


1

ในคำตอบของ ramtam ที่ฉันชอบส่วน find ทำงานได้ดี แต่ส่วนที่เหลือไม่ได้หากเส้นทางมีช่องว่าง ฉันไม่ค่อยคุ้นเคยกับ sed แต่ฉันสามารถแก้ไขคำตอบนั้นเป็น:

find . -name "*_test.rb" | perl -pe 's/^((.*_)test.rb)$/"\1" "\2spec.rb"/' | xargs -n2 mv

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

find . -name "olddir" | perl -pe 's/^((.*)olddir)$/"\1" "\2new directory"/' | xargs -n2 mv

1

ฉันไม่ได้หัวใจที่จะทำมันอีกครั้ง แต่ผมเขียนนี้ในการตอบcommandline ค้นหา Sed Exec ที่นั่นผู้ถามต้องการทราบวิธีย้ายทรีทั้งหมดโดยอาจไม่รวมไดเร็กทอรีหรือสองไดเร็กทอรีและเปลี่ยนชื่อไฟล์และไดเร็กทอรีทั้งหมดที่มีสตริง"OLD"เป็น"NEW" แทนแทน

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

นอกจากนี้ยังหลีกเลี่ยงการวนซ้ำอย่างชัดเจนให้มากที่สุด นอกเหนือจากการsedค้นหาซ้ำสำหรับรูปแบบที่ตรงกันมากกว่าหนึ่งรายการแล้วยังไม่มีการเรียกซ้ำแบบอื่นอีกเท่าที่ฉันรู้

และสุดท้ายนี้เป็นnullตัวคั่นโดยสิ้นเชิง- จะไม่เดินทางกับอักขระใด ๆ ในชื่อไฟล์ใด ๆ ยกเว้นไฟล์null. ฉันไม่คิดว่าคุณควรมีแบบนั้น

อย่างไรก็ตามนี่เป็นเรื่องที่รวดเร็วจริงๆ ดู:

% _mvnfind() { mv -n "${1}" "${2}" && cd "${2}"
> read -r SED <<SED
> :;s|${3}\(.*/[^/]*${5}\)|${4}\1|;t;:;s|\(${5}.*\)${3}|\1${4}|;t;s|^[0-9]*[\t]\(mv.*\)${5}|\1|p
> SED
> find . -name "*${3}*" -printf "%d\tmv %P ${5} %P\000" |
> sort -zg | sed -nz ${SED} | read -r ${6}
> echo <<EOF
> Prepared commands saved in variable: ${6}
> To view do: printf ${6} | tr "\000" "\n"
> To run do: sh <<EORUN
> $(printf ${6} | tr "\000" "\n")
> EORUN
> EOF
> }
% rm -rf "${UNNECESSARY:=/any/dirs/you/dont/want/moved}"
% time ( _mvnfind ${SRC=./test_tree} ${TGT=./mv_tree} \
> ${OLD=google} ${NEW=replacement_word} ${sed_sep=SsEeDd} \
> ${sh_io:=sh_io} ; printf %b\\000 "${sh_io}" | tr "\000" "\n" \
> | wc - ; echo ${sh_io} | tr "\000" "\n" |  tail -n 2 )

   <actual process time used:>
    0.06s user 0.03s system 106% cpu 0.090 total

   <output from wc:>

    Lines  Words  Bytes
    115     362   20691 -

    <output from tail:>

    mv .config/replacement_word-chrome-beta/Default/.../googlestars \
    .config/replacement_word-chrome-beta/Default/.../replacement_wordstars        

หมายเหตุ:ข้างต้นfunctionอาจต้องใช้GNUเวอร์ชันsedและfindเพื่อจัดการfind printfและsed -z -eและการ:;recursive regex test;tโทรอย่างถูกต้อง หากสิ่งเหล่านี้ไม่พร้อมใช้งานสำหรับคุณฟังก์ชันนี้อาจซ้ำซ้อนได้โดยมีการปรับเปลี่ยนเล็กน้อยเล็กน้อย

สิ่งนี้ควรทำทุกอย่างที่คุณต้องการตั้งแต่ต้นจนจบด้วยความยุ่งยากเล็กน้อย ฉันทำforkด้วยsedแต่ฉันก็ฝึกsedเทคนิคการแตกแขนงแบบวนซ้ำดังนั้นฉันจึงมาที่นี่ ฉันเดาว่ามันเหมือนกับการตัดผมลดราคาที่โรงเรียนสอนตัดผม นี่คือขั้นตอนการทำงาน:

  • rm -rf ${UNNECESSARY}
    • ฉันจงใจละทิ้งการเรียกใช้งานที่อาจลบหรือทำลายข้อมูลทุกชนิด คุณพูดถึงสิ่งที่./appอาจไม่ต้องการ ลบหรือย้ายไปที่อื่นก่อนหรือคุณอาจสร้าง\( -path PATTERN -exec rm -rf \{\} \)กิจวัตรประจำวันfindเพื่อทำแบบเป็นโปรแกรม แต่เป็นของคุณทั้งหมด
  • _mvnfind "${@}"
    • ประกาศอาร์กิวเมนต์และเรียกใช้ฟังก์ชันผู้ปฏิบัติงาน ${sh_io}มีความสำคัญอย่างยิ่งในการบันทึกผลตอบแทนจากฟังก์ชัน ${sed_sep}เข้ามาในวินาทีใกล้ นี่คือสตริงโดยพลการที่ใช้อ้างอิงการsedเรียกซ้ำของฟังก์ชัน หาก${sed_sep}ถูกตั้งค่าเป็นค่าที่อาจพบได้ในพา ธ หรือชื่อไฟล์ใด ๆ ของคุณที่ดำเนินการ ... เอาล่ะอย่าปล่อยให้เป็น
  • mv -n $1 $2
    • ต้นไม้ทั้งต้นถูกย้ายจากต้น จะช่วยประหยัดอาการปวดหัวได้มาก เชื่อฉัน. ส่วนที่เหลือของสิ่งที่คุณต้องการทำ - การเปลี่ยนชื่อ - เป็นเพียงเรื่องของข้อมูลเมตาของระบบไฟล์ ตัวอย่างเช่นหากคุณย้ายสิ่งนี้จากไดรฟ์หนึ่งไปยังอีกไดรฟ์หนึ่งหรือข้ามขอบเขตของระบบไฟล์ใด ๆ คุณควรทำพร้อมกันด้วยคำสั่งเดียว ยังปลอดภัยกว่าด้วย สังเกต-noclobberชุดตัวเลือกสำหรับmv; ตามที่เขียนไว้ฟังก์ชันนี้จะไม่ใส่${SRC_DIR}ตำแหน่งที่มี${TGT_DIR}อยู่แล้ว
  • read -R SED <<HEREDOC
    • ฉันพบคำสั่งทั้งหมดของ sed ที่นี่เพื่อประหยัดในการหลีกหนีความยุ่งยากและอ่านเป็นตัวแปรเพื่อป้อนข้อมูลด้านล่าง คำอธิบายด้านล่าง
  • find . -name ${OLD} -printf
    • เราเริ่มfindกระบวนการ เมื่อfindเราค้นหาเฉพาะสิ่งที่ต้องการเปลี่ยนชื่อเนื่องจากเราได้ดำเนินการแบบ place-to-place ทั้งหมดmvด้วยคำสั่งแรกของฟังก์ชันแล้ว แทนที่จะดำเนินการโดยตรงกับfindเช่นการexecโทรเราใช้คำสั่งนี้เพื่อสร้างบรรทัดคำสั่งแบบไดนามิกด้วย-printf.
  • %dir-depth :tab: 'mv '%path-to-${SRC}' '${sed_sep}'%path-again :null delimiter:'
    • หลังจากfindค้นหาไฟล์แล้วเราต้องการให้สร้างและพิมพ์คำสั่ง( ส่วนใหญ่ ) โดยตรงเราจะต้องดำเนินการเปลี่ยนชื่อของคุณ การ%dir-depthตรึงไว้ที่จุดเริ่มต้นของแต่ละบรรทัดจะช่วยให้แน่ใจว่าเราไม่ได้พยายามเปลี่ยนชื่อไฟล์หรือไดเร็กทอรีในแผนผังด้วยออบเจ็กต์หลักที่ยังไม่ได้เปลี่ยนชื่อ findใช้เทคนิคการเพิ่มประสิทธิภาพทุกประเภทเพื่อเดินโครงสร้างระบบไฟล์ของคุณและไม่ใช่สิ่งที่แน่นอนว่าจะส่งคืนข้อมูลที่เราต้องการตามลำดับที่ปลอดภัยสำหรับการดำเนินการ นี่คือเหตุผลที่เราต่อไป ...
  • sort -general-numerical -zero-delimited
    • เราจัดเรียงfindเอาต์พุตทั้งหมดตาม%directory-depthเพื่อให้เส้นทางที่ใกล้เคียงที่สุดกับ $ {SRC} ทำงานก่อน วิธีนี้จะหลีกเลี่ยงข้อผิดพลาดที่อาจเกิดขึ้นเกี่ยวกับการmvเข้าไฟล์ไปยังตำแหน่งที่ไม่มีอยู่จริงและช่วยลดความจำเป็นในการวนซ้ำ ( อันที่จริงคุณอาจจะรู้สึกยากที่จะหาลูปเลยก็ได้ )
  • sed -ex :rcrs;srch|(save${sep}*til)${OLD}|\saved${SUBSTNEW}|;til ${OLD=0}
    • ฉันคิดว่านี่เป็นการวนซ้ำเพียงครั้งเดียวในสคริปต์ทั้งหมดและจะวนซ้ำเพียงครั้งที่สองที่%Pathพิมพ์สำหรับแต่ละสตริงในกรณีที่มีค่ามากกว่าหนึ่ง $ {OLD} ที่อาจต้องแทนที่ วิธีแก้ปัญหาอื่น ๆ ทั้งหมดที่ฉันจินตนาการว่าเกี่ยวข้องกับsedกระบวนการที่สองและในขณะที่การวนรอบสั้น ๆ อาจไม่เป็นที่ต้องการ แต่แน่นอนว่ามันจะเกิดการวางไข่และทำให้กระบวนการทั้งหมดเกิดขึ้น
    • โดยพื้นฐานแล้วสิ่งsedที่ค้นหาคือ $ {sed_sep} จากนั้นเมื่อพบแล้วให้บันทึกและอักขระทั้งหมดที่พบจนกว่าจะพบ $ {OLD} ซึ่งจะแทนที่ด้วย $ {NEW} จากนั้นกลับไปที่ $ {sed_sep} และค้นหา $ {OLD} อีกครั้งในกรณีที่เกิดขึ้นมากกว่าหนึ่งครั้งในสตริง หากไม่พบระบบจะพิมพ์สตริงที่แก้ไขไปstdout(ซึ่งจะจับอีกครั้งในลำดับถัดไป) และสิ้นสุดการวนซ้ำ
    • วิธีนี้หลีกเลี่ยงการแยกวิเคราะห์สตริงทั้งหมดและทำให้แน่ใจว่าครึ่งแรกของmvสตริงคำสั่งซึ่งต้องรวม $ {OLD} แน่นอนจะรวมไว้ด้วยและครึ่งหลังจะถูกเปลี่ยนแปลงหลาย ๆ ครั้งเท่าที่จำเป็นในการล้าง ชื่อ $ {OLD} จากmvเส้นทางปลายทางของ
  • sed -ex...-ex search|%dir_depth(save*)${sed_sep}|(only_saved)|out
    • ทั้งสองสายที่นี่เกิดขึ้นโดยที่สอง-exec forkในครั้งแรกที่เราได้เห็นเราปรับเปลี่ยนmvคำสั่งเป็นที่จัดทำโดยfind's -printfคำสั่งฟังก์ชั่นได้ตามความจำเป็นที่จะต้องปรับเปลี่ยนการอ้างอิงทั้งหมดของ $ {OLD} ไปยัง $ {NEW} แต่เพื่อที่จะทำดังนั้นเราจึงต้องใช้บางส่วน จุดอ้างอิงตามอำเภอใจซึ่งไม่ควรรวมอยู่ในผลลัพธ์สุดท้าย ดังนั้นเมื่อsedเสร็จสิ้นทุกสิ่งที่ต้องทำเราสั่งให้ลบจุดอ้างอิงออกจากการระงับบัฟเฟอร์ก่อนที่จะส่งต่อไป

และตอนนี้เรากลับมาแล้ว

read จะได้รับคำสั่งที่มีลักษณะดังนี้:

% mv /path2/$SRC/$OLD_DIR/$OLD_FILE /same/path_w/$NEW_DIR/$NEW_FILE \000

มันจะreadลงใน${msg}ฐานะ${sh_io}ที่สามารถตรวจสอบได้ที่จะออกไปข้างนอกของฟังก์ชัน

เย็น.

- ไมค์


1

ฉันสามารถจัดการกับชื่อไฟล์ที่มีช่องว่างได้โดยทำตามตัวอย่างที่ onitake แนะนำ

สิ่งนี้จะไม่แตกหากเส้นทางมีช่องว่างหรือสตริงtest:

find . -name "*_test.rb" -print0 | while read -d $'\0' file
do
    echo mv "$file" "$(echo $file | sed s/test/spec/)"
done

1

นี่คือตัวอย่างที่ควรใช้ได้ในทุกกรณี ทำงานแบบวนซ้ำต้องการเพียงแค่เชลล์และรองรับชื่อไฟล์ที่มีช่องว่าง

find spec -name "*_test.rb" -print0 | while read -d $'\0' file; do mv "$file" "`echo $file | sed s/test/spec/`"; done

0
$ find spec -name "*_test.rb"
spec/dir2/a_test.rb
spec/dir1/a_test.rb

$ find spec -name "*_test.rb" | xargs -n 1 /usr/bin/perl -e '($new=$ARGV[0]) =~ s/test/spec/; system(qq(mv),qq(-v), $ARGV[0], $new);'
`spec/dir2/a_test.rb' -> `spec/dir2/a_spec.rb'
`spec/dir1/a_test.rb' -> `spec/dir1/a_spec.rb'

$ find spec -name "*_spec.rb"
spec/dir2/b_spec.rb
spec/dir2/a_spec.rb
spec/dir1/a_spec.rb
spec/dir1/c_spec.rb

อ่า .. ฉันไม่รู้วิธีใช้ sed อื่นนอกจากใส่ลอจิกในเชลล์สคริปต์แล้วเรียกสิ่งนั้นใน exec ไม่เห็นข้อกำหนดในการใช้ sed ในตอนแรก
Damodharan R

0

คำถามของคุณดูเหมือนจะเกี่ยวกับ sed แต่เพื่อให้บรรลุเป้าหมายในการเปลี่ยนชื่อซ้ำฉันขอแนะนำสิ่งต่อไปนี้โดยฉีกขาดจากคำตอบอื่นที่ฉันให้ไว้ที่นี่: การเปลี่ยนชื่อซ้ำใน bash

#!/bin/bash
IFS=$'\n'
function RecurseDirs
{
for f in "$@"
do
  newf=echo "${f}" | sed -e 's/^(.*_)test.rb$/\1spec.rb/g'
    echo "${f}" "${newf}"
    mv "${f}" "${newf}"
    f="${newf}"
  if [[ -d "${f}" ]]; then
    cd "${f}"
    RecurseDirs $(ls -1 ".")
  fi
done
cd ..
}
RecurseDirs .

วิธีการsedทำงานโดยไม่หนี()ถ้าคุณไม่ได้ตั้งค่า-rตัวเลือก?
mikeserv

0

วิธีที่ปลอดภัยยิ่งขึ้นในการเปลี่ยนชื่อด้วย find utils และ sed regular expression type:

  mkdir ~/practice

  cd ~/practice

  touch classic.txt.txt

  touch folk.txt.txt

ลบส่วนขยาย ".txt.txt" ดังนี้ -

  cd ~/practice

  find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} \;

หากคุณใช้ + แทน; เพื่อที่จะทำงานในโหมดแบตช์คำสั่งด้านบนจะเปลี่ยนชื่อเฉพาะไฟล์ที่ตรงกันไฟล์แรกเท่านั้น แต่ไม่ใช่รายการไฟล์ทั้งหมดที่ตรงกันโดย 'find'

  find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} +

0

นี่คือ oneliner ที่ดีที่ทำเคล็ดลับ Sed ไม่สามารถจัดการสิ่งนี้ได้อย่างถูกต้องโดยเฉพาะอย่างยิ่งถ้าตัวแปรหลายตัวถูกส่งผ่าน xargs ด้วย -n 2 การแทนที่ bash จะจัดการสิ่งนี้ได้อย่างง่ายดายเช่น:

find ./spec -type f -name "*_test.rb" -print0 | xargs -0 -I {} sh -c 'export file={}; mv $file ${file/_test.rb/_spec.rb}'

การเพิ่ม -type -f จะ จำกัด การดำเนินการย้ายไฟล์เท่านั้น -print 0 จะจัดการพื้นที่ว่างในพา ธ



0

นี่คือวิธีการทำงานของฉัน:

for FILE in {{FILE_PATTERN}}; do echo ${FILE} | mv ${FILE} $(sed 's/{{SOURCE_PATTERN}}/{{TARGET_PATTERN}}/g'); done
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.