Tại sao sed không nhận dạng là một tab?


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

Tôi hy vọng sedtập lệnh này sẽ chèn tabvào trước mỗi dòng, $filenametuy nhiên nó không phải vậy. Vì lý do nào đó, nó đang chèn một tthay thế.


1
Vì sed có thể khác nhau giữa các nền tảng (cụ thể là BSD / MacOSX so với Linux), có thể hữu ích khi chỉ định nền tảng mà bạn đang sử dụng sed.
Isaac

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

Đối với người dùng OS X (macOS), hãy tham khảo câu hỏi này .
Franklin Yu

Câu trả lời:


129

Không phải tất cả các phiên bản đều sedhiểu \t. Thay vào đó, chỉ cần chèn một tab chữ (nhấn Ctrl- Vsau đó Tab).


2
À vâng; để làm rõ: không phải tất cả các phiên bản của sed hiểu \tở phần thay thế của biểu thức (nó được công nhận \tở phần khớp mẫu tốt)
John Weldon

3
awwwwwwwwwwwwwwwwwww, ok đó là khá thú vị. Và kỳ lạ. Tại sao bạn lại làm cho nó nhận ra nó ở một nơi mà không phải ở nơi khác ...?
sixtyfootersdude

2
Được gọi từ một tập lệnh, điều đó sẽ không hoạt động: các tab sẽ bị sh. Ví dụ: mã sau đây từ một tập lệnh shell sẽ thêm $ TEXT_TO_ADD, mà không thêm nó vào trước bởi một lập bảng: sed "$ {LINE} a \\ $ TEXT_TO_ADD" $ FILE.
Dereckson

2
@Dereckson và những người khác - xem câu trả lời này: stackoverflow.com/a/2623007/48082
Cheeso

2
Dereckson s / can / can /?
Douglas tổ chức vào

41

Sử dụng Bash, bạn có thể chèn ký tự TAB theo chương trình như sau:

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

Điều này rất hữu ích.
Cheeso

1
Bạn đã đi đúng hướng với $'string'sự giải thích nhưng thiếu. Trong thực tế, tôi nghi ngờ, vì cách sử dụng cực kỳ khó hiểu mà bạn có thể hiểu chưa đầy đủ (như hầu hết chúng ta làm với bash). Xem giải thích của tôi bên dưới: stackoverflow.com/a/43190120/117471
Bruno Bronosky

1
Hãy nhớ rằng BASH sẽ không mở rộng các biến như $TABbên trong dấu ngoặc kép, vì vậy bạn sẽ cần sử dụng dấu ngoặc kép.
nealmcb

Hãy cẩn thận về việc sử dụng *bên trong dấu ngoặc kép ... điều này sẽ được coi là một hình cầu, không phải là regex mà bạn dự định.
levigroker

27

@sedit đã đi đúng hướng, nhưng hơi khó xử khi xác định một biến.

Giải pháp (bash cụ thể)

Cách để làm điều này trong bash là đặt một ký hiệu đô la trước chuỗi được trích dẫn duy nhất của bạn.

$ 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

Nếu chuỗi của bạn cần bao gồm mở rộng biến, bạn có thể đặt các chuỗi được trích dẫn lại với nhau như sau:

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

Giải trình

Trong bash $'string'gây ra "mở rộng ANSI-C". Và đó là những gì hầu hết chúng ta mong đợi khi chúng tôi sử dụng những thứ như \t, \r, \n, vv Từ: https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html#ANSI_002dC-Quoting

Các từ có dạng $ 'string' được xử lý đặc biệt. Từ mở rộng thành chuỗi , với các ký tự thoát ra sau dấu gạch chéo ngược được thay thế như được chỉ định bởi tiêu chuẩn ANSI C. Các chuỗi thoát dấu gạch chéo ngược, nếu có, được giải mã ...

Kết quả mở rộng được trích dẫn một lần, như thể ký hiệu đô la không có mặt.

Giải pháp (nếu bạn phải tránh bash)

Cá nhân tôi nghĩ rằng hầu hết các nỗ lực để tránh bash là ngớ ngẩn bởi vì tránh bash này KHÔNG * làm cho mã của bạn trở nên linh hoạt. (Mã của bạn sẽ kém giòn hơn nếu bạn tập hợp nó bash -euhơn là nếu bạn cố gắng tránh bash và sử dụng sh[trừ khi bạn là một ninja POSIX tuyệt đối].) * câu trả lời.

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

