เปลี่ยนหลายไฟล์


194

คำสั่งต่อไปนี้เป็นการเปลี่ยนแปลงเนื้อหาของ 2 ไฟล์อย่างถูกต้อง

sed -i 's/abc/xyz/g' xaa1 xab1 

แต่สิ่งที่ฉันต้องทำคือเปลี่ยนไฟล์หลาย ๆ ไฟล์แบบไดนามิกและฉันไม่รู้ชื่อไฟล์ ฉันต้องการเขียนคำสั่งที่จะอ่านไฟล์ทั้งหมดจากไดเรกทอรีปัจจุบันเริ่มต้นด้วยxa*และsedควรเปลี่ยนเนื้อหาไฟล์


63
คุณหมายถึงsed -i 's/abc/xyz/g' xa*อะไร
Paul R

3
คำตอบที่นี่ไม่พอเพียง ดูunix.stackexchange.com/questions/112023/…
Isaac

คำตอบ:


136

ดีกว่า:

for i in xa*; do
    sed -i 's/asd/dfg/g' $i
done

เพราะไม่มีใครรู้ว่ามีไฟล์อยู่กี่ไฟล์และทำลายขีด จำกัด บรรทัดคำสั่งได้ง่าย

นี่คือสิ่งที่เกิดขึ้นเมื่อมีไฟล์มากเกินไป:

# grep -c aaa *
-bash: /bin/grep: Argument list too long
# for i in *; do grep -c aaa $i; done
0
... (output skipped)
#

18
หากมีไฟล์จำนวนมากคุณจะทำลายขีด จำกัด บรรทัดคำสั่งในforคำสั่ง เพื่อปกป้องตัวเองจากสิ่งนั้นคุณต้องใช้find ... | xargs ...
เกล็นแจ็

1
ฉันไม่รู้การนำไปใช้ แต่รูปแบบ "xa *" จำเป็นต้องขยายออกไปในบางจุด เชลล์ทำการขยายที่แตกต่างforจากที่ทำไว้echoหรือgrepไม่?
เกล็นแจ็

4
ดูคำตอบที่อัพเดต หากคุณต้องการข้อมูลเพิ่มเติมโปรดถามคำถามอย่างเป็นทางการเพื่อให้ผู้คนสามารถช่วยคุณได้
lenik

5
ในคำสั่ง sed คุณต้องใช้"$i"แทน$iเพื่อหลีกเลี่ยงการแยกคำในชื่อไฟล์ด้วยช่องว่าง มิฉะนั้นจะดีมาก
Wildcard

4
เกี่ยวกับรายการฉันเชื่อว่าความแตกต่างนั้นforเป็นส่วนหนึ่งของไวยากรณ์ภาษาไม่เพียง แต่ในตัว สำหรับsed -i 's/old/new' *การขยายตัวของ*ทั้งหมดจะต้องถูกส่งผ่านเป็นรายการที่จะระงับและฉันค่อนข้างแน่ใจว่าสิ่งนี้จะต้องเกิดขึ้นก่อนที่sedกระบวนการจะสามารถเริ่มต้นได้ การใช้การforวนซ้ำรายการแบบเต็ม (ส่วนขยาย*) จะไม่ถูกส่งผ่านเป็นคำสั่งเพียงเก็บไว้ในหน่วยความจำเชลล์และวนซ้ำ ฉันไม่ได้มีการอ้างอิงใด ๆ เลยแม้ว่าจะเป็นไปได้ว่ามันเป็นความแตกต่าง (ฉันชอบที่จะได้ยินจากคนที่มีความรู้มากขึ้น ... )
Wildcard

167

ฉันประหลาดใจที่ไม่มีใครพูดถึงอาร์กิวเมนต์ -exec เพื่อค้นหาซึ่งมีไว้สำหรับกรณีการใช้งานประเภทนี้ถึงแม้ว่ามันจะเริ่มต้นกระบวนการสำหรับชื่อไฟล์ที่ตรงกันแต่ละชื่อ:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} \;

อีกวิธีหนึ่งสามารถใช้ xargs ซึ่งจะทำให้กระบวนการน้อยลง:

find . -type f -name 'xa*' | xargs sed -i 's/asd/dsg/g'

หรือมากกว่านั้นให้ใช้+ ตัวแปร execแทน;in find เพื่ออนุญาตให้ find จัดเตรียมไฟล์มากกว่าหนึ่งไฟล์ต่อการเรียกใช้กระบวนการย่อย:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} +

8
ฉันต้องแก้ไขคำสั่งในคำตอบนี้: find ./ -type f -name 'xa*' -exec sed -i '' 's/asd/dsg/g' {} \;นั่นคือที่ตั้งสำหรับคำสั่ง find ./และเครื่องหมายคำพูดคู่เดียวหลังจาก-iสำหรับ OSX
shelbydz

