오류 "bash:"를 해결하는 방법!디':Bash 명령 대체에서 이벤트를 찾을 수 없습니다.

StackOverflow https://stackoverflow.com//questions/25003162

문제

VNC 서버 시작 이벤트의 출력을 구문 분석하려고 하는데 명령 대체에서 sed를 사용하여 구문 분석하는 중에 문제가 발생했습니다.특히 원격 VNC 서버는 다음과 같은 방식으로 시작됩니다.

address1="user1@lxplus.cern.ch"
VNCServerResponse="$(ssh "${address1}" 'vncserver' 2>&1)"

이 시작 이벤트에서 생성된 표준 오류 출력은 서버를 추출하고 정보를 표시하기 위해 구문 분석됩니다.이 시점에서 변수의 내용은 VNCServerResponse 다음과 같은 것입니다.

New 'lxplus0186.cern.ch:1 (user1)' desktop is lxplus0186.cern.ch:1

Starting applications specified in /afs/cern.ch/user/u/user1/.vnc/xstartup
Log file is /afs/cern.ch/user/u/user1/.vnc/lxplus0186.cern.ch:1.log

서버를 추출하고 정보를 표시하기 위해 이 출력을 다음 방법으로 구문 분석할 수 있습니다.

echo "${VNCServerResponse}" | sed '/New.*desktop.*is/!d' \
    | awk -F" desktop is " '{print $2}'

결과는 다음과 같습니다.

lxplus0186.cern.ch:1

내가 하고 싶은 것은 다음과 같은 명령 대체에서 이 구문 분석을 사용하는 것입니다.

VNCServerAndDisplayNumber="$(echo "${VNCServerResponse}" \
    | sed '/New.*desktop.*is/!d' | awk -F" desktop is " '{print $2}')"

이 작업을 시도할 때 다음 오류가 표시됩니다.

bash: !d': event not found

이 문제를 어떻게 해결해야 할지 잘 모르겠습니다.명령 대체에 sed를 사용하는 방식에 문제가 있는 것 같습니다.안내해 주시면 감사하겠습니다.

도움이 되었습니까?

해결책

문제는 큰따옴표 내에서 bash가 확장을 시도한다는 것입니다. !d 서브쉘에 전달하기 전에.큰따옴표를 제거하여 이 문제를 해결할 수 있지만 스크립트를 단순화하는 방법도 제안합니다.

VNCServerAndDisplayNumber=$(echo "$VNCServerResponse" | awk '/desktop/ {print $NF}')

이는 단순히 "desktop"이라는 단어가 포함된 줄의 마지막 필드를 인쇄합니다.

최신 bash에서는 파이핑 대신 herestring을 사용할 수 있습니다. echo:

VNCServerAndDisplayNumber=$(awk '/desktop/ {print $NF}' <<<"$VNCServerResponse")

다른 팁

Bash 기록 확장은 bash 명령줄 파서에서 매우 이상한 부분이며, 아래에 설명된 예상치 못한 기록 확장이 분명히 발생하고 있습니다.그러나 일반적으로 기록 확장은 스크립트에서 활성화되지 않기 때문에 스크립트에서 어떤 종류의 기록 확장도 예상치 못한 일입니다.심지어 스크립트도 source (또는 .) 내장.

기록 확장 활성화(또는 비활성화) 방법

기록 확장을 제어하는 ​​두 가지 셸 옵션이 있습니다.

  • set -o history:내역을 기록하기 위해 필요합니다.

  • set -H (또는 set -o histexpand):기록 확장을 활성화하려면 추가로 필요합니다.

기록 확장을 인식하려면 이 두 옵션을 모두 설정해야 합니다.(이 상호작용에 대한 매뉴얼이 불분명하다는 것을 알았지만 충분히 논리적입니다.)

Bash 매뉴얼에 따르면 이러한 옵션은 비대화형 쉘에 대해 설정되어 있지 않으므로 스크립트에서 기록 확장을 활성화하려는 경우(그리고 이를 원하는 이유는 상상할 수 없습니다) 두 가지를 모두 설정해야 합니다.

set -o history -o histexpand

다음으로 실행되는 스크립트의 상황 source 더 복잡합니다(그리고 제가 말하려는 내용은 bash v4에만 적용되며 문서화되지 않았기 때문에 향후 변경될 수 있습니다).[노트 3]

