web-dev-qa-db-ja.com

Bashで部分文字列を抽出する

someletters_12345_moreleters.extという形式のファイル名を考えて、私は5桁を抽出してそれらを変数に入れたいと思います。

その点を強調するために、私はx文字数のファイル名と、それから両側に1つのアンダースコアで囲まれた5桁の文字列、それからx文字数の別のセットを持っています。 5桁の数字をとり、それを変数に入れたいのです。

私はこれが達成されることができるさまざまな方法の数に非常に興味があります。

600
Berek Bryan

cut :を使う

echo 'someletters_12345_moreleters.ext' | cut -d'_' -f 2

より一般的な

INPUT='someletters_12345_moreleters.ext'
SUBSTRING=$(echo $INPUT| cut -d'_' -f 2)
echo $SUBSTRING
591
FerranB

x が定数の場合、次のパラメータ展開は部分文字列抽出を実行します。

b=${a:12:5}

ここで、 12 はオフセット(ゼロベース)、 5 は長さです。

数字の周囲のアンダースコアが入力で唯一のものである場合は、2つのステップで(それぞれ)プレフィックスとサフィックスを削除できます。

tmp=${a#*_}   # remove prefix ending in "_"
b=${tmp%_*}   # remove suffix starting with "_"

他のアンダースコアがある場合は、もっとトリッキーですが、とにかく実現可能です。 1つの式で両方の展開を実行する方法を誰かが知っていれば、私も知りたいです。

提示された両方のソリューションは純粋なbashであり、プロセスの生成は含まれていないため非常に高速です。

930
JB.

そのようなシーケンスの最初のものを使用して、数字がファイル名のどこにでもあることができる一般的な解決策:

number=$(echo $filename | egrep -o '[[:digit:]]{5}' | head -n1)

変数の一部を正確に抽出するもう1つの解決策:

number=${filename:offset:length}

あなたのファイル名が常にstuff_digits_...のフォーマットを持っているなら、あなたはawkを使うことができます:

number=$(echo $filename | awk -F _ '{ print $2 }')

数字以外のすべてを削除するさらに別の解決策として、

number=$(echo $filename | tr -cd '[[:digit:]]')

cut -c startIndx-stopIndxを使ってみてください

75
brown.2179

より厳密な情報が必要な場合は、次のようにman bashで検索することもできます。

$ man bash [press return key]
/substring  [press return key]
[press "n" key]
[press "n" key]
[press "n" key]
[press "n" key]

結果:

 $ {parameter:offset} 
 $ {parameter:offset:length} 
部分文字列の展開。 offsetで指定された文字から開始して、
パラメータの最大文字数まで拡張します。 
 lengthを省略すると、offsetで指定された文字からパラメータstart  -  
の部分文字列に展開されます。 lengthとoffsetは
算術式です(下記の算術評価を参照)。 
 offsetが0未満の数値に評価されると、その値はparameterの値の末尾からのオフセットとして
使用されます。 - で始まる算術
式は、前の:からの空白
で区切られ、Use Default 
 Values展開と区別される必要があります。長さが
ゼロ未満の数値に評価され、parameterが@ではなく、インデックス付き配列または連想
配列でもない場合、値の最後からのオフセット
として解釈されます。文字数ではなくパラメータ数が大きくなり、展開 -  
は2つのオフセットの間の文字になります。 parameterが
 @の場合、結果はoffから始まる長さ位置パラメーター -  
 setです。 parameterが@または
 *で添字を付けられた添字付き配列名の場合、結果は
 $ {parameter [offset]}で始まる配列の長さのメンバになります。指定された配列の最大インデックスより1大きい負のオフセットが
を基準にして取られます。連想配列に部分
文字列展開を適用すると、未定義の結果が生成されます。負のオフセットは、コロンと混同しないように少なくとも1つのスペースでコロンから分離する必要があることに注意してください。 
位置パラメータが使用されていない限り、部分文字列のインデックス付けはゼロから始まります。その場合、インデックス付け
はデフォルトで1から始まります。 offsetが0で、位置
パラメータが使用されている場合は、リストの先頭に$ 0が付きます。
31
jperelli

この純粋なbashソリューションが登場しなかったのは驚きです。

a="someletters_12345_moreleters.ext"
IFS="_"
set $a
echo $2
# prints 12345

IFSを以前の値、またはその後unset IFSに再設定することをお勧めします。

18
user1338062

Jor's answer(私にとってはうまくいきません)の答えを基にしてください。

substring=$(expr "$filename" : '.*_\([^_]*\)_.*')
18
PEZ

要件に従う

私は、x文字数のファイル名と、両側に1つのアンダースコアで囲まれた5桁の文字列、それからx文字数の別のセットを持っています。 5桁の数字をとり、それを変数に入れたいのです。

私は役に立つかもしれないいくつかのgrepの方法を見つけました:

$ echo "someletters_12345_moreleters.ext" | grep -Eo "[[:digit:]]+" 
12345

以上

$ echo "someletters_12345_moreleters.ext" | grep -Eo "[[:digit:]]{5}" 
12345

そして-Po構文で:

$ echo "someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d+' 
12345

または、5文字に合わせるようにしたい場合は、

$ echo "someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d{5}' 
12345

最後に、変数に格納するためにはvar=$(command)構文を使うだけです。

11
fedorqui

サブプロセスがなくても、次のことが可能です。

shopt -s extglob
front=${input%%_+([a-zA-Z]).*}
digits=${front##+([a-zA-Z])_}

これの非常に小さい変形はksh93でも動作します。

10
Darron

次の概念に焦点を当てると、
「(1つまたは複数の)数字の連続」

数値を抽出するためにいくつかの外部ツールを使用することができます。
他のすべての文字(sedまたはtr)を非常に簡単に消去できます。

name='someletters_12345_moreleters.ext'

echo $name | sed 's/[^0-9]*//g'    # 12345
echo $name | tr -c -d 0-9          # 12345

しかし、$ nameが数回連続している場合、上記は失敗します。

"name = someletters_12345_moreleters_323_end.ext"の場合、次のようになります。

echo $name | sed 's/[^0-9]*//g'    # 12345323
echo $name | tr -c -d 0-9          # 12345323

正規表現(regex)を使う必要があります。
sedおよびPerlで最初の実行のみ(3245ではなく12345)を選択するには、以下のようにします。

echo $name | sed 's/[^0-9]*\([0-9]\{1,\}\).*$/\1/'
Perl -e 'my $name='$name';my ($num)=$name=~/(\d+)/;print "$num\n";'

しかし、直接やることもできます bashで (1)

regex=[^0-9]*([0-9]{1,}).*$; \
[[ $name =~ $regex ]] && echo ${BASH_REMATCH[1]}

これにより、任意の長さの数字の最初のランを抽出することができます。
他のテキスト/文字で囲まれている。

regex=[^0-9]*([0-9]{5,5}).*$;は、正確に5桁の数字に一致します。 :-)

(1):各短いテキストに対して外部ツールを呼び出すよりも速いです。大きなファイルに対してsedやawkの内部ですべての処理を行うよりも速くはありません。

10
user2350426

これは、数字の最初のブロックに一致し、周囲のアンダースコアには依存しない、プレフィックスサフィックス解決法です(JBおよびDarronによって提供された解決法に似ています)。

str='someletters_12345_morele34ters.ext'
s1="${str#"${str%%[[:digit:]]*}"}"   # strip off non-digit prefix from str
s2="${s1%%[^[:digit:]]*}"            # strip off non-digit suffix from s1
echo "$s2"                           # 12345
9
codist

これが私のやり方です。

FN=someletters_12345_moreleters.ext
[[ $FN =~ _([[:digit:]]{5})_ ]] && NUM=${BASH_REMATCH[1]}

注:上記は正規表現であり、アンダースコアで囲まれた5桁の特定のシナリオに制限されています。異なるマッチングが必要な場合は、正規表現を変更してください。

8
nicerobot

私はsedのregexグループを扱う能力が大好きです。

> var="someletters_12345_moreletters.ext"
> digits=$( echo $var | sed "s/.*_\([0-9]\+\).*/\1/p" -n )
> echo $digits
12345

もう少し一般的なオプションは not で、数字シーケンスの始まりを示すアンダースコア_があると仮定します。したがって、たとえばシーケンスの前にあるすべての非数を取り除きます:s/[^0-9]\+\([0-9]\+\).*/\1/p


> man sed | grep s/regexp/replacement -A 2
s/regexp/replacement/
    Attempt to match regexp against the pattern space.  If successful, replace that portion matched with replacement.  The replacement may contain the special  character  &  to
    refer to that portion of the pattern space which matched, and the special escapes \1 through \9 to refer to the corresponding matching sub-expressions in the regexp.

あなたがあまりにも正規表現に自信がないのであれば、これについての詳細:

  • sは_s_ubstituteのためのものです
  • [0-9]+は1桁以上の数字に一致
  • \1は、正規表現出力のグループn.1にリンクします(この場合、グループ0が完全一致、グループ1が括弧内の一致です)。
  • pフラグは_p_rinting用です

sedの正規表現処理を機能させるために、すべてのエスケープ\があります。

6
Campa

与えられたtest.txtは "ABCDEFGHIJKLMNOPQRSTUVWXYZ"を含むファイルです。

cut -b19-20 test.txt > test1.txt # This will extract chars 19 & 20 "ST" 
while read -r; do;
> x=$REPLY
> done < test1.txt
echo $x
ST
5
Rick Osman

私の答えはあなたがあなたの弦から何を欲しているかについてもっとコントロールするでしょう。文字列から12345を抽出する方法についてのコードは、次のとおりです。

str="someletters_12345_moreleters.ext"
str=${str#*_}
str=${str%_more*}
echo $str

abcのような文字や_-のような特殊文字を含む何かを抽出したい場合、これはより効率的です。例:あなたの文字列がこのようなもので、someletters_の後から_moreleters.extの前にあるものすべてが欲しいなら:

str="someletters_123-45-24a&13b-1_moreleters.ext"

私のコードでは、あなたが望むものを正確に述べることができます。説明:

#*一致するキーを含む前の文字列を削除します。ここで私たちが言及したキーは_%です。それは一致するキーを含む次の文字列を削除します。ここで私たちが言及した鍵は '_more *'です

あなた自身でいくつかの実験をしてください、そしてあなたはこれが面白いと思うでしょう。

わかりました、ここに空のストリングとの純粋なパラメータ置換が行きます。警告は、 someletters および moreletters を文字としてのみ定義したことです。それらが英数字の場合、これはそのままでは機能しません。

filename=someletters_12345_moreletters.ext
substring=${filename//@(+([a-z])_|_+([a-z]).*)}
echo $substring
12345
2
morbeo

phpのsubstr( 'abcdefg'、2-1、3)と同じです。

echo 'abcdefg'|tail -c +2|head -c 3
2
diyism

Bash組み込みの 'expr'コマンドもあります。

INPUT="someletters_12345_moreleters.ext"  
SUBSTRING=`expr match "$INPUT" '.*_\([[:digit:]]*\)_.*' `  
echo $SUBSTRING
1
jor

少し遅れましたが、この問題に遭遇したところ、次のことがわかりました。

Host:/tmp$ asd=someletters_12345_moreleters.ext 
Host:/tmp$ echo `expr $asd : '.*_\(.*\)_'`
12345
Host:/tmp$ 

日付に%Nが含まれていない組み込みシステムでミリ秒単位の解像度を取得するために使用しました。

set `grep "now at" /proc/timer_list`
nano=$3
fraction=`expr $nano : '.*\(...\)......'`
$debug nano is $nano, fraction is $fraction
1
russell

Bashソリューション:

IFS="_" read -r x digs x <<<'someletters_12345_moreleters.ext'

これはxと呼ばれる変数を上書きします。 var xはvar _に変更することができます。

input='someletters_12345_moreleters.ext'
IFS="_" read -r _ digs _ <<<"$input"
1
user2350426