이것은 제가 이해할 수 없는 일반적인 주제의 구체적인 예입니다.
수년 동안 나는 다음과 같이 정규식과 sed를 사용하여 디렉토리의 모든 파일에서 문자열의 모든 항목을 재귀적으로 찾고 교체해 왔습니다.
#FIND $GLOBALS['timechecks'] and REPLACE with completely_different_string
shopt -s globstar dotglob;
for file in /var/www/**/*; do
if [[ -f $file ]] && [[ -w $file ]]; then
sed -i -- 's/\$GLOBALS\['\''timechecks'\''\]/completely_different_string/g' "$file"
fi
done
문제는 bash에서 정규식을 사용하는 데 있어서 내가 모르는 사이에 놓친 기본적인 것이 있다는 것입니다. 따라서 구체적인 예에 대한 해결책을 찾을 수 없습니다.
목표 문자열에 갇혔어요
$GLOBALS['timechecks']=addTimeCheck_sparky($GLOBALS['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
내가 생각해낸 정규식이 작동하지 않습니다.
이것은 내 스크립트의 sed 줄과 내가 생각해낸 검색 정규식이지만 아무 소용이 없습니다.
\$GLOBALS\['\''timechecks'\''\]=addTimeCheck_sparky[(]$GLOBALS\['\''timechecks'\''\][,][ ]number_format[(]microtime[(]true[)][,]6[,]'\''\.'\''[,]'\'''\''[)][,][ ]__LINE__[],[ ]basename[(]__FILE__[)][)][;]
정규식 디버거
이 예에서는 정규식 디버거를 사용하고 있는데 정규식이 내 대상 문자열을 찾았지만 작동하지 않는 것으로 나타났습니다. 디버거는 다음 위치에 있습니다.이 링크. 이것은 내 대상 문자열을 찾기 위해 표시되는 정규식입니다.
\$GLOBALS\['timechecks\'\]=addTimeCheck_sparky\(\$GLOBALS\[\'timechecks\'\], number_format\(microtime\(true\),6,\'\.\',''\), __LINE__, basename\(__FILE__\)\)
정규식 디버거 출력 문제:
먼저 정규 표현식을 시도했습니다.
- 거기에서 실행할 때 디버거의 정규식이 작동하는 이유를 모르지만 내 bash 스크립트에서는 작동하지 않습니다.
- 이 정규식은 bash 및 sed에서 배운 정규식에 비해 "잘못된" 것처럼 보입니다.
- 이 작업을 수행하는 스크립트에 디버거의 정규식을 삽입하면 작동하지 않습니다.
- 이해가 안 되니까 고칠 수가 없어요.
기본적인 문제는 bash/sed에서 작동하도록 디버거의 유효한 정규식을 변환하는 것에 대해 아무것도 모른다는 것입니다.
"bash에서 sed와 함께 정규식을 사용하는 방법"을 검색했지만 이것이 잠재적인 문제라는 사실에 대한 설명을 찾지 못했습니다.
관련 질문: 대상 문자열을 입력으로 받아들이고 이를 찾을 수 있는 정규식을 제공하는 생성기가 없는 이유는 무엇입니까?
답변1
\$GLOBALS\['\''timechecks'\''\]=addTimeCheck_sparky[(]$GLOBALS
^
그곳에는 아직 탈출하지 못한 남자가 있었다 $
.
\['\''timechecks'\''\][,][ ]number_format[(]microtime[(]true[)]
[,]6[,]'\''\.'\''[,]'\'''\''[)][,][ ]__LINE__[],[ ]basename[(]__FILE__[)][)][;]
^^
아마도 그렇게 되어야 할 것입니다 [,]
.
실제로 이스케이프하지 않는 것은 $
중요하지 않습니다(적어도 GNU sed의 경우). 그러나 이는 [],[ ]
내부에 공백이 있는 대괄호 표현식입니다 [],
. 그러나 이것은 유효한 정규식이므로 원하는 것이 아니므로 오류가 발생하지 않습니다.
그런데 사실 인용한다는 것은 참으로 고통스러운 일입니다. 때로는 그것을 피하는 것이 가장 좋습니다.
패턴과 대체 문자열, 테스트 파일을 일부 파일에 넣어 보겠습니다.
$ cat pat
$GLOBALS['timechecks']=addTimeCheck_sparky($GLOBALS['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
$ cat repl
hello!
$ cat test.txt
foo
$GLOBALS['timechecks']=addTimeCheck_sparky($GLOBALS['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
bar
그런 다음 문자열을 Perl로 바꿉니다.
$ pat=$(< pat) repl=$(< repl) perl -i.bak -pe 's/\Q$ENV{pat}/$ENV{repl}/' test.txt
$ cat test.txt
foo
hello!
bar
파일에서 문자열을 읽을 때 쉘 명령줄에서 문자열을 인용할 필요가 없습니다. 또한 패턴이 변수에서 가져와 \Q
사용되는 경우 패턴의 특수 문자를 이스케이프할 필요가 없습니다. 여기에서는 문자열이 명령줄 인수보다 더 잘 작동하므로 환경을 통해 Perl에 문자열을 전달합니다 -i
. s 처럼 각 입력 라인에 대해 주어진 스크립트를 실행하는 것처럼 동작을 -p
만듭니다 .perl
sed
-i.bak
sed
-i
관련 질문: 대상 문자열을 입력으로 받아들이고 이를 찾을 수 있는 정규식을 제공하는 생성기가 없는 이유는 무엇입니까?
훌륭한. 정규식은 여러 문자열과 일치하도록 설계된 패턴과 함께 사용되는 경우가 많으며 프로그램에서 어떤 부분이 달라질 수 있는지 알기 어려울 수 있습니다. 항상 고정된 문자열을 찾고 있다면 특수 문자를 이스케이프 처리하는 것이 좀 더 간단합니다. 그러나 실제로는 정규식 엔진이 처음에는 필요하지 않습니다. 일반적인 Unix 도구에서는 매우 일반적입니다.
댓글에서 다음과 같이 언급하셨습니다.
생각해보세요. 행이 해당 문자열과 일치하는 경우 이를 대체하기 위해 알아야 할 사항은 다음과 같습니다.
$GLOBALS['timechecks']=addTimeCheck_sparky
그것은 마치
sed -- -e 's/^.*GLOBALS..timechecks..=addTimeCheck_sparky.*$/hello/'
이를 일치시키고 전체 라인을 교체하는 데 사용할 수 있습니다. 물론 이것은 #GLOBALS_atimecheckses=addTimeCheck_sparky
속임수를 쓰고 모든 특수 문자를 .
. 그러나 당신은 요점을 이해합니다.
또한 원본 파일을 먼저 백업하는 경우 언제든지 백업 복사본을 만든 다음 실행하여 diff original.txt processed.txt
변경 사항을 확인할 수 있습니다.
답변2
나를 위해 일했습니다 :
sed -- 's/\$GLOBALS\['\''timechecks'\''\]/completely_different_string/g' <<'END'
foo
$GLOBALS['timechecks']=addTimeCheck_sparky($GLOBALS['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
bar
END
foo
completely_different_string=addTimeCheck_sparky(completely_different_string, number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
bar
이는 Mac의 기본 BSD sed 및 GNU sed에서 작동합니다.
용어 문제: "bash sed"가 없습니다. bash는 대화형 셸이자 프로그래밍 언어이기도 합니다. sed는 다른 프로그래밍 언어입니다. Bash 관점에서 sed는 $PATH에 있는 ls
or grep
또는 ... 와 같은 또 다른 명령일 뿐입니다.
답변3
자동화된 솔루션이 필요합니다. 참조하고 추적할 항목이 너무 많습니다.
2단계 솔루션(100% 완벽하지는 않음(병리학적 코너 케이스가 있을 수 있음))은 다음과 같습니다.
문자열을 변수로 그대로 가져옵니다.
- 왜? (참조된) 변수( )의 내용은
"$var"
쉘에 의해 (다시) 수정되지 않기 때문입니다. - 어떻게? 사용하다선두여기 - 문자열.
단계는 다음과 같습니다.
- 쓰기:
IFS= read -r var <<\END
명령줄에 - 처리하려는 동일한 문자열을 복사하여 붙여넣고 Enter 키를 누릅니다.
- 쓰고
END
Enter를 다시 누르십시오.
그러면 var 변수에는 명령줄에서 복사한 것과 똑같은 문자열이 포함되며 변경 사항도 없고 따옴표도 제거되지 않으며 아무것도 없고 문자열만 포함됩니다.
당신이보아야 할 것은 :
$ IFS= read -r var <<\END > $GLOBALS['timechecks']=addTimeCheck_sparky($GLOBALS['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__)); > END
완료되었습니다. 예, 정말 복잡한 부분입니다. 복사하여 붙여넣기만 하면 됩니다.
다음 문자열을 에코할 수 있습니다.$ echo "$var" $GLOBALS['timechecks']=addTimeCheck_sparky($GLOBALS['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
printf '%s\n' "$var" to avoid issues with some values of
글쎄, var -`를 사용하는 것이 더 나을 것이지만that may start with a
이 예에서는 echo가 잘 작동합니다.- 왜? (참조된) 변수( )의 내용은
이 시점부터는 추가 입력/입력/"수동 이스케이프"를 완료할 필요가 없습니다.
다음 명령을 복사하여 붙여넣기만 하면 됩니다.
var 값을 사용하여 sed에서 사용되는 정확한 정규식을 생성하여 정확하게 일치시킵니다. 허용되는 정규식은
sed
다음과 같습니다.POSIX용 BRE(기본 정규 표현식).
BRE에는 여러 가지 특수 문자가 있습니다\
.
[
*
*
^
$
.
이러한 문자가 모두 인용된 경우 정규식은 실제로 원래 문자열을 축어적으로 표현한 것입니다. 이 작업은 쉽습니다(\.*^$[
).$ echo "$var" | sed 's#\([\.*^$[]\)#\\\1#g' $GLOBALS\['timechecks']=addTimeCheck_sparky($GLOBALS\['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
기존 백슬래시(
\
), 리드([
), 점(.
), 별표(*
), 곡절(^
) 및 달러 기호($
)는 인용(이스케이프)됩니다. 이렇게 하면 가능한 정규식 구성이 모두 파괴var
되고 모두 간단한 문자열로 변환됩니다. "대괄호 표현식"([
), "모든 문자"(.
), 반복(*
), 앵커(^$
) 및 백슬래시( )를 분리합니다\
. 이스케이프 가 필요 하지 않거나
필요 하지 않습니다. 탈출구가 없으면 그대로 남아 있으므로 (특별) 같지 않습니다. 이스케이프( )되면 가 되고 특별한 값을 잃습니다.(
)
{
}
\(
\(
\\(
지금은 볼 수 없는 병리적인 극단적인 경우가 있을 수 있지만, 99.2%의 경우 간단한 전환만으로 충분합니다.
그런 다음 변경된 문자열을 캡처하여 sed에서 사용할 수 있습니다.
$ reg=$(echo "$var" | sed 's#\([\.*^$[]\)#\\\1#g')
$ echo "$var" | sed 's#'"$reg"'# ===any string=== #'
===any string===
변환이 올바른 경우 sed 명령은 전체 초기 문자열을 캡처하여 오른쪽 문자열로 바꿔야 합니다.
물론, 문자열의 짧은 부분을 일치시키려면 일치시키려는 부분부터 시작하면 됩니다.
추가의 변수에서 올바른 문자열을 얻기 위해 어떤 종류의 문자열을 작성해야 하는지 확인하려면(추가 인용 계층이 필요함) 다음을 사용할 수 있습니다(bash 4.3+).
$ myvar=$(echo "${var}" | sed 's#\([\.*^$[]\)#\\\1#g')
$ echo "${myvar@Q}"
'\$GLOBALS\['\''timechecks'\'']=addTimeCheck_sparky(\$GLOBALS\['\''timechecks'\''], number_format(microtime(true),6,'\''\.'\'','\'''\''), __LINE__, basename(__FILE__));'
다음과 같이 쓰면:
$ myvar='\$GLOBALS\['\''timechecks'\'']=addTimeCheck_sparky(\$GLOBALS\['\''timechecks'\''], number_format(microtime(true),6,'\''\.'\'','\'''\''), __LINE__, basename(__FILE__));'
첫 번째 수준 참조가 제거되고 myvar
사용해야 하는 문자열을 얻을 수 있습니다.
무엇이 잘못되었는지 확인하기 위해 초기 시도와 비교할 수 있습니다.
Bad: \$GLOBALS\['\''timechecks'\''\]=addTimeCheck_sparky[(]$GLOBALS\['\''timechecks'\''\][,][ ]number_format[(]microtime[(]true[)][,]6[,]'\''\.'\''[,]'\'''\''[)][,][ ]__LINE__[],[ ]basename[(]__FILE__[)][)][;]
Good: '\$GLOBALS\['\''timechecks'\'']=addTimeCheck_sparky(\$GLOBALS\['\''timechecks'\''], number_format(microtime(true),6,'\''\.'\'','\'''\''), __LINE__, basename(__FILE__));'
이것이 여러분에게 무엇이든 인용할 수 있는 일반적인 절차를 제공하기를 바랍니다.
노트:sed에 대한 기본 BRE 정규식에 대해 위의 절차를 구축했습니다. 이것들은 모두 sed(기본적으로)가 이해하는 정규식입니다. sed가 호출되면 sed -E
확장 정규식( )이 사용됩니다. ERE
ERE에는 몇 가지 변경 사항이 있습니다. 특수 문자 목록은 다음과 같이 늘어납니다. .[\()*+?{|^$
따라서 이스케이프는 다음과 같아야 합니다(아니요, 여기서는 역참조를 허용하지 않기 때문에 확장 정규식을 사용할 수 없습니다).
sed 's@\([\.()*+?{|^$[]\)@\\\1@g'
어떻게 작동하는지 볼 수 있습니다.내가 준비한 페이지
PCRE(Perl) JavaScript, PHP 또는 기타 정규 표현식에 대해 말하는 것이 아닙니다.sed는 그것들을 사용할 수 없습니다, 마침표는 쓸모가 없습니다.
관련된: