Sed — 파일에서 단어의 첫 k 개 인스턴스를 바꿉니다.


24

k단어 의 첫 번째 인스턴스 만 바꾸고 싶습니다 .

어떻게해야합니까?

예 : 파일 foo.txt에 단어 'linux'가 100 번 나타납니다.

처음 50 번만 교체해야합니다.



특별히 sed가 필요합니까, 아니면 다른 도구를 사용할 수 있습니까? 명령 행에서 작업해야합니까, 아니면 텍스트 편집기가 허용됩니까?
evilsoup

명령 행에서 작동하는 것은 허용됩니다.
narendra-choudhary

답변:


31

아래의 첫 번째 섹션에서는 sed한 줄에서 첫 번째 k 발생을 변경하는 방법 을 설명 합니다. 두 번째 섹션은이 접근 방식을 확장하여 파일에 표시되는 행에 관계없이 파일의 첫 번째 k 발생 만 변경합니다.

라인 지향 솔루션

표준 sed를 사용하면 한 줄에서 단어의 k 번째 발생을 대체하는 명령이 있습니다. 경우 k예를 들어, 3 :

sed 's/old/new/3'

또는 모든 발생을 다음으로 바꿀 수 있습니다.

sed 's/old/new/g'

이것들 중 어느 것도 당신이 원하는 것이 아닙니다.

GNU sed는 k 번째 발생을 변경하는 확장 기능을 제공합니다. k가 3 인 경우 :

sed 's/old/new/g3'

이들은 원하는 것을 수행하기 위해 결합 될 수 있습니다. 처음 3 개의 발생을 변경하려면 다음을 수행하십시오.

$ echo old old old old old | sed -E 's/\<old\>/\n/g4; s/\<old\>/new/g; s/\n/old/g'
new new new old old

\n우리는 그것이 라인에서 절대로 발생하지 않을 수 있기 때문에 여기서 유용합니다.

설명:

우리는 세 가지 sed대체 명령을 사용 합니다.

  • s/\<old\>/\n/g4

    이는 GNU 확장은 네 번째 및 이후 모든 교체 old와를 \n.

    확장 정규식 기능 \<은 단어의 시작과 \>일치하고 단어의 끝 과 일치하는 데 사용됩니다 . 이렇게하면 완전한 단어 만 일치합니다. 확장 정규식에는 -E옵션이 필요합니다 sed.

  • s/\<old\>/new/g

    처음 세 항목 만 old남아 있으며이 모두가로 바뀝니다 new.

  • s/\n/old/g

    네 번째 및 나머지 모든 발생은 첫 번째 단계 old로 대체되었습니다 \n. 그러면 원래 상태로 돌아갑니다.

비 GNU 솔루션

GNU sed를 사용할 수없고의 처음 세 항목을 old로 변경 new하려면 세 s명령 을 사용하십시오 .

$ echo old old old old old | sed -E -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
new new new old old

k작은 숫자 일 때 잘 작동 하지만 크게 확장되지 않습니다 k.

GNU 이외의 일부 sed는 세미콜론과 명령 결합을 지원하지 않으므로 여기에있는 각 명령은 자체 -e옵션으로 소개됩니다 . 또한 당신의 확인해야 할 수 있습니다 sed지원하는 단어 경계 기호 \<\>.

파일 지향 솔루션

sed에게 전체 파일을 읽은 다음 대체를 수행하도록 지시 할 수 있습니다. 예를 들어, oldBSD 스타일 sed 를 사용하여 처음 세 가지 항목을 바꾸려면 :

sed -E -e 'H;1h;$!d;x' -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'

sed 명령 H;1h;$!d;x은 전체 파일을 읽습니다.

위의 방법은 GNU 확장을 사용하지 않기 때문에 BSD (OSX) sed에서 작동해야합니다. 이 접근법에는 sed긴 줄을 처리 할 수 있는 방법이 필요합니다 . GNU sed는 괜찮을 것입니다. 비 GNU 버전을 사용하는 사용자 sed는 긴 줄을 처리하는 능력을 테스트해야합니다.

하는 GNU가 나오지도, 우리는 더 사용할 수 있습니다 g위에서 설명한 트릭을하지만,로 \n대체 \x00처음 세 항목을 대체하기 :

sed -E -e 'H;1h;$!d;x; s/\<old\>/\x00/g4; s/\<old\>/new/g; s/\x00/old/g'

이 접근 방식은 규모 k가 커짐에 따라 확장 됩니다. 그러나 이것은 \x00원래 문자열에없는 것으로 가정 합니다. 문자 \x00를 bash 문자열에 넣을 수 없으므로 일반적으로 안전한 가정입니다.