* Câu trả lời tốt nhất? Có, bởi vì một ví dụ về những gì mà hầu hết các trình viết lệnh shell chống bash sẽ làm sai trong mã của họ là sử dụng echo '\t'như trong câu trả lời của @ robrecord . Điều đó sẽ hoạt động đối với tiếng vang GNU, nhưng không hiệu quả với tiếng vang BSD. Điều đó được giải thích bởi The Open Group tại http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16 Và đây là một ví dụ về lý do tại sao việc cố gắng tránh các cơ sở sản xuất kinh doanh thường không thành công.


8

Tôi đã sử dụng một cái gì đó như thế này với Bash shell trên Ubuntu 12.04 (LTS):

Để nối một dòng mới với tab, thứ hai khi đối sánh đầu tiên :

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

Để thay thế đầu tiên bằng tab, thứ hai :

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

4
Lối thoát kép là chìa khóa, tức là sử dụng \\tvà không \t.
zamnuts

Tôi cũng phải sử dụng dấu ngoặc kép thay vì dấu ngoặc kép trên Ubuntu 16.04 và Bash 4.3.
caw

4

Sử dụng $(echo '\t') . Bạn sẽ cần trích dẫn xung quanh mẫu.

Ví dụ. Để xóa một tab:

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

5
Thật buồn cười khi bạn đang sử dụng một tính năng cụ thể "GNU echo" (diễn giải \ t dưới dạng ký tự tab) để giải quyết một lỗi cụ thể "BSD sed" (diễn giải \ t là 2 ký tự riêng biệt). Có lẽ, nếu bạn có "GNU echo", bạn cũng sẽ có "GNU sed". Trong trường hợp đó, bạn sẽ không cần sử dụng tiếng vang. Với BSD echo echo '\t'sẽ xuất ra 2 ký tự riêng biệt. Cách di động POSIX là sử dụng printf '\t'. Đây là lý do tại sao tôi nói: Đừng cố gắng làm cho mã của bạn trở nên di động bằng cách không sử dụng bash. Nó khó hơn bạn nghĩ. Sử dụng bashlà điều dễ di chuyển nhất mà hầu hết chúng ta có thể làm.
Bruno Bronosky

3

Bạn không cần phải sử dụng sedđể thay thế khi trên thực tế, bạn chỉ muốn chèn một tab vào trước dòng. Thay thế cho trường hợp này là một hoạt động tốn kém so với chỉ in nó ra, đặc biệt là khi bạn đang làm việc với các tệp lớn. Nó cũng dễ đọc hơn vì nó không phải là regex.

ví dụ như sử dụng awk

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


0

sedkhông hỗ trợ \t, cũng không phải các chuỗi thoát khác như \ncho vấn đề đó. Cách duy nhất tôi tìm thấy để làm điều đó là thực sự chèn ký tự tab trong tập lệnh bằng cách sử dụngsed .

Điều đó nói rằng, bạn có thể muốn xem xét sử dụng Perl hoặc Python. Đây là một tập lệnh Python ngắn mà tôi đã viết mà tôi sử dụng cho tất cả các luồng 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
Và phiên bản Perl sẽ là vỏ một lót "perl -pe 's / a / b /' filename" hoặc "một cái gì đó | perl -pe 's / a / b /'"
tiftik

0

Thay vì BSD sed, tôi sử dụng perl:

ct@MBA45:~$ python -c "print('\t\t\thi')" |perl -0777pe "s/\t/ /g"
   hi

0

Tôi nghĩ rằng những người khác đã làm sáng tỏ này đầy đủ cho cách tiếp cận khác ( sed, AWK, vv). Tuy nhiên, bashcác câu trả lời cụ thể của tôi (được thử nghiệm trên macOS High Sierra và CentOS 6/7) theo sau.

1) Nếu OP muốn sử dụng phương pháp tìm kiếm và thay thế tương tự như những gì họ đề xuất ban đầu, thì tôi sẽ đề xuất sử dụng perlcho điều này, như sau. Lưu ý: dấu gạch chéo ngược trước dấu ngoặc đơn cho regex không cần thiết và dòng mã này phản ánh cách $1sử dụng tốt hơn so \1với perltoán tử thay thế (ví dụ: theo tài liệu Perl 5 ).

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

2) Tuy nhiên, như đã chỉ ra bởi ghostdog74 , vì thao tác mong muốn thực sự là chỉ cần thêm một tab ở đầu mỗi dòng trước khi thay đổi tệp tmp thành tệp đầu vào / đích ( $filename), tôi khuyên bạn nên sử dụng perllại nhưng với sửa đổi sau (S):

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

3) Tất nhiên, tệp tmp là không cần thiết , vì vậy tốt hơn là chỉ làm mọi thứ 'tại chỗ' (thêm -icờ) và đơn giản hóa mọi thứ thành một lớp lót thanh lịch hơn với

perl -i -pe $'s/^/\t/' $filename
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.