ทำไม sed ถึงไม่รู้จัก \ t เป็นแท็บ


106
sed "s/\(.*\)/\t\1/" $filename > $sedTmpFile && mv $sedTmpFile $filename

ฉันคาดหวังว่าsedสคริปต์นี้จะแทรกtabด้านหน้าของทุกบรรทัด$filenameแต่มันไม่ใช่ ด้วยเหตุผลบางประการจึงเป็นการแทรกtแทน


1
เนื่องจาก sed อาจแตกต่างกันไประหว่างแพลตฟอร์ม (โดยเฉพาะ BSD / MacOSX กับ Linux) การระบุแพลตฟอร์มที่คุณใช้ sed อาจเป็นประโยชน์
Isaac

sed "s / (. *) / # \ 1 /" $ filename | tr '#' '\ t'> $ sedTmpFile && mv $ sedTmpFile $ filename.
user2432405

สำหรับผู้ใช้ OS X (macOS) โปรดดูคำถามนี้
Franklin Yu

คำตอบ:


129

ไม่ได้ทุกรุ่นเข้าใจsed \tเพียงใส่แท็บลิเทอรัลแทน (กดCtrl- Vแล้วTab)


2
อ่าใช่; เพื่อชี้แจง: ไม่ใช่ทุกเวอร์ชันที่เข้าใจ\tในส่วนทดแทนของนิพจน์ (จำได้\tในส่วนการจับคู่รูปแบบได้ดี)
John Weldon

3
awwwwwwwwwwwwwwwwwww โอเคที่น่าสนใจทีเดียว และแปลก. ทำไมคุณถึงทำให้มันรับรู้ได้ในที่เดียว แต่ไม่ใช่ที่อื่น ... ?
sixtyfootersdude

2
เรียกจากสคริปต์ซึ่งจะไม่ทำงาน: แท็บจะถูกละเว้นโดย sh ตัวอย่างเช่นโค้ดต่อไปนี้จากเชลล์สคริปต์จะเพิ่ม $ TEXT_TO_ADD โดยไม่ต้องเติมเงินล่วงหน้าด้วยการจัดตาราง: sed "$ {LINE} a \\ $ TEXT_TO_ADD" $ FILE
Dereckson

2
@Dereckson และคนอื่น ๆ - ดูคำตอบนี้: stackoverflow.com/a/2623007/48082
Cheeso

2
Dereckson s / can / can't /?
ดักลาสจัดขึ้น

41

การใช้ Bash คุณสามารถแทรกอักขระ TAB โดยทางโปรแกรมได้ดังนี้:

TAB=$'\t' 
echo 'line' | sed "s/.*/${TAB}&/g" 
echo 'line' | sed 's/.*/'"${TAB}"'&/g'   # use of Bash string concatenation

สิ่งนี้มีประโยชน์มาก
Cheeso

1
คุณมาถูกทางแล้ว$'string'แต่ขาดคำอธิบาย อันที่จริงฉันสงสัยว่าเนื่องจากการใช้งานที่น่าอึดอัดใจอย่างมากซึ่งคุณอาจมีความเข้าใจที่ไม่สมบูรณ์ (เหมือนที่พวกเราส่วนใหญ่ทำกับ bash) ดูคำอธิบายของฉันด้านล่าง: stackoverflow.com/a/43190120/117471
Bruno Bronosky

1
โปรดจำไว้ว่า BASH จะไม่ขยายตัวแปรเช่น$TABในเครื่องหมายคำพูดเดี่ยวดังนั้นคุณจะต้องใช้เครื่องหมายคำพูดคู่
nealmcb

ระมัดระวังเกี่ยวกับการใช้*เครื่องหมายคำพูดคู่ภายใน ... สิ่งนี้จะถือว่าเป็นรูปแบบเดียวไม่ใช่ regex ที่คุณต้องการ
levigroker

28

@ แก้ไขมาถูกทางแล้ว แต่การกำหนดตัวแปรมันค่อนข้างอึดอัด

โซลูชัน (ทุบตีเฉพาะ)

วิธีทำใน bash คือใส่เครื่องหมายดอลลาร์ไว้หน้าสตริงที่ยกมา

$ echo -e '1\n2\n3'
1
2
3

$ echo -e '1\n2\n3' | sed 's/.*/\t&/g'
t1
t2
t3