기록 기록(및 그에 따른 확장)은 다음에서 꺼집니다. source'd 스크립트이지만 내가 아는 한 표시되지 않는 내부 플래그를 통해서입니다.확실히 에는 나오지 않네요 $SHELLOPTS.이후 sourced 스크립트는 현재 bash 컨텍스트에서 실행되며 셸 옵션을 포함하여 현재 실행 환경을 공유합니다.그래서 실행에서 source대화형 세션에서 시작된 d 스크립트에는 두 가지가 모두 표시됩니다. history 그리고 histexpand ~에 $SHELLOPTS, 그러나 기록 확장은 발생하지 않습니다.이를 활성화하려면 다음을 수행해야 합니다.

set -o history

이는 기록 기록을 억제하는 내부 플래그를 재설정하는 부작용이 있기 때문에 무작동이 아닙니다.설정 histexpand 쉘 옵션에는 이러한 부작용이 없습니다.

간단히 말해서, 어떻게 스크립트에서 히스토리 확장을 활성화했는지는 잘 모르겠지만(실제로 오작동하는 명령이 대화형 셸이 아닌 스크립트에 있었다면), 그렇게 하지 않는 것이 좋습니다. 정말 좋은 이유가 있습니다.

기록 확장을 구문 분석하는 방법

bash의 기록 확장 구현은 다음과 함께 작동하도록 설계되었습니다. readline, 명령 입력 중에 수행할 수 있도록 합니다.(기본적으로 이 함수는 메타-^;일반적으로 메타 ~이다 ESC, 그러나 이를 사용자 정의할 수도 있습니다.) 그러나 이는 각 행이 입력된 직후, bash 구문 분석이 수행되기 전에 수행됩니다.