คำสั่ง find ทำงานตามที่ให้มาโดย ealfonso ซึ่ง./เท่ากับ.และหลัง-iมีเพียงพารามิเตอร์ backupsuffix
uhausbrand

-execตัวเลือกในการหาพร้อมกับ{} +จะเพียงพอที่จะแก้ปัญหาดังกล่าวและควรจะปรับสำหรับความต้องการมากที่สุด แต่xargsเป็นตัวเลือกที่ดีกว่าโดยทั่วไปเพราะมันยังช่วยให้การประมวลผลแบบขนานกับ-pตัวเลือก เมื่อการขยาย glob ของคุณมีขนาดใหญ่พอที่จะล้นความยาวบรรทัดคำสั่งของคุณคุณมีแนวโน้มที่จะได้รับประโยชน์จากการเร่งความเร็วในการรันตามลำดับ
Amit Naidu

78

คุณสามารถใช้ grep และ sed ด้วยกัน สิ่งนี้ช่วยให้คุณค้นหาไดเรกทอรีย่อยซ้ำ

Linux: grep -r -l <old> * | xargs sed -i 's/<old>/<new>/g'
OS X: grep -r -l <old> * | xargs sed -i '' 's/<old>/<new>/g'

For grep:
    -r recursively searches subdirectories 
    -l prints file names that contain matches
For sed:
    -i extension (Note: An argument needs to be provided on OS X)

3
โบนัสของวิธีการนี้สำหรับฉันคือฉันสามารถแอบเข้าไปในgrep -vโฟลเดอร์เพื่อหลีกเลี่ยงคอมไพล์ได้grep -rl <old> . | grep -v \.git | xargs sed -i 's/<old>/<new>/g'
Martin Lyne

ทางออกที่ดีที่สุดสำหรับ mac!
มาร์ควิส Blount

30

คำสั่งเหล่านั้นจะไม่ทำงานในค่าเริ่มต้นsedที่มาพร้อมกับ Mac OS X

จากman 1 sed:

-i extension
             Edit files in-place, saving backups with the specified
             extension.  If a zero-length extension is given, no backup 
             will be saved.  It is not recommended to give a zero-length
             extension when in-place editing files, as you risk corruption
             or partial content in situations where disk space is exhausted, etc.

พยายาม

sed -i '.bak' 's/old/new/g' logfile*

และ

for i in logfile*; do sed -i '.bak' 's/old/new/g' $i; done

ทำงานได้ดีทั้งคู่


2
@sumek ต่อไปนี้เป็นตัวอย่างเทอร์มินัลเซสชันบน OS X ที่แสดงให้เห็นว่ามีการแทนที่เกิดขึ้นทั้งหมด: GitHub Gist
funroll

ฉันใช้สิ่งนี้เพื่อแทนที่สองบรรทัดที่แตกต่างกันในไฟล์ปรับแต่งเว็บไซต์ของฉันทั้งหมดด้วยสายการบินเดียวด้านล่าง sed -i.bak "s / supercache_proxy_config / proxy_includes \ / supercache_config / g; s / basic_proxy_config / proxy_include \ / basic_proxy_config / g" ไซต์พร้อมใช้งาน / * อย่าลืมลบไฟล์ * .bak ประโยชน์ด้านสุขอนามัยของระบบ
Josiah

19

@PaulRโพสต์นี้เป็นความคิดเห็น แต่ผู้คนควรมองว่ามันเป็นคำตอบ (และคำตอบนี้ใช้ได้ดีที่สุดสำหรับความต้องการของฉัน):

sed -i 's/abc/xyz/g' xa*

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


สมมติว่าคุณมีเครื่องหมายทับซ้าย - ขวาในการแทนที่ของคุณ เช่นกับ sed -i 's|auth-user-pass nordvpn.txt|auth-user-pass /etc/openvpn/nordvpn.txt|g' *.ovpnfilepaths
LéoLéopold Hertz 준영

10

อีกวิธีที่หลากหลายมากขึ้นคือการใช้find:

sed -i 's/asd/dsg/g' $(find . -type f -name 'xa*')

1
ผลลัพธ์ของคำสั่ง find นั้นได้รับการขยายดังนั้นจึงไม่สามารถแก้ไขปัญหาได้ คุณควรใช้ -exec
ealfonso

@erjoalgo ใช้งานได้เนื่องจากคำสั่ง sed สามารถจัดการไฟล์อินพุตได้หลายไฟล์ การขยายคำสั่ง find เป็นสิ่งจำเป็นอย่างยิ่งในการทำให้มันใช้งานได้
dkinzer

มันทำงานได้ตราบใดที่จำนวนไฟล์ไม่ได้เพิ่มเข้าไปในขีด จำกัด บรรทัดคำสั่ง
ealfonso

