sed에서 탐욕스럽지 않은 일치

sed에서 탐욕스럽지 않은 일치

Bash 스크립트에는 다음 변수가 있습니다.

file_name='this_is_the_hart_part.csv'

사용

var2=$(echo $file_name | sed -e 's/_{2}\(.*\)_{3}/\1/')

하위 문자열 "the"(변수 $file_name의 밑줄 숫자 2와 3 사이)를 추출하고 싶습니다.

하지만 $file_name과 동일한 $var2를 돌려받습니다. sed 명령을 어떻게 변경해야 합니까?

답변1

에서 지원하는 정규 표현식 유형은 sed과의 비탐욕적 일치를 허용하지 않습니다 *.

세 번째로 구분된 필드를 얻고 싶습니다 _. 다음을 사용하면 가장 쉽습니다 cut.

cut -d '_' -f 3

또는 다음을 사용하여 awk:

awk -F '_' '{ print $3 }'

또는 셸에서 처음 두 개의 필드를 연속적으로 제거한 다음 끝 부분을 잘라냅니다.

str=${file_name#*_}
str=${str#*_}
str=${str%%_*}

"$str"the마지막에 하는 말이겠지 . 이 마지막 변형을 사용하는 것이 이 세 가지 중에서 가장 빠르고 가장 강력한 방법이 될 것입니다.

변수 대체를 수행하면 첫 번째 밑줄을 포함하여 선행 비트가 제거된 ${variable#*_}문자열이 생성됩니다 . $variable${variable%%_*}첫 번째 밑줄부터 끝까지 모든 것을 제거합니다 $variable. 이는 표준 변수 대체입니다.

파일 이름에 변수 대체를 사용하면 개행 문자가 포함된 파일 이름에 대처할 수 있다는 이점 awksed있습니다 cut. 일반적으로 파일 이름에는 줄 중심 텍스트 편집 도구를 사용하지 마십시오.

게다가 을(를) 사용하고 있습니다 echo $file_name. 는 인용되지 않았 으므로 단어 분할( 기본적으로 공백, 탭 및 개행 문자 $file_name의 일부이기도 한 모든 문자에서)을 거치고 생성된 단어에 파일 이름 글로빙 문자가 포함된 경우 현재 디렉터리의 파일 이름과 일치됩니다. $IFS껍질로. 그리고 파일 이름의 백슬래시도 사라지거나 원치 않는 효과를 가져올 수 있습니다(확장을 인용하더라도). 쉘 은 또한 인용되지 않은 ksh값에 대해 중괄호 확장을 수행합니다 .$file_name

답변2

첫 번째 메모 sed텍스트기본적으로 한 번에 한 줄씩 작동하는 유틸리티입니다. 파일 이름에는 모든 문자(개행 포함)는 물론 문자가 아닌 문자(문자가 아닐 수도 있음)를 포함할 수 있습니다.텍스트).

또한,변수를 따옴표로 묶지 않은 상태로 두는 것은 매우 특별한 의미를 갖습니다., 당신은 거의 그렇게 하고 싶지 않을 것입니다.잠재적으로 매우 위험함.

또한,echo임의의 데이터를 출력하는 데 사용할 수 없습니다 . printf대신 사용하세요..

또한 Bourne과 유사한 쉘의 변수 할당 구문은 var=value, 가 아닙니다 $var=value.

다음을 사용하여 의 전체 출력 echo(또는 더 나은 )을 의 패턴 공간 printf에 로드할 수 있습니다.sed

printf '%s\n' "$filename" | sed -e :1 -e '$!{N;b1' -e '}'

그런 다음 두 번째와 세 번째 사이의 부분을 추출하는 코드를 추가할 수 있습니다 _.

var2=$(
  printf '%s\n' "$filename" |
   sed -ne :1 -e '$!{N;b1' -e '}' -e 's/^\([^_]*_\)\{2\}\([^_]*\)_.*/\2/p'
)

탐욕스럽지 않은 부분은 [^_]*(비문자의 시퀀스 )을 사용하여 해결됩니다. 이는 우리가 과거 경계와 일치하지 않는다는 보장 _과는 반대로 (비록 많은 구현에서 여전히 비문자로 인해 질식하게 됩니다)..*_

이 경우 대신 쉘 매개변수 확장 연산자를 사용할 수 있습니다.

case $filename in
  (*_*_*_*) var2=${filename#*_*_}; var2=${var2%%_*};;
  (*)       var2=;;
esac

파일 이름이 텍스트가 아니거나 추출하려는 부분이 개행 문자로 끝나는 경우 더 잘 작동합니다(또한 더 효율적입니다).

일부 쉘에는 다음과 같은 고급 연산자가 zsh있거나 ksh93다음과 같은 고급 연산자가 있습니다.

  • zsh:

    분할하여 _세 번째 필드를 얻습니다.

    var2=${"${(@s:_:)filename}"[3]}
    

    및 역참조 사용 ${var/pattern/replacement}(이 경우 변수에 밑줄이 3개 이상 포함되어 있는지 또는 대체 항목이 없는지 먼저 확인해야 합니다).

    set -o extendedglob
    var2=${filename/(#b)*_*_(*)_*/$match[1]}
    
  • ksh93:

    var2=${filename/*_*_@(*)_*/\1}
    

답변3

sed@Kusalananda는 잘못된 도구이며 탐욕스럽지 않은 일치를 수행할 수 없다는 것이 맞습니다 . 그러나 탐욕스럽지 않은 [^_]*일치 에 대한 해결 방법을 사용할 수 있습니다. _

따라서 귀하의 경우 다음과 같이 할 수 있습니다.

printf '%s\n' "$file_name" | sed -e 's/^[^_]*_[^_]*_\([^_]*\).*$/\1/g'

하지만... 귀하의 사용 사례에는 다른 도구를 사용하는 것이 더 좋습니다...

관련 정보