파일에 텍스트가 있다고 가정합니다.
(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처럼 작동하여 각 입력 줄을 기본 변수에 자동으로 저장합니다$_
. - 옵션은
-e
Perl에게 스크립트 파일이 아닌 명령줄에서 코드를 프로그래밍하도록 지시합니다. - 코드는 교체(
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