Bash에서 부분 문자열 추출


728

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

요점을 강조하기 위해 x 개의 문자를 가진 파일 이름을 가진 다음 5 개의 문자 시퀀스를 한쪽의 밑줄로 묶은 다음 x 개의 문자 세트로 묶습니다. 5 자리 숫자를 가져 와서 변수에 넣고 싶습니다.

이 작업을 수행 할 수있는 다양한 방법에 관심이 있습니다.


5
JB의 답변이 투표에서 분명히 승리하고 있습니다-수락 된 답변을 변경할 때입니까?
Jeff

3
질문이 모호하기 때문에 대부분의 답변이 귀하의 질문에 답변하지 않는 것 같습니다. "x 개의 문자를 가진 파일 이름이 있고 그 뒤에 5 개의 문자가 하나의 밑줄로 묶인 다음 x 개의 문자가 또 다른 세트로 표시 됩니다. " 그 정의에 따르면 abc_12345_def_67890_ghi_def유효한 입력입니다. 무슨 일이 일어나고 싶어? 5 자리 시퀀스가 ​​하나만 있다고 가정 해 봅시다. 당신은 여전히이 abc_def_12345_ghi_jkl1234567_12345_1234567또는 12345d_12345_12345e유효 입력으로 입력의 당신의 정의를 기반으로 답변의 대부분은 아래이 문제를 처리하지 않습니다.
gman

2
이 질문에는 너무 구체적인 입력 예가 있습니다. 이 때문에이 특별한 경우에 대한 많은 답변 이 있습니다 (숫자 만, 동일한 _구분 기호, 대상 문자열을 한 번만 포함하는 입력 등). 제일 (가장 일반적이고 가장 빠른) 대답은 십년 만 7 upvotes, 기타 제한된 답변이있는 동안 수백 후, 있습니다. 개발자에 대한 믿음을 잃게 만듭니다 😞
Dan Dascalescu

답변:


691

사용 :

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/…
tripleee

'-f'다음의 숫자 '2'는 쉘에게 두 번째 서브 스트링 세트를 추출하도록 지시하는 것입니다.
Sandun

1086

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

b=${a:12:5}

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

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

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

22
JB에서는 "12"가 오프셋 (0부터 시작)이고 "5"가 길이임을 명확히해야합니다. 또한 @gontard의 링크에 +1하여 모두 배치합니다!
Doktor J

1
스크립트 내에서 이것을 "sh run.sh"로 실행하는 동안 불량 대체 오류가 발생할 수 있습니다. 이를 방지하려면 다음 변경 run.sh에 대한 권한 (chmod를 + X run.sh) 및 "./run.sh"로 스크립트 실행
Ankur

2
오프셋 매개 변수도 음수 일 수 있습니다 (BTW). 콜론에 붙지 않도록주의해야합니다. 그렇지 않으면 bash가이를 :-"기본값 사용"대체로 해석합니다 . 따라서 ${a: -12:5}끝에서 5 자 12 자, 끝 ${a: -12:-5}12와 끝 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 bash에서 proly startIndx-$((lastIndx-1))
brown.2179

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

1
문제는 기본적으로 파이프를 사용하기 때문에 입력이 동적이라는 것입니다. git log --oneline | head -1 | cut -c 9-(end -1)
Niklas

이것은 line=git log --oneline | head -1` && echo $ line | $ (($ {# 라인} -1))`하지만이 특별한 경우에 더 좋을 수도 사용하도록 - 컷 (9) -c 나오지 으로git log --oneline | head -1 | sed -e 's/^[a-z0-9]* //g'
brown.2179

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

2
위에서 언급 한 음수 값을 갖는 매우 중요한주의 사항 : -로 시작하는 산술 표현식은 앞의 공백과 공백으로 구분해야합니다. 기본값 사용 확장과 구별됩니다. var의 마지막 네 문자를 얻으려면 :${var: -4}
sshow

26

내가하는 방법은 다음과 같습니다.

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

설명:

배쉬 특정 :

