배경:
특정 문자열과 관련된 값을 삽입하는 bash 스크립트(MacOS 10.9.5)를 작성하고 싶습니다. 스크립트에서 가능한 각 관련 값을 정의하겠습니다.
예를 들어 링크 텍스트와 관련된 텍스트를 변수 yahoo
로 정의할 수 있습니다.www.yahoo.com
XX_yahoo="www.yahoo.com"
XX_
기존 변수와의 이름 충돌을 피하기 위해 접두사가 추가됩니다 . 그런 다음 내 스크립트는
\MakeLink[yahoo]{}
그리고
\MakeLink[yahoo]{www.yahoo.com}
매크로 대괄호 안의 링크 텍스트를 기존 변수와 \MakeLink
일치시킵니다 . 일부 텍스트에 대한 변수가 제공되지 않으면 링크 텍스트의 제목 케이스를 사용합니다. 그러므로,
\MakeLink[foo bar]{}
되어야 한다
\MakeLink[foo bar]{Foo Bar}
아래 스크립트는 다음 상황을 처리합니다.
- 링크 텍스트는아니요공간이 있고,
- 링크 텍스트 변수가 아직 정의되지 않았습니다.
질문:
링크 텍스트에 가능한 값의 수는 수천 개가 될 수 있고 그 안에 공백이 있을 수 있으므로 제 질문은 다음과 같습니다.
- 이것이 최선의 방법입니까? 배열을 변수로 사용하는 것이 더 좋을까요?
링크 텍스트에 공백이 있는 경우 어떻게 처리해야 합니까? 예를 들어, 나는
\MakeLink[the google]{}
로 대체됩니다
\MakeLink[the google]{www.google.com}.
노트
- 있을 것으로 추측할 수 있다.오직
\MakeLink
한 줄에 한 번씩 나타납니다. MakeTitleCase
제목과 같이 대소문자가 변경되지 않는 단어 목록을 갖도록 매크로를 개선해야 하지만 나중에 수정할 수 있습니다 .
기존 솔루션의 알려진 문제:
\MakeLink
앞에 백슬래시를 생략하더라도 일치가 계속 발생하기 때문에 일치하는 방식에 문제가 있습니다 . 테스트 케이스 첫 번째 단락의 마지막 줄을 참조하세요.- 내 파일
?
에 하나가 있으면 뭔가sed
잘못된 것 같습니다. - 링크 텍스트에 공백이 포함된 경우를 어떻게 처리해야 할지 잘 모르겠습니다.
스크립트
#!/bin/bash
## Can't have a backslash in the values of these variables, which is ok for my purposes.
XX_yahoo="www.yahoo.com"
XX_google="www.google.com"
function MakeTitleCase {
echo $(echo "$1" | awk '{for(j=1;j<=NF;j++){ $j=toupper(substr($j,1,1)) substr($j,2) }}1')
}
while read -d $'\n' LINE; do
## Extract target which is the text within the square brackets of "\MakeLink[target]{}"
TARGET=$(echo ${LINE} | sed -e 's?\]{}.*??' -e 's?\MakeLink\[??')
TEMP=XX_${TARGET}
if [ -z "${!TEMP}" ]; then
REPLACEMENT=$(MakeTitleCase "${TARGET}")
else
REPLACEMENT=${!TEMP}
fi
## Incorrect handling of leading backslash for the match.
echo "${LINE}" | sed "s?\MakeLink\[${TARGET}\]{}?\\\MakeLink\[${TARGET}\]{${REPLACEMENT}}?";
done
exit 0
입력 파일 예:
A very popular site on the internet was
\MakeLink[yahoo]{} but was surpassed by
\MakeLink[google]{} due to its
MakeLink[search engine]{}.
Due to its dominance
\MakeLink[the google]{} has had to deal with
\MakeLink[antitrust issues]{}.
현재 출력:
A very popular site on the internet was
\MakeLink[yahoo]{www.yahoo.com} but was surpassed by
\MakeLink[google]{www.google.com} due to its
\MakeLink[search engine]{Search Engine}.
Due to its dominance
\MakeLink[the google]{The Google} has had to deal with
\MakeLink[antitrust issues]{Antitrust Issues}.
원하는 출력:
위의 내용에 대한 유일한 변경 사항은 관련 텍스트 the google
이며MakeLink[search engine]{}
아니요선행 백슬래시가 누락되어 변경되었습니다.
A very popular site on the internet was
\MakeLink[yahoo]{www.yahoo.com} but was surpassed by
\MakeLink[google]{www.google.com} due to its
MakeLink[search engine]{}.
Due to its dominance
\MakeLink[the google]{www.google.com} has had to deal with
\MakeLink[antitrust issues]{Antitrust Issues}.
답변1
Perl이 구조에 옵니다:
#!/usr/bin/perl
use warnings;
use strict;
my %replace = ( yahoo => 'www.yahoo.com',
google => 'www.google.com',
'search engine' => 'Search Engine',
'the google' => 'The Google',
'antitrust issues' => 'Antitrust Issues',
);
while (<>) {
s/\\MakeLink\[(.*?)\]\{\}/\\MakeLink[$1]{$replace{$1}}/g;
print;
}
대체 해시 테이블을 생성하고 이를 대체에 사용합니다. 최신 bash 버전에서는 해시 테이블을 생성할 수 있지만 sed에서 직접 사용할 수 없으므로 직접적인 bash+sed 대응 항목은 없습니다.
답변2
choroba의 답변과 유사하지만(나는 당신을 보지 않고 이 글을 썼습니다. 맹세합니다!) 하드코딩 없이 헤더 쉘을 처리합니다.
#!/usr/bin/perl
use strict;
use warnings;
my %links = (
yahoo => "www.yahoo.com",
google => "www.google.com",
);
$links{"the $_"} = $links{$_} for keys %links;
while (<>) {
s{\\MakeLink\[(.+?)\]\{\}}{
sprintf "\\MakeLink[%s]{%s}",
$1,
exists $links{$1} ? $links{$1}
: join " ", map {ucfirst lc} split " ", $1;
}eg;
print;
}
실행하세요:
$ perl link.pl input
A very popular site on the internet was
\MakeLink[yahoo]{www.yahoo.com} but was surpassed by
\MakeLink[google]{www.google.com} due to its
MakeLink[search engine]{}.
Due to its dominance
\MakeLink[the google]{www.google.com} has had to deal with
\MakeLink[antitrust issues]{Antitrust Issues}.
답변3
귀하의 스크립트를 검토하지는 않았지만 다음 두 곳에서 인용 문제(표시를 원하지 않을 때 특별한 의미가 있는 문자)가 발생하는 것으로 확인되었습니다.
read -d $'\n' LINE
(복잡한 작성 방법read LINE
)은 백슬래시 이스케이프 문자를 구문 분석하므로 효과적으로 백슬래시를 먹습니다. 완료read -r LINE
. 이 명령은 선행 및 후행 공백도 제거합니다. 이를 방지하려면 다음을 수행하십시오IFS= read -r LINE
.- 변수를 sed 스크립트로 바꿉니다. 이러한 변수의 내용은 사용자가 의도한 검색 문자열이나 대체 텍스트가 아닌 sed 스크립트로 구문 분석됩니다. 이것은
?
파일의 문제입니다.$TARGET
sed는 에 나타날 때 이를 봅니다?
. 이 문제를 해결하려면 sed의 모든 특수 문자 앞에 백슬래시 문자를 추가하십시오(그리고 정규식 및 대체 텍스트에서는 다른 문자를 이스케이프해야 합니다!).
사실... 위에 쓴 대로 하지 마세요. 나는 단지 무엇이 잘못되었는지 설명하고 있지만 드라이버를 사용하여 못을 박기 때문에 스크립트를 완전히 다시 작성해야 합니다.
연관 배열이 있는 bash를 사용하고 있습니다. 구성된 이름으로 변수를 사용하는 것은 더 나은 방법을 사용할 수 없을 때 편리한 방법이지만 적절한 데이터 구조보다 사용하기가 더 어렵습니다. XX_yahoo
변수가 실제로 환경에서 가져와야 하는 경우가 아니면 연관 배열을 사용하십시오.
typeset -A targets
targets[yahoo]='www.yahoo.com'
셸에서 파일을 한 줄씩 구문 분석하는 것이 가능하지만 while read …
대용량 파일(느림)이나 구문이 사소하지 않은 파일(알았듯이 구문 분석을 수행할 때 콘텐츠를 올바르게 구문 분석하기가 어렵습니다)에는 적합하지 않습니다. 다음) 셸에서 sed와 같은 외부 도구 간에 앞뒤로 전환합니다. 귀하의 임무는 awk 스크립트(또는 다른 답변에서와 같이 Perl)에서 주요 내용을 가져오는 것입니다.
어쨌든 awk를 사용하려는 경우 awk에서 직접 연관 배열을 정의할 수도 있습니다.
테스트되지 않은 코드.
#!/bin/awk -f
BEGIN {
targets[yahoo]="www.yahoo.com";
targets[google]="www.google.com";
}
function MakeTitleCase(text) {
split(text, words);
text = "";
for (w in words) {
text = text toupper(substr(w,1,1)) substr(w,2)
}
return text;
}
/^ *\\MakeLink\[[^][{}]*\]{}/ {
target_start = index($0, "[") + 1;
target_end = index($0, "]") - 1;
target = substr($0, target_start, target_end - target_start);
if (target in targets) {
replacement = targets[target];
} else {
replacement = MakeTitleCase(target);
}
$0 = substr($0, 1, target_start-1) replacement substr($0, target_end);
}
1