ค้นหาและแทนที่ภายในไฟล์ข้อความจากคำสั่ง Bash


561

วิธีที่ง่ายที่สุดในการค้นหาและแทนที่สตริงอินพุตที่ระบุพูดabcและแทนที่ด้วยสตริงอื่นคือพูดXYZในไฟล์/tmp/file.txt?

ฉันกำลังเขียนแอพและใช้ IronPython เพื่อดำเนินการคำสั่งผ่าน SSH - แต่ฉันไม่รู้จัก Unix ที่ดีและไม่รู้ว่าจะหาอะไร

ฉันได้ยินมาว่า Bash นอกเหนือจากการเป็นอินเตอร์เฟสบรรทัดคำสั่งอาจเป็นภาษาสคริปต์ที่ทรงพลังมาก ดังนั้นถ้าเป็นจริงฉันคิดว่าคุณสามารถทำสิ่งเหล่านี้ได้

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

คำตอบ:


928

วิธีที่ง่ายที่สุดคือใช้ sed (หรือ perl):

sed -i -e 's/abc/XYZ/g' /tmp/file.txt

ซึ่งจะเรียกใช้ sed เพื่อทำการแก้ไขแบบแทนที่เนื่องจาก-iตัวเลือก สิ่งนี้สามารถเรียกได้จากการทุบตี

หากคุณต้องการใช้เพียงแค่ทุบตีจริงๆสิ่งต่อไปนี้สามารถใช้ได้:

while read a; do
    echo ${a//abc/XYZ}
done < /tmp/file.txt > /tmp/file.txt.t
mv /tmp/file.txt{.t,}

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


3
ยกเว้นว่าการเรียกใช้ mv นั้นค่อนข้างจะเหมือนกับ 'non Bash' เหมือนกับการใช้ sed ฉันเกือบจะพูดถึงเสียงก้องเหมือนกัน แต่มันเป็นตัวเชลล์
slim

5
อาร์กิวเมนต์ -i สำหรับ sed ไม่มีอยู่สำหรับ Solaris (และฉันคิดว่ามีการใช้งานอื่น) อย่างไรก็ตามโปรดจำไว้ เพียงแค่ใช้เวลาหลายนาทีในการหาที่ ...
Panky

2
หมายเหตุถึงตนเอง: เกี่ยวกับการแสดงออกปกติของsed: s/..../..../ - Substituteและ /g - Global
เช็คซัม

89
หมายเหตุสำหรับผู้ใช้ Mac ที่ได้รับinvalid command code Cข้อผิดพลาด ... สำหรับการแทนที่แบบแทนที่ BSD sedต้องการนามสกุลไฟล์หลัง-iแฟล็กเนื่องจากจะบันทึกไฟล์สำรองด้วยส่วนขยายที่กำหนด ตัวอย่างเช่น: sed -i '.bak' 's/find/replace/' /file.txt คุณสามารถข้ามการสำรองข้อมูลโดยใช้สตริงว่างดังนี้:sed -i '' 's/find/replace/' /file.txt
Austin

8
เคล็ดลับ: ถ้าคุณต้องการใช้ repalce ที่ไม่ตรงตามตัวพิมพ์ใหญ่s/abc/XYZ/gi
Boris D. Teoharov

166

ปกติแล้วการจัดการไฟล์จะไม่ได้รับการจัดการโดย Bash แต่โดยโปรแกรมที่ Bash ใช้เช่น:

perl -pi -e 's/abc/XYZ/g' /tmp/file.txt

-iธงบอกให้ทำทดแทนในสถานที่

ดูman perlrunรายละเอียดเพิ่มเติมรวมถึงวิธีสำรองข้อมูลไฟล์ต้นฉบับ


37
คนเจ้าระเบียบในตัวฉันบอกว่าคุณไม่แน่ใจว่า Perl จะมีอยู่ในระบบ แต่ในทุกวันนี้ไม่ค่อยเป็นเช่นนั้น บางทีฉันอาจแสดงอายุของฉัน
ผอม

3
คุณช่วยแสดงตัวอย่างที่ซับซ้อนกว่านี้ได้ไหม สิ่งที่ต้องการแทนที่ "chdir / blah" ด้วย "chdir / blah2" ฉันพยายามperl -pi -e 's/chdir (?:\\/[\\w\\.\\-]+)+/chdir blah/g' textแต่ฉันได้รับข้อผิดพลาดโดยไม่มีช่องว่างระหว่างรูปแบบและคำต่อไปนี้ถูกคัดค้านที่ -e บรรทัด 1 ไม่ตรงกัน (ใน regex; ทำเครื่องหมายโดย <- ที่นี่ใน m / (chdir) () - < ?: \\ / ที่ -e บรรทัด 1
CMCDragonkai

@CMCDragonkai ตรวจสอบคำตอบนี้: stackoverflow.com/a/12061491/2730528
Alfonso Santiago

69

ฉันแปลกใจเมื่อฉันพบสิ่งนี้ ...

มีreplaceคำสั่งที่มาพร้อมกับ"mysql-server"แพ็กเกจดังนั้นหากคุณติดตั้งให้ลองใช้งาน:

# replace string abc to XYZ in files
replace "abc" "XYZ" -- file.txt file2.txt file3.txt

# or pipe an echo to replace
echo "abcdef" |replace "abc" "XYZ"

ดูman replaceเพิ่มเติมเกี่ยวกับสิ่งนี้


12
มีสองสิ่งที่เป็นไปได้ที่นี่: ก) replaceเป็นเครื่องมืออิสระที่มีประโยชน์และคน MySQL ควรปล่อยมันแยกกันและขึ้นอยู่กับมัน b) replaceต้องใช้ MySQL o_O ทั้งสองวิธีการติดตั้ง mysql-server เพื่อแทนที่จะเป็นสิ่งที่ผิด :)
ฟิลิปทำเนียบขาว

