sed를 사용하여 여러 줄 문자열을 바꾸려면 어떻게해야합니까?


243

\n사용하여 대체 패턴을 추가 하면 패턴 sed이 일치하지 않습니다. 예:

$ cat > alpha.txt
This is
a test
Please do not
be alarmed

$ sed -i'.original' 's/a test\nPlease do not/not a test\nBe/' alpha.txt

$ diff alpha.txt{,.original}

$ # No differences printed out

이 기능을 작동 시키려면 어떻게해야합니까?


여기에 스마트 해결 방법 : unix.stackexchange.com/a/445666/61742 . 물론 성능이 떨어집니다! 필요에 따라 교체를 수행하는 다른 좋은 옵션은 awk, perl 및 python입니다. 다른 것들도 많지만 awk가 다양한 Linux 배포판에서 가장 보편적이라고 생각합니다. 감사!
Eduardo Lucio

답변:


235

간단한 전화에서 나오지도 , 그것을 가지고 하나 개 , 즉 패턴 공간, 텍스트의 라인을. \n입력에서 구분 된 한 줄의 텍스트 패턴 공간의 한 줄에는 없습니다 \n... 그래서 정규 표현식이 아무것도 찾지 못하는 이유입니다.

패턴 공간으로 여러 줄을 읽고 놀랍게도 잘 처리 할 수 ​​있지만 일반적인 노력보다 더 많은 노력이 필요합니다. Sed에는 이러한 유형의 작업을 허용하는 명령 세트가 있습니다. 여기 에 sed명령 요약 링크가 있습니다. . 내가 찾은 최고의 것인데, 나는 굴러 갔다.

그러나 sed의 마이크로 명령을 사용하기 시작하면 "한 줄짜리"아이디어를 잊어 버리십시오. 느낌이들 때까지 구조화 된 프로그램처럼 배치하는 것이 유용합니다 ... 놀랍도록 간단하고 똑같이 특이합니다. 텍스트 편집의 "어셈블러 언어"로 생각할 수 있습니다.

요약 : sed를 간단한 것, 그리고 조금 더 사용하기 위해 사용하십시오. 그러나 일반적으로 한 줄로 작업 할 수 없을 때 대부분의 사람들은 다른 것을 선호합니다. 다른
누군가가 다른 것을 제안하게 할 것입니다. 최선의 선택이 무엇인지 확실하지 않습니다 (sed를 사용하지만 펄을 충분히 알지 못하기 때문입니다.)


sed '/^a test$/{
       $!{ N        # append the next line when not on the last line
         s/^a test\nPlease do not$/not a test\nBe/
                    # now test for a successful substitution, otherwise
                    #+  unpaired "a test" lines would be mis-handled
         t sub-yes  # branch_on_substitute (goto label :sub-yes)
         :sub-not   # a label (not essential; here to self document)
                    # if no substituion, print only the first line
         P          # pattern_first_line_print
         D          # pattern_ltrunc(line+nl)_top/cycle
         :sub-yes   # a label (the goto target of the 't' branch)
                    # fall through to final auto-pattern_print (2 lines)
       }    
     }' alpha.txt  

여기에는 동일한 스크립트가 있으며, 읽기 및 작업하기가 더 어려운 것으로 요약되어 있지만 일부는 확실하게 하나의 라이너를 호출 합니다.

sed '/^a test$/{$!{N;s/^a test\nPlease do not$/not a test\nBe/;ty;P;D;:y}}' alpha.txt

내 명령 "치트 시트"입니다

:  # label
=  # line_number
a  # append_text_to_stdout_after_flush
b  # branch_unconditional             
c  # range_change                     
d  # pattern_delete_top/cycle          
D  # pattern_ltrunc(line+nl)_top/cycle 
g  # pattern=hold                      
G  # pattern+=nl+hold                  
h  # hold=pattern                      
H  # hold+=nl+pattern                  
i  # insert_text_to_stdout_now         
l  # pattern_list                       
n  # pattern_flush=nextline_continue   
N  # pattern+=nl+nextline              
p  # pattern_print                     
P  # pattern_first_line_print          
q  # flush_quit                        
r  # append_file_to_stdout_after_flush 
s  # substitute                                          
t  # branch_on_substitute              
w  # append_pattern_to_file_now         
x  # swap_pattern_and_hold             
y  # transform_chars                   

