문자열 교체를 위해 정규식과 AWK를 사용하는 방법은 무엇입니까?

문자열 교체를 위해 정규식과 AWK를 사용하는 방법은 무엇입니까?

파일에 텍스트가 있다고 가정합니다.

(bookmarks
("Chapter 1 Introduction 1" "#1"
("1.1 Problem Statement and Basic Definitions 23" "#2")
("Exercises 31" "#30")
("Notes and References 42" "#34"))
)

"각 숫자에 11을 추가하고, 있는 경우 각 행에 1을 추가하고 싶습니다 .

(bookmarks
("Chapter 1 Introduction 12" "#12"
("1.1 Problem Statement and Basic Definitions 34" "#13")
("Exercises 42" "#41")
("Notes and References 53" "#45"))
)

GNU AWK와 정규 표현식을 사용한 솔루션은 다음과 같습니다.

awk -F'#' 'NF>1{gsub(/"(\d+)\""/, "\1+11\"")}'

(\d+)\"즉, 로 대체하고 싶습니다 \1+10\". \1여기서 그룹은 으로 표시됩니다 (\d+). 그러나 이것은 작동하지 않습니다. 어떻게 작동하게 할 수 있나요?

gawk가 최선의 해결책이 아니라면, 또 무엇을 사용할 수 있습니까?

답변1

이것을 시도해 보세요(느림이 필요함).

awk '{a=gensub(/.*#([0-9]+)(\").*/,"\\1","g",$0);if(a~/[0-9]+/) {gsub(/[0-9]+\"/,a+11"\"",$0);}print $0}' YourFile

시험당신의 예를 들어보세요:

