Programing

Bash에서 부분 문자열 추출

lottogame 2020. 10. 2. 21:20
반응형

Bash에서 부분 문자열 추출


형식의 파일 이름이 주어지면 someletters_12345_moreleters.ext5 자리 숫자를 추출하여 변수에 넣고 싶습니다.

따라서 요점을 강조하기 위해 x 개의 문자가있는 파일 이름이 있고 그 다음에는 양쪽에 단일 밑줄로 둘러싸인 5 자리 시퀀스가 ​​있고 다른 x 개의 문자 집합이 있습니다. 5 자리 숫자를 가져 와서 변수에 넣고 싶습니다.

저는 이것이 달성 될 수있는 다양한 방법에 매우 관심이 있습니다.


사용 :

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

더 일반적인 :

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

경우 X는 상수 추출 부분 문자열 다음 파라미터 확장 행한다이다 :

b=${a:12:5}

여기서 12 는 오프셋 (0부터 시작)이고 5 는 길이입니다.

숫자 주변의 밑줄이 입력에서 유일한 경우, 두 단계로 접두사와 접미사를 (각각) 제거 할 수 있습니다.

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

다른 밑줄이 있으면 더 까다롭기는하지만 어쨌든 가능할 것입니다. 하나의 표현으로 두 확장을 수행하는 방법을 아는 사람이 있다면 저도 알고 싶습니다.

제시된 두 솔루션 모두 순수한 bash이며 프로세스 생성이 필요하지 않으므로 매우 빠릅니다.


다음 시퀀스 중 첫 번째 시퀀스를 사용하여 파일 이름의 어느 위치 에나 숫자가있을 수있는 일반적인 솔루션 :

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:]]')

그냥 사용 해봐 cut -c startIndx-stopIndx


더 엄격한 정보를 원하는 사람이 있다면 다음과 같이 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}
              부분 문자열 확장. 최대 길이의 문자로 확장됩니다.
              오프셋으로 지정된 문자에서 시작하는 매개 변수. 만약
              길이가 생략되고 매개 변수 시작의 하위 문자열로 확장됩니다.
              오프셋으로 지정된 문자에서 ing. 길이와 오프셋은
              산술 표현식 (아래의 산술 평가 참조). 만약
              오프셋이 0보다 작은 숫자로 평가되면 값이 사용됩니다.
              매개 변수 값의 끝에서 오프셋으로. 산수
              -로 시작하는 표현식은 공백으로 구분해야합니다.
              이전에서 : 기본값 사용과 구별
              가치 확장. 길이가 다음보다 작은 수로 평가되는 경우
              0이고 매개 변수가 @가 아니고 색인화되거나 연관되지 않음
              배열, 값의 끝에서 오프셋으로 해석됩니다.
              여러 문자가 아닌 매개 변수의
              sion은 두 오프셋 사이의 문자입니다. 매개 변수가
              @, 결과는 off에서 시작하는 길이 위치 매개 변수입니다.
              세트. 매개 변수가 @ 또는
              *, 결과는 다음으로 시작하는 배열의 길이 멤버입니다.
              $ {parameter [offset]}. 음의 오프셋은
              하나는 지정된 배열의 최대 인덱스보다 큽니다. 보결-
              연관 배열에 적용된 문자열 확장은 unde‐
              벌금형 결과. 음수 오프셋은 분리해야합니다.
              혼동을 피하기 위해 콜론에서 적어도 하나의 공백으로
              :-확장. 하위 문자열 인덱싱은
              위치 매개 변수가 사용됩니다.이 경우 인덱싱
              기본적으로 1부터 시작합니다. 오프셋이 0이고 위치
              매개 변수가 사용되며 $ 0이 목록 앞에 붙습니다.

jor의 답변을 기반으로 작성 (나에게 적합하지 않음) :

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

이 순수한 bash 솔루션이 나오지 않은 것에 놀랐습니다.

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

IFS를 이전 또는 unset IFS이후의 값으로 재설정하고 싶을 것입니다 !


방법은 다음과 같습니다.

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

Note: the above is a regular expression and is restricted to your specific scenario of five digits surrounded by underscores. Change the regular expression if you need different matching.


Following the requirements

I have a filename with x number of characters then a five digit sequence surrounded by a single underscore on either side then another set of x number of characters. I want to take the 5 digit number and put that into a variable.

I found some grep ways that may be useful:

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

or better

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

And then with -Po syntax:

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

Or if you want to make it fit exactly 5 characters:

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

Finally, to make it be stored in a variable it is just need to use the var=$(command) syntax.


Without any sub-processes you can:

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

A very small variant of this will also work in ksh93.


If we focus in the concept of:
"A run of (one or several) digits"

We could use several external tools to extract the numbers.
We could quite easily erase all other characters, either sed or tr:

name='someletters_12345_moreleters.ext'

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

But if $name contains several runs of numbers, the above will fail:

If "name=someletters_12345_moreleters_323_end.ext", then:

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

We need to use regular expresions (regex).
To select only the first run (12345 not 323) in sed and perl:

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

But we could as well do it directly in bash(1) :

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

This allows us to extract the FIRST run of digits of any length
surrounded by any other text/characters.

Note: regex=[^0-9]*([0-9]{5,5}).*$; will match only exactly 5 digit runs. :-)

(1): faster than calling an external tool for each short texts. Not faster than doing all processing inside sed or awk for large files.


Here's a prefix-suffix solution (similar to the solutions given by JB and Darron) that matches the first block of digits and does not depend on the surrounding underscores:

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

I love sed's capability to deal with regex groups:

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

A slightly more general option would be not to assume that you have an underscore _ marking the start of your digits sequence, hence for instance stripping off all non-numbers you get before your sequence: 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.

More on this, in case you're not too confident with regexps:

  • s is for _s_ubstitute
  • [0-9]+ matches 1+ digits
  • \1 links to the group n.1 of the regex output (group 0 is the whole match, group 1 is the match within parentheses in this case)
  • p flag is for _p_rinting

All escapes \ are there to make sed's regexp processing work.


Given test.txt is a file containing "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

My answer will have more control on what you want out of your string. Here is the code on how you can extract 12345 out of your string

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

This will be more efficient if you want to extract something that has any chars like abc or any special characters like _ or -. For example: If your string is like this and you want everything that is after someletters_ and before _moreleters.ext :

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

With my code you can mention what exactly you want. Explanation:

#* It will remove the preceding string including the matching key. Here the key we mentioned is _ % It will remove the following string including the matching key. Here the key we mentioned is '_more*'

Do some experiments yourself and you would find this interesting.


similar to substr('abcdefg', 2-1, 3) in php:

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

Ok, here goes pure Parameter Substitution with an empty string. Caveat is that I have defined someletters and moreletters as only characters. If they are alphanumeric, this will not work as it is.

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

There's also the bash builtin 'expr' command:

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

A little late, but I just ran across this problem and found the following:

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

I used it to get millisecond resolution on an embedded system that does not have %N for date:

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

A bash solution:

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

This will clobber a variable called x. The var x could be changed to the var _.

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

참고URL : https://stackoverflow.com/questions/428109/extract-substring-in-bash

반응형