sedが\ tをタブとして認識しないのはなぜですか?


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

このsedスクリプトtabでは、すべての行の前にが挿入されることを期待していますが、$filenameそうではありません。何らかの理由でt代わりにを挿入しています。


1
sedはプラットフォーム間で異なる可能性があるため(特に、BSD / MacOSXとLinux)、sedを使用しているプラ​​ットフォームを指定すると役立つ場合があります。
アイザック

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

OS X(macOS)ユーザーについては、この質問を参照しください。
Franklin Yu

回答:


129

のすべてのバージョンがsed理解できるわけではありません\t。代わりにリテラルタブを挿入するだけです(Ctrl- Vを押してからTab)。


2
ああそう。明確にするために:sedのすべてのバージョンが\t式の置換部分で理解できるわけではありません(\tパターンマッチング部分でうまく認識されます)
John Weldon

3
awwwwwwwwwwwwwwwwwww、わかりました。そして奇妙です。なぜ1つの場所でそれを認識させ、他の場所では認識させないのですか...
sixtyfootersdude 2010

2
スクリプトから呼び出されますが、機能しません。タブはshによって無視されます。たとえば、シェルスクリプトからの次のコードは、表を前に付けずに$ TEXT_TO_ADDを追加します。sed "$ {LINE} a \\ $ TEXT_TO_ADD" $ FILE
デレクソン2013年

2
@Derecksonなど-この回答を参照してください:stackoverflow.com/a/2623007/48082
Cheeso

2
デレクソンs / can / can / not /?
ダグラス

41

Bashを使用すると、次のようにプログラムでTAB文字を挿入できます。

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

これは非常に役立ちます。
Cheeso 2013年

1
あなたは正しい軌道に乗っていまし$'string'たが、説明が足りませんでした。実際、私は、おそらく私たちのほとんどがbashで行っているように、あなたがおそらく不完全な理解をしている非常に扱いにくい使用法のためと思います。以下の私の説明を参照してください:stackoverflow.com/a/43190120/117471
Bruno Bronosky 2017

1
BASHは$TAB単一引用符内のように変数を展開しないので、二重引用符を使用する必要があることに注意してください。
nealmcb

*二重引用符の内側の使用に注意してください...これは意図した正規表現としてではなく、グロブとして扱われます。
levigroker

27

@seditは正しいパスにありましたが、変数を定義するのは少し厄介です。

ソリューション(bash固有)

これを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

説明

bash $'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を避ける必要がある場合)

私は個人的に、bashismを回避してもコードが移植可能にならないため、bashを回避するためのほとんどの努力はばかげていると思います。(あなたがコードをシバンしたbash -eu場合、bashを避けて使用しようとしたsh場合(あなたが絶対的なPOSIX忍者でない限り)よりもコードの脆弱性は少なくなります。)しかし、それについて宗教的な議論をするのではなく、私はあなたに最高のものを与えます*答え。

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

*ベストアンサー?はい、ほとんどのアンチバッシュシェルスクリプターがコードで間違ったことの1つの例はecho '\t'@ robrecordの回答のように使用するためです。GNUエコーでは機能しますが、BSDエコーでは機能しません。これは、http: //pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16のThe Open Groupによって説明されています。これが、バシズムを回避しようとする試みが通常失敗する理由の例です。


8

Ubuntu 12.04(LTS)のBashシェルで次のようなものを使用しました。

最初に一致したときに2番目のタブに新しい行を追加するには:

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

最初tab、secondに置き換えるには:

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エコー」固有の機能(\ tをタブ文字として解釈する)を使用して、「BSD sed」固有のバグ(\ tを2つの別々の文字として解釈する)を解決しているのはおかしいです。おそらく、「GNU echo」がある場合は、「GNU sed」もあるでしょう。その場合、エコーを使用する必要はありません。BSDでは、echo echo '\t'は2つの別々の文字を出力します。POSIXポータブルな方法はを使用することprintf '\t'です。これが私が言う理由です:bashを使用しないことによってコードを移植可能にしようとしないでください。思ったより難しいです。を使用することbashは、ほとんどの人が実行できる最もポータブルなことです。
Bruno Bronosky 2017

3

sed実際には、行の前にタブを挿入するだけの場合は、を使用して置換を行う必要はありません。この場合の置換は、特に大きなファイルで作業している場合は、印刷するだけの場合と比べてコストがかかります。正規表現ではないので、読みやすくなっています。

たとえばawkを使用する

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


0

sedはサポートしていません。また、その\tような他のエスケープシーケンスもサポートしていません\n。私が見つけた唯一の方法は、実際にスクリプトにタブ文字を挿入することでしたsed

そうは言っても、PerlやPythonの使用を検討する必要があるかもしれません。これは私が書いた短いPythonスクリプトで、すべてのストリームの正規表現に使用します。

#!/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のバージョンは、シェルの1行の「perl -pe 's / a / b /' filename」または「something | perl -pe 's / a / b /'」になります
tiftik


0

私は他の人が他のアプローチ(のために適切にこれを明らかにしたと思うsedAWKなど)。ただし、私のbash固有の回答(macOS High SierraおよびCentOS 6/7でテスト済み)は以下のとおりです。

1)OPが最初に提案したものと同様の検索と置換の方法を使用したい場合perlは、次のように使用することをお勧めします。注:正規表現の括弧の前のバックスラッシュは必要ありません。このコード行は、置換演算子(Perl 5のドキュメントなど$1を使用するよりも使用する方が良い方法を反映しています。\1perl

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

2)ただし、ghostdog74で指摘されているように、目的の操作は実際にはtmpファイルを入力/ターゲットファイル()に変更する前に各行の先頭にタブ追加するだけなので、もう一度$filenameお勧めperlしますが、次の変更を加えます(秒):

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.