Vỏ là một giao diện cho hệ điều hành. Nó thường là một ngôn ngữ lập trình mạnh mẽ hơn hoặc ít hơn theo đúng nghĩa của nó, nhưng với các tính năng được thiết kế để dễ dàng tương tác cụ thể với hệ điều hành và hệ thống tệp. Ngữ nghĩa của shell POSIX (sau đây được gọi là "shell") có một chút đột biến, kết hợp một số tính năng của LISP (các biểu thức s có rất nhiều điểm chung với tách từ shell ) và C (phần lớn cú pháp số học của shell ngữ nghĩa xuất phát từ C).
Phần gốc khác của cú pháp shell đến từ việc nuôi dưỡng nó như một hỗn hợp các tiện ích UNIX riêng lẻ. Hầu hết những gì thường là nội trang trong shell thực sự có thể được thực hiện dưới dạng các lệnh bên ngoài. Nó ném nhiều neophytes trong một vòng lặp khi chúng nhận ra rằng nó /bin/[
tồn tại trên nhiều hệ thống.
$ if '/bin/[' -f '/bin/['; then echo t; fi
t
w?
Điều này có ý nghĩa hơn nhiều nếu bạn nhìn vào cách một trình bao được thực hiện. Đây là một triển khai tôi đã làm như một bài tập. Nó bằng Python, nhưng tôi hy vọng đó không phải là sự cố treo máy đối với bất kỳ ai. Nó không quá mạnh, nhưng nó có tính hướng dẫn:
#!/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()
Tôi hy vọng phần trên làm rõ rằng mô hình thực thi của một shell là khá nhiều:
1. Expand words.
2. Assume the first word is a command.
3. Execute that command with the following words as arguments.
Mở rộng, phân giải lệnh, thực thi. Tất cả ngữ nghĩa của shell đều gắn liền với một trong ba điều này, mặc dù chúng phong phú hơn nhiều so với cách triển khai mà tôi đã viết ở trên.
Không phải tất cả các lệnh fork
. Trên thực tế, có một số lệnh không có ý nghĩa gì khi được triển khai dưới dạng các lệnh bên ngoài (chẳng hạn như chúng sẽ phải làm như vậy fork
), nhưng ngay cả những lệnh đó thường có sẵn dưới dạng lệnh bên ngoài để tuân thủ nghiêm ngặt POSIX.
Bash xây dựng dựa trên cơ sở này bằng cách thêm các tính năng và từ khóa mới để nâng cao trình bao POSIX. Nó gần như tương thích với sh, và bash phổ biến đến mức một số tác giả kịch bản mất nhiều năm mà không nhận ra rằng một tập lệnh có thể không thực sự hoạt động trên một hệ thống nghiêm ngặt POSIXly. (Tôi cũng tự hỏi làm thế nào mọi người có thể quan tâm nhiều đến ngữ nghĩa và phong cách của một ngôn ngữ lập trình, và quá ít đối với ngữ nghĩa và phong cách của trình bao, nhưng tôi lại phân biệt.)
Thứ tự đánh giá
Đây là một câu hỏi hơi khó: Bash diễn giải các biểu thức theo cú pháp chính từ trái sang phải, nhưng trong cú pháp số học, nó tuân theo C. ưu tiên. Tuy nhiên, biểu thức khác với mở rộng . Từ EXPANSION
phần của sổ tay bash:
Thứ tự của các khai triển là: mở rộng dấu ngoặc nhọn; khai triển dấu ngã, mở rộng tham số và biến, khai triển số học và thay thế lệnh (thực hiện theo kiểu từ trái sang phải); tách từ; và mở rộng tên đường dẫn.
Nếu bạn hiểu phân loại chữ, mở rộng tên đường dẫn và mở rộng tham số, bạn đang trên đường hiểu hầu hết những gì bash làm. Lưu ý rằng việc mở rộng tên đường dẫn đến sau wordsplitting là rất quan trọng, vì nó đảm bảo rằng một tệp có khoảng trắng trong tên của nó vẫn có thể được khớp với một hình cầu. Đây là lý do tại sao việc sử dụng tốt các mở rộng toàn cầu sẽ tốt hơn so với các lệnh phân tích cú pháp nói chung.
Phạm vi
Phạm vi chức năng
Giống như ECMAscript cũ, shell có phạm vi động trừ khi bạn khai báo rõ ràng các tên trong một hàm.
$ foo() { echo $x; }
$ bar() { local x; echo $x; }
$ foo
$ bar
$ x=123
$ foo
123
$ bar
$ …
Môi trường và "phạm vi" quy trình
Các biểu mẫu con kế thừa các biến của vỏ mẹ của chúng, nhưng các loại quy trình khác không kế thừa các tên chưa được báo cáo.
$ x=123
$ ( echo $x )
123
$ bash -c 'echo $x'
$ export x
$ bash -c 'echo $x'
123
$ y=123 bash -c 'echo $y'
123
Bạn có thể kết hợp các quy tắc xác định phạm vi sau:
$ foo() {
> local -x bar=123
> bash -c 'echo $bar'
> }
$ foo
123
$ echo $bar
$
Kỷ luật đánh máy
Ừm, các kiểu. Vâng. Bash thực sự không có các kiểu và mọi thứ đều mở rộng thành một chuỗi (hoặc có lẽ một từ sẽ thích hợp hơn.) Nhưng chúng ta hãy kiểm tra các kiểu mở rộng khác nhau.
Dây
Khá nhiều thứ có thể được coi là một chuỗi. Barewords trong bash là những chuỗi mà ý nghĩa của nó phụ thuộc hoàn toàn vào sự mở rộng được áp dụng cho nó.
Không mở rộng
Có thể đáng giá để chứng minh rằng một từ trần trụi thực sự chỉ là một từ, và những câu trích dẫn không thay đổi gì về điều đó.
$ echo foo
foo
$ 'echo' foo
foo
$ "echo" foo
foo
Mở rộng chuỗi con
$ fail='echoes'
$ set -x
$ "${fail:0:-2}" Hello World
+ echo Hello World
Hello World
Để biết thêm về các bản mở rộng, hãy đọc Parameter Expansion
phần của sách hướng dẫn. Nó khá mạnh mẽ.
Số nguyên và biểu thức số học
Bạn có thể nhập các tên với thuộc tính số nguyên để yêu cầu trình bao xử lý phía bên phải của các biểu thức gán là số học. Sau đó, khi tham số mở rộng, nó sẽ được đánh giá là số nguyên toán học trước khi mở rộng thành… một chuỗi.
$ foo=10+10
$ echo $foo
10+10
$ declare -i foo
$ foo=$foo
$ echo $foo
20
$ echo "${foo:0:1}"
2
Mảng
Đối số và tham số vị trí
Trước khi nói về mảng, có thể cần thảo luận về các tham số vị trí. Những lập luận để một kịch bản shell có thể được truy xuất thông qua các thông số đánh số, $1
, $2
, $3
, vv Bạn có thể truy cập tất cả các thông số này cùng một lúc sử dụng "$@"
, trong đó mở rộng có nhiều điểm tương đồng với mảng. Bạn có thể đặt và thay đổi các tham số vị trí bằng cách sử dụng set
hoặc shift
nội trang, hoặc đơn giản bằng cách gọi shell hoặc một hàm shell với các tham số sau:
$ 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
Hướng dẫn sử dụng bash đôi khi cũng đề cập đến $0
như một tham số vị trí. Tôi thấy điều này khó hiểu, vì nó không bao gồm nó trong số đối số $#
, nhưng nó là một tham số được đánh số, vì vậy meh. $0
là tên của shell hoặc script shell hiện tại.
Mảng
Cú pháp của mảng được mô hình hóa dựa trên các tham số vị trí, vì vậy, hầu hết bạn nên coi mảng như một loại "tham số vị trí bên ngoài" được đặt tên, nếu bạn muốn. Mảng có thể được khai báo bằng cách sử dụng các phương pháp sau:
$ foo=( element0 element1 element2 )
$ bar[3]=element3
$ baz=( [12]=element12 [0]=element0 )
Bạn có thể truy cập các phần tử mảng theo chỉ mục:
$ echo "${foo[1]}"
element1
Bạn có thể cắt các mảng:
$ printf '"%s"\n' "${foo[@]:1}"
"element1"
"element2"
Nếu bạn coi một mảng như một tham số bình thường, bạn sẽ nhận được chỉ số thứ không.
$ echo "$baz"
element0
$ echo "$bar"
$ …
Nếu bạn sử dụng dấu ngoặc kép hoặc dấu gạch chéo ngược để ngăn phân tách từ, mảng sẽ duy trì phân tách từ được chỉ định:
$ foo=( 'elementa b c' 'd e f' )
$ echo "${#foo[@]}"
2
Sự khác biệt chính giữa mảng và các tham số vị trí là:
- Tham số vị trí không thưa thớt. Nếu
$12
được đặt, bạn cũng có thể chắc chắn $11
là đã được đặt. (Nó có thể được đặt thành chuỗi trống, nhưng $#
sẽ không nhỏ hơn 12.) Nếu "${arr[12]}"
được đặt, không có gì đảm bảo rằng nó "${arr[11]}"
được đặt và độ dài của mảng có thể nhỏ hơn 1.
- Phần tử thứ 0 của một mảng rõ ràng là phần tử thứ 0 của mảng đó. Trong các tham số vị trí, phần tử thứ 0 không phải là đối số đầu tiên mà là tên của tập lệnh shell hoặc shell.
- Đối với
shift
một mảng, bạn phải cắt và gán lại nó, chẳng hạn như arr=( "${arr[@]:1}" )
. Bạn cũng có thể làm được unset arr[0]
, nhưng điều đó sẽ làm cho phần tử đầu tiên ở chỉ mục 1.
- Mảng có thể được chia sẻ ngầm giữa các hàm shell dưới dạng toàn cầu, nhưng bạn phải chuyển rõ ràng các tham số vị trí cho một hàm shell để nó nhìn thấy chúng.
Việc sử dụng mở rộng tên đường dẫn để tạo mảng tên tệp thường rất tiện lợi:
$ dirs=( */ )
Lệnh
Các lệnh là quan trọng, nhưng chúng cũng được đề cập sâu hơn so với hướng dẫn sử dụng của tôi. Đọc SHELL GRAMMAR
phần. Các loại lệnh khác nhau là:
- Lệnh đơn giản (ví dụ
$ startx
)
- Đường ống (ví dụ
$ yes | make config
) (lol)
- Danh sách (ví dụ
$ grep -qF foo file && sed 's/foo/bar/' file > newfile
)
- Lệnh ghép (ví dụ
$ ( cd -P /var/www/webroot && echo "webroot is $PWD" )
)
- Coprocesses (Phức tạp, không có ví dụ)
- Hàm (Một lệnh ghép được đặt tên có thể được coi như một lệnh đơn giản)
Mô hình thực thi
Tất nhiên, mô hình thực thi liên quan đến cả một đống và một ngăn xếp. Đây là loài đặc hữu của tất cả các chương trình UNIX. Bash cũng có một ngăn xếp cuộc gọi cho các hàm shell, có thể nhìn thấy thông qua việc sử dụng caller
nội trang lồng nhau .
Người giới thiệu:
- Các
SHELL GRAMMAR
phần của cuốn cẩm nang bash
- Các XCU Shell Command Language tài liệu
- Các Bash Hướng dẫn trên wiki Greycat của.
- Lập trình nâng cao trong môi trường UNIX
Hãy đóng góp ý kiến nếu bạn muốn tôi mở rộng thêm theo một hướng cụ thể.