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


728

形式someletters_12345_moreleters.extでファイル名を指定して、5桁を抽出して変数に入れたいと思います。

つまり、要点を強調するために、x文字のファイル名と、両側に1つの下線で囲まれた5桁のシーケンスと、x文字の別のセットがあります。5桁の数字を変数に入れたいのですが。

これを実現できるさまざまな方法に非常に興味があります。


5
JBの答えは明らかに票を獲得しています-受け入れられた答えを変更する時?
ジェフ

3
質問があいまいであるため、ほとんどの回答が質問の答えになっていないようです。「x文字のファイル名があり、次に両側に1つのアンダースコアで囲まれた5桁のシーケンスがあり、次にx文字の別のセットがあるその定義によりabc_12345_def_67890_ghi_def、有効な入力です。何がしたいですか?5桁のシーケンスが1つしかないと仮定します。入力の定義に基づいて有効な入力が残っているabc_def_12345_ghi_jklか、1234567_12345_1234567または12345d_12345_12345e有効であり、以下の回答のほとんどはこれを処理しません。
gman

2
この質問には、具体的すぎる入力例があります。そのため、この特定のケースに対して多くの具体的な回答が得られました(数字のみ、同じ_区切り文字、ターゲット文字列を1回だけ含む入力など)。最高の(最も一般的かつ最速の)答えは、他の限定された答えが数百を持っていながら、10年後、わずか7 upvotesを持っています。私は😞開発者の信頼を失う作り
ダンDascalescu

回答:


692

カットを使用:

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

より一般的な:

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

1
より一般的な答えは、まさに私が探していたものです、ありがとう
Berek Bryan

71
-fフラグは、プログラマーが慣れている0ベースのインデックスではなく、1ベースのインデックスを取ります。
Matthew G

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

3
echo変数に不規則な空白やシェルのメタ文字を含めることができないことが確実でない限り、引数を二重引用符で囲む必要があります。詳細は、stackoverflow.com
questions / 10067266 /…を

'-f'の後の番号 '2'は、サブストリングの2番目のセットを抽出するようにシェルに指示することです。
Sandun、2018

1088

場合、xは定数であり、次のパラメータの拡張を行うには、抽出サブストリング:

b=${a:12:5}

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

数字の周りの下線のみが入力に含まれている場合は、2つのステップでプレフィックスとサフィックスを(それぞれ)削除できます。

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

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

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


