web-dev-qa-db-ja.com

テキストファイル内の数値のgrep範囲

テキストファイルに次のテキストがあります

$ cat test
20180618:
20180619:
20180620:
20180621:
20180622:
20180623:
20180624:

以下のような範囲の数値でgrepを試みましたが、

$ grep 201806{19..21} test
grep: 20180619: No such file or directory
grep: 20180620: No such file or directory
grep: 20180621: No such file or directory

ZSHとbashの両方でエラーが発生しています。 grepが検索文字列をファイルとして受け取っているようです。

私は別の方法を試しました:

$ grep 201806* test       
zsh: no matches found: 201806*

ZSHでのみエラーが発生します。 ZSHで*を使用する正しい方法は何ですか?また、grepとgrepの数値範囲をどのように区別できますか?

1
smc
grep 201806{19..21} test

シェルによって次のように展開されます。

grep 20180619 20180620 20180621 test

grepは、2018061920180620、およびtestの3つのファイル内で20180621を検索すると理解します。

次のように変更した場合:

grep -e201806{19..21} test

次に、それは次のように拡張されます。

grep -e20180619 -e20180620 -e20180621 test

これは、eで検索するgrepに3つのtestxpressionsを与えます。

またはあなたがすることができます:

printf '%s\n' 201806{19..21} | grep -f - test

式を入力行数としてgrepに渡す場合(一部の実装では、/dev/stdinの代わりに-が必要になる場合があります)。

具体的には、zshを使用して、次のようにすることもできます。

numbers=({19..21} 25 31)
grep -E "201801(${(j:|:)numbers})" test

(j:|:)パラメーター拡張フラグを使用して配列要素を|(拡張正規表現代替演算子)で結合するため、EREとして使用できます。

または、次の方法でその配列を正規表現スカラーに関連付けることができます。

$ typeset -T re numbers '|'
$ numbers=({19..21} 25 31)
$ echo $re
19|20|21|25|31

正規表現には通常、番号範囲のマッチング機能はありませんが、zshパターン(extendedglobの機能は正規表現と機能的に同等です)は<x-y>演算子(一連の10進数):

print -rl -- ${(M)${(f)"$(<test)"}:#*201806<19-21>*}
2

はい、grepはデフォルトでfirst引数のみを正規表現として扱います。

この意味は

_grep {1..9} file
_

に拡大します

_grep 1 2 3 4 5 6 7 8 9 file
_

他のオペランドで一致する式として_1_を指定してgrepを呼び出し、これらの他のオペランドはファイル名であることが期待されます。

他のコマンド:

_grep 201806* test
_

これは、ファイル名のグロビングパターンとして_201806*_に一致しようとします。現在のディレクトリに名前が_201806_で始まるファイルがないため、zshシェルはパターンの展開に失敗し、エラーメッセージ_no matches found_を表示します。

他のボーンのようなシェルでは、パターンがどのファイル名とも一致しなかった場合、パターンは展開されずに残り、grepの正規表現として使用されていました。正規表現として使用した場合、式_201806*_は、_20180_の後に0個以上の_6_文字が続くものと一致します。 _2018066666_。

代わりに、範囲に一致する正規表現を作成することができます。

_grep -E '201806(19|20|21)' test
_

または

_grep -E '201806(19|2[01])' test
_

_-E_は、grepに式の_|_(代替)を理解させるために必要です(この代替により、拡張正規表現になります)。


ブレース展開から正規表現を作成することもできます。

_set -- {19..21}
re=$( IFS='|'; printf '201806(%s)' "$*" )

grep -E "$re" test
_

これにより、最初に位置パラメータ_$1_、_$2_および_$3_が範囲内の必要な数値に設定されます。次に、変数re201806(%s)に設定され、printfは_%s_を_|_で区切られたこれらの数値に置き換えます。

grep呼び出しは、正規表現として201806(19|20|21)を使用します。

6
Kusalananda

引用符で囲まれていない文字列は、コマンドが実行される前にシェルによって解釈されます。この場合、試行したコマンドはgrep 20180619 20180620 20180621 testに展開されます

$ echo grep 201806{19..21} test
grep 20180619 20180620 20180621 test

回避策の1つは、正規表現の変更を指定することです。

$ grep -E '201806(19|20|21)' test
20180619:
20180620:
20180621:

正規表現を使用して数値範囲を作成できますが、簡単ではありません。詳細については、 https://www.regular-expressions.info/numericranges.html を参照してください。


別のオプションはawkを使用することです

$ awk -F: '$1>=20180619 && $1<=20180621' ip.txt
20180619:
20180620:
20180621:

ここでは、:で行を分割し、最初のフィールド$1を必要な範囲と比較します

2
Sundeep
  1. [〜#〜] posix [〜#〜]utilsを使用したシェル(bashなし):

    seq 20180618 20180624 | grep -f - test
    
  2. numgrep

    numgrep '/20180618..20180624/' < test
    
1
agc