ขีด จำกัด นั้นขึ้นอยู่กับทรัพยากรหน่วยความจำที่มีอยู่ในเครื่องและมันก็เหมือนกับขีด จำกัด สำหรับผู้บริหาร
dkinzer

4
นั่นไม่จริงเลย ในคำสั่งของคุณด้านบน $ (find. ... ) จะได้รับการขยายเป็นคำสั่งเดียวซึ่งอาจยาวมากหากมีไฟล์ที่ตรงกันหลายไฟล์ ถ้ามันยาวเกินไป (เช่นในระบบของฉันขีด จำกัด อยู่ที่ประมาณ 2097152 ตัวอักษร) คุณอาจได้รับข้อผิดพลาด: "รายการอาร์กิวเมนต์ยาวเกินไป" และคำสั่งจะล้มเหลว โปรด google ข้อผิดพลาดนี้เพื่อรับพื้นหลังในนี้
ealfonso

2

ฉันใช้findสำหรับงานที่คล้ายกัน มันค่อนข้างง่าย: คุณต้องผ่านมันไปเป็นอาร์กิวเมนต์เพื่อsedสิ่งนี้:

sed -i 's/EXPRESSION/REPLACEMENT/g' `find -name "FILE.REGEX"`

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


1
ตรงนี้เป็นเช่นเดียวกับคำตอบของ @ dkinzer
นายเทาร

0

คุณสามารถทำ

' xxxx ' ยูข้อความค้นหาและจะแทนที่ด้วย ' ปปปป '

grep -Rn '**xxxx**' /path | awk -F: '{print $1}' | xargs sed -i 's/**xxxx**/**yyyy**/'

0

หากคุณสามารถเรียกใช้สคริปต์ได้นี่คือสิ่งที่ฉันทำในสถานการณ์ที่คล้ายกัน:

การใช้พจนานุกรม / hashMap (อาเรย์แบบเชื่อมโยง) และตัวแปรสำหรับsedคำสั่งนั้นเราสามารถวนลูปผ่านอาร์เรย์เพื่อแทนที่สตริงจำนวนมาก การรวมไวด์การ์ดในการname_patternจะอนุญาตให้แทนที่แบบแทนที่ในไฟล์ที่มีรูปแบบ (ซึ่งอาจเป็นสิ่งที่ต้องการname_pattern='File*.txt') ในไดเรกทอรีเฉพาะ ( source_dir) การเปลี่ยนแปลงทั้งหมดจะถูกเขียนในlogfileในdestin_dir

#!/bin/bash
source_dir=source_path
destin_dir=destin_path
logfile='sedOutput.txt'
name_pattern='File.txt'

echo "--Begin $(date)--" | tee -a $destin_dir/$logfile
echo "Source_DIR=$source_dir destin_DIR=$destin_dir "

declare -A pairs=( 
    ['WHAT1']='FOR1'
    ['OTHER_string_to replace']='string replaced'
)

for i in "${!pairs[@]}"; do
    j=${pairs[$i]}
    echo "[$i]=$j"
    replace_what=$i
    replace_for=$j
    echo " "
    echo "Replace: $replace_what for: $replace_for"
    find $source_dir -name $name_pattern | xargs sed -i "s/$replace_what/$replace_for/g" 
    find $source_dir -name $name_pattern | xargs -I{} grep -n "$replace_for" {} /dev/null | tee -a $destin_dir/$logfile
done

echo " "
echo "----End $(date)---" | tee -a $destin_dir/$logfile

ครั้งแรกที่อาร์เรย์คู่มีการประกาศแต่ละคู่เป็นสตริงทดแทนแล้วWHAT1จะถูกแทนที่ด้วยFOR1และOTHER_string_to replaceจะถูกแทนที่สำหรับในแฟ้มstring replaced File.txtในวงอาร์เรย์จะอ่านสมาชิกคนแรกของทั้งคู่จะถูกเรียกเป็นและครั้งที่สองเป็นreplace_what=$i ค้นหาคำสั่งในไดเรกทอรีชื่อไฟล์ (ที่อาจมีตัวแทน) และแทนที่คำสั่งในไฟล์เดียวกัน (s) สิ่งที่ถูกกำหนดไว้ก่อนหน้า ในที่สุดฉันได้เพิ่มการเปลี่ยนเส้นทางไปยัง logfile เพื่อบันทึกการเปลี่ยนแปลงที่เกิดขึ้นในไฟล์replace_for=$jfindsed -igrep

นี้ทำงานสำหรับฉันในGNU Bash 4.3 sed 4.2.2และขึ้นอยู่กับคำตอบ VasyaNovikov สำหรับ ห่วงมากกว่า tuples ในทุบตี

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.