$ echo -e '1\n2\n3' | sed $'s/.*/\t&/g'
    1
    2
    3

หากสตริงของคุณต้องการรวมส่วนขยายตัวแปรคุณสามารถรวมสตริงที่ยกมาเข้าด้วยกันดังนี้:

$ timestamp=$(date +%s)
$ echo -e '1\n2\n3' | sed "s/.*/$timestamp"$'\t&/g'
1491237958  1
1491237958  2
1491237958  3

คำอธิบาย

ในการทุบตี$'string'ทำให้เกิด "การขยาย ANSI-C" และนั่นคือสิ่งที่พวกเราส่วนใหญ่คาดหวังว่าเมื่อเราใช้สิ่งที่ชอบ\t, \r, \nฯลฯ จาก: https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html#ANSI_002dC-Quoting

คำในรูปแบบ$ 'string'ได้รับการปฏิบัติเป็นพิเศษ คำขยายเป็นสตริงโดยแทนที่อักขระที่ใช้เครื่องหมายแบ็กสแลชจะถูกแทนที่ตามที่ระบุโดยมาตรฐาน ANSI C ลำดับการหลีกเลี่ยงแบ็กสแลชหากมีอยู่จะถูกถอดรหัส ...

ผลลัพธ์ที่ขยายเป็นเครื่องหมายคำพูดเดี่ยวราวกับว่าไม่มีเครื่องหมายดอลลาร์

วิธีแก้ไข (ถ้าคุณต้องหลีกเลี่ยงการทุบตี)

โดยส่วนตัวแล้วฉันคิดว่าความพยายามส่วนใหญ่ในการหลีกเลี่ยงการทุบตีเป็นเรื่องไร้สาระเพราะการหลีกเลี่ยงการทุบตีไม่ได้ทำให้โค้ดของคุณพกพาได้ (รหัสของคุณจะเปราะน้อยกว่าถ้าคุณใช้มันbash -euมากกว่าถ้าคุณพยายามหลีกเลี่ยงการทุบตีและใช้sh[เว้นแต่คุณจะเป็นนินจา POSIX ที่แท้จริง]) แต่แทนที่จะมีข้อโต้แย้งทางศาสนาเกี่ยวกับเรื่องนั้นฉันจะให้สิ่งที่ดีที่สุดแก่คุณ * คำตอบ

$ echo -e '1\n2\n3' | sed "s/.*/$(printf '\t')&/g"
    1
    2
    3

* คำตอบที่ดีที่สุด? ใช่เพราะหนึ่งในตัวอย่างของสิ่งที่มากที่สุดป้องกันทุบตี scripters เปลือกจะทำผิดในรหัสของพวกเขาคือการใช้echo '\t'ในขณะที่@ robrecord ของคำตอบ ซึ่งจะใช้ได้กับ GNU echo แต่ไม่ใช่ BSD echo ที่อธิบายโดย The Open Group ที่http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16และนี่คือตัวอย่างว่าทำไมการพยายามหลีกเลี่ยงการทุบตีมักจะล้มเหลว


8

ฉันเคยใช้อะไรแบบนี้กับ Bash shell บน Ubuntu 12.04 (LTS):

ในการต่อท้ายบรรทัดใหม่ด้วยแท็บอันดับที่สองเมื่อจับคู่ครั้งแรก :

sed -i '/first/a \\t second' filename

ในการแทนที่แท็บแรกด้วยแท็บที่สอง :

sed -i 's/first/\\t second/g' filename

4
หนีคู่เป็นกุญแจสำคัญในการใช้งานเช่นไม่\\t \t
zamnuts

ฉันต้องใช้เครื่องหมายคำพูดคู่แทนคำพูดเดี่ยวบน Ubuntu 16.04 และ Bash 4.3
caw

4

ใช้$(echo '\t'). คุณจะต้องมีเครื่องหมายคำพูดรอบรูปแบบ

เช่น. ในการลบแท็บ:

sed "s/$(echo '\t')//"