ใช้งานได้กับ mac เท่านั้น ในอูบุนตูของฉันฉัน centos คำสั่งนั้นไม่มีอยู่
พอล

1
นั่นเป็นเพราะคุณไม่ได้mysql-serverติดตั้งแพ็คเกจ ตามที่ @rayro ชี้replaceเป็นส่วนหนึ่งของมัน
Phius

2
"คำเตือน: การแทนที่ถูกเลิกใช้และจะถูกลบในเวอร์ชันอนาคต"
Steven Vachon

1
ระวังอย่ารันคำสั่ง REPLACE บน Windows! บน Windows คำสั่ง REPLACE สำหรับการทำสำเนาไฟล์อย่างรวดเร็ว ไม่เกี่ยวข้องกับการสนทนานี้
Maor

53

นี่เป็นโพสต์เก่า แต่สำหรับทุกคนที่ต้องการใช้ตัวแปรตามที่ @centurian กล่าวว่าคำพูดเดี่ยวหมายความว่าไม่มีอะไรจะขยาย

วิธีง่าย ๆ ในการรับตัวแปรคือการทำ string concatenation เนื่องจากสิ่งนี้ทำโดยการตีข่าวใน bash ต่อไปนี้ควรจะได้ผล:

sed -i -e "s/$var1/$var2/g" /tmp/file.txt

39

Bash เช่นเดียวกับ shell อื่น ๆ เป็นเพียงเครื่องมือสำหรับการประสานงานคำสั่งอื่น ๆ โดยทั่วไปแล้วคุณจะพยายามใช้คำสั่ง UNIX มาตรฐาน แต่แน่นอนว่าคุณสามารถใช้ Bash เพื่อเรียกใช้สิ่งต่าง ๆ รวมถึงโปรแกรมที่คอมไพล์ของคุณเอง, สคริปต์เชลล์อื่น ๆ , สคริปต์ Python และ Perl เป็นต้น

ในกรณีนี้มีสองวิธีที่จะทำ

หากคุณต้องการอ่านไฟล์และเขียนไปยังไฟล์อื่นทำการค้นหา / แทนที่ในขณะที่คุณใช้งานใช้ sed:

sed 's/abc/XYZ/g' <infile >outfile

หากคุณต้องการแก้ไขไฟล์ในสถานที่ (เช่นถ้าเปิดไฟล์ในตัวแก้ไขให้แก้ไขแล้วบันทึกไว้) ให้คำแนะนำกับตัวแก้ไขบรรทัด 'ex'

echo "%s/abc/XYZ/g
w
q
" | ex file

Ex เป็นเช่น vi โดยไม่มีโหมดเต็มหน้าจอ คุณสามารถให้คำสั่งเดียวกับที่ใช้กับคำสั่ง ':'


