여러 문자열을 한 번에 교체

여러 문자열을 한 번에 교체

저는 일반적인 Unix 도구(bash, sed, awk, Perl 등)를 사용하여 템플릿 파일의 자리 표시자 문자열을 구체적인 값으로 바꾸는 방법을 찾고 있습니다. 중요한 점은 교체가 단일 패스로 수행된다는 것입니다. 즉, 이미 스캔/교체된 콘텐츠는 다른 교체를 고려해서는 안 됩니다. 예를 들어 두 시도 모두 실패했습니다.

echo "AB" | awk '{gsub("A","B");gsub("B","A");print}'
>> AA

echo "AB" | sed 's/A/B/g;s/B/A/g'
>> AA

이 경우 올바른 결과는 물론 BA입니다.

일반적으로 이 솔루션은 주어진 대체 문자열 중 하나와 가장 긴 일치 항목을 찾기 위해 입력을 왼쪽에서 오른쪽으로 스캔하는 것과 같습니다. 각 일치에 대해 대체를 수행하고 입력의 해당 지점부터 계속합니다(입력을 읽거나 읽지 않고). 수행된 대체는 일치를 위해 고려되어야 합니다. 실제로 대체 결과가 전체 또는 부분적으로 다른 대체에 대해 고려되지 않는다는 점을 제외하면 세부 사항은 중요하지 않습니다.

노트나는 올바른 일반적인 솔루션을 찾고 있습니다. 아무리 불가능해 보이더라도 특정 입력(입력 파일, 검색 및 바꾸기 쌍)에 대해 실패하는 솔루션을 제안하지 마십시오.

답변1

좋습니다. 일반적인 해결책입니다. 다음 bash 함수에는 2k인수가 필요합니다. 각 쌍은 자리 표시자와 대체 항목으로 구성됩니다. 문자열을 적절하게 인용하여 함수에 전달할 수 있습니다. 인수 수가 홀수이면 암시적 null 인수가 추가되어 마지막 자리 표시자의 발생을 효과적으로 제거합니다.

\자리 표시자나 대체 항목 모두 NUL 문자를 포함할 수 없지만, 예를 들어 s를 원하는 경우 표준 C 이스케이프를 사용할 수 있습니다 \0(따라서 a를 원하면 NULa를 작성해야 합니다).\\\

POSIX 계열 시스템(lex 및 cc)에 있어야 하는 표준 빌드 도구가 필요합니다.

replaceholder() {
  local dir=$(mktemp -d)
  ( cd "$dir"
    { printf %s\\n "%option 8bit noyywrap nounput" "%%"
      printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
      printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
    } | lex && cc lex.yy.c
  ) && "$dir"/a.out
  rm -fR "$dir"
}

필요한 경우 인수가 이스케이프된다고 가정 \하지만, 큰따옴표가 있는 경우 이스케이프해야 합니다. 이것이 두 번째 printf의 두 번째 매개변수가 수행하는 작업입니다. lex기본 작업은 이므로 ECHO걱정할 필요가 없습니다.

예제를 실행해 보세요(의심스러운 분들에게는 그냥 값싼 비즈니스 노트북일 뿐입니다):

$ time echo AB | replaceholder A B B A
BA

real    0m0.128s
user    0m0.106s
sys     0m0.042s
$ time printf %s\\n AB{0000..9999} | replaceholder A B B A > /dev/null

real    0m0.118s
user    0m0.117s
sys     0m0.043s

더 큰 입력의 경우 최적화 플래그를 제공하는 것이 유용할 수 cc있으며 현재 Posix 호환성을 위해 사용하는 것이 가장 좋습니다 c99. 좀 더 야심찬 구현에서는 생성된 실행 파일을 매번 생성하는 대신 캐시하려고 시도할 수도 있지만 생성하는 데 비용이 많이 들지는 않습니다.

편집하다

당신이 가지고 있다면TCC를 사용하면 임시 디렉토리를 생성하는 번거로움을 피하고 더 빠른 컴파일 시간을 누릴 수 있으며 이는 일반 크기 입력에 도움이 됩니다.

treplaceholder () { 
  tcc -run <(
  {
    printf %s\\n "%option 8bit noyywrap nounput" "%%"
    printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
    printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
  } | lex -t)
}

$ time printf %s\\n AB{0000..9999} | treplaceholder A B B A > /dev/null

real    0m0.039s
user    0m0.041s
sys     0m0.031s

답변2

printf 'STRING1STRING1\n\nSTRING2STRING1\nSTRING2\n' |
od -A n -t c -v -w1 |
sed 's/ \{1,3\}//;s/\\$/&&/;H;s/.*//;x
     /\nS\nT\nR\nI\nN\nG\n1/s//STRING2/
     /\nS\nT\nR\nI\nN\nG\n2/s//STRING1/
     /\\n/!{x;d};s/\n//g;s/./\\&/g' |
     xargs printf %b

###OUTPUT###

STRING2STRING2

STRING1STRING2
STRING1

sed이와 같은 것은 항상 스트림 에 나타나는 대상 문자열의 각 항목을 한 줄에 한 번씩만 대체합니다 . 이것이 제가 생각할 수 있는 가장 빠른 방법입니다. 그리고 다시 C를 쓰지 않습니다. 하지만 이것은하다원하는 경우 Null 구분 기호를 안정적으로 처리할 수 있습니다. 바라보다이 답변어떻게 작동하는지 알아보세요. 특수 쉘 문자나 이와 유사한 문자가 포함된 경우에는 문제가 되지 않습니다.ASCII 로케일에만 해당됩니다. 즉, od멀티바이트 문자는 같은 줄에 출력되지 않고 하나만 출력됩니다. 이것이 문제라면 추가해야 합니다 iconv.

답변3

일방 perl통행. 누군가 불가능하다고 말해도 찾아냈지만 일반적으로 단순한 일치 및 교체는 불가능하며, 더 나쁜 경우에는 NFA 역추적으로 인해 예상치 못한 결과가 나올 수도 있습니다.

일반적으로 이 문제는 대체 튜플의 순서와 길이에 따라 다른 결과가 발생한다는 점에 유의해야 합니다. 즉:

A B
AA CC

입력 AAA결과는 BBB또는 입니다 CCB.

코드는 다음과 같습니다.

#!/usr/bin/perl

$v='if (0) {} ';
while (($a,$b)=split /\s+/, <DATA>) {
  $k.=$a.'|';
  $v.='elsif ($& eq \''.$a.'\') {print \''.$b.'\'} ';
}
$k.='.';
$v.='else {print $&;}';

eval "
while (<>) {
  \$_ =~ s/($k)/{$v}/geco;
}";  
print "\n";


__DATA__
A    B
B    A
abba baab
baab abbc
abbc aaba

체커보드 토끼:

$ echo 'ABBabbaBBbaabAAabbc'|perl script
$ BAAbaabAAabbcBBaaba

관련 정보