Bash 스크립트의 의미론?


87

내가 아는 다른 어떤 언어보다 더 작은 것이 필요할 때마다 인터넷 검색으로 Bash를 "배웠습니다". 결과적으로 작동하는 것처럼 보이는 작은 스크립트를 함께 패치 워크 할 수 있습니다. 그러나 나는 무슨 일이 일어나고 있는지 정말로 알지 못하고 프로그래밍 언어로서 Bash에 대한 더 공식적인 소개를 바라고있었습니다. 예 : 평가 순서는 무엇입니까? 범위 지정 규칙은 무엇입니까? 타이핑 규칙은 무엇입니까? 예를 들어 모든 것이 문자열입니까? 프로그램의 상태는 무엇입니까? 변수 이름에 문자열을 키-값으로 할당하는 것입니다. 그 이상이 있습니까, 예를 들어 스택? 힙이 있습니까? 등등.

이런 식의 통찰력을 얻기 위해 GNU Bash 매뉴얼을 참조하려고했지만, 내가 원하는 것이 아닌 것 같습니다. 핵심 시맨틱 모델에 대한 설명 이라기보다는 통사론 적 설탕 목록에 가깝습니다. 백만개가 넘는 온라인 "bash 자습서"는 더 나쁩니다. 아마도 나는 먼저 공부 sh하고 Bash를 이것 위에 통사론 적 설탕으로 이해해야할까요? 하지만 이것이 정확한 모델인지는 모르겠습니다.

어떤 제안?

편집 : 이상적으로 내가 찾고있는 것에 대한 예를 제공하라는 요청을 받았습니다. 내가 "공식적 의미론"이라고 생각하는 것에 대한 다소 극단적 인 예는 "자바 스크립트의 본질"에 대한이 문서입니다 . 아마도 약간 덜 공식적인 예는 Haskell 2010 보고서 입니다.


3
는 IS 고급 Bash 스크립팅 가이드 은 "만 일"중 하나?
choroba 2014

2
나는 bash가 "핵심 시맨틱 모델"을 가지고 있다고 확신하지 못한다 (음, 아마도 "거의 모든 것이 문자열이다"). 나는 그것이 정말 구문론적인 설탕이라고 생각합니다.
Gordon Davisson 2014

4
"신택스 설탕의 세탁 목록"이라고 부르는 것은 실제로 실행의 매우 중요한 부분 인 확장의 의미 론적 모델입니다. 90 %의 버그와 혼란은 확장 모델을 이해하지 못하기 때문입니다.
그 다른 사람

4
나는 쉘 스크립트를 어떻게 작성 하는가? 처럼 읽는다면 왜 이것이 광범위한 질문이라고 생각할 수 있는지 알 수 있습니다 . 그러나 진짜 질문은 특히 쉘 언어와 bash의 형식적 의미와 기초무엇입니까? , 하나의 일관된 답변으로 좋은 질문입니다. 재 개설 투표.
kojiro 2014

1
나는 linuxcommand.org에서 꽤 많은 것을 배웠고 명령 줄 쉘 스크립트 작성 에 대한 더 심층적 인 책의 무료 pdf도 있습니다
samrap 2014-04-22

답변:


107

쉘은 운영 체제의 인터페이스입니다. 일반적으로 그 자체로는 다소 강력한 프로그래밍 언어이지만 특히 운영 체제 및 파일 시스템과 쉽게 상호 작용할 수 있도록 설계된 기능이 있습니다. POSIX 쉘 (이하 "쉘"이라고 함)의 의미는 LISP의 일부 기능 (s- 표현식은 쉘 단어 분할 과 많은 공통점이 있음 ) 및 C (쉘의 산술 구문 의 대부분)를 결합한 약간의 멍청한 것입니다. 의미론은 C)에서 비롯됩니다.