33

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

  1. sedและedมีประโยชน์มาก ... ด้วยมือ ดูรหัสนี้จาก @Johnny:

    sed -i -e 's/abc/XYZ/g' /tmp/file.txt
  2. เมื่อข้อ จำกัด ของฉันคือการใช้มันในเชลล์สคริปต์ไม่สามารถใช้ตัวแปรภายในแทน "abc" หรือ "XYZ" BashFAQดูเหมือนว่าจะเห็นด้วยกับสิ่งที่ฉันเข้าใจอย่างน้อย ดังนั้นฉันไม่สามารถใช้:

    x='abc'
    y='XYZ'
    sed -i -e 's/$x/$y/g' /tmp/file.txt
    #or,
    sed -i -e "s/$x/$y/g" /tmp/file.txt

    แต่เราจะทำอย่างไร @Johnny พูดว่าใช้while read...แต่น่าเสียดายที่ไม่ใช่จุดจบของเรื่อง ต่อไปนี้ทำงานได้ดีกับฉัน:

    #edit user's virtual domain
    result=
    #if nullglob is set then, unset it temporarily
    is_nullglob=$( shopt -s | egrep -i '*nullglob' )
    if [[ is_nullglob ]]; then
       shopt -u nullglob
    fi
    while IFS= read -r line; do
       line="${line//'<servername>'/$server}"
       line="${line//'<serveralias>'/$alias}"
       line="${line//'<user>'/$user}"
       line="${line//'<group>'/$group}"
       result="$result""$line"'\n'
    done < $tmp
    echo -e $result > $tmp
    #if nullglob was set then, re-enable it
    if [[ is_nullglob ]]; then
       shopt -s nullglob
    fi
    #move user's virtual domain to Apache 2 domain directory
    ......
  3. อย่างที่เราสามารถดูได้ว่าnullglobมีการตั้งค่าหรือไม่มันจะทำงานผิดปกติเมื่อมีสตริงที่มี a *ใน:

    <VirtualHost *:80>
     ServerName www.example.com

    ซึ่งกลายเป็น

    <VirtualHost ServerName www.example.com

    ไม่มีตัวยึดมุมสิ้นสุดและ Apache2 ไม่สามารถโหลดได้

  4. การแยกวิเคราะห์แบบนี้ควรช้ากว่าการค้นหาและแทนที่แบบ one-hit แต่อย่างที่คุณเห็นแล้วมีตัวแปรสี่ตัวสำหรับรูปแบบการค้นหาสี่แบบที่แตกต่างกันซึ่งทำงานในรอบการแยกวิเคราะห์หนึ่งรอบ

ทางออกที่เหมาะสมที่สุดที่ฉันสามารถนึกได้ด้วยสมมติฐานที่กำหนดของปัญหา


12
ใน (2) - คุณสามารถทำได้sed -e "s/$x/$y/"และมันจะทำงาน ไม่ใช่เครื่องหมายคำพูดคู่ มันอาจสร้างความสับสนได้อย่างจริงจังหากสตริงในตัวแปรนั้นมีอักขระที่มีความหมายพิเศษ ตัวอย่างเช่นถ้า x = "/" หรือ x = "\" เมื่อคุณพบปัญหาเหล่านี้อาจหมายความว่าคุณควรหยุดใช้เชลล์สำหรับงานนี้
บาง

สวัสดีฉันเห็นว่าคุณไม่ชอบการใช้ Perl ด้วย ทางออกของคุณคืออะไร? เพราะในความเป็นจริงฉันต้องการเปลี่ยนเส้นทางในไฟล์แบบไดนามิกซึ่งหมายความว่าฉันมีจำนวนมาก / ในสตริง!
มาห์ดี

20

คุณสามารถใช้sed:

sed -i 's/abc/XYZ/gi' /tmp/file.txt

ใช้-iสำหรับ "เพิกเฉยกรณี" หากคุณไม่แน่ใจว่าข้อความที่ต้องการค้นหานั้นเป็นabcหรือABCหรือAbCฯลฯ

คุณสามารถใช้findและsedหากคุณไม่รู้จักชื่อไฟล์ของคุณ:

find ./ -type f -exec sed -i 's/abc/XYZ/gi' {} \;

ค้นหาและแทนที่ในไฟล์ Python ทั้งหมด:

find ./ -iname "*.py" -type f -exec sed -i 's/abc/XYZ/gi' {} \;

12

คุณสามารถใช้edคำสั่งเพื่อทำการค้นหาในไฟล์และแทนที่:

# delete all lines matching foobar 
ed -s test.txt <<< $'g/foobar/d\nw' 

ดูเพิ่มเติมใน "การแก้ไขไฟล์ผ่านสคริปต์ด้วยed "


3
โซลูชันนี้ไม่ขึ้นกับความเข้ากันไม่ได้ของ GNU / FreeBSD (Mac OSX) (ไม่เหมือนsed -i <pattern> <filename>) ดีมาก!
Peterino

11

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

file_contents=$(</tmp/file.txt)
echo "${file_contents//abc/XYZ}" > /tmp/file.txt

เนื้อหาไฟล์ทั้งหมดจะถูกพิจารณาเป็นสตริงยาวหนึ่งบรรทัดรวมถึง linebreaks