5
이것은 라인에만 적용되며 모든 라인에서 처음 4 가지 발생을 변경합니다.

1
@mikeserv 훌륭한 아이디어! 답변이 업데이트되었습니다.
John1024

(1) GNU 및 GNU 이외의 sed에 대해 언급하고 제안 tr '\n' '|' < input_file | sed …합니다. 그러나 물론 이것은 전체 입력을 한 줄로 변환하며 일부 비 sed sed는 임의로 긴 줄을 처리 할 수 ​​없습니다. (2) 위의 인용 된 문자열 '|'은 임의의 문자 또는 문자열로 대체되어야합니다.… 그러나 tr문자를 문자열 (길이> 1)로 대체 할 수는 없습니다 . (3) 마지막 예에서 -e 's/\<old\>/new/' -e 's/\<old\>/w/' | tr '\000' '\n'\>/new. 이것은 오타 인 것 같습니다 -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/' | tr '\000' '\n'.
G-Man, 'Reinstate

@ G-Man 감사합니다! 답변을 업데이트했습니다.
John1024

이 때문에 못생긴
루이 매덕스

8

Awk 사용

awk 명령을 사용하여 단어의 처음 N을 대체로 대체 할 수 있습니다.
단어가 완전히 일치하는 경우에만 명령이 바뀝니다.

아래의 예에서, 나는 처음 교체하고 27발생을 old함께new

하위 사용

awk '{for(i=1;i<=NF;i++){if(x<27&&$i=="old"){x++;sub("old","new",$i)}}}1' file

이 명령은 일치 할 때까지 각 필드를 반복 old합니다. 카운터가 27 미만인지 확인하고 증분 한 다음 줄의 첫 번째 일치 항목을 대체합니다. 그런 다음 다음 필드 / 라인으로 이동하고 반복합니다.

필드를 수동으로 교체

awk '{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file

그것은 이미 최대 인 필드에 마커를 가지고 같이 명령과 유사하지만, 전에 ($i), 그것은 단지에서 필드의 값을 변경 old하는 방법에 대해 new.

점검하기 전에

awk '/old/&&x<27{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file

라인에 오래된 것이 포함되어 있고 카운터가 27 미만인지 확인하면 라인 SHOULD이 거짓 일 때 라인을 처리하지 않으므로 약간의 속도 향상을 제공합니다.

결과

예 :

old bold old old old
old old nold old old
old old old gold old
old gold gold old old
old old old man old old
old old old old dog old
old old old old say old
old old old old blah old

new bold new new new
new new nold new new
new new new gold new
new gold gold new new
new new new man new new
new new new new dog new
new new old old say old
old old old old blah old

문자열 "old"가 * word old 앞에 오면 첫 번째 (sub 사용)는 잘못된 일을합니다 . 예를 들면, "노인에 약간의 금을 줘."→ "노인 일부 gnew를주십시오."
G-남자 '는 분석 재개 모니카'말한다

@ G-남자 내가 잊었 네 $i비트를, 그 편집하고, 감사합니다 :)

7

문자열의 처음 세 인스턴스 만 바꾸고 싶다고 가정 해보십시오.

seq 11 100 311 | 
sed -e 's/1/\
&/g'              \ #s/match string/\nmatch string/globally 
-e :t             \ #define label t
-e '/\n/{ x'      \ #newlines must match - exchange hold and pattern spaces
-e '/.\{3\}/!{'   \ #if not 3 characters in hold space do
-e     's/$/./'   \ #add a new char to hold space
-e      x         \ #exchange hold/pattern spaces again
-e     's/\n1/2/' \ #replace first occurring '\n1' string w/ '2' string
-e     'b t'      \ #branch back to label t
-e '};x'          \ #end match function; exchange hold/pattern spaces
-e '};s/\n//g'      #end match function; remove all newline characters

참고 : 위의 설명은 포함 된 주석에서 작동하지 않을 것입니다
... 또는 내 예제의 경우 '1'...

산출:

22
211
211
311

나는 두 가지 주목할만한 기술을 사용합니다. 우선 1한 줄에서 모든 항목 이로 바뀝니다 \n1. 이런 식으로 다음에 재귀 교체를 수행 할 때 교체 문자열에 교체 문자열이 포함되어 있으면 발생을 두 번 대체하지 않을 수 있습니다 . 예를 들어, 내가 바꿀 경우 hehey그것은 여전히 작동합니다.

나는 이것을 다음과 같이한다 :

s/1/\
&/g

둘째, h각 발생에 대해 오래된 공간에 문자를 추가하여 교체를 계산합니다 . 3에 도달하면 더 이상 발생하지 않습니다. 이 정보를 데이터에 적용하고 \{3\}원하는 총 교체 횟수와 교체하려는 /\n1/주소로 주소를 변경하는 경우 원하는 만큼만 ​​교체해야합니다.