셸 구문의 또 다른 루트는 개별 UNIX 유틸리티의 혼란으로 발전한 것입니다. 쉘에 내장 된 대부분의 기능은 실제로 외부 명령으로 구현 될 수 있습니다. /bin/[많은 시스템에 존재 한다는 것을 알게되면 많은 쉘 초보자를 루프에 던집니다 .

$ if '/bin/[' -f '/bin/['; then echo t; fi # Tested as-is on OS X, without the `]`
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:
      # We're in a child process
      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 우선 순위를 따릅니다. 하지만 식은 확장 과 다릅니다 . EXPANSIONbash 매뉴얼 의 섹션에서 :

확장 순서는 다음과 같습니다. 중괄호 확장; 물결표 확장, 매개 변수 및 변수 확장, 산술 확장 및 명령 대체 (왼쪽에서 오른쪽 방식으로 수행) 단어 분할; 및 경로 이름 확장.

단어 분리, 경로 이름 확장 및 매개 변수 확장을 이해하고 있다면 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' # another way to transiently export a name
123

다음 범위 지정 규칙을 결합 할 수 있습니다.

$ foo() {
>   local -x bar=123 # Export foo, but only in this scope
>   bash -c 'echo $bar'
> }
$ foo
123
$ echo $bar

$

타이핑 규율

음, 유형. 네. Bash에는 실제로 유형이 없으며 모든 것이 문자열로 확장됩니다 (또는 단어 가 더 적절할 것입니다.) 그러나 다른 유형의 확장을 살펴 보겠습니다.

문자열

거의 모든 것을 문자열로 취급 할 수 있습니다. bash의 베어 워드는 그 의미가 적용된 확장에 전적으로 의존하는 문자열입니다.

확장 없음

겉으로 드러난 단어는 실제로 단어 일 뿐이며 따옴표는 그것에 대해 아무것도 바꾸지 않는다는 것을 입증하는 것이 좋습니다.

$ echo foo
foo
$ 'echo' foo
foo
$ "echo" foo
foo
부분 문자열 확장
$ fail='echoes'
$ set -x # So we can see what's going on
$ "${fail:0:-2}" Hello World
+ echo Hello World
Hello World

확장에 대한 자세한 내용 Parameter Expansion은 설명서 섹션을 참조하십시오. 꽤 강력합니다.

정수 및 산술 표현식

정수 속성으로 이름을 추가하여 할당 표현식의 오른쪽을 산술로 처리하도록 쉘에 지시 할 수 있습니다. 그런 다음 매개 변수가 확장 될 때 문자열로 확장되기 전에 정수 수학으로 평가됩니다.

$ foo=10+10
$ echo $foo
10+10
$ declare -i foo
$ foo=$foo # Must re-evaluate the assignment
$ echo $foo
20
$ echo "${foo:0:1}" # Still just a string
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" # Even if the zeroth index isn't set

$ …

단어 분리를 방지하기 위해 따옴표 또는 백 슬래시를 사용하는 경우 배열은 지정된 단어 분리를 유지합니다.

$ foo=( 'elementa b c' 'd e f' )
$ echo "${#foo[@]}"
2

배열과 위치 매개 변수의 주요 차이점은 다음과 같습니다.

  1. 위치 매개 변수는 희소하지 않습니다. $12이 설정되어 있으면 이 설정되었는지 $11도 확인할 수 있습니다 . (빈 문자열로 설정할 수 있지만 $#12보다 작지 않습니다.) "${arr[12]}"이 설정되어 있으면 설정되는 보장이 없으며 "${arr[11]}"배열 길이는 1만큼 작을 수 있습니다.
  2. 배열의 0 번째 요소는 해당 배열의 0 번째 요소입니다. 위치 매개 변수에서 0 번째 요소는 첫 번째 인수 가 아니라 쉘 또는 쉘 스크립트의 이름입니다.
  3. shift배열, 당신은 조각을 가지고처럼, 다시 할당 arr=( "${arr[@]:1}" ). 할 수도 unset arr[0]있지만 인덱스 1에 첫 번째 요소가됩니다.
  4. 배열은 전역으로 셸 함수간에 암시 적으로 공유 될 수 있지만이를 보려면 위치 매개 변수를 셸 함수에 명시 적으로 전달해야합니다.

경로 이름 확장을 사용하여 파일 이름 배열을 만드는 것이 편리한 경우가 많습니다.

$ dirs=( */ )

명령어

명령이 핵심이지만 매뉴얼에서 할 수있는 것보다 더 깊이 다루기도합니다. SHELL GRAMMAR섹션을 읽으십시오 . 다른 종류의 명령은 다음과 같습니다.

  1. 간단한 명령 (예를 들어 $ startx)
  2. 파이프 라인 (예 $ yes | make config:) (웃음)
  3. 목록 (예를 들어 $ grep -qF foo file && sed 's/foo/bar/' file > newfile)
  4. 화합물 명령 (예 $ ( cd -P /var/www/webroot && echo "webroot is $PWD" ))
  5. 코 프로세스 (복잡함, 예 없음)
  6. 함수 (간단한 명령으로 처리 할 수있는 명명 된 복합 명령)

실행 모델

물론 실행 모델에는 힙과 스택이 모두 포함됩니다. 이것은 모든 UNIX 프로그램에 고유합니다. Bash는 또한 caller내장의 중첩 된 사용을 통해 볼 수있는 쉘 함수에 대한 호출 스택을 가지고 있습니다 .

참조 :

  1. SHELL GRAMMARbash 매뉴얼 의 섹션
  2. XCU 셸 명령 언어 문서
  3. Greycat 위키 의 Bash 가이드 .
  4. UNIX 환경의 고급 프로그래밍

특정 방향으로 더 확장하고 싶다면 의견을 남겨주세요.


16
+1 : 좋은 설명입니다. 예제와 함께 이것을 작성하는 데 소요 된 시간을 감사하십시오.
jaypal singh 2014-04-22

+1 for yes | make config;-) 그러나 진지하게, 아주 좋은 글입니다.
Digital Trauma

이걸 읽기 시작 했어요 .. 좋아요. 몇 가지 의견을 남길 것입니다. 1) 더 큰 놀라움은 당신이 볼 때 제공 /bin/[하고 /bin/test종종 같은 applicaton 2) "첫 번째 단어는 명령입니다 가정합니다." -당신이 임무를 할 때를 기대하십시오 ...
Karoly Horvath 2014

