느낌표`!`가 때때로 bash를 화나게하는 이유는 무엇입니까?


14

!명령 줄 기록의 맥락에서 명령 줄에 특별한 의미가 있음을 알고 있지만 실행 스크립트에서 느낌표가 때때로 구문 분석 오류를 일으킬 수 있습니다. 이벤트
와 관련이 있다고 생각 event하지만 이벤트 가 무엇인지 또는 무엇을 하는지 모르겠습니다 . 그럼에도 불구하고 같은 명령이 상황에 따라 다르게 작동 할 수 있습니다.
아래의 마지막 예는 오류를 발생시킵니다. 그러나 왜 같은 코드가 명령 대체 외부에서 작동했을 때? .. GNU bash 4.1.5 사용

# This works, with or without a space between ! and p
  { echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }
# bar
# bar

# This works, works when there is a space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"
# bar

# This causes an ERROR, with NO space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"
# bash: !p': event not found


@Warren .. 감사합니다. QA를 보았지만 실제로 백 슬래시를 피하는 방법에 대해서만 이야기합니다 ... 내 문제 이미 이스케이프 된 코드가 한 상황에서 다른 곳이 아닌 왜 작동하는지에 더 관련이 있습니다 ...
Peter.O

@fred : "아마도 이미 탈출했다"? 나는 탈출구를 전혀 보지 못하고 큰 따옴표를 사용하고 있습니다. 내 (개정 된) 답변을 참조하십시오. 탈출 한 부분은 무엇입니까?
Caleb

@ 칼 레브. 예, 잘못된 용어를 사용했습니다 protected. 더 적합했을 것입니다. ( '작은 따옴표'로 보호)
Peter.O

간단한 할당에만 관심이 있다면 var=$(…)(큰 따옴표 는 사용할 수 없음) 예상대로 작동합니다. 간단한 할당의 값 부분은 단어 분할의 대상이나 (이 예 (내장 명령을 통해 수행 과제의 진실하지 않을 수 있지만 글 로빙 아니기 때문에 여전히 "안전"입니다 export, local모든 쉘에서) 등). 불행하게도, 큰 따옴표는 다른 문맥에서 다른 유형의 확장을 계속하면서 단어 분리 및 글 로빙으로부터 보호하는 방법이므로 간단한 할당을 넘어 확장되지 않습니다.
크리스 존슨

답변:


12

!문자는 배쉬의 역사 대체를 호출합니다. 그 뒤에 문자열이 나오면 (실패한 예와 같이) 해당 문자열로 시작한 마지막 기록 이벤트로 확장하려고 시도합니다. $var해당 문자열의 값으로 확장되는 것처럼 !echo기록의 마지막 에코 명령으로 확장됩니다.

공간은 그러한 확장에서 끊임없는 특성입니다. 먼저 이것이 변수와 어떻게 작동하는지 주목하십시오.

# var="like"
# echo "$var"
like
# echo "$"
$
# echo "Do you $var frogs?"
Do you like frogs?       <- as expected, variable name broken at space
# echo "Do you $varfrogs?"
Do you?                  <- $varfrogs not defined, replaced with blank
# echo "Do you $ var frogs?"
Do you $ var frogs?      <- $ not a valid variable name, ignored

역사 확장에도 같은 일이 일어날 것입니다. 뱅 문자 ( !)는 히스토리 교체 시퀀스를 시작하지만 문자열 뒤에 오는 경우에만 시작합니다. 공백으로 따라 가면 바꾸기 시퀀스의 일부가 아닌 리터럴 뱅이됩니다.

작은 따옴표를 사용하여 변수 및 기록 확장에 대한 이러한 종류의 대체를 피할 수 있습니다. 첫 번째 예제는 작은 따옴표를 사용하여 잘 실행되었습니다. 마지막 예제는 큰 따옴표로 묶여 있으므로 다른 작업을 수행하기 전에 확장 시퀀스를 bash 스캔했습니다. 첫 번째가 트립되지 않은 유일한 이유는 위와 같이 공백이 분리 문자이기 때문입니다.


고맙습니다 Caleb .. 또 다른 사전 구상이 사라졌습니다 ... bash 구문 분석이 가장 안쪽 대괄호 또는 괄호에서 수행 된 다음 바깥쪽으로 작동한다고 생각했습니다. bash는 내 가정과 다르게 구문 분석하는 것 같습니다.
Peter.O