167
지금 쏴 최악의 구문!
길리

53
이것은 환상적인 설명이지만 @Gili에 동의하는 경향이 있습니다.
gatoatigrado

11
치트 시트에 모든 것이 있습니다.
konsolebox

3
t여기서 명령 을 사용하기 위해 레이블이 필요하지 않습니다. 레이블을 지정하지 않으면 기본적으로 스크립트 끝으로 분기됩니다. 그래서 sed '/^a test$/{$!{N;s/^a test\nPlease do not$/not a test\nBe/;t;P;D}}' alpha.txt정확하게 모든 상황에서 명령과 동일한다. 물론이 특정 파일에 sed '/test/{N;s/.*/not a test\nBe/}' alpha.txt대해서도 동일한 작업을 수행하지만 첫 번째 예제는 가능한 모든 파일에 논리적으로 동일 합니다. 또한 \n대체 문자열에서 줄 바꿈이 생성되지 않습니다. 이를 위해서는 백 슬래시`\`와 실제 줄 바꿈이 필요합니다.
와일드 카드

9
구문은 GNU에 따라 다릅니다 ( #명령의 \nRHS에서 이전 명령과 구분되지 않음 s). GNU 를 사용하면 NUL로 구분 된 레코드를 사용할 sed수 있습니다 -z(그리고 텍스트로 정의 된 경우 전체 입력에서 slurp (정의상 NUL을 포함하지 않음)).
Stéphane Chazelas

181

perl대신에 사용하십시오 sed:

$ perl -0777 -i.original -pe 's/a test\nPlease do not/not a test\nBe/igs' alpha.txt
$ diff alpha.txt{,.original}
2,3c2,3
< not a test
< Be
---
> a test
> Please do not

-pi -e표준 "제자리에서 교체"명령 줄 순서이며 -0777은 perl이 파일 전체를 제거합니다. 자세한 정보는 perldoc perlrun 을 참조하십시오 .


3
감사! 여러 줄 작업을 위해 펄이 손을 내립니다! `$ perl -pi -e 's / bar / baz /'fileA`를 사용하여 파일을 변경했습니다.
Nicholas Tolley Cottrell

3
원래 포스터가 sedawk 또는 perl을 사용하여 요청 하고 응답하는 것이 매우 일반적입니다 . 나는 그것이 주제가 아니라고 생각하므로 미안하지만, 마이너스를 발사했습니다.
Rho Phi

68
+1 & Roberto에 동의하지 않습니다. 더 나은 방법을 무지하기 위해 특별히 언급 된 질문이 있습니다. 실질적인 맥락 차이 (예 : 여기)가없는 경우, 최적의 솔루션은 최소한 질문 별 솔루션만큼 프로파일을 가져야합니다.
geotheory

56
sed위의 대답은 Perl의 대답이 주제라는 것을 증명 한다고 생각합니다 .
reinierpost 10

7
조금 더 쉬움 : "-p0e"를 사용하면 "-0777"이 필요하지 않습니다. unix.stackexchange.com/a/181215/197502
Weidenrinde

96

\n기호를 다른 기호 로 바꾸고 평소대로 작동하는 것이 좋습니다 .

예 : 작동하지 않는 소스 코드 :

cat alpha.txt | sed -e 's/a test\nPlease do not/not a test\nBe/'

다음으로 변경할 수 있습니다.

cat alpha.txt | tr '\n' '\r' | sed -e 's/a test\rPlease do not/not a test\rBe/'  | tr '\r' '\n'

아무도 모른다면 \n유닉스 라인 엔딩, \r\n윈도우, \r클래식 맥 OS입니다. 일반적인 UNIX 텍스트는 \r기호를 사용하지 않으므로이 경우에 사용하는 것이 안전합니다.

이국적인 기호를 사용하여 \ n을 임시로 바꿀 수도 있습니다. 예를 들어-\ f (양식 피드 기호). 여기에서 더 많은 기호를 찾을 수 있습니다 .

cat alpha.txt | tr '\n' '\f' | sed -e 's/a test\fPlease do not/not a test\fBe/'  | tr '\f' '\n'

11
이 영리한 핵에 +1! 편집중인 파일의 내용이 확실하지 않은 경우 이국적인 기호를 사용하여 줄 바꿈을 임시로 대체하는 데 유용한 정보가 특히 유용합니다.
L0j1k

이 대신 OS X에서 작성된, 하나의 모든 인스턴스를 교체 할 필요가 작동하지 않습니다 \r에 인수에 sed$(printf '\r').
abeboparebop

@abeboparebop : 큰 발견! 👍 또는 homebrew를 사용하여 GNU sed를 설치하십시오. stackoverflow.com/a/30005262
ssc

@abeboparebop, OSX에서는 $sed 문자열 앞에을 추가하여을 ( 를)로 변환하지 못하도록해야 \r합니다 r. 간단한 예 : sed $'s/\r/~/'. 전체 예 :cat alpha.txt | tr '\n' '\r' | sed $'s/a test\rPlease do not/not a test\rBe/' | tr '\r' '\n'
wisbucky

40

모든 것을 고려하여, 전체 파일을 삼키고는 갈 수있는 가장 빠른 방법이 될 수 있습니다.

기본 구문은 다음과 같습니다.

sed -e '1h;2,$H;$!d;g' -e 's/__YOUR_REGEX_GOES_HERE__...'

파일이 엄청 나면 전체 파일을 고 블링하는 것이 옵션이 아닐 수 있습니다. 그러한 경우, 여기에 제공된 다른 답변은 작은 메모리 공간에서 작동하도록 보장되는 맞춤형 솔루션을 제공합니다.

다른 모든 핵 및 슬래시 상황의 경우, -e '1h;2,$H;$!d;g'원래 sed정규 표현식 인수 앞에 붙는 것만으로도 작업이 완료됩니다.

예 :

$ echo -e "Dog\nFox\nCat\nSnake\n" | sed -e '1h;2,$H;$!d;g' -re 's/([^\n]*)\n([^\n]*)\n/Quick \2\nLazy \1\n/g'
Quick Fox
Lazy Dog
Quick Snake
Lazy Cat

무엇을 -e '1h;2,$H;$!d;g'합니까?

1, 2,$, $!부품 라인이 직접 다음 명령이 실행 라인이 한도를 지정자입니다.

  • 1: 첫 줄만
  • 2,$: 두 번째부터 시작하는 모든 줄
  • $!: 마지막 이외의 모든 줄

확장되어 N 라인 입력의 각 라인에서 발생합니다.

  1: h, d
  2: H, d
  3: H, d
  .
  .
N-2: H, d
N-1: H, d
  N: H, g

g명령 라인에서 지정된 것이 아니라, 위의 d명령은 특별한 조항이있다 " 시작 다음주기를. "이것은 방지 g마지막을 제외한 모든 라인에서 실행.

각 명령의 의미는 다음과 같습니다.

  • h다음 H각 광고 카피들로의 입력 라인을 상기 sed집의 보류 공간 . (임의의 텍스트 버퍼를 생각하십시오.)
  • 그런 d다음 각 행을 버려서 해당 행이 출력에 기록되지 않도록합니다. 그러나 홀드 공간 은 유지됩니다.
  • 마지막으로, 맨 마지막 줄에 g로부터 모든 라인의 축적 복원 보류 공간을 그 때문에 sed(오히려 라인 한번에 한 방식에 비해) 전체 입력에서의 정규 표현식을 실행할 수 있으며, 따라서 할 수있다 에 일치합니다 \n.

38

sed: 세 가지 명령 멀티 라인 작업을 관리 할 수있다 N, D그리고 P(에 비교 정상 n , dp).

이 경우 패턴의 첫 번째 행을 일치시키고 N두 번째 행을 패턴 공간 에 추가 한 다음 s대체를 수행 하는 데 사용할 수 있습니다.

다음과 같은 것 :

/a test$/{
  N
  s/a test\nPlease do not/not a test\nBe/
}

2
대단해! 허용 된 답변보다 간단하고 여전히 효과적입니다.
jeyk

그리고 홀드 공간 (관련된 모든 것들 G, H, x...)를. s명령 을 사용하여 패턴 공간에 더 많은 선을 추가 할 수 있습니다 .
Stéphane Chazelas


이 솔루션은 다음과 같은 경우에는 작동하지 않습니다. "This is \ na test \ na test \ n 경고하지 마십시오"
mug896

@ mug896 당신은 아마 여러 N명령 이 필요할 것입니다
loa_in_

15

당신은 할 수 있지만 어렵습니다 . 다른 도구로 전환하는 것이 좋습니다. 바꾸려는 텍스트의 어떤 부분과도 일치하지 않는 정규식이 있다면 GNU awk에서 awk 레코드 구분 기호로 사용할 수 있습니다.

awk -v RS='a' '{gsub(/hello/, "world"); print}'

검색 문자열에 연속 된 줄 바꿈이 두 개 이상없는 경우 awk의 "단락 모드"(하나 이상의 빈 줄이 별도의 레코드)를 사용할 수 있습니다.

awk -v RS='' '{gsub(/hello/, "world"); print}'

쉬운 해결책은 Perl을 사용하고 파일을 메모리에 완전히로드하는 것입니다.

perl -0777 -pe 's/hello/world/g'

1
perl 명령을 파일에 적용하는 방법?
sebix

2
@sebix perl -0777 -pe '…' <input-file >output-file. 파일을 제자리에 수정하려면perl -0777 -i -pe '…' filename
Gilles

3
또한 GNU보기 sed-z(그 대답이 게시 된 후 2012 년 추가) 옵션을 : seq 10 | sed -z 's/4\n5/a\nb/'.
Stéphane Chazelas

7

나는 이것이 2 줄 일치에 대한 sed 솔루션이라고 생각합니다.

sed -n '$!N;s@a test\nPlease do not@not a test\nBe@;P;D' alpha.txt

3 줄 일치를 원하면 ...

sed -n '1{$!N};$!N;s@aaa\nbbb\nccc@xxx\nyyy\nzzz@;P;D'

4 줄 일치를 원한다면 ...

sed -n '1{$!N;$!N};$!N;s@ ... @ ... @;P;D'

"s"명령의 교체 부품이 줄을 줄이면 이와 같이 조금 더 복잡해집니다

# aaa\nbbb\nccc shrink to one line "xxx"

sed -n '1{$!N};$!N;/aaa\nbbb\nccc/{s@@xxx@;$!N;$!N};P;D'

repacement 부분이 선을 키우면 다음과 같이 조금 더 복잡해집니다

# aaa\nbbb\nccc grow to five lines vvv\nwww\nxxx\nyyy\nzzz

sed -n '1{$!N};$!N;/aaa\nbbb\nccc/{s@@vvv\nwww\nxxx\nyyy\nzzz@;P;s/.*\n//M;P;s/.*\n//M};P;D'

이것은 정상에 나아갈 것입니다! 방금 두 줄로 대체하기 위해 "-n"대신 "-i"를 사용했습니다. 왜냐하면 그것은 필자가 필요로하는 것이기 때문입니다.
Nagev

5
sed -i'.original' '/a test/,/Please do not/c not a test \nBe' alpha.txt

여기 /a test/,/Please do not/에서 (여러 줄) 텍스트 블록으로 간주되며 c, 변경 명령 다음에 새 텍스트가 있습니다.not a test \nBe

바꿀 텍스트가 매우 길면 ex 구문을 제안 합니다.


문제는 sed가 / a test /와 / Please do not / 사이의 모든 최종 텍스트를 대체한다는 것입니다. : :
noonex

4
sed -e'$!N;s/^\(a test\n\)Please do not be$/not \1Be/;P;D' <in >out

입력 창을 약간 넓 힙니다.

꽤 쉽습니다. 표준 치환 외에; 당신은 단지 필요 $!N, P그리고 D여기.


4

Perl 외에도 스트림 (및 파일)의 여러 줄 편집을위한 일반적이고 편리한 접근 방식은 다음과 같습니다.

먼저 원하는대로 새로운 고유 줄 구분 기호를 만듭니다.

$ S=__ABC__                     # simple
$ S=__$RANDOM$RANDOM$RANDOM__   # better
$ S=$(openssl rand -hex 16)     # ultimate

그런 다음 sed 명령 (또는 다른 도구)에서 \ n을 $ {S}로 바꿉니다.

$ cat file.txt | awk 1 ORS=$S |  sed -e "s/a test${S}Please do not/not a test\nBe/" | awk 1 RS=$S > file_new.txt

(awk는 ASCII 줄 구분 기호를 당신의 것으로 바꾸고 그 반대도 마찬가지입니다.)


2

이것은 OS X (10.10을 사용하고 있습니다)에서 작동하도록 xara의 영리한 답변을 약간 수정 한 것입니다.

cat alpha.txt | tr '\n' '\r' | sed -e 's/a test$(printf '\r')Please do not/not a test$(printf '\r')Be/'  | tr '\r' '\n'

을 명시 적으로 사용하는 대신 \r을 사용해야 $(printf '\r')합니다.


1
printf '\r'(또는 echo -e '\r')가 제대로 작동 하는 동안 쉘 구문 $'\r'을 사용하여 이스케이프 된 리터럴을 참조 할 수 있습니다 . 예를 들어, echo hi$'\n'there사이의 개행을 메아리 hithere. 각 백 슬래시 있도록 마찬가지로 전체 문자열을 포장 할 수 있습니다 \ : 그 이후 문자 탈출합니다echo $'hi\nthere'
Dejay 클레이튼

1

sed를 사용하여 파일에 몇 줄의 HTML을 추가하고 싶었습니다. 일반적으로 나는 단지 perl을 사용하지만, sed, bash 및 그 밖의 다른 많은 상자에있었습니다. 문자열을 한 줄로 변경하고 bash / sed가 \ t \ n을 보간하면 모든 것이 잘 작동한다는 것을 알았습니다.

HTML_FILE='a.html' #contains an anchor in the form <a name="nchor" />
BASH_STRING_A='apples'
BASH_STRING_B='bananas'
INSERT="\t<li>$BASH_STRING_A<\/li>\n\t<li>$BASH_STRING_B<\/li>\n<a name=\"nchor\"\/>"
sed -i "s/<a name=\"nchor"\/>/$INSERT/" $HTML_FILE

큰 따옴표와 슬래시를 피하는 기능을 사용하는 것이 더 깨끗하지만 때로는 추상화가 시간의 도둑입니다.


1

GNU sed에는 -zOP가 적용하려고 시도한 구문을 사용할 수 있는 옵션이 있습니다. ( 맨 페이지 )

예:

$ cat alpha.txt
This is
a test
Please do not
be alarmed
$ sed -z 's/a test\nPlease do not\nbe/not a test\nBe/' -i alpha.txt
$ cat alpha.txt
This is
not a test
Be alarmed

주의 : 사용 ^하고 $이제 NUL 문자 ( \n)로 구분 된 줄의 시작과 끝과 일치하는 경우 . 또한 모든 ( \n-로 구분 된) 줄 에서 일치하는 항목을 대체하려면 g전역 대체 (예 :)에 플래그 를 사용하는 것을 잊지 마십시오 s/.../.../g.


크레딧 : @ stéphane-chazelas 는 위의 주석에서 -z를 처음 언급했습니다.


0

Sed는 줄 바꿈에서 입력을 중단합니다. 루프 당 한 줄만 유지합니다.
따라서 \n패턴 공간에 패턴 공간이 없으면 (개행) 과 일치하는 방법이 없습니다 .

그러나 루프를 사용하여 sed 가 패턴 공간에 두 개의 연속적인 선을 유지할 수있는 방법이 있습니다 .

sed 'N;l;P;D' alpha.txt

N과 P 사이에 필요한 처리를 추가하십시오 ( l).

이 경우 (2 줄) :

$ sed 'N;s/a test\nPlease do not/not a test\nBe/;P;D' alpha.txt
This is
not a test
Be
be alarmed

또는 세 줄로 :

$ sed -n '1{$!N};$!N;s@a test\nPlease do not\nbe@not a test\nDo\nBe@;P;D' alpha.txt 
This is
not a test
Do
Be alarmed

그것은 같은 양의 줄이 바뀌 었다고 가정합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.