녹슨 쉘 스크립팅 기술을 복습하려고 하는데 사례 설명에 문제가 있습니다. 아래 프로그램의 목표는 사용자가 제공한 문자열이 대문자로 시작하는지 소문자로 시작하는지 평가하는 것입니다.
# practicing case statements
echo "enter a string"
read yourstring
echo -e "your string is $yourstring\n"
case "$yourstring" in
[A-Z]* )
echo "your string begins with a Capital Letter"
;;
[a-z]* )
echo "your string begins with a lowercase letter"
;;
*)
echo "your string did not begin with an English letter"
;;
esac
myvar=nope
case $myvar in
N*)
echo "begins with CAPITAL 'N'"
;;
n*)
echo "begins with lowercase 'n'"
;;
*)
echo "hahahaha"
;;
esac
소문자로 시작하는 문자열(예: 따옴표 없이 "mystring")을 입력하면 Case 문은 내 입력을 첫 번째 사례와 일치시키고 문자열이 대문자로 시작한다는 것을 알려줍니다. 나는 명백한 구문이나 논리 오류를 만들고 있는지 확인하기 위해 두 번째 사례 설명을 작성했지만(아마도 여전히 그렇습니다) 동일한 문제가 없습니다. 두 번째 케이스 구조는 $myvar에 포함된 문자열이 소문자로 시작한다는 것을 정확하게 알려줍니다.
Case 문의 첫 줄에 $yourstring을 묶기 위해 따옴표를 사용해 보았고, 따옴표를 사용하지 않으려고 했습니다. "shopt" 옵션에 대해 읽고 "nocasematch"가 꺼져 있는지 확인했습니다. (좋은 측정을 위해 열어서 다시 시도했지만 첫 번째 Case 문에서 여전히 올바른 결과를 얻지 못했습니다.) 또한 sh 및 bash를 사용하여 스크립트를 실행해 보았지만 출력은 동일했습니다. (실행 비트를 설정하지 않았기 때문에 "sh ./case1.sh" 및 "bash ./case1.sh"를 사용하여 명시적으로 셸을 호출했습니다. 파일을 복사하고 새 파일에 실행 비트를 설정해도 산출.)
"-x" 디버그 옵션을 사용하여 셸을 실행하면 출력되는 모든 내용을 이해할 수 없지만 출력에는 첫 번째 "case" 줄에서 첫 번째 패턴 이후 명령을 실행하는 셸이 진행되는 것으로 표시됩니다. 나는 이것을 입력 문자열과 일치하는 첫 번째 패턴으로 해석하지만 이유는 잘 모르겠습니다.
처음 두 모드(및 해당 명령)의 순서를 전환하면 Case 문이 소문자에서는 성공하지만 "MYSTRING"이 소문자로 시작하는 것으로 잘못 보고됩니다. 모든 문자는 패턴에서 먼저 나타나는 문자와 일치하는 것으로 감지되므로 논리 오류가 있는 것 같지만...무슨 것인지 잘 모르겠습니다.
unix.com에서 "소문자 및 대문자에 대한 테스트는 [az] 및 [AZ]입니다. 일부 로케일 및/또는 Linux 배포판에서는 더 이상 작동하지 않습니다."라고 제안하는 "pludi"의 게시물을 찾았습니다.https://www.unix.com/shell-programming-and-scripting-128929-example-switch-case-bash.html) 물론, 문자 범위를 [[:upper:]] 및 [[:lower:]]로 바꾸면 문제가 해결되었습니다.
저는 Fedora 31을 사용하고 있으며 로케일 출력은 다음과 같습니다.
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=
문자 범위를 이해하지 못하거나, Case 문에서 패턴 일치가 작동하는 방식을 이해하지 못하거나, 기본 셸 기능이 변경되었는지(그리고 그 이유는 무엇입니까?) 궁금합니다. 누구든지 인내심을 갖고 설명을 해주시면 매우 감사하겠습니다. 또한 기꺼이 문서를 읽어보겠습니다. 감사해요!
답변1
의심할 바 없이 다른 사람들이 그 자리를 대신할 수 있다는 것은 간단한 대답입니다.
이제 문자 세트 순서는 사용된 로케일에 따라 달라집니다. 로케일 개념은 다양한 인종 그룹과 다양한 언어를 지원하기 위해 도입되었습니다. 출력에서 볼 수 있듯이 locale
이제 데이터 정렬뿐만 아니라 몇 가지 다른 영역이 해결되었습니다.
귀하의 경우 미국이고 정렬 및 정리 목적으로 알파벳은 AaBbCc...Zz 또는 A=a, B=b, C=c 등입니다(어느 것인지 잊어버렸고 컴퓨터에 없어 다음 중 하나를 확인할 수 있습니다). 그들을). 로케일은 복잡하며 일부 로케일에는 정렬 및 대조에 표시되지 않는 문자가 있을 수 있습니다. 동일한 문자라도 사용되는 로케일에 따라 다르게 정렬될 수 있습니다.
발견한 대로 소문자를 식별하는 올바른 방법은 [[:lower:]]
; 필요한 경우 악센트 문자를 포함하고 다른 알파벳(그리스어, 키릴 문자 등)의 소문자도 포함합니다.
기본 정렬을 원하는 경우 설정을 통해 애플리케이션별로 또는 명령별로 복원할 수 있습니다 LC_ALL=C
. 인간이 만든 예를 들자면,
grep some_pattern | LC_ALL=C sort | nl
답변2
사전 순서와 ASCII 순서 사이에는 끊임없는 싸움이 있었습니다.
오랫동안.
유니코드 관점에서 문자는 지역 규칙에 따라 정렬되어야 합니다.사전순서이므로 A b B ...는 미국 문자(ASCII 문자)를 나타냅니다. 이는 일반적으로 en_US.utf-8 로케일의 [a-zA-Z] 범위와 일치합니다. 국제화는 일반적으로 이에 동의합니다.
프로그래머의 관점에서 보면 C 언어로 인해 [az]는 아래와 같이 97에서 122 사이의 ASCII 문자만 일치해야 합니다.하나바이트 값. [AZ] 똑같습니다. 이는 일반적으로 문자를 바이트로 정의하는 C 언어의 정의와 일치합니다. 일부 시나리오 작가는 이 정의를 사용하기를 원합니다.
투쟁은 종종 한 해석에서 다른 해석으로 이동합니다.
때로는 [az] 범위가 abcdefghijklmnopqrstuvwxyz
. 로 바뀌기도
하고 때로는 aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYz
.
세부 사항은 복잡합니다. 역사적인. 전투는 여전히 격렬합니다.
따라서 다음과 같은 결과를 얻을 수 있습니다(테스트 문자열 book
).
- bash 버전 2, 3, 4의 경우 "문자열은 대문자로 시작합니다"
- bash 버전 5(및 1)의 경우 "문자열은 소문자로 시작합니다"
- 대부분의 쉘은 이를 "소문자"로 보고합니다.
문자열 úber
(en_US.UTF-8)을 테스트하면 다음과 같은 결과를 얻을 수 있습니다.
- ksh/ATT-sh의 "소문자"
- dash, zsh, bash 5.0+ 또는 [lm]ksh의 "영문 문자가 아닙니다."
- bash 2, 3, 4의 "대문자".
그리고 문자열 Úber
.
그래서 결과도 다양합니다.
a-z
LC_ALL=C를 설정하여 소문자만(및 A-Z
대문자만) 해석하도록 할 수도 있습니다 . 이렇게 하면 의 데이터 정렬만 고정됩니다 C
. 로케일이 변경되면 아무 것도 변경되지 않습니다. 더 강력한 스크립트이지만 적응성이 떨어지는 스크립트입니다.
사용할 수 있는 옵션도 있지만 [[:lower:]]
역시 ASCII 범위 az가 보장됩니다.오직C 언어 환경에서. POSIX의 향후 버전(아직 2020년에 출시되지 않음)에서는 모든 로케일에 대해 시행될 수 있습니다.
모든 것을 고려해 볼 때, 외부 결정(Unix 사양의 쉘 개발자의)이 코드 범위를 변경하지 않도록 보장하는 유일한 안전한 방법은 다음과 같습니다.
# practicing case statements
echo "enter a string"
read yourstring
echo -e "your string is $yourstring\n"
low='abcdefghijklmnopqrstuvwxyz'
cap='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
case "$yourstring" in
[$cap]* ) echo "your string begins with a Capital Letter" ;;
[$low]* ) echo "your string begins with a lowercase letter" ;;
*) echo "your string did not begin with an English letter" ;;
esac