정규 표현식 (RE) : _([[:digit:]]{5})_

  • _ 일치하는 문자열의 경계를 구분 / 고정하기위한 리터럴입니다.
  • () 캡처 그룹 만들기
  • [[:digit:]] 캐릭터 클래스라고 생각합니다.
  • {5} 이전 문자, 클래스 (이 예에서와 같이) 또는 그룹 중 정확히 5 개가 일치해야 함을 의미합니다.

영어로, 당신은 다음과 같이 행동 할 수 있습니다 : FN문자열은 우리가 볼 때까지 문자별로 반복됩니다_ 캡처 그룹이 열리는 지점을 5 자리를 맞추려고합니다. 일치하는 지점이이 지점에 도달하면 캡처 그룹은 이동 된 5 자리 숫자를 저장합니다. 다음 문자가 _인 경우 조건이 성공하고에서 캡처 그룹을 사용할 수 있으며 BASH_REMATCH다음 NUM=명령문을 실행할 수 있습니다. 일치하는 부분이 실패하면 저장된 세부 정보가 삭제되고 _. 예를 들어 FNwhere _1 _12 _123 _1234 _12345_인 경우 일치하는 항목을 찾기 전에 네 개의 잘못된 시작이 발생합니다.


3
이것은 내가 한 것처럼 둘 이상의 것을 추출해야하더라도 작동하는 일반적인 방법입니다.
zebediah49

3
이것은 실제로 가장 일반적인 답변이며 받아 들여야합니다. 고정 된 위치의 문자열이나 동일한 구분 기호 (활성화 cut) 사이의 정규 표현식에서만 작동합니다 . 또한 외부 명령 실행에 의존하지 않습니다.
Dan Dascalescu

1
이 답변은 범죄에 대한 반박입니다.
chepner 2016 년

대단해! 상황에 따라 다른 시작 / 중지 딜리 미터 (_ 대신)와 가변 길이 번호 (. {5}의 경우)를 사용하도록이를 조정했습니다. 누군가이 흑 마술을 분해하고 설명 할 수 있습니까?

1
@Paul 나는 내 답변에 더 자세한 내용을 추가했습니다. 희망이 도움이됩니다.
nicerobot

21

이 순수한 bash 솔루션이 나타나지 않았다는 것에 놀랐습니다.

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

IFS를 이전 또는 unset IFS이후의 값으로 재설정하려고합니다 .


1
그것은 순수 bash는 솔루션이 아니다, 나는 그것이 (/ 빈 / SH) 순수한 쉘에서 작동 생각
kayn

5
+1 IFS매개 변수 를 설정 해제 하고 위치 를 지정하지 않아도되는 다른 방법을 쓸 수 있습니다 .IFS=_ read -r _ digs _ <<< "$a"; echo "$digs"
kojiro

2
이것은 경로명 확장의 대상이됩니다! (그래서 고장났습니다).
gniourf_gniourf 오전

20

장님의 대답을 바탕으로 (나에게 효과적이지 않음) :

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

12
정규 표현식은 복잡한 것이 있고 밑줄을 세는 것만으로는 충분하지 않은 실제 거래 cut입니다.
Aleksandr Levchuk

12

요구 사항에 따라

나는 x 개의 문자를 가진 파일 이름을 가지고 있고 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. 답변을 수정했습니다.
신경 전달 물질

11

우리가 다음의 개념에 집중한다면 :
"하나 또는 여러 자리수의 런"

몇 가지 외부 도구를 사용하여 숫자를 추출 할 수 있습니다.
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

정규 표현식 (정규식)을 사용해야합니다.
sed 및 perl에서 첫 번째 실행 (12345가 아닌 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

2

PHP에서 substr ( 'abcdefg', 2-1, 3)과 유사합니다 :

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

이것은 해당 입력에 대해 매우 구체적입니다. 일반적인 질문에 대한 유일한 일반적인 해결책 (OP가 요청해야 함)은 정규 표현식사용하는 것 입니다.
Dan Dascalescu

1

bash 내장 'expr'명령도 있습니다.

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

4
expr내장되지 않습니다.
gniourf_gniourf

1
에서 =~지원 하는 연산자 에 비추어도 필요하지 않습니다 [[.
chepner 2016 년

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

배쉬 솔루션 :

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.