XYZ สามารถเป็นตัวแปรได้เช่น$replacementกันและข้อดีอย่างหนึ่งของการไม่ใช้ sed นี่คือคุณไม่จำเป็นต้องกังวลว่าการค้นหาหรือแทนที่สตริงอาจมีอักขระตัวคั่นรูปแบบ sed (โดยปกติ แต่ไม่จำเป็นต้อง /) ข้อเสียคือไม่สามารถใช้นิพจน์ทั่วไปหรือการดำเนินการที่ซับซ้อนมากขึ้น


มีคำแนะนำในการใช้สิ่งนี้ด้วยอักขระแท็บหรือไม่ ด้วยเหตุผลบางอย่างสคริปต์ของฉันไม่พบสิ่งใดที่มีแท็บหลังจากเปลี่ยนจาก sed ที่มีจำนวนมากหนีไปใช้วิธีนี้
Brian Hannay

2
หากคุณต้องการใส่แท็บในสตริงที่คุณกำลังเปลี่ยนคุณสามารถทำได้ด้วยไวยากรณ์ "dollared single quotes" ของ Bash ดังนั้นแท็บจะแสดงด้วย $ '\ t' และคุณสามารถทำ $ echo 'tab' $ '\ t''separated'> testfile; $ file_contents = $ (<testfile); $ echo "$ {file_contents // $ '\ t' / TAB}"; tabTABseparated `
johnraff


5

ลองใช้คำสั่งเชลล์ต่อไปนี้:

find ./  -type f -name "file*.txt" | xargs sed -i -e 's/abc/xyz/g'

4
นี่เป็นคำตอบที่ยอดเยี่ยมสำหรับ "ฉันจะบังเอิญไฟล์ทั้งหมดในไดเรกทอรีย่อยทั้งหมดได้อย่างไร" แต่นั่นดูเหมือนจะไม่เป็นสิ่งที่ถามไว้ที่นี่
tripleee

ไวยากรณ์นี้ใช้ไม่ได้กับเวอร์ชัน BSD sedใช้sed -i''แทน
kenorb

5

หากต้องการแก้ไขข้อความในไฟล์แบบไม่โต้ตอบคุณต้องแก้ไขข้อความในสถานที่เช่นเสียงเรียกเข้า

นี่คือตัวอย่างง่ายๆวิธีใช้จากบรรทัดคำสั่ง:

vim -esnc '%s/foo/bar/g|:wq' file.txt

นี่เทียบเท่ากับ@slim คำตอบของอดีต editor ซึ่งโดยพื้นฐานแล้วเป็นสิ่งเดียวกัน

นี่คือexตัวอย่างการปฏิบัติน้อย

การแทนที่ข้อความfooด้วยbarในไฟล์:

ex -s +%s/foo/bar/ge -cwq file.txt

การลบช่องว่างต่อท้ายสำหรับหลายไฟล์:

ex +'bufdo!%s/\s\+$//e' -cxa *.txt

การแก้ไขปัญหา (เมื่อเทอร์มินัลติดอยู่):

  • เพิ่ม-V1พารามิเตอร์เพื่อแสดงข้อความ verbose
  • -cwq!กองทัพเลิกโดย:

ดูสิ่งนี้ด้วย:


ต้องการที่จะทำแทนแบบโต้ตอบ จึงลอง "vim -esnc '% s / foo / bar / gc |: wq' file.txt" แต่เทอร์มินัลติดอยู่ตอนนี้ เราจะสร้างการแทนที่แบบโต้ตอบได้อย่างไรโดยไม่มีเชลล์ bash ทำตัวแปลก ๆ
เถาวัลย์

ในการแก้ปัญหาเพิ่มเพื่อบังคับให้เลิกใช้-V1 wq!
kenorb

2

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

#!/bin/bash
python
filetosearch = '/home/ubuntu/ip_table.txt'
texttoreplace = 'tcp443'
texttoinsert = 'udp1194'

s = open(filetosearch).read()
s = s.replace(texttoreplace, texttoinsert)
f = open(filetosearch, 'w')
f.write(s)
f.close()
quit()

1

คุณสามารถใช้คำสั่ง rpl ตัวอย่างเช่นคุณต้องการเปลี่ยนชื่อโดเมนในโครงการ php ทั้งหมด

rpl -ivRpd -x'.php' 'old.domain.name' 'new.domain.name' ./path_to_your_project_folder/  

นี่ไม่ใช่สาเหตุที่ชัดเจน แต่เป็นวิธีที่รวดเร็วและมีประโยชน์ :)

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