18
@SpencerRathbun bash: ${${a#*_}%_*}: bad substitution私はGNUのbash 4.2.45に。
JB。

2
@jonnyB、過去に働いたことがあります。私は同僚からそれが止まったと言われ、彼らはそれをsedコマンドか何かに変更しました。歴史の中でそれを見て、私はshそれをおそらくダッシュだったスクリプトで実行していました。この時点では、もう機能させることができません。
Spencer Rathbun 2013年

22
JB、「12」がオフセット(ゼロベース)であり、「5」が長さであることを明確にする必要があります。また、それをすべてレイアウトする@gontardのリンクの+1!
Doktor J

1
これを「sh run.sh」としてスクリプト内で実行すると、Bad Substitutionエラーが発生する可能性があります。それを避けるために、その変更をrun.shの権限(chmodの+ X run.sh)とは、「./run.sh」としてスクリプトを実行
はAnkur

2
ところで、オフセットパラメータも負の値にできます。それをコロンに接着しないように注意する必要があります。そうしないと、bashはそれを:-「デフォルト値を使用する」置換として解釈します。したがって${a: -12:5}、最後から12文字の5文字、および${a: -12:-5}end-12とend-5の間の7文字が生成されます。
JB。

97

最初のシーケンスを使用して、ファイル名のどこにでも番号を指定できる一般的なソリューション:

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

変数の一部のみを抽出する別のソリューション:

number=${filename:offset:length}

ファイル名が常に形式であるstuff_digits_...場合、awkを使用できます。

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

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

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

2
ファイルの最後の行から数字/単語を抽出したい場合はどうすればよいですか?
Sahra

93

使用してみてください cut -c startIndx-stopIndx


2
startIndex-lastIndex-1のようなものはありますか?
Niklas

1
@Niklas in bash、proly startIndx-$((lastIndx-1))
brown.2179 2015

3
start=5;stop=9; echo "the rain in spain" | cut -c $start-$(($stop-1))
brown.2179 2015

1
問題は、パイプを使用して取得するため、入力が動的であるため、基本的には動的であるということです。git log --oneline | head -1 | cut -c 9-(end -1)
Niklas

これは、line=git log --oneline |のように2つの部分に分かれば、cutで実行できます。ヘッド-1` &&エコー$ line | cut -c 9-$(($ { git log --oneline | head -1 | sed -e 's/^[a-z0-9]* //g'
#line

34

より厳密な情報が必要な場合は、このように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}
              サブストリング展開。最大長の文字まで拡張
              オフセットで指定された文字から始まるパラメーター。もし
              長さは省略され、パラメータstart-の部分文字列に展開されます
              オフセットで指定された文字に移動します。長さとオフセットは
              算術式(以下の算術評価を参照)。もし
              オフセットはゼロより小さい数値に評価され、値が使用されます
              パラメータの値の末尾からのオフセットとして。算術
              -で始まる式は空白で区切る必要があります
              上記から:デフォルトの使用と区別される
              値の拡張。長さが次の数値に評価される場合
              ゼロであり、パラメータが@でなく、インデックス付きでも連想でもない
              配列、それは値の終わりからのオフセットとして解釈されます
              文字数ではなくパラメータの
              sionは、2つのオフセット間の文字です。パラメータが
              @、結果はオフで始まる長さ位置パラメータです
              セットする。パラメータが@が添え字付きのインデックス付き配列名の場合
              *、結果は配列のメンバーで始まる長さです
              $ {parameter [offset]}。負のオフセットは、
              指定された配列の最大インデックスよりも1つ大きい。サブ-
              連想配列に文字列拡張を適用すると、
              罰金の結果。負のオフセットは分離する必要があることに注意してください
              混乱しないように、コロンから少なくとも1スペース分
              :-展開。部分文字列のインデックスは、次の場合を除いてゼロベースです
              位置パラメータが使用されます。この場合、インデックス付け
              デフォルトでは1から始まります。オフセットが0で、位置
              パラメータが使用され、$ 0がリストの先頭に追加されます。

2
上記のように負の値に関する非常に重要な警告:-で始まる算術式は、先行する:から空白で区切る必要があり、[デフォルト値の使用]拡張と区別されます。だから、VARの最後の4つの文字を取得します${var: -4}
sshow

26

ここに私がそれをする方法があります:

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

説明:

Bash固有:

正規表現(RE): _([[:digit:]]{5})_

  • _ 一致する文字列の一致する境界を区別/アンカーするリテラル
  • () キャプチャグループを作成する
  • [[:digit:]] キャラクタークラスです。
  • {5} 前の文字、クラス(この例のように)、またはグループが正確に5つ一致する必要があることを意味します

英語では、これは次のように動作すると考えることができます。FN文字列は_、キャプチャグループが開かれ、5桁の一致が試行されるが表示されるまで、文字ごとに繰り返されます。この時点でその照合が成功した場合、キャプチャグループは通過した5桁を保存します。次の文字がである_場合、条件は成功し、キャプチャグループはで使用可能にBASH_REMATCHなり、次のNUM=ステートメントを実行できます。マッチングの一部が失敗した場合、保存された詳細は破棄され、の後に文字ごとの処理が続行されます_。たとえば、FNwhereの場合_1 _12 _123 _1234 _12345_、一致が見つかるまでに4つの誤った開始があります。


3
これは、私がしたように、複数のものを抽出する必要がある場合でも機能する一般的な方法です。
zebediah49 2013

3
これは確かに最も一般的な答えであり、受け入れられるべきです。これは、固定位置の文字列だけでなく、同じ区切り文字(を有効にするcut)の間の正規表現でも機能します。また、外部コマンドの実行に依存しません。
Dan Dascalescu、

1
この答えは犯罪的に過小評価されています。
chepner

これは素晴らしい!私はこれを、状況に応じて、異なる開始/停止dilimeter(_を置き換える)と可変長の数値({5}の場合は。)を使用するように調整しました。誰かがこの黒魔術を打ち破って説明できますか?
ポール

1
@Paul回答に詳細を追加しました。お役に立てば幸いです。
nicerobot

21

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

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

おそらく、IFSを以前またはunset IFSその後の値にリセットする必要があります。


1
純粋なbashソリューションではありません。純粋なシェル(/ bin / sh)で機能すると思います
kayn

5
1あなたがこれを解除することを避けるために別の方法で書くことができるIFSと位置パラメータを:IFS=_ read -r _ digs _ <<< "$a"; echo "$digs"
幸次郎

2
これはパス名拡張の対象です!(壊れているので)。
gniourf_gniourf 2015年

20

ジョーの答えに基づく(これは私にはうまくいきません):

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

12
正規表現は、何か複雑なことがあり、単にアンダースコアを数えるだけではcutそれができない場合の真の取引です。
Aleksandr Levchuk 2011

12

要件に従う

私は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)構文を使用するだけです。


2
今日ではegrepを使用する必要がないと思いますInvocation as 'egrep' is deprecated; use 'grep -E' instead。コマンド自体が警告します。回答を編集しました。
神経伝達物質2014年

11


「一連の(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で最初の実行(323ではなく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

サブプロセスなしでできること:

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

これの非常に小さなバリアントもksh93で機能します。


9

以下は、数字の最初のブロックに一致し、周囲の下線に依存しない接頭辞-接尾辞の解法(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

7

sed正規表現グループを処理するの機能が大好きです。

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

もう少し一般的なオプションは、数字シーケンスの開始を示すアンダースコアがあると想定しないことです。_たとえば、シーケンスの前に取得するすべての非数値を削除します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

私の答えは、あなたがあなたのストリングから何を望んでいるかをより詳細に制御するでしょう。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 *」です

いくつかの実験を自分で行うと、これは興味深いものになります。


6

与えられた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

これはその特定の入力に非常に特有です。一般的な質問(OPが尋ねるべき)に対する唯一の一般的な解決策は、正規表現使用することです。
Dan Dascalescu、

3

では、空の文字列を使用した純粋なパラメータ置換を行います。注意点は、私が定義されているということですsomelettersmorelettersを唯一の文字として。英数字の場合は、そのままでは機能しません。

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

2
すばらしいが、少なくともbash v4が必要
olibre 2015年


1

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

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

4
expr組み込みではありません。
gniourf_gniourf

1
また、で=~サポートされている演算子を考慮すると、これは必要ありません[[
chepner

1

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

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

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

JSとJavaの実装に似た包括的な終わり。これを望まない場合は+1を削除してください。

substring() {
    local str="$1" start="${2}" end="${3}"

    if [[ "$start" == "" ]]; then start="0"; fi
    if [[ "$end"   == "" ]]; then end="${#str}"; fi

    local length="((${end}-${start}+1))"

    echo "${str:${start}:${length}}"
} 

例:

    substring 01234 0
    01234
    substring 012345 0
    012345
    substring 012345 0 0
    0
    substring 012345 1 1
    1
    substring 012345 1 2
    12
    substring 012345 0 1
    01
    substring 012345 0 2
    012
    substring 012345 0 3
    0123
    substring 012345 0 4
    01234
    substring 012345 0 5
    012345

その他の呼び出し例:

    substring 012345 0
    012345
    substring 012345 1
    12345
    substring 012345 2
    2345
    substring 012345 3
    345
    substring 012345 4
    45
    substring 012345 5
    5
    substring 012345 6

    substring 012345 3 5
    345
    substring 012345 3 4
    34
    substring 012345 2 4
    234
    substring 012345 1 3
    123

どういたしまして。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.