sedが置換文字列を解釈しないようにする方法はありますか?[閉まっている]


14

sedを使用してキーワードを文字列に置き換える場合、sedは置換文字列を解釈しようとします。置換文字列に「/」文字など、sedが特殊と見なす文字が含まれている場合、当然、置換文字列にsedに動作方法を指示する文字を含めることを意図していない限り、失敗します。

例:

VAR="hi/"

sed "s/KEYWORD/$VAR/g" somefile

特殊文字の置換文字列を解釈しないようにsedに指示する方法はありますか?内容が何であっても、ファイルのキーワードを変数の内容に置き換えることができるようにしたいだけです。


特殊文字を入れて特殊文字にしたくsedない場合は、バックスラッシュでエスケープします。 VAR='hi\/'そのような問題はありません。
ワイルドカード2016年

6
なぜすべての反対票か それは私には完全に合理的な質問のようです
roaima

sed(1)取得した内容を解釈するだけです。あなたの場合、それはシェル補間を介してそれを取得します。思い通りにできないと思いますが、マニュアルをチェックしてください。私はPerlで(これはsedかなり豊かな正規表現を使用して、妥当な置換を行います)、文字列が文字どおりに解釈されるように指定できることを知っています。もう一度、マニュアルを確認してください。
フォンブランド

回答:


4