나는 -e가독성을 위해 모든 것을 수행했습니다. POSIXly 다음과 같이 쓸 수 있습니다.

nl='
'; sed "s/1/\\$nl&/g;:t${nl}/\n/{x;/.\{3\}/!{${nl}s/$/./;x;s/\n1/2/;bt$nl};x$nl};s/\n//g"

그리고 GNU sed:

sed 's/1/\n&/g;:t;/\n/{x;/.\{3\}/!{s/$/./;x;s/\n1/2/;bt};x};s/\n//g'

sed그것은 또한 행 지향적 이라는 것을 기억하십시오 -그것은 전체 파일을 읽지 않고 다른 편집기에서 종종 그렇듯이 루프백을 시도합니다. sed간단하고 효율적입니다. 즉, 종종 다음과 같은 작업을 수행하는 것이 편리합니다.

다음은 간단한 실행 명령으로 묶는 작은 셸 함수입니다.

firstn() { sed "s/$2/\
&/g;:t 
    /\n/{x
        /.\{$(($1))"',\}/!{
            s/$/./; x; s/\n'"$2/$3"'/
            b t
        };x
};s/\n//g'; }

그래서 나는 할 수 있습니다 :

seq 11 100 311 | firstn 7 1 5

...그리고 얻다...

55
555
255
311

...또는...

seq 10 1 25 | firstn 6 '\(.\)\([1-5]\)' '\15\2'

...를 얻기 위해 ...

10
151
152
153
154
155
16
17
18
19
20
251
22
23
24
25

... 또는 작은 크기로 예제와 일치 시키기 위해 :

yes linux | head -n 10 | firstn 5 linux 'linux is an os kernel'
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux
linux
linux
linux
linux

4

Perl의 짧은 대안 :

perl -pe 'BEGIN{$n=3} 1 while s/old/new/ && ++$i < $n' your_file

`$ n $의 값을 원하는대로 변경하십시오.

작동 방식 :

  • 모든 줄에 new대해 old( s/old/new/) 를 계속 대체하려고 시도 하고 가능할 때마다 변수 $i( ++$i)를 증가시킵니다 .
  • 총 대체 수 1 while ...보다 적은 $n수의 교체를 수행하고 해당 라인에서 하나 이상의 대체를 수행 할 수있는 한 라인 ( ) 에서 계속 작동 합니다.

4

쉘 루프를 사용하십시오 ex!

{ for i in {1..50}; do printf %s\\n '0/old/s//new/'; done; echo x;} | ex file.txt

예, 약간 구피입니다.

;)

참고 : old파일에 인스턴스가 50 개 미만인 경우 실패 할 수 있습니다. (테스트하지 않았습니다.) 그렇다면 파일을 수정하지 않은 채로 둡니다.


더 좋은 방법은 Vim을 사용하는 것입니다.

vim file.txt
qqgg/old<CR>:s/old/new/<CR>q49@q
:x

설명:

q                                # Start recording macro
 q                               # Into register q
  gg                             # Go to start of file
    /old<CR>                     # Go to first instance of 'old'
            :s/old/new/<CR>      # Change it to 'new'
                           q     # Stop recording
                            49@q # Replay macro 49 times

:x  # Save and exit

: S // 새로운 <CR> 빈 정규식은 마지막으로 사용한 검색을 재사용하기 때문에, 잘 작동한다
인 Eike

3

간단하지만 빠르지 않은 해결책은 https : //.com/questions/148451/how-to-use-sed-to-replace-only-the-first-occurrence-in-a에 설명 된 명령을 반복하는 것입니다. -파일

for i in $(seq 50) ; do sed -i -e "0,/oldword/s//newword/"  file.txt  ; done

이 특정 sed 명령은 GNU sed 및 newwordoldword의 일부가 아닌 경우에만 작동합니다 . GNU sed의 경우 파일의 첫 번째 패턴 만 바꾸는 방법 은 여기를 참조 하십시오 .


"old"를 "bold"로 바꾸면 문제가 발생할 수 있음을 식별하는 +1
G-Man, 'Reinstate

2

GNU awk를 사용 RS하면 단어 구분자 로 대체단어로 레코드 구분 기호 를 설정할 수 있습니다 . 그런 다음 k나머지에 대한 원래 레코드 구분 기호를 유지하면서 출력의 레코드 구분 기호를 첫 번째 레코드 의 대체 단어로 설정하는 경우

awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, NR <= limit? replacement: RT}' file

또는

awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, limit--? replacement: RT}' file
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.