@KarolyHorvath 예, 변수가 복잡하기 때문에 의도적으로 데모 셸에서 할당을 제외했습니다. 그 데모 셸은이 답변을 염두에두고 작성되지 않았습니다. 훨씬 더 일찍 작성되었습니다. 나는 그것을 execle만들고 첫 번째 단어를 환경에 보간 할 수 있다고 생각 하지만 여전히 꽤 복잡하게 만들 것입니다.
kojiro 2014

@kojiro : 아, 그건 너무 복잡해 졌어, 그건 확실히 내 의도가 아니 었어! 그러나 할당은 약간 다르게 작동하며 (x) IMHO는 텍스트 어딘가에 언급해야합니다. (x) : 그리고 약간의 혼란의 근원 ... 사람들이 a = 1일하지 않는다고 불평하는 것을 몇 번이나 보았는지 더 이상 셀 수 없습니다 .)
Karoly Horvath 2014

5

"입력 규칙은 무엇입니까? 예를 들어 모든 것이 문자열입니다"질문에 대한 대답 Bash 변수는 문자열입니다. 그러나 Bash는 변수가 정수일 때 변수에 대한 산술 연산 및 비교를 허용합니다. 규칙 Bash 변수에 대한 예외는 문자열이 해당 변수가 조판되거나 다르게 선언 된 경우입니다.

$ A=10/2
$ echo "A = $A"           # Variable A acting like a String.
A = 10/2

$ B=1
$ let B="$B+1"            # Let is internal to bash.
$ echo "B = $B"           # One is added to B was Behaving as an integer.
B = 2

$ A=1024                  # A Defaults to string
$ B=${A/24/STRING01}      # Substitute "24"  with "STRING01".
$ echo "B = $B"           # $B STRING is a string
B = 10STRING01

$ B=${A/24/STRING01}      # Substitute "24"  with "STRING01".
$ declare -i B
$ echo "B = $B"           # Declaring a variable with non-integers in it doesn't change the contents.
B = 10STRING01

$ B=${B/STRING01/24}      # Substitute "STRING01"  with "24".
$ echo "B = $B"
B = 1024

$ declare -i B=10/2       # Declare B and assigning it an integer value
$ echo "B = $B"           # Variable B behaving as an Integer
B = 5

옵션 의미 선언 :

  • -a 변수는 배열입니다.
  • -f 함수 이름 만 사용합니다.
  • -i 변수가 정수로 처리됩니다. 변수에 값이 할당되면 산술 평가가 수행됩니다.
  • -p 각 변수의 속성과 값을 표시합니다. -p를 사용하면 추가 옵션이 무시됩니다.
  • -r 변수를 읽기 전용으로 만듭니다. 이러한 변수는 후속 할당 문에서 값을 할당 할 수 없으며 설정을 해제 할 수도 없습니다.
  • -t 각 변수에 추적 속성을 제공합니다.
  • -x 환경을 통해 후속 명령으로 내보낼 각 변수를 표시합니다.

1

bash 맨 페이지는 대부분의 맨 페이지보다 훨씬 더 많은 정보를 가지고 있으며 요청한 내용 중 일부를 포함합니다. 10 년이 넘는 스크립팅 bash 이후의 나의 가정은 sh의 확장으로서의 역사 때문에 sh와의 하위 호환성을 유지하기 위해 약간의 펑키 한 구문을 가지고 있다는 것입니다.

FWIW, 내 경험은 당신과 같았습니다. 다양한 책 (예 : O'Reilly "Learning the Bash Shell"등)이 구문에 도움이되지만 다양한 문제를 해결하는 이상한 방법이 많이 있으며 그중 일부는 책에 포함되어 있지 않으며 검색해야합니다.

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