交換部品で唯一の4の特殊文字があります\&改行や区切り文字(REF

$ VAR='abc/def&ghi\foo
next line'

$ repl=$(sed -e 's/[&\\/]/\\&/g; s/$/\\/' -e '$s/\\$//' <<<"$VAR")

$ echo "$repl"
abc\/def\&ghi\\foo\
next line

$ echo ZYX | sed "s/Y/$repl/g"
Zabc/def&ghi\foo
next lineX

これには、Anttiのソリューションと同じ問題があります。置換文字列が特定の長さを超えると、「引数リストが長すぎます」というエラーが発生します。また、置換文字列に「[」、「]」、「*」、「。」などの文字がある場合はどうなりますか?sedは本当にそれらを解釈しないでしょうか?
Tal

の置換側は正規表現でs///なく、実際には単なる文字列です(バックスラッシュとエスケープとを除く&)。置換文字列が長すぎる場合、シェルのワンライナーはあなたのソリューションではありません。
グレンジャックマン2016年

たとえば、置換文字列がbase64でエンコードされたテキストである場合(プレースホルダーをSHA256キーで置き換える場合など)は、非常に便利なリストです。その場合は、心配するのは単なる区切り文字です。
ヒース

4

-p(入力のループを想定)および-e(コマンドラインでプログラムを与える)でsedの代わりにPerlを使用できます。Perlを使用すると、シェルでこれら補間せずに環境変数にアクセスできます。変数をエクスポートする必要があることに注意してください。

export VAR='hi/'
perl -p -e 's/KEYWORD/$ENV{VAR}/g' somefile

変数をどこにでもエクスポートしたくない場合は、そのプロセスにのみ変数を提供します。

PATTERN="$VAR" perl -p -e 's/KEYWORD/$ENV{PATTERN}/g' somefile

Perlの正規表現の構文は、デフォルトではsedの構文とわずかに異なることに注意してください。


これは非常に有望であるように見えましたが、テストすると、置換文字列が長すぎるため「引数リストが長すぎます」というエラーが発生します。これは理にかなっています。 perlに変換するため、その長さに制限があります。
Tal

1
いいえ、引数ではなくPATTERN 環境変数に入ります。いずれの場合も、このエラーはでありE2BIG、を使用しsedた場合と同じように発生します。
Antti Haapala 2016年

2

ほとんどの変数値を正しく処理する非常に簡単な解決策は、非表示文字をsedの代替コマンドの区切り文字として使用することです。

では、viCtrl-V(より一般的には^V)を入力することで、任意の制御文字をエスケープできます。したがって、いくつかの制御文字(^Aこれらの場合は区切り文字としてよく使用します)を使用すると、sedドロップする変数にその非表示文字が存在する場合にのみ、コマンドが中断します。

したがって、次のように入力"s^V^AKEYWORD^V^A$VAR^V^Ag"すると、(でvi)得られる結果は次のようになります。

sed "s^AKEYWORD^A$VAR^Ag" somefile

これは$VAR、非印刷文字が含まれていない限り機能します^A


もちろん、ユーザーの入力をの値に渡す場合は$VAR、すべてのベットがオフになり、平均的なユーザーがコントロール文字を入力するのが難しいのではなく、入力を完全にサニタイズするほうがよいでしょう。


ただし、実際にはデリミタ文字列以外にも注意する必要があります。たとえば&、置換文字列に含まれるは、「一致したテキスト全体」を意味します。例えば、s/stu../my&/あなたが持っている可能性がある場合など、「mystung」と「のMyStuff」、「刺さ」、そうで「もの」を代わる任意のあなたがしている置換文字列としてでは落ちていますが、リテラルを使用したいという変数に文字を変数の値のみの場合、変数を置換文字列として使用する前に、データをサニタイズする必要がありますsed。(ただし、データのサニタイズはこれで行うこともできますsed。)


それは私のポイントのようなものです-文字列を別の文字列で置き換えることは非常に簡単な操作です。sedが気に入らない文字を見つけ出し、sedを使用して独自の入力をサニタイズするのと同じくらい複雑にする必要がありますか?それはばかげて不必要に複雑に聞こえます。私はプロのプログラマーではありませんが、bashを含め、これまでに出会ったほとんどすべての言語でキーワードを文字列に置き換える小さな関数をコーディングできると確信しています-単純なLinuxを期待していました既存のツールを使用したソリューション-そこに存在するものがないとは信じられません。
Tal

1
@Tal、別のコメントで言及したように、置換文字列が「数百ページの長さ」である場合...「単純な」使用例とは言い難い。ちなみに、ここでの答えはPerlです。私はPerlを学んだことがありません。複雑さは、ここであなたに任意の入力できるようにしたいということから来ている置換文字列正規表現を
ワイルドカード2016年

使用できるソリューションは他にも多数あり、その多くは非常にシンプルです。たとえば、置換文字列が実際に行ベースであり、行の途中に挿入する必要がない場合は、sedinsertコマンドを使用します。しかしsed、複雑な方法で膨大な量のテキストを処理するための良いツールではありません。でこれを行う方法を示す別の回答を投稿しawkます。
ワイルドカード2016年

1

あなたは代わりに,またはを使うことができ|、それはそれをセパレーターとして扱い、技術的には何でも使うことができます

マニュアルページから

\cregexpc
           Match lines matching the regular expression regexp.  The  c  may
      be any character.

ご覧のとおり、区切り文字の前の\で始めて、区切り文字として使用できます。

ドキュメントhttp://www.gnu.org/software/sed/manual/sed.html#The-_0022s_0022-Commandから:

The / characters may be uniformly replaced by any other single character 
within any given s command.

The / character (or whatever other character is used in its stead) can appear in 
the regexp or replacement only if it is preceded by a \ character.

例:

sed -e 'somevar|s|foo|bar|'
echo "Hello all" | sed "s_all_user_"
echo "Hello all" | sed "s,all,user,"

echo "Hello/ World" | sed "s,Hello/,Neo,"


あなたは、置換文字列で単一の特定の文字の使用を許可することについて話している-この場合、 "/"。置換文字列を完全に解釈しようとするのを防ぐことについて話している。使用する文字( "/"、 "、"、 "|"など)に関係なく、置換文字列にその文字がポップアップ表示される危険があります。また、最初の文字は、sedが気にする唯一の特殊文字ではありませんか?
Tal

@Tal no代わりに何でも取る/ことができ、/私が指摘したように喜んで無視します..実際、それを探して文字列に置き換えることもできます>>>私は例で編集しました>>>これらものはそれほど安全ではなく、あなたはいつも賢い男を見つけるでしょう
user3566929

@Talなぜそれを解釈させないようにしますか?つまり、sedそもそもを使用しているのですが、あなたのプロジェクトは何ですか?
user3566929 2016年

キーワードを文字列に置き換えるだけです。sedは、Linuxでこれを行う最も一般的な方法のようです。文字列の長さは100ページにすることができます。文字列をサニタイズして、sedが文字化けしないようにしたくありません-文字列内の任意の文字を処理できるようにしたいと思います。内の意味。
Tal

1
@Tal bashは文字列操作用ではありません。まったく、まったく、まったく。これは、ファイル操作およびコマンド調整です。たまたま、文字列用の便利な機能がいくつか組み込まれていますが、それが主な目的である場合は、本当に制限があり、非常に高速ではありません。「シェルループを使用してテキストを処理することが悪い習慣と見なされるのはなぜですか?」を参照してください いくつかのツールされている最も強力に最も基本的なものから順に、あるテキスト処理のために設計された:sedawkおよびPerlの。
ワイルドカード2016年

1

それが行ベースで、1行だけを置き換える場合は、を使用してファイル自体に置換行を付加しprintf、その最初の行をsedのホールドスペースに格納し、必要に応じてドロップすることをお勧めします。これにより、特殊文字をまったく気にする必要がなくなります。(ここでの唯一の仮定は、$VAR改行のない1行のテキストを含むということです。これは既にコメントで述べたとおりです。)改行以外は、VARは何でも含むことができ、これは関係なく機能します。

VAR=whatever
{ printf '%s\n' "$VAR";cat somefile; } | sed '1{h;d;};/KEYWORD/g'

printf '%s\n'の内容に$VAR関係なく、の内容をリテラル文字列として出力し、その後に改行が続きます。(echoの内容が$VARハイフンで始まる場合など、場合によっては他のことも行います—に渡されるオプションフラグとして解釈されますecho。)

中括弧は、に渡されるのprintfコンテンツの前に出力を付加するために使用されます。ここでは、中括弧をそれ自体で区切る空白文字が重要です。中括弧の前のセミコロンも同様です。somefilesed

1{h;d;};sedコマンド内のテキストの最初の行を格納するsed保持空間次いで、dライン(というより、それを印刷)elete。

/KEYWORD/を含むすべての行に次のアクションを適用しますKEYWORD。アクションはgetで、ホールドスペースの内容を取得し、パターンスペースの代わりにドロップします。つまり、現在の行全体です。(これは行の一部だけを置き換えるためのものではありません。)ちなみに、ホールドスペースは空にならず、パターンスペースにコピーされ、そこにあるものはすべて置き換えられます。

正規表現をアンカーして、KEYWORD だけを含み、その行にKEYWORD以外に何もない行だけを含む行と一致しないようにするには、行の先頭のアンカー(^)と行の終わりのアンカー($)を追加します。あなたの正規表現:

VAR=whatever
{ printf '%s\n' "$VAR";cat somefile; } | sed '1{h;d;};/^KEYWORD$/g'

VARが1行の場合に最適です。VARは1行ではなく「100ページになる可能性がある」とコメントで実際に触れました。混乱させて申し訳ありません。
Tal

0

Bashのパターン置換パラメーター拡張を使用して、置換文字列のスラッシュをバックスラッシュでエスケープできます。フォワードスラッシュもBashでエスケープする必要があるため、少し面倒です。

$ var='a/b/c';var="${var//\//\\/}";echo 'this is a test' | sed "s/i/$var/g"

出力

tha/b/cs a/b/cs a test

パラメータ拡張を直接sedコマンドに入れることができます:

$ var='a/b/c';echo 'this is a test' | sed "s/i/${var//\//\\/}/g"

最初のフォームはもう少し読みやすいと思います。もちろん、複数のsedコマンドで同じ置換パターンを再利用する場合は、変換を1回だけ行うのが理にかなっています。

別のオプションは、sedを使用する代わりに、awk、perlまたはPythonで記述されたスクリプト、またはCプログラムを使用して置換を行うことです。


以下は、置換するキーワードが入力ファイルの完全な行(改行は数えない)である場合に機能するPythonの簡単な例です。ご覧のとおり、これは基本的にBashの例と同じアルゴリズムですが、入力ファイルをより効率的に読み取ります。

import sys

#Get the keyword and replacement texts from the command line
keyword, replacement = sys.argv[1:]
for line in sys.stdin:
    #Strip any trailing whitespace
    line = line.rstrip()
    if line == keyword:
        line = replacement
    print(line)

これは入力をサニタイズするもう1つの方法であり、特定の1文字( '/')のみを処理するため、優れた方法ではありません。ワイルドカードが指摘したように、デリミタ文字列以外にも注意すべきことがあります。
Tal

公正な呼び出し。たとえば、置換テキストにバックスラッシュでエスケープされたシーケンスが含まれている場合、それらは解釈されるため、望ましくない場合があります。それを回避する1つの方法は、問題のある文字(または全体)を- \xスタイルのエスケープシーケンスに変換することです。または、前の段落で述べたように、任意の入力を処理できるプログラムを使用します。
PM 2Ring 2016年

@Tal:簡単なPythonの例を回答に追加します。
PM 2Ring 2016年

Pythonスクリプトはうまく機能し、私の関数が行うことを正確に実行しているようですが、はるかに効率的です。残念ながら、メインスクリプトが(私の場合のように)bashである場合、これには2番目の外部Pythonスクリプトの使用が必要です。
Tal

-1

これは私が行った方法です:

#Replaces a keyword with a long string
#
#This is normally done with sed, but sed
#tries to interpret the string you are
#replacing the keyword with too hard
#
#stdin - contents to look through
#Arg 1 - keyword to replace
#Arg 2 - what to replace keyword with
replace() {
        KEYWORD="$1"
        REPLACEMENT_STRING="$2"

        while IFS= read -r LINE
        do
                if [[ "$LINE" == "$KEYWORD" ]]
                then
                        printf "%s\n" "$REPLACEMENT_STRING"
                else
                        printf "%s\n" "$LINE"
                fi
        done < /dev/stdin
}

私のキーワードはそれ自体で一列に並んでいるので、これは私の場合にはうまくいきます。キーワードが他のテキストと並んでいる場合、これは機能しません。

私自身のソリューションのコーディングを伴わない、これを行う簡単な方法があるかどうか、私はまだ本当に知りたいです。


1
特殊文字と堅牢性が本当に心配な場合は、まったく使用echoしないでください。 代わりに使用してくださいprintf そして、シェルループでテキスト処理を行うのは悪い考えです。
ワイルドカード2016年

1
質問でキーワードが常に完全な行になることを言及した場合、それは役に立ちました。FWIW、bash readはかなり遅いです。これは、インタラクティブなユーザー入力の処理用であり、テキストファイルの処理用ではありません。stdinを文字ごとに読み取り、各文字に対してシステムコールを作成するため、処理が遅くなります。
PM 2Ring 2016年

@PM 2Ring私の質問は、キーワードがそれ自体の行にあることについては言及していませんでした。なぜなら、そのような限られた数のケースでのみ機能する答えは欲しくないからです。だった。また、自分のコードが効率的であると言ったことは一度もありません。もし効率が良ければ、別の方法を探していません...
Tal

@ワイルドカード私が何かを見落としているのでない限り、printfは絶対的に特殊文字を解釈し、デフォルトの 'echo'が解釈するよりもはるかに多く解釈します。そのままprintf "hi\n"印刷しながら、printfは改行を印刷しますecho "hi\n"
Tal

@Talの「f」はprintf「フォーマット」を意味します。最初の引数printfフォーマット指定子です。その指定子が%s\nである場合、つまり「文字列の後に改行が続く」ことを意味する場合、次の引数のも解釈または変換さprintf ません。(もちろん、シェルはそれを解釈できます。リテラル文字列の場合は一重引用符で囲み、変数の展開が必要な場合は二重引用符で囲みます。)詳細については、私の回答をprintf参照しください。
ワイルドカード'18年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.