kent$  echo '(bookmarks
("Chapter 1 Introduction 1" "#1"
("1.1 Problem Statement and Basic Definitions 2" "#2")
("Exercises 30" "#30")
("Notes and References 34" "#34"))
)
'|awk '{a=gensub(/.*#([0-9]+)(\").*/,"\\1","g",$0);if(a~/[0-9]+/) {gsub(/[0-9]+\"/,a+11"\"",$0);}print $0}'   
(bookmarks
("Chapter 1 Introduction 12" "#12"
("1.1 Problem Statement and Basic Definitions 13" "#13")
("Exercises 41" "#41")
("Notes and References 45" "#45"))
)

두 숫자(예: 1"과 "#1")가 다르거나 패턴의 동일한 행에 더 많은 숫자가 있는 경우(예: 23"...32"..."#123") 행에 유의하세요.


고쳐 쓰다

@Tim(OP)은 같은 줄의 후속 숫자가 "다를 수 있다고 말했기 때문에 이전 솔루션을 일부 변경하여 새 예제에서 작동하도록 만들었습니다.

그런데, 이 예에서는 디렉토리 구조인 것 같아서 두 숫자의 차이점이 무엇인지 이해가 되지 않습니다. 첫 번째는 인쇄된 페이지 번호이고, 두 번째 #이 붙은 것은 페이지 색인입니다. 내가 맞나요?

무슨 일이 있어도 귀하의 요구 사항은 귀하가 가장 잘 알고 있습니다. 이제 여전히 gawk를 사용하는 새로운 솔루션입니다(읽기 쉽도록 명령을 여러 줄로 분할했습니다).

awk 'BEGIN{FS=OFS="\" \"#"}{if(NF<2){print;next;}
        a=gensub(/.* ([0-9]+)$/,"\\1","g",$1);
        b=gensub(/([0-9]+)\"/,"\\1","g",$2); 
        gsub(/[0-9]+$/,a+11,$1);
        gsub(/^[0-9]+/,b+11,$2);
        print $1,$2
}' yourFile

시험그리고 당신 것새로운예:

kent$  echo '(bookmarks
("Chapter 1 Introduction 1" "#1"
("1.1 Problem Statement and Basic Definitions 23" "#2")
("Exercises 31" "#30")
("Notes and References 42" "#34"))
)
'|awk 'BEGIN{FS=OFS="\" \"#"}{if(NF<2){print;next;}
        a=gensub(/.* ([0-9]+)$/,"\\1","g",$1);
        b=gensub(/([0-9]+)\"/,"\\1","g",$2); 
        gsub(/[0-9]+$/,a+11,$1);
        gsub(/^[0-9]+/,b+11,$2);
        print $1,$2
}'                        
(bookmarks
("Chapter 1 Introduction 12" "#12"
("1.1 Problem Statement and Basic Definitions 34" "#13")
("Exercises 42" "#41")
("Notes and References 53" "#45"))
)


편집 2@Tim의 의견을 바탕으로

(1) FS=OFS="\" \"#"은 입력 및 출력의 필드 구분 기호가 큰따옴표, 공백, 큰따옴표 및 #임을 의미합니까? 왜 큰따옴표를 두 번 지정해야 합니까?

입력 및 출력 부분 모두의 구분 기호가 정확합니다. 구분 기호를 다음과 같이 정의합니다.

" "#

원하는 두 숫자를 캡처하는 것이 더 쉽기 때문에 두 개의 큰따옴표가 있습니다(예제 입력을 기반으로).

(2)/.*([0-9]+)$/, $는 문자열의 끝을 나타냅니까?

정확히!

(3) gensub()의 세 번째 매개변수에서 "g"와 "G"의 차이점은 무엇입니까? G와 g 사이에는 차이가 없습니다. 이것을 살펴보십시오:

gensub(regexp, replacement, how [, target]) #
    Search the target string target for matches of the regular expression regexp. 
    If "how" is a string beginning with ‘g’ or ‘G’ (short for “global”), then 
        replace all matches of regexp with replacement.

이것은에서 온 것입니다http://www.gnu.org/s/gawk/manual/html_node/String-Functions.html. gensub의 자세한 사용법을 읽을 수 있습니다.

답변2

정규식 대체를 제공하는 거의 모든 도구와 달리 awk는 \1대체 텍스트와 같은 역참조를 허용하지 않습니다. GNU Awk를 사용하면 일치하는 그룹에 액세스할 수 있습니다match기능~, 그러나 or sub또는 와 함께 사용할 수는 없습니다 gsub.

또한 \1지원되는 경우에도 코드 조각은 +11숫자 계산을 수행하는 대신 문자열을 추가합니다. 또한 정규 표현식이 올바르지 않습니다. "42""and not 과 같은 항목이 일치하고 있습니다 "#42".

다음은 awk 솔루션입니다(경고, 테스트되지 않음). 한 줄에 한 번만 교체를 수행합니다.

awk '
  match($0, /"#[0-9]+"/) {
    n = substr($0, RSTART+2, RLENGTH-3) + 11;
    $0 = substr($0, 1, RSTART+1) n substr($0, RSTART+RLENGTH-1)
  }
  1 {print}'

Perl을 사용하면 더 쉬울 것입니다.

perl -pe 's/(?<="#)[0-9]+(?=")/$1+11/e'

답변3

awk수행할 수는 있지만 역참조를 사용하더라도 직접적이지는 않습니다.
GNU awk양식의 (부분) 역참조가 있습니다.뿌리.

의 인스턴스는 123"일시적으로 래핑 \x01되고 \x02수정되지 않은 것으로 표시됩니다( sub().co 의 경우).

또는 루프를 단계별로 실행하여 언제든지 후보를 변경할 수 있습니다. 이 경우 역참조 및 "괄호"는 필요하지 않지만 문자 인덱스는 추적해야 합니다.

awk '{$0=gensub(/([0-9]+)\"/, "\x01\\1\"\x02", "g", $0 )
      while ( match($0, /\x01[0-9]+\"\x02/) ) {
        temp=substr( $0, RSTART, RLENGTH )
        numb=substr( temp, 2, RLENGTH-3 ) + 11
        sub( /\x01[0-9]+\"\x02/, numb "\"" ) 
      } print }'

gensub다음은 배열 split\x01필드 구분 기호를 사용하는 또 다른 접근 방식입니다 (예:나뉘다).. \x02 배열 요소를 산술 덧셈의 후보로 표시합니다.

awk 'BEGIN{ ORS="" } {
     $0=gensub(/([0-9]+)\"/, "\x01\x02\\1\x01\"", "g", $0 )
     split( $0, a, "\x01" )
     for (i=0; i<length(a); i++) { 
       if( substr(a[i],1,1)=="\x02" ) { a[i]=substr(a[i],2) + 11 }
       print a[i]
     } print "\n" }'

답변4

(g)awk의 솔루션이 상당히 복잡해지기 때문에 Perl에 대체 솔루션을 추가하고 싶었습니다.

perl -wpe 's/\d+(?=")/$&+11/eg' < in.txt > out.txt

설명하다:

  • 경고를 활성화하는 옵션 -w(이렇게 하면 부작용이 발생할 수 있음을 경고합니다).
  • Option은 -p코드 주위를 순환하는 것을 의미하며 sed 또는 awk처럼 작동하여 각 입력 줄을 기본 변수에 자동으로 저장합니다 $_.
  • 옵션은 -ePerl에게 스크립트 파일이 아닌 명령줄에서 코드를 프로그래밍하도록 지시합니다.
  • 코드는 교체( s/.../.../) 에 대한 정규 표현식입니다 $_. 숫자 시퀀스 뒤에 가 오면 "숫자에 11을 더한 것으로 해석되는 해당 시퀀스로 대체됩니다.
  • 이것너비가 0인 긍정적 예측 어설션 (?=pattern)찾으십시오. 하지만 "일치 항목으로 가져오지 마십시오. 그러면 교체 시 반복할 필요가 없습니다. 대체의 MATCH 변수에는 $&숫자만 포함됩니다.
  • /e정규식에 대한 수정자는 perl대체가 문자열이 아닌 코드로 "수행"되도록 지시합니다.
  • 수정자는 /g교체를 "전역"으로 만들고 행의 모든 ​​항목에서 이를 반복합니다.

불행하게도 MATCH 변수는 $&Perl 5.20 이전 버전의 코드 성능을 저하시킵니다. 더 빠르고 복잡하지 않은 솔루션은 그룹화 및 역참조를 사용하는 것입니다 $1.

perl -wpe 's/(\d+)?="/$1+11/eg' < in.txt > out.txt

예측 어설션이 너무 혼란스러우면 따옴표를 명시적으로 바꿀 수도 있습니다.

perl -wpe 's/(\d+)"/$1+11 . q{"}/eg' < in.txt > out.txt

관련 정보