1
인용은 중첩 문자열에서 변경하지 않고 bash에서 충분히 혼란 스럽습니다. 있는 그대로, 교체 과정에서 매우 일찍 발생합니다. 이 예를 고려하십시오.var=word; echo "test '$var'"; echo 'test "$var"'
Caleb

.. 그렇습니다. 나는 인용 부호 안에 인용 부호가 있다는 것을 알고 있었다 ... 나의 오해는 명령 치환의 괄호 안에있는 코드가 그 괄호를 둘러싼 것에 따라 별도로 분석 될 것이라고 생각했다. 그러나 분명히 아닙니다. 감사합니다.
Peter.O

6

Caleb 에서 이미 말했듯이! bash의 히스토리 대체를 호출하는 데 사용됩니다.

나처럼 그런 기능이 필요하지 않다고 생각되면 다음 줄을 삽입하여 비활성화 할 수 있습니다 ~/.bashrc.

set +H

역사는 위쪽 화살표 및 복구 할 수 있기 때문에 나는 그것을 필요하지 않습니다 Ctrl- r증분 역 검색. 자세한 단축키 목록은 bash 매뉴얼 페이지 의 히스토리 조작 명령 섹션을 참조하십시오 .


2
너없이 어떻게 살아 !!?
Caleb

고마워요. 이식성 측면에서는 문제가 될 수 있지만 set +H스크립트에서 사용하면 다음 과 같이 작동합니다. : +1
Peter.O

2
@fred : 이상한, 일반적으로 역사 확장은 대화식 쉘에 대해서만 "켜져"있습니다.
enzotib

@enzo .. 다시 감사합니다 .. 나는 명령 줄에서 테스트했습니다 .. 아! 학습이 그렇게 재미 있지 않다면 지루할 것입니다 ... 커피를 언급 했습니까? 그것도 도움이됩니다 :)
Peter.O

야, 그게 틀림 없어. 이 이유로 명령 행에서 실패한 스크립트에서 붙여 넣은 코드를 복사했습니다. 기록 확장은 스크립트에서 문제가되지 않았지만 대화식 쉘에 있습니다.
Caleb

2

첫 번째 예 :

{ echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }

로 줄일 수 있었다

echo '! p' 
echo '!p'

작은 따옴표 내에서 모든 문자는 리터럴 값을 유지합니다. 따라서 !특별한 의미를 잃어 버렸고 역사 확장은 이루어지지 않았습니다.

두 번째 및 세 번째 예 :

var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"

var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"

로 줄일 수 있었다

echo "'! p'"

echo "'!p'"

'! p''!p'이중 인용 된 문자열의 일부는 본질적으로.

큰 따옴표 내에서 모든 문자 는 , 및을 제외한 리터럴 값을 유지합니다 .$`\!

즉의 작은 따옴표를 의미 '! p'하고 '!p'그들의 특별한 의미를 잃었습니다 (예 : 탈출 할 수없는 !)하지만 !여전히 따라서 역사 확장이 수행되는 특별한 의미를 유지합니다.

그러나 !뒤에 공백 문자가 있으면 히스토리 확장이 수행되지 않습니다.

인용 man bash :

인용

[...]

작은 따옴표로 문자를 묶으면 따옴표 안에 각 문자의 리터럴 값이 유지됩니다. [...]

큰 따옴표로 문자를 묶으면 $,`, \ 및 히스토리 확장이 사용 가능한 경우!를 제외하고 따옴표 내 모든 문자의 리터럴 값을 유지합니다. [...] 활성화 된 경우!가 아닌 경우 히스토리 확장이 수행됩니다. 큰 따옴표로 묶는 것은 백 슬래시를 사용하여 이스케이프됩니다. ! 앞에 오는 백 슬래시 제거되지 않습니다.

역사 확장

[...]

히스토리 확장은 히스토리 확장 문자의 등장으로 소개됩니다. 기본적으로. 역 슬래시 (\)와 작은 따옴표 만 기록 확장 문자를 인용 할 수 있습니다.

공백, 탭, 줄 바꾸기, 캐리지 리턴 및 =와 같이 인용 부호가없는 경우에도 히스토리 확장 문자 바로 다음에있는 경우 여러 문자가 히스토리 확장을 금지합니다. extglob 셸 옵션이 활성화되면 확장도 금지됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.