grep、awk、またはsedを使用して文字列リテラルまで行の部分文字列を取得するには?

grep、awk、またはsedを使用して文字列リテラルまで行の部分文字列を取得するには?

テキストファイルを処理しようとしていて、特定の文字列リテラルが行の末尾に表示された場合は省略します。たとえば、

源泉:

ABC 123
DEF, characters I don't want
GHI, these characters are ok

希望の出力:

ABC 123
DEF
GHI, these characters are ok

これにより、grep -v ', characters I don't want$'行全体が無視されます。

部分文字列がawk必要なので、単純な列を作成することはできません。, these characters are ok

cut区切り文字は複数の文字()でなければならないため、区切り文字を使用して分割することはできません, characters I don't want

Pythonでは非常に簡単です。たとえば、次のようになります。string.split(", characters I don't want", 1)[0]

(余談として、このような複雑な状況では、Pythonがより読みやすく、メンテナンスが容易なときにgrep、awk、またはsedを使用するユースケースが実際にPythonより優れているかどうか疑問に思います。)

答え1

ここで最も明白なのは、次のことですsed

<source sed "s/, characters I don't want\$//"

シェルでエスケープされた行の末尾で文字列を見つけたら、sその文字列を置き換えます(後でシェルに何かが表示される場合に備えて、将来の証拠として)。$\$$/

その文字列の後の項目(存在する場合)を削除するには、に置き換えます。\$ただし、ユーザーロケールで有効なテキストではない場合でも、最後まですべての項目が一致するように.*Cのロケールを変更する必要があります。.*

<source LC_ALL=C sed "s/, characters I don't want.*//"

GNUgrepまたは互換バージョンの場合、Perl と同様の正規表現サポートでビルドすると、次のようになります。

<source LC_ALL=C grep -Po "^.*?(?=(, characters I don't want)?\$)"

または、その文字列の後のすべての内容を削除します(存在する場合)。

<source LC_ALL=C grep -Po "^.*?(?=, characters I don't want|\$)"

またはpcregrep(Perlと同様の正規表現サポートがGNUで有効になっている場合、grepこれは実際にサンプルアプリケーションとして提供されますが、pcregrepGNU以上の機能を持つlibpcreを介して行われますgrep):

<source pcregrep -o1 "^(.*?)(, characters I don't want)?\$"

または、その文字列の後のすべての内容を削除します(存在する場合)。

<source pcregrep -o1 "^(.*?)(, characters I don't want|\$)"

削除するテキストに/正規表現演算子(意味のない改行、コマンド引数、または環境変数に渡すことができるNUL文字を除く)を含めることができ、シェル変数に格納されている場合は、次のようにします。いいえこれにより、sed "s/$string\$//"コマンド注入の脆弱性が発生する可能性があります。

Perl-grepの場合は、次のものを使用できます。

string='/.*\^$'
<source LC_ALL=C grep -Po "^.*?(?=(\Q$string)?\$)"
<source pcregrep -o1 "^(.*?)(\Q$string\E)?\$"

または、その文字列の後のすべての内容を削除します(存在する場合)。

<source LC_ALL=C grep -Po "^.*?(?=\Q$string|\$)"
<source pcregrep -o1 "^(.*?)(\Q$string\E|\$)"

これは深刻な結果をもたらさなくても、$string孤立したsの窒息を引き起こします。\Esed

あるいは、任意の文字列を渡すためのメカニズムを使用して、オプションのあるモードでperl直接使用することもできます(ここではおおよそのオプションを渡すために使用されますが、直接使用することもできます(Pythonに対応)。または環境変数(連想配列にマップされます) )。文字列は正規表現で引用できます(ここではinは問題ではありません):sed-p-s@ARGVsys.argv%ENV\Q\E$string

<source perl -spe 's/\Q$string\E$//' -- -string="$string"

または、その文字列の後のすべての内容を削除します(存在する場合)。

<source perl -spe 's/\Q$string\E.*$//' -- -string="$string"

perlデフォルトでは、入力はユーザーのロケール文字セットでエンコードされず、バイトとして扱われるため、ここでロケールを変更する必要はありません。

対照的に、行区切り文字はパターン空間(sed基本的$_に機能する場所perl)に含まれており、対応する正規表現演算子はトピックの終わりまたはトピックの終わりの行区切り文字の前に一致するので処理できます。区切られた行と無制限の行。s///$

答え2

awkを使用してください。

$ awk 'n=index($0 RS,", characters I don\047t want" RS){$0=substr($0,1,n-1)} 1' file
ABC 123
DEF
GHI, these characters are ok

これはリテラル文字列比較を実行するため、正規表現メタ文字を含む文字列を次の入力と一致させようとした場合にも機能します。

$ cat file2
ABC 123
DEF, .*, .*
GHI, .* ok

予想される結果は次のとおりです。

$ awk 'n=index($0 RS,", .*" RS){$0=substr($0,1,n-1)} 1' file2
ABC 123
DEF, .*
GHI, .* ok

正規表現のメタ文字に興味がない場合は、次のことができます。

$ awk '{sub(/, characters I don\047t want$/,"")} 1' file
ABC 123
DEF
GHI, these characters are ok

ただし、予期しない結果が表示されます。

$ awk '{sub(/, .*$/,"")} 1' file2
ABC 123
DEF
GHI

そして期待される出力を得るためには、メタ文字をリテラルにするためにエスケープする必要があります。

$ awk '{sub(/, \.\*$/,"")} 1' file2
ABC 123
DEF, .*
GHI, .* ok

実際に望むのは、文字通りの文字列比較だけであることを考慮すると、これは扱いにくくなります。

バラよりhttp://awk.freeshell.org/PrintASingleQuote\047代わりになぜ'

pythonの代わりにawkが使用される理由 - awkは必須のPOSIXツールなので、すべてのPOSIX準拠のUnixインストールに存在することが保証されていますが、pythonはそうではありません.必要です。 。私たちはどちらが読みやすくなりやすいかについて同意する必要があると思います。

答え3

行末の内容を事前に知っていれば、変数の拡張などの機能をサポートする Bash やシェルでその内容をフィルタリングするのはかなり簡単です。たとえば、

#!/usr/bin/env bash
line='DEF, characters I do not want'
echo "${line%, characters I do not want}"

以下を印刷します。

DEF

この構文は、最後から内容を削除してから文字列の内容を返します${var%string}。この例では、削除する文字列は ""です。文字列が最後にない場合は、コンテンツ全体が返されます。変数の先頭から文字列を削除するバリアントと、内容の中間にある文字列を置き換えるか削除する代替バリアントがあります。$var%, characters I do not want$line

上記の例では、変数に文字列を割り当てるときに一重引用符を使用して発生する複雑さを避けるためにdon't-> が変更されたことを認識しています。do not$line

このアプローチの利点は、スクリプトが単純なフィルタリングを実行するために外部コマンドを呼び出す必要がないことです。 しかし、これはPythonの強力な機能を置き換えることができますか?。おそらくそうではありませんが、これにはPythonの代わりにシェルスクリプトを使用するように動機付ける他の要因があります。

関連情報