쉘은 운영 체제의 인터페이스입니다. 일반적으로 그 자체로는 다소 강력한 프로그래밍 언어이지만 특히 운영 체제 및 파일 시스템과 쉽게 상호 작용할 수 있도록 설계된 기능이 있습니다. POSIX 쉘 (이하 "쉘"이라고 함)의 의미는 LISP의 일부 기능 (s- 표현식은 쉘 단어 분할 과 많은 공통점이 있음 ) 및 C (쉘의 산술 구문 의 대부분)를 결합한 약간의 멍청한 것입니다. 의미론은 C)에서 비롯됩니다.
셸 구문의 또 다른 루트는 개별 UNIX 유틸리티의 혼란으로 발전한 것입니다. 쉘에 내장 된 대부분의 기능은 실제로 외부 명령으로 구현 될 수 있습니다. /bin/[
많은 시스템에 존재 한다는 것을 알게되면 많은 쉘 초보자를 루프에 던집니다 .
$ if '/bin/[' -f '/bin/['; then echo t; fi
t
와트?
쉘이 어떻게 구현되었는지 살펴보면 훨씬 더 의미가 있습니다. 여기 제가 연습으로 한 구현이 있습니다. 파이썬으로되어 있지만 누구에게도 끊이지 않기를 바랍니다. 그다지 강력하지는 않지만 유익합니다.
#!/usr/bin/env python
from __future__ import print_function
import os, sys
'''Hacky barebones shell.'''
try:
input=raw_input
except NameError:
pass
def main():
while True:
cmd = input('prompt> ')
args = cmd.split()
if not args:
continue
cpid = os.fork()
if cpid == 0:
os.execl(args[0], *args)
else:
os.waitpid(cpid, 0)
if __name__ == '__main__':
main()
위의 내용이 셸의 실행 모델이 꽤 많이 있음을 분명히 해주기를 바랍니다.
1. Expand words.
2. Assume the first word is a command.
3. Execute that command with the following words as arguments.
확장, 명령 해결, 실행. 쉘의 모든 의미는 위에서 작성한 구현보다 훨씬 풍부하지만이 세 가지 중 하나에 묶여 있습니다.
모든 명령이 아닙니다 fork
. 사실, 외부로 구현 된 (그렇게해야하는 것처럼) 의미 가없는 몇 가지 명령이 fork
있지만 심지어 엄격한 POSIX 준수를 위해 외부로 사용할 수있는 경우가 많습니다.
Bash는 POSIX 쉘을 향상시키기 위해 새로운 기능과 키워드를 추가하여이 기반을 구축합니다. sh와 거의 호환이 가능하며 bash는 매우 유비쿼터스이므로 일부 스크립트 작성자는 스크립트가 실제로 POSIX 방식으로 엄격한 시스템에서 작동하지 않을 수 있다는 사실을 깨닫지 못한 채 몇 년이 걸립니다. (나는 또한 사람들이 하나의 프로그래밍 언어의 의미와 스타일에 대해 그렇게 많은 관심을 갖고 쉘의 의미와 스타일에 대해 그렇게별로 신경 쓰지 않을 수 있는지 궁금합니다.
평가 순서
이것은 약간의 속임수 질문입니다. Bash는 기본 구문의 표현식을 왼쪽에서 오른쪽으로 해석하지만 산술 구문에서는 C 우선 순위를 따릅니다. 하지만 식은 확장 과 다릅니다 . EXPANSION
bash 매뉴얼 의 섹션에서 :
확장 순서는 다음과 같습니다. 중괄호 확장; 물결표 확장, 매개 변수 및 변수 확장, 산술 확장 및 명령 대체 (왼쪽에서 오른쪽 방식으로 수행) 단어 분할; 및 경로 이름 확장.
단어 분리, 경로 이름 확장 및 매개 변수 확장을 이해하고 있다면 bash가 수행하는 대부분의 작업을 잘 이해하고있는 것입니다. 단어 분리 후에 오는 경로 이름 확장은 이름에 공백이있는 파일이 여전히 glob과 일치 할 수 있도록 보장하기 때문에 중요합니다. 이것이 일반적으로 glob 확장을 잘 사용하는 것이 명령 구문 분석 보다 나은 이유 입니다.
범위
기능 범위
이전 ECMAscript와 마찬가지로 셸은 함수 내에서 명시 적으로 이름을 선언하지 않는 한 동적 범위를 갖습니다.
$ foo() { echo $x; }
$ bar() { local x; echo $x; }
$ foo
$ bar
$ x=123
$ foo
123
$ bar
$ …
환경 및 프로세스 "범위"
서브 쉘은 부모 쉘의 변수를 상속하지만 다른 종류의 프로세스는 내 보내지 않은 이름을 상속하지 않습니다.
$ x=123
$ ( echo $x )
123
$ bash -c 'echo $x'
$ export x
$ bash -c 'echo $x'
123
$ y=123 bash -c 'echo $y'
123
다음 범위 지정 규칙을 결합 할 수 있습니다.
$ foo() {
> local -x bar=123
> bash -c 'echo $bar'
> }
$ foo
123
$ echo $bar
$
타이핑 규율
음, 유형. 네. Bash에는 실제로 유형이 없으며 모든 것이 문자열로 확장됩니다 (또는 단어 가 더 적절할 것입니다.) 그러나 다른 유형의 확장을 살펴 보겠습니다.
문자열
거의 모든 것을 문자열로 취급 할 수 있습니다. bash의 베어 워드는 그 의미가 적용된 확장에 전적으로 의존하는 문자열입니다.
확장 없음
겉으로 드러난 단어는 실제로 단어 일 뿐이며 따옴표는 그것에 대해 아무것도 바꾸지 않는다는 것을 입증하는 것이 좋습니다.
$ echo foo
foo
$ 'echo' foo
foo
$ "echo" foo
foo
부분 문자열 확장
$ fail='echoes'
$ set -x
$ "${fail:0:-2}" Hello World
+ echo Hello World
Hello World
확장에 대한 자세한 내용 Parameter Expansion
은 설명서 섹션을 참조하십시오. 꽤 강력합니다.
정수 및 산술 표현식
정수 속성으로 이름을 추가하여 할당 표현식의 오른쪽을 산술로 처리하도록 쉘에 지시 할 수 있습니다. 그런 다음 매개 변수가 확장 될 때 문자열로 확장되기 전에 정수 수학으로 평가됩니다.
$ foo=10+10
$ echo $foo
10+10
$ declare -i foo
$ foo=$foo
$ echo $foo
20
$ echo "${foo:0:1}"
2
배열
인수 및 위치 매개 변수
배열에 대해 이야기하기 전에 위치 매개 변수에 대해 논의 할 가치가 있습니다. 쉘 스크립트의 인수는 숫자 매개 변수를 사용하여 액세스 할 수 있습니다 $1
, $2
, $3
, 등 당신은 사용하여 한 번에 모든 매개 변수에 액세스 할 수 있습니다 "$@"
배열과 많은 공통점을 가지고있는 확장. set
또는 shift
내장 기능을 사용하거나 다음 매개 변수로 쉘 또는 쉘 함수를 호출 하여 위치 매개 변수를 설정하고 변경할 수 있습니다 .
$ bash -c 'for ((i=1;i<=$#;i++)); do
> printf "\$%d => %s\n" "$i" "${@:i:1}"
> done' -- foo bar baz
$1 => foo
$2 => bar
$3 => baz
$ showpp() {
> local i
> for ((i=1;i<=$#;i++)); do
> printf '$%d => %s\n' "$i" "${@:i:1}"
> done
> }
$ showpp foo bar baz
$1 => foo
$2 => bar
$3 => baz
$ showshift() {
> shift 3
> showpp "$@"
> }
$ showshift foo bar baz biz quux xyzzy
$1 => biz
$2 => quux
$3 => xyzzy
bash 매뉴얼은 때때로 $0
위치 매개 변수 라고도합니다 . 인수 count에 포함되지 않았기 때문에 혼란 스럽지만 $#
번호가 매겨진 매개 변수이므로 meh. $0
쉘 또는 현재 쉘 스크립트의 이름입니다.
배열
배열의 구문은 위치 매개 변수를 모델로하므로 원하는 경우 배열을 "외부 위치 매개 변수"의 명명 된 종류로 생각하는 것이 좋습니다. 다음 접근 방식을 사용하여 배열을 선언 할 수 있습니다.
$ foo=( element0 element1 element2 )
$ bar[3]=element3
$ baz=( [12]=element12 [0]=element0 )
인덱스로 배열 요소에 액세스 할 수 있습니다.
$ echo "${foo[1]}"
element1
배열을 분할 할 수 있습니다.
$ printf '"%s"\n' "${foo[@]:1}"
"element1"
"element2"
배열을 일반 매개 변수로 취급하면 0 번째 인덱스를 얻게됩니다.
$ echo "$baz"
element0
$ echo "$bar"
$ …
단어 분리를 방지하기 위해 따옴표 또는 백 슬래시를 사용하는 경우 배열은 지정된 단어 분리를 유지합니다.
$ foo=( 'elementa b c' 'd e f' )
$ echo "${#foo[@]}"
2
배열과 위치 매개 변수의 주요 차이점은 다음과 같습니다.
- 위치 매개 변수는 희소하지 않습니다.
$12
이 설정되어 있으면 이 설정되었는지 $11
도 확인할 수 있습니다 . (빈 문자열로 설정할 수 있지만 $#
12보다 작지 않습니다.) "${arr[12]}"
이 설정되어 있으면 설정되는 보장이 없으며 "${arr[11]}"
배열 길이는 1만큼 작을 수 있습니다.
- 배열의 0 번째 요소는 해당 배열의 0 번째 요소입니다. 위치 매개 변수에서 0 번째 요소는 첫 번째 인수 가 아니라 쉘 또는 쉘 스크립트의 이름입니다.
- 에
shift
배열, 당신은 조각을 가지고처럼, 다시 할당 arr=( "${arr[@]:1}" )
. 할 수도 unset arr[0]
있지만 인덱스 1에 첫 번째 요소가됩니다.
- 배열은 전역으로 셸 함수간에 암시 적으로 공유 될 수 있지만이를 보려면 위치 매개 변수를 셸 함수에 명시 적으로 전달해야합니다.
경로 이름 확장을 사용하여 파일 이름 배열을 만드는 것이 편리한 경우가 많습니다.
$ dirs=( */ )
명령어
명령이 핵심이지만 매뉴얼에서 할 수있는 것보다 더 깊이 다루기도합니다. SHELL GRAMMAR
섹션을 읽으십시오 . 다른 종류의 명령은 다음과 같습니다.
- 간단한 명령 (예를 들어
$ startx
)
- 파이프 라인 (예
$ yes | make config
:) (웃음)
- 목록 (예를 들어
$ grep -qF foo file && sed 's/foo/bar/' file > newfile
)
- 화합물 명령 (예
$ ( cd -P /var/www/webroot && echo "webroot is $PWD" )
)
- 코 프로세스 (복잡함, 예 없음)
- 함수 (간단한 명령으로 처리 할 수있는 명명 된 복합 명령)
실행 모델
물론 실행 모델에는 힙과 스택이 모두 포함됩니다. 이것은 모든 UNIX 프로그램에 고유합니다. Bash는 또한 caller
내장의 중첩 된 사용을 통해 볼 수있는 쉘 함수에 대한 호출 스택을 가지고 있습니다 .
참조 :
SHELL GRAMMAR
bash 매뉴얼 의 섹션
- XCU 셸 명령 언어 문서
- Greycat 위키 의 Bash 가이드 .
- UNIX 환경의 고급 프로그래밍
특정 방향으로 더 확장하고 싶다면 의견을 남겨주세요.