기본적으로 기록 확장 문자는 다음과 같습니다. !, 그리고 - 대부분 문서화되어 있듯이 - 다음을 제외하고 기록 확장을 트리거합니다.

  1. 뒤에 공백이 있거나 =

  2. 쉘 옵션인 경우 extglob 설정되어 있고 그 뒤에는 ( [참고 1]

  3. 작은따옴표로 묶인 문자열에 나타나는 경우

  4. 앞에 a가 있는 경우 \ [참고 2 및 아래 참조]

  5. 앞에 오는 경우 $ 또는 ${ [참고 1]

  6. 앞에 오는 경우 [ [참고 1]

  7. (bash v4.3 기준) 큰따옴표로 묶인 문자열의 마지막 문자인 경우.

여기서 즉각적인 문제는 세 번째 사례에 대한 정확한 해석입니다. ! 작은따옴표로 묶인 문자열 안에 나타납니다.보통, bash 명령 대체를 위한 새로운 인용 컨텍스트를 시작합니다($(...) 또는 더 이상 사용되지 않는 백틱 표기법).예를 들어:

$ s=SUBSTITUTED
$ # The interior single quotes are just characters
$ echo "'Echoing $s'"
'Echoing SUBSTITUTED'
$ # The interior single quotes are single quotes
$ echo "$(echo 'Echoing $s')"
Echoing $s

그러나 기록 확장 스캐너는 그다지 지능적이지 않습니다.따옴표는 추적하지만 명령 대체는 추적하지 않습니다.따라서 위 예의 작은따옴표는 모두 큰따옴표로 묶인 작은따옴표, 즉 일반 문자입니다.따라서 역사 확장은 두 가지 모두에서 발생합니다.

# A no-op to indicated history expansion
$ HIST() { :; }
# Single-quoted strings inhibit history expansion
$ HIST
$ echo '!!'
!!
# Double-quoted strings allow history expansion
$ HIST
$ echo "'!!'"
echo "'HIST'"
'HIST'
# ... and it applies also to interior command substitution.
$ HIST
$ echo "$(echo '!!')"
echo "$(echo 'HIST')"
HIST

따라서 다음과 같은 완벽하게 정상적인 명령이 있는 경우 sed '/foo/!d' file, 여기서 작은따옴표가 기록 확장으로부터 보호할 것으로 예상하고 이를 큰따옴표로 묶은 명령 대체 안에 넣습니다.

result="$(sed '/foo/!d' file)"

당신은 갑자기 그 사실을 알게 되었습니다. ! 역사 확장 캐릭터입니다.더 나쁜 것은 백슬래시로 느낌표를 이스케이프 처리하여 이 문제를 해결할 수 없다는 것입니다. "\!" 히스토리 확장을 금지하지만 백슬래시를 제거하지는 않습니다.

$ echo "\!"
\!

이 특정 예와 OP의 예에서는 변수 할당의 오른쪽이 파일 이름 확장이나 단어 분할을 거치지 않기 때문에 큰따옴표가 완전히 필요하지 않습니다.그러나 큰따옴표를 제거하면 의미가 변경되는 다른 상황도 있습니다.

# Undesired history expansion
printf "The answer is '%s'\n" "$(sed '/foo/!d' file)"

# Undesired word splitting
printf "The answer is '%s'\n" $(sed '/foo/!d' file)

이 경우 가장 좋은 해결책은 sed 인수를 변수에 넣는 것입니다.

# Works
sed_prog='/foo/!d'
printf "The answer is '%s'\n" "$(sed "$sed_prog" file)"

($sed_prog 주위의 따옴표는 이 경우에는 필요하지 않았지만 일반적으로 그럴 것이며 해를 끼치지 않습니다.)


노트:

  1. 다음 문자가 여는 괄호의 형태인 경우 기록 확장 금지는 문자열의 나머지 부분에 해당하는 닫는 괄호가 있는 경우에만 작동합니다.그러나 여는 괄호와 실제로 일치할 필요는 없습니다.예를 들어:

    # No matching close parenthesis
    $ echo "!("
    bash: !: event not found
    # The matching close parenthesis has nothing to do with the open
    $ echo "!(" ")"
    !( )
    # An actual extended glob: files whose names don't start with a
    $ echo "!(a*)"
    b
    
  2. 에 표시된대로 bash 설명서에서는 역사 확장 문자 바로 앞에 백슬래시가 오면 일반 문자로 처리됩니다.이것은 말 그대로 사실입니다.백슬래시가 나중에 이스케이프 문자로 간주되는지 여부는 중요하지 않습니다.

    $ echo \!
    !
    $ echo \\!
    \!
    $ echo \\\!
    \!
    

    \ 또한 큰따옴표 안의 히스토리 확장을 금지하지만 \! 큰따옴표로 묶인 문자열 내의 유효한 이스케이프 시퀀스가 ​​아니므로 백슬래시가 제거되지 않습니다.

    $ echo "\!"
    \!
    $ echo "\\!"
    \!
    $ echo "\\\!"
    \\!
    
  3. 이 글을 쓰면서 bash v4.2의 소스 코드를 언급하고 있으므로 문서화되지 않은 동작은 v4.3부터 완전히 다를 수 있습니다.

큰 따옴표로 $(...) 명령 대체를 래핑하지 마십시오.쉘에 따옴표의 내용에 대한 평가를 수행하고 기록 대체 확장 기능을 치는 것입니다.따옴표를 떨어 뜨리면 껍질이 그렇게하기 위해 껍질을 말하고 그 문제를 해결하지 못할 것입니다.

, 해당 따를 삭제하면 출력이 공백이나 뉴라인 또는 뉴라인을 포함 할 수있는 경우에도 해당 할당 라인에서 안전합니다.해당 종류의 할당은 일반적인 쉘 실행 라인에서 명령 대체 또는 변수 평가가있는 방식으로 분할되지 않습니다.

또는 실행하기 전에 쉘 / 스크립트에서 기록 확장을 비활성화하십시오.(기본적으로 스크립트를 실행할 때는 꺼야합니다.)

이는 기록 확장이 활성화된 경우에만 발생하며 일반적으로 스크립트에서는 발생하지 않으며 절대 있어서는 안 됩니다.

이 문제를 해결하려고 하기보다는 히스토리 확장이 활성화된 이유와 활성화되지 않도록 하려면 어떻게 해야 하는지 알아보세요.

  1. 다음을 사용하여 스크립트를 실행하는 경우 . foo 또는 source foo, 사용 ./foo 대신에.

  2. 이것을 함수로 작성한다면 .bashrc 또는 이와 유사한 경우 별도의 스크립트로 만드는 것을 고려해보세요.

  3. 스크립트(또는 BASH_ENV)가 명시적으로 set -H, 하지 않다.

다음과 같이 인용해 보세요 '' 또는 \ 또는 다음을 사용하여 기록 확장을 비활성화합니다. set +H 또는 shopt -u -o histexpand.보다 역사 확장.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top