
컨텍스트: GNU/Linux 우분투.
수천 줄로 구성된 파일이 있고 두 개의 특정 키워드 사이에 있는 일부 줄을 삭제하는 스크립트를 원합니다.
초기 파일은 다음과 같습니다.
bla bla
...
bla bla
keyword1
bla bla
...
bla bla
keyword2
bla bla
...
bla bla
keyword1
와 사이의 부분을 제외한 모든 파일을 유지하고 싶습니다 keyword2
.
생각해 보고 keyword1
파일 keyword2
에 한 번만 나타날 수 있습니다. 이러한 키워드는 줄 앞이나 뒤에 공백이나 <
또는 같은 다른 문자가 있을 수 있습니다.>
키워드가 포함된 줄은 다음과 같습니다(실제로는 XML 기반 파일입니다).
<keyword2>
키워드는 파일에 남아 있을 수도 있고 첨부된 텍스트와 함께 제거될 수도 있으며 두 가지 결과 모두 만족합니다.
계속 사용하는 방법을 모르겠습니다 grep
. 잘 모르겠습니다 awk
. 작동할까요?
답변1
샘플 텍스트를 파일에 넣고 file
키워드 <>
를 사용하여 테스트했습니다.
이 명령은 sed
키워드를 삭제합니다
$ < file sed '/keyword1/,/keyword2/d'
bla bla
...
bla bla
bla bla
...
bla bla
이 명령은 sed
키워드를 예약합니다
$ < file sed -n -e '1,/keyword1/p' -e '/keyword2/,$p'
bla bla
...
bla bla
<keyword1>
<keyword2>
bla bla
...
bla bla
답변2
Raku(이전 Perl_6) 사용
raku -ne '.put unless /keyword1/ ^fff^ /keyword2/;'
입력 예:
bla bla
...
bla bla
keyword1
bla bla
...
bla bla
keyword2
bla bla
...
bla bla
예제 출력:
bla bla
...
bla bla
keyword1
keyword2
bla bla
...
bla bla
즉, Raku의 -ne
명령줄 플래그는 Raku에게 자동으로 인쇄하지 않고 코드를 실행하도록 지시합니다. 인쇄는 .put
첫 번째 명령(개행 문자인 "print-using-terminator")으로 수행됩니다. .
앞의 점은 put
이에 대한 약어이며 대상 변수(이 경우 입력 행의 데이터가 포함됨)를 나타냅니다.$_.put
$_
이 fff
지시어는 두 개의 주변 정규식을 기반으로 켜거나 끄는 Raku의 sed와 유사한 "트리거" 연산자입니다. Raku(및 Perl5)에서는 unless
yes 입니다 if not
. 마지막으로 ^
주변 캐럿은 Raku에게 엔드포인트를 제외하라고 fff
지시합니다 .^fff^
unless
부정이기 때문에 ^fff^
끝점 제외를 무효화하여 출력에 합계를 유지합니다 keyword1
. 출력에서 합계를 제거하는 대신 keyword2
사용하십시오 .fff
^fff^
keyword1
keyword2
(실제로 파일을 구문 분석하려면 XML
Raku의 모듈을 사용하여 한 줄의 Raku 솔루션을 만들 수 있습니다 XML
.)
https://unix.stackexchange.com/search?q=Raku+%5BXML%5D
https://github.com/raku-community-modules/XML
https://raku.org
답변3
sed에 대한 이전 제안은 "키워드"가 줄의 유일한 단어가 아닌 경우 예상된 결과를 제공하지 않습니다. 위치에 관계없이 임의의 단락에서 두 단어 사이에서 텍스트를 추출하려면 특히 Perl이 필요합니다.Perl 파일 읽기
예를 들어 다음과 같은 텍스트가 있다고 가정해 보겠습니다.
Sir Arthur Conan Doyle was born on May 22, 1859, in Edinburgh.
He studied medicine at the University of Edinburgh and began to write stories while he was a student.
Over his life he produced more than 30 books, 150 short stories, poems, plays, and essays across a wide range
of genres.
His most famous creation is the detective Sherlock Holmes, who he introduced in his first novel, A Study in Scarlet (1887).
This was followed in 1889 by an historical novel, Micah Clarke.
여기서 핵심 단어는 각각 '의학'과 '셜록 홈즈'입니다.
sed의 결과는 단락의 첫 번째 줄과 마지막 줄을 정확히 삭제합니다. 그리고 예상되는 결과는 문장의 앞부분과 포함된 부분 medicine
, 그리고 뒤와 포함된 부분 도 제거해야 합니다 Holmes
.
Perl의 File Slurp를 사용해 봅시다:
perl -0777 -i -pe 'push @a,/medicine(.*?)Holmes/s;END{print "@a"}' myparagraph.txt
산출:
at the University of Edinburgh and began to write stories while he was a student.
Over his life he produced more than 30 books, 150 short stories, poems, plays, and essays across a wide range
of genres.
His most famous creation is the detective Sherlock
답변4
작업할 실제 XML 문서가 없으므로 관련 문서가 다음과 같다고 가정하겠습니다.
<?xml version="1.0"?>
<root>
<entry>
<name>Joe</name>
<number>133</number>
</entry>
<entry>
<name>Mary</name>
<number>123</number>
</entry>
<entry>
<name>Stan</name>
<number>233</number>
</entry>
</root>
작업도 약간 불분명하므로 방법을 보여 드리겠습니다.
entry
주어진 값을 가진 노드 중 하나를 제거합니다name
.- 값이 주어지면
number
노드의 값을 변경합니다.entry
name
entry
값이 주어지면 노드 중 하나의 내용을 삭제합니다name
.
이 작업은 먼저 상당히 일반적인 명령줄 XML 구문 분석기를 사용하여 수행된 xmlstarlet
다음 덜 알려진 구문 분석기를 사용하여 수행되었습니다 xq
(https://kislyuk.github.io/yq/), 유명한 JSON 파서의 래퍼입니다 jq
.
먼저 XPath 구문을 사용하십시오 xmlstarlet
.
스탠 제거:
xmlstarlet ed \ --var name '"Stan"' \ --delete '//entry[name = $name]' file.xml
이는 XPath 문자열을 가져와
"Stan"
내부 변수에 할당하고 이를 사용하여 특정 값을 가진 노드를$name
선택합니다 . 노드를 찾기 위해 특정 경로 대신 사용하기 때문에 노드 는 문서의 어느 곳에나 있을 수 있습니다 .entry
name
entry
//entry
/root/entry
발견된 노드가 삭제
xmlstarlet
되고 결과 XML 문서가 표준 출력에 기록됩니다.생성된 문서:
<?xml version="1.0"?> <root> <entry> <name>Joe</name> <number>133</number> </entry> <entry> <name>Mary</name> <number>123</number> </entry> </root>
Stan의 번호를 455로 변경합니다.
xmlstarlet ed \ --var name '"Stan"' \ --var value '455' \ --update '//entry[name = $name]/number' \ --expr '$value' file.xml
entry
이는 관심 있는 노드를 선택하기 위해 XPath 문자열이 포함된 내부 변수를 사용한다는 점에서 첫 번째 명령과 유사합니다 .$name
발견된 노드를 삭제하지는 않지만number
내부 변수에 제공된 값으로 하위 노드를 업데이트합니다$value
.생성된 문서:
<?xml version="1.0"?> <root> <entry> <name>Joe</name> <number>133</number> </entry> <entry> <name>Mary</name> <number>123</number> </entry> <entry> <name>Stan</name> <number>455</number> </entry> </root>
Stan의 기록 지우기:
xmlstarlet ed \ --var name '"Stan"' \ --update '//entry[name = $name]' \ --value '' file.xml
이는 해당 값을 빈 문자열로 업데이트하여 노드를 지울 수 있음을 다시 보여줍니다.
생성된 문서:
<?xml version="1.0"?> <root> <entry> <name>Joe</name> <number>133</number> </entry> <entry> <name>Mary</name> <number>123</number> </entry> <entry/> </root>
xq
래퍼는 jq
XML 문서를 구문 분석 하고 이를 JSON으로 트랜스코딩합니다. 그런 다음 jq
결과 JSON 문서에 표현식을 적용하고 선택적으로 이를 다시 XML로 변환합니다.
이 답변의 시작 부분에 있는 문서를 보면 입력이 XML 문서인 경우에도 xq
다음과 같은 JSON 문서가 내부적으로 사용됩니다.
{
"root": {
"entry": [
{
"name": "Joe",
"number": "133"
},
{
"name": "Mary",
"number": "123"
},
{
"name": "Stan",
"number": "233"
}
]
}
}
스탠 제거:
xq --xml-output \ --arg name 'Stan' \ 'del(.root.entry[] | select(.name == $name))' file.xml
이
del()
함수를 사용하여jq
지정된 경로를 삭제합니다. 경로는 명령줄에서 설정한 내부 변수 의 값을.root.entry
키로 갖는 배열에서 요소를 선택하여 찾습니다 ..name
$name
Stan의 번호를 455로 변경합니다.
xq --xml-output \ --arg name 'Stan' \ --arg value 455 \ '(.root.entry[] | select(.name == $name)).number |= $value' file.xml
이는 이전 표현식과 유사하지만 선택한 노드를 삭제하는 대신 내부 변수를 사용하여
del()
키에 액세스하고 해당 값을 업데이트합니다..number
$value
Stan의 기록 지우기:
xq --xml-output \ --arg name 'Stan' \ '(.root.entry[] | select(.name == $name)) |= null' file.xml
다시 한번 비슷한 표현식을 사용하여 관심 있는 노드를 선택한 다음 이를
null
비우도록 업데이트합니다.empty
대신 in 을 사용하면null
노드가 제거되므로 이는 위의 첫 번째 지점과 동일한 결과를 얻는 또 다른 방법입니다.
xmlstarlet
이들과 / 표현식 xq
의 주요 차이점 jq
은 절대 경로에 with를 사용하는 xq
반면, 관심 있는 노드를 재귀적으로 검색하기 위해 //
XPath 표현식에 with를 사용한다는 것입니다 . xmlstarlet
재귀 검색을 사용할 수도 있지만 xq
이는 약간 까다로우며 여기서 사용하기로 선택한 예에서는 이를 필요로 하지 않습니다.