명령은 변수에 저장되고 셸에서 실행될 수 있습니다(좋은 방법은 아니지만). 예:
command='ls -l A* "B\" type"'
$command
A
, 및 로 "B\"
시작하는 파일이 나열됩니다 type"
. 매개변수 분리 및 와일드카드를 수행하지만 따옴표 및 이스케이프는 제거하지 않습니다. 이 동작으로 인해 배열을 지원하지 않는 셸에서 변수를 사용하여 임의의 인수를 전달하는 것이 매우 어렵고 find
다른 명령을 안전하게 결합하는 것이 불가능해집니다 for
(자주 논의됨). 따옴표가 없는 변수 확장에서는 많은 문자가 제어되지 않으므로 와일드카드 사용도 제한됩니다( `'"*?\n
리터럴이 포함된 와일드카드 시퀀스를 저장하고 올바르게 재사용할 수 없음).
상황은 다음과 같습니다매우변수의 따옴표와 이스케이프 시퀀스가 다른 경우 처리할 수 있습니다. 그런데 왜 대부분의 쉘은 실제로 이것을 수행하지 않습니까? 내가 눈치 채지 못한 몇 가지 모호한 고려 사항을 고려하여 특별히 설계된 것입니까, 아니면 단순히 호환성을 유지하기 위해 전달된 것입니까? 비슷한 질문이 있는 것으로 알고 있습니다.Bash 변수 확장이 따옴표를 유지하는 이유는 무엇입니까?그리고"변수의 명령"에서 인용/이스케이프/확장 문제이 동작은 논의되지만 거기에 있는 답변에서는 원인에 대해 설명하지 않습니다.
답변1
이 동작으로 인해 변수를 사용하여 임의의 매개변수를 전달하는 것이 매우 어렵습니다 [...]
아마도. 그러나 확장 결과를 모든 일반적인 명령줄 처리에 적용하지 않고 임의의 인수 전체를 전달하는 것은 불가능합니다.
예를 들어, 어딘가에서 파일 이름을 가져와서 명령에 전달하려고 시도하는 스크립트를 생각해 보세요. 다음을 통해 파일 이름을 얻는다고 가정합니다 read
.
echo -n "please enter filename: "
read -r filename
some command "$filename"
이제 사용자가 유사한 파일 이름을 입력하면 작은따옴표로 인해 구문 오류가 발생하면서 don't stop me now.txt
실행이 중단됩니다.some command
마찬가지로, 스크립트가 예와 같이 실행되고 myscript don*.txt
명령줄 인수에서 파일 이름을 가져오는 경우:
filename=$1
some command "$filename"
다시 한 번 $filename
(또는 $1
이미) 작은따옴표가 포함됩니다.
더 나쁜 것은 파일 이름이나 사용자 입력 문자열에 대체 명령이 포함될 수 있다는 점입니다.그냥 변수를 사용하세요아무 명령이나 실행하세요. 스크립트 작성자는 스크립트 외부에서 읽은 모든 문자열에 이스케이프 문자를 힘들게 추가해야 하며, 이를 수행하는 일부 방법으로 인해 확장된 처리가 트리거될 수 있습니다. 게다가 사람들은 그렇게 하지 않으며 쉘은 도구로 사용하기에는 덜 안전합니다.
(원하는 경우 확장을 처리할 필요가 없으며 따옴표와 백슬래시만 처리할 수 있지만 짝이 없는 따옴표 문제는 여전히 존재합니다.)
물론 read
필요한 이스케이프만 추가해야 한다고 말할 수도 있지만 다른 모든 유형의 입력에도 추가해야 합니까? 문자열 연산은 어떻게 작동하나요? 따옴표도 처리해야 하나요? ${#var}
가변 길이만큼 간단한 것 조차도 구현하는 데 더 많은 비용이 듭니다. 여러 개의 서로 다른 따옴표 붙은 문자열을 포함하는 변수의 길이는 무엇을 의미합니까?
마지막으로 고려하는 것이 가장 좋습니다.암호스크립트와 스크립트의 차이점데이터스크립트는 이를 처리하고 코드에 명시적으로 설정된 방식으로만 데이터가 처리되도록 난독화되지 않도록 구성합니다. 변수 확장을 인용한 것을 기억한다면, 이것이 쉘이 하는 일과 거의 같습니다.
변수의 데이터를 그대로 사용하는 것은 다른 모든 프로그래밍 언어에서도 마찬가지입니다. 예를 들어, 이 C 코드 조각에서 인쇄된 문자열은 "foo bar"
따옴표와 함께 런타임 환경에서 구문 분석되지 않습니다.
char *s = "\"foo bar\"";
printf("%s\n", s);
s = "foo()"
마찬가지로, 반대의 경우 printf()
호출은 함수를 호출하지 않고 foo()
문자열만 인쇄합니다 foo()
. (해석된 언어와 컴파일된 언어에 대해 논쟁하고 싶다면 예제를 Perl 또는 Python으로 변경할 수 있습니다.)
이제 이것은 귀하의 제안이 2022년에 제게는 좋은 생각이 아닌 이유에 대한 논쟁일 뿐입니다. 하지만 실제로는 "이유"와 설계 근거를 묻고 있습니다. 이런 일은 2022년이 아니라 1970년대와 1980년대쯤에 일어났습니다.위키피디아에서 언급됨Bourne Shell의 첫 번째 릴리스는 1979년에 이루어졌습니다. 그것은 아주 오래 전, 기존 컴퓨팅의 역사가 지금보다 훨씬 짧았던 때였습니다. 이제 우리는 쉘 어레이와 같은 다른 도구를 만드는 데 도움이 될 수 있는 뒤늦은 판단의 이점을 얻었습니다. 더 빠른 컴퓨터와 더 많은 메모리.
나는 디자인 뒤에 숨은 실제 설명이 "이것이 그들이 처음 모든 것을 알아냈을 때 염두에 두었던 것인데 어떤 이유에서인지 멈춰 있었다"라는 문구에 따른 것일 수 있다는 생각을 무시하지 않을 것입니다. 이전 버전과의 호환성은 두 가지 방식으로 작동합니다. 적어도 이제는 배열이 포함된 쉘과 완전히 다른 쉘이 있습니다.
답변2
따옴표 제거는 원칙적으로 쉘 구문의 따옴표에만 적용되므로컴파일 가능한 함수. 즉, 대체된 셸에서 실제로 런타임 인용문 제거를 수행할 필요가 없습니다. 이런 식으로 해석되는 추상화이지만 문법을 구문 분석할 때 실제로 따옴표가 제거될 수 있습니다.
이와 같은 명령줄 구성 요소는 "foo $bar"
인용된 단위로 바뀔 수 있습니다. 쉘의 파서는 이것이 인용되었다는 것을 기억할 것입니다. 그러나 실제 인용문은 아닙니다 foo $bar
. 항목이 런타임에 처리되면 $bar
값이 그대로 보간됩니다. 이와 같은 명령줄 항목은 abc$bar
따옴표가 없는 단위가 될 수 있지만 런타임 의미는 를 삽입 $bar
한 다음 필드로 분할하고 경로 이름 확장을 수행하는 것입니다.
이 모델에서 변수의 내용을 인용한다는 것은 쉘이 런타임에 어휘 검색 및 구문 분석 활동도 수행해야 함을 의미합니다.
이는 기본적으로 구문 키워드가 변수에서 나올 수 없는 이유와 같습니다. 예를 들면 다음과 같습니다.
thenvar=then
fivar=fi
# nonsense
if command; $thenvar
echo command succeeded
$fivar
쉘이 키워드의 $thenvar
내용을 인식할 수 없는 이유는 무엇입니까 then
?
똑같은 이유로 변수에 저장된 따옴표를 구문 따옴표로 인식하지 않습니다.
이제 쉘은 런타임과 구문 분석 시간 사이에 "수준을 혼합"합니다. 확장에 따옴표가 없으면 여러 필드로 분할됩니다. 또한 확장에 와일드카드 문자가 포함되어 있으면 이러한 문자가 활성화됩니다.
틀림없이 이러한 함수는 구문이지만 데이터에서 동적으로 나올 수도 있습니다.
아쉽게도 쉘 프로그래밍에 혼란과 오류를 초래하는 것은 이러한 기능과 레벨 혼합입니다. 인용하는 것을 잊어버리면 데이터의 축어적 부분 *
또는 공백이 손상될 수 있습니다.?
데이터에서 인용문을 처리하면 더 많은 혼란과 오류가 발생합니다. 예를 들어 변수에 불균형 따옴표가 포함되어 있으면 구문 오류가 발생합니다. 그렇죠? 무엇을 기다립니다? 런타임 데이터로 인해 구문 오류가 발생했습니까? 아니면 구문의 따옴표 균형을 맞추기 위해 변수에 따옴표를 허용합니까? 이것이 작동합니까?
quote='"'
echo "foo bar$quote # does $quote close the open quote?
알다시피, 그것은 매우 빨리 우스꽝스러워집니다.