k
단어 의 첫 번째 인스턴스 만 바꾸고 싶습니다 .
어떻게해야합니까?
예 : 파일 foo.txt
에 단어 'linux'가 100 번 나타납니다.
처음 50 번만 교체해야합니다.
k
단어 의 첫 번째 인스턴스 만 바꾸고 싶습니다 .
어떻게해야합니까?
예 : 파일 foo.txt
에 단어 'linux'가 100 번 나타납니다.
처음 50 번만 교체해야합니다.
답변:
아래의 첫 번째 섹션에서는 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 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에게 전체 파일을 읽은 다음 대체를 수행하도록 지시 할 수 있습니다. 예를 들어, old
BSD 스타일 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 문자열에 넣을 수 없으므로 일반적으로 안전한 가정입니다.
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'
.
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
문자열의 처음 세 인스턴스 만 바꾸고 싶다고 가정 해보십시오.
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
. 이런 식으로 다음에 재귀 교체를 수행 할 때 교체 문자열에 교체 문자열이 포함되어 있으면 발생을 두 번 대체하지 않을 수 있습니다 . 예를 들어, 내가 바꿀 경우 he
에 hey
그것은 여전히 작동합니다.
나는 이것을 다음과 같이한다 :
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
쉘 루프를 사용하십시오 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
간단하지만 빠르지 않은 해결책은 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 및 newword 가 oldword의 일부가 아닌 경우에만 작동합니다 . GNU sed의 경우 파일의 첫 번째 패턴 만 바꾸는 방법 은 여기를 참조 하십시오 .
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