k
나는 단어의 첫 번째 인스턴스 만을 바꾸고 싶습니다 .
어떻게 해야 하나요?
예를 들어. 파일에 foo.txt
'linux'라는 단어가 100번 포함되어 있다고 가정합니다.
처음 50개 항목만 교체하면 됩니다.
답변1
sed
아래의 첫 번째 부분 에서는 행에서 처음 k번 발생하는 항목을 변경하는 방법을 설명합니다 . 두 번째 부분은 나타나는 줄에 관계없이 파일에서 처음 k개의 항목만 변경하여 이 접근 방식을 확장합니다.
라인 중심 솔루션
표준 sed에는 한 줄에서 k번째로 나타나는 단어를 바꾸는 명령이 있습니다. 예를 들어 3인 경우 k
:
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
여기서 where \n
는 한 줄에 절대 나타나지 않을 것이기 때문에 유용합니다.
설명하다:
세 가지 대체 명령을 사용합니다 sed
.
s/\<old\>/\n/g4
이는 네 번째 및 이후의 모든 항목
old
을\n
.\<
단어의 시작과\>
끝을 일치시키기 위한 확장된 정규식 기능입니다 . 이렇게 하면 완전한 단어만 일치됩니다. 확장 정규식에는-E
옵션이 필요합니다sed
.s/\<old\>/new/g
처음 3개 항목만 유지하면
old
모두 대체됩니다new
.s/\n/old/g
네 번째 및 나머지 모든 발생은 첫 번째 단계
old
로 대체됩니다 .\n
이렇게 하면 원래 상태로 복원됩니다.
비 GNU 솔루션
GNU sed를 사용할 수 없고 의 처음 3개 항목을 로 변경하려면 다음 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
긴 줄을 처리하는 능력을 테스트해야 합니다.
g
GNU sed를 사용하면 위의 트릭을 한 단계 더 발전시킬 수 있지만 처음 세 개의 항목을 ,로 \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 문자열에 문자를 넣는 것은 불가능하므로 이는 일반적으로 안전한 가정입니다.
답변2
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
이러한 동작이 false일 때 해당 행을 처리하지 않으므로 속도가 약간 향상됩니다.
결과
예를 들어
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
답변3
문자열의 처음 세 인스턴스만 바꾸고 싶다고 가정해 보겠습니다.
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
답변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