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 사이의 밑줄이 그어진 숫자)를 추출하고 싶습니다.

하지만 $var2는 $file_name과 같습니다. 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

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

또한 따옴표로 묶이지 않았기 echo $file_name때문에 $file_name단어 분할(기본적으로 공백, 탭 및 줄 바꿈이기도 한 모든 문자 $IFS)을 수행하고 결과 단어(파일 이름이 일치하는 문자를 포함하는 경우)가 됩니다 . 현재 디렉토리의 파일 이름과 쉘이 일치하는지 확인합니다. 파일 이름의 백슬래시는 사라지거나 원치 않는 영향을 미칠 수도 있습니다(확장자를 인용하더라도). 따옴표가 없으면 쉘은 ksh값에 대해 중괄호 확장도 수행합니다.$file_name

답변2

가장 먼저 주의할 점 sed텍스트기본적으로 한 번에 한 줄만 처리하는 유틸리티이지만 파일 이름에는 모든 문자(줄 바꿈 포함) 또는 문자가 아닌 문자(문자가 아닐 수 있음)도 포함될 수 있습니다.텍스트).

반품,따옴표가 없는 변수는 매우 특별한 의미를 갖습니다., 당신은 이것을 거의 원하지 않을 것입니다.잠재적으로 매우 위험함.

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

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

echoprintf다음을 사용하여 전체 출력을 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

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

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

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

하지만... 귀하의 사용 사례에 대해서는 다른 도구를 사용하는 것이 더 나을 것입니다...

관련 정보