5
เป็นเรื่องตลกที่คุณกำลังใช้คุณลักษณะเฉพาะ "GNU echo" (แปล \ t เป็นอักขระแท็บ) เพื่อแก้ปัญหาข้อบกพร่องเฉพาะ "BSD sed" (ตีความ \ t เป็นอักขระแยกกัน 2 ตัว) สมมติว่าหากคุณมี "GNU echo" คุณก็จะมี "GNU sed" ด้วย ซึ่งในกรณีนี้คุณไม่จำเป็นต้องใช้เสียงสะท้อน ด้วย BSD echo echo '\t'จะแสดงอักขระแยกกัน 2 ตัว วิธีแบบพกพา POSIX printf '\t'คือการใช้งาน นี่คือเหตุผลที่ฉันพูดว่า: อย่าพยายามทำให้โค้ดของคุณพกพาได้โดยไม่ใช้ bash มันยากกว่าที่คุณคิด การใช้bashเป็นสิ่งที่พกพาสะดวกที่สุดที่พวกเราส่วนใหญ่ทำได้
Bruno Bronosky

3

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

เช่นใช้ awk

awk '{print "\t"$0}' $filename > temp && mv temp $filename


0

sedไม่รองรับ\tหรือลำดับการหลบหนีอื่น ๆ เช่น\nสำหรับเรื่องนั้น วิธีเดียวที่ฉันพบว่าทำได้คือการแทรกอักขระแท็บในสคริปต์โดยใช้sed.

ที่กล่าวมาคุณอาจต้องการพิจารณาใช้ Perl หรือ Python นี่คือสคริปต์ Python สั้น ๆ ที่ฉันเขียนว่าฉันใช้สำหรับ regex'ing ของสตรีมทั้งหมด:

#!/usr/bin/env python
import sys
import re

def main(args):
  if len(args) < 2:
    print >> sys.stderr, 'Usage: <search-pattern> <replace-expr>'
    raise SystemExit

  p = re.compile(args[0], re.MULTILINE | re.DOTALL)
  s = sys.stdin.read()
  print p.sub(args[1], s),

if __name__ == '__main__':
  main(sys.argv[1:])

2
และเวอร์ชัน Perl จะเป็น shell one-liner "perl -pe 's / a / b /' filename" หรือ "something | perl -pe 's / a / b /'"
tiftik


0

ผมคิดว่าคนอื่น ๆ ได้ชี้แจงนี้เพียงพอสำหรับวิธีการอื่น ๆ ( sed, AWKฯลฯ ) อย่างไรก็ตาม - bashคำตอบเฉพาะของฉัน(ทดสอบบน macOS High Sierra และ CentOS 6/7) เป็นไปตาม

1) หาก OP ต้องการใช้วิธีการค้นหาและแทนที่ที่คล้ายกับที่พวกเขาเสนอในตอนแรกฉันขอแนะนำให้ใช้perlสำหรับสิ่งนี้ดังนี้ หมายเหตุ:เครื่องหมายวงเล็บก่อนสำหรับ regex ไม่ควรจะเป็นสิ่งที่จำเป็นและสายรหัสนี้สะท้อนให้เห็นถึงวิธีการที่$1ดีกว่าที่จะใช้งานกว่า\1กับperlผู้ประกอบการเปลี่ยนตัว (เช่นต่อPerl 5 เอกสาร )

perl -pe 's/(.*)/\t$1/' $filename > $sedTmpFile && mv $sedTmpFile $filename

2) อย่างไรก็ตามตามที่ghostdog74ชี้ให้เห็นเนื่องจากการดำเนินการที่ต้องการจริงๆแล้วเพียงแค่เพิ่มแท็บที่จุดเริ่มต้นของแต่ละบรรทัดก่อนที่จะเปลี่ยนไฟล์ tmp เป็นไฟล์อินพุต / เป้าหมาย ( $filename) ฉันขอแนะนำperlอีกครั้ง แต่ด้วยการปรับเปลี่ยนต่อไปนี้ (s):

perl -pe 's/^/\t/' $filename > $sedTmpFile && mv $sedTmpFile $filename
## OR
perl -pe $'s/^/\t/' $filename > $sedTmpFile && mv $sedTmpFile $filename

3) แน่นอนว่าไฟล์ tmp นั้นฟุ่มเฟือยดังนั้นจึงเป็นการดีกว่าที่จะทำทุกอย่าง 'ในสถานที่' (เพิ่ม-iค่าสถานะ) และลดความซับซ้อนของสิ่งต่าง ๆ ให้เป็นหนึ่งซับที่หรูหรายิ่งขึ้นด้วย

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