Sự khác biệt giữa shell dựng và từ khóa shell là gì?


Câu trả lời:


45

Có một sự khác biệt lớn giữa nội dung và từ khóa, theo cách Bash phân tích mã của bạn. Trước khi chúng tôi nói về sự khác biệt, hãy liệt kê tất cả các từ khóa và nội dung:

Nội dung:

$ compgen -b
.         :         [         alias     bg        bind      break     
builtin   caller    cd        command   compgen   complete  compopt   
continue  declare   dirs      disown    echo      enable    eval      
exec      exit      export    false     fc        fg        getopts   
hash      help      history   jobs      kill      let       local     
logout    mapfile   popd      printf    pushd     pwd       read      
readarray readonly  return    set       shift     shopt     source    
suspend   test      times     trap      true      type      typeset   
ulimit    umask     unalias   unset     wait                          

Từ khóa:

$ compgen -k
if        then      else      elif      fi        case      
esac      for       select    while     until     do        
done      in        function  time      {         }         
!         [[        ]]        coproc              

Lưu ý rằng, ví dụ [là một nội dung và đó [[là một từ khóa. Tôi sẽ sử dụng hai cái này để minh họa sự khác biệt bên dưới, vì chúng là các toán tử nổi tiếng: mọi người đều biết chúng và sử dụng chúng thường xuyên (hoặc nên).

Một từ khóa được quét và hiểu bởi Bash từ rất sớm trong quá trình phân tích cú pháp. Điều này cho phép ví dụ như sau:

string_with_spaces='some spaces here'
if [[ -n $string_with_spaces ]]; then
    echo "The string is non-empty"
fi

Điều này hoạt động tốt, và Bash sẽ vui vẻ đầu ra

The string is non-empty

Lưu ý rằng tôi đã không trích dẫn $string_with_spaces. Trong khi đó:

string_with_spaces='some spaces here'
if [ -n $string_with_spaces ]; then
    echo "The string is non-empty"
fi

cho thấy Bash không vui:

bash: [: too many arguments

Tại sao nó hoạt động với các từ khóa mà không phải với nội dung? bởi vì khi Bash phân tích mã, nó sẽ thấy [[đó là một từ khóa và hiểu rất sớm rằng nó đặc biệt. Vì vậy, nó sẽ tìm kiếm sự đóng cửa ]]và sẽ đối xử với bên trong một cách đặc biệt. Một dựng sẵn (hoặc lệnh) được coi là một lệnh thực tế sẽ được gọi với các đối số. Trong ví dụ cuối cùng này, bash hiểu rằng nó nên chạy lệnh [với các đối số (hiển thị một dòng trên mỗi dòng):

-n
some
spaces
here
]

kể từ khi mở rộng biến, loại bỏ trích dẫn, mở rộng tên đường dẫn và tách từ xảy ra. Lệnh [hóa ra được xây dựng trong trình bao, do đó, nó thực thi nó với các đối số này, dẫn đến lỗi, do đó khiếu nại.

Trong thực tế, bạn thấy rằng sự khác biệt này cho phép hành vi tinh vi, điều đó sẽ không thể xảy ra với các nội trang (hoặc lệnh).

Trong thực tế, làm thế nào bạn có thể phân biệt một nội dung với một từ khóa? đây là một thử nghiệm thú vị để thực hiện:

$ a='['
$ $a -d . ]
$ echo $?
0

Khi Bash phân tích cú pháp dòng $a -d . ], nó thấy không có gì đặc biệt (nghĩa là không có bí danh, không chuyển hướng, không từ khóa), vì vậy nó chỉ thực hiện mở rộng biến. Sau khi mở rộng biến, nó thấy:

[ -d . ]

Vì vậy, thực thi lệnh (dựng sẵn) [với các đối số -d, .], tất nhiên là đúng (điều này chỉ kiểm tra xem có phải .là một thư mục không).

Bây giờ hãy nhìn đi:

$ a='[['
$ $a -d . ]]
bash: [[: command not found

Oh. Đó là bởi vì khi Bash nhìn thấy dòng này, nó không thấy gì đặc biệt, và do đó mở rộng tất cả các biến và cuối cùng thấy:

[[ -d . ]]

Tại thời điểm này, việc mở rộng bí danh và quét từ khóa đã được thực hiện từ lâu và sẽ không được thực hiện nữa, vì vậy Bash cố gắng tìm lệnh được gọi [[, không tìm thấy nó và phàn nàn.

Dọc theo cùng một dòng:

$ '[' -d . ]
$ echo $?
0
$ '[[' -d . ]]
bash: [[: command not found

$ \[ -d . ]
$ echo $?
0
$ \[[ -d . ]]
bash: [[: command not found

Bí danh mở rộng là một cái gì đó khá đặc biệt quá. Bạn đã thực hiện tất cả những điều sau đây ít nhất một lần:

$ alias ll='ls -l'
$ ll
.... <list of files in long format> ....
$ \ll
bash: ll: command not found
$ 'll'
bash: ll: command not found

Lý do là như nhau: mở rộng bí danh xảy ra lâu trước khi mở rộng biến và loại bỏ trích dẫn.


Từ khóa vs Bí danh

Bây giờ bạn nghĩ điều gì xảy ra nếu chúng ta xác định bí danh là một từ khóa?

$ alias mytest='[['
$ mytest -d . ]]
$ echo $?
0

Ồ, nó hoạt động! vì vậy bí danh có thể được sử dụng để từ khóa bí danh! rất vui được biết


Kết luận: nội dung thực sự hoạt động giống như các lệnh: chúng tương ứng với một hành động được thực thi với các đối số trải qua quá trình mở rộng biến trực tiếp và phân tách từ và tạo khối. Nó thực sự giống như có một lệnh bên ngoài ở đâu đó /binhoặc /usr/binđược gọi với các đối số được đưa ra sau khi mở rộng biến, v.v. Lưu ý rằng khi tôi nói nó thực sự giống như có một lệnh bên ngoài, tôi chỉ có ý nghĩa đối với các đối số, tách từ, nối, mở rộng biến, vv Một dựng sẵn có thể sửa đổi trạng thái bên trong của vỏ!

Mặt khác, từ khóa được quét và hiểu từ rất sớm và cho phép thực hiện hành vi vỏ phức tạp: trình bao sẽ có thể cấm chia tách từ hoặc mở rộng tên đường dẫn, v.v.

Bây giờ hãy nhìn vào danh sách các nội dung và từ khóa và cố gắng tìm hiểu tại sao một số cần phải là từ khóa.


!là một từ khóa. Có vẻ như có thể bắt chước hành vi của nó với một chức năng:

not() {
    if "$@"; then
        return false
    else
        return true
    fi
}

nhưng điều này sẽ cấm các cấu trúc như:

$ ! ! true
$ echo $?
0

hoặc là

$ ! { true; }
echo $?
1

Tương tự như vậy time: sẽ mạnh hơn khi có từ khóa để có thể điều chỉnh thời gian các lệnh và đường ống phức tạp phức tạp với các chuyển hướng:

$ time grep '^#' ~/.bashrc | { i=0; while read -r; do printf '%4d %s\n' "$((++i))" "$REPLY"; done; } > bashrc_numbered 2>/dev/null

Nếu timenơi chỉ một lệnh (thậm chí BUILTIN), nó sẽ chỉ nhìn thấy những lập luận grep, ^#/home/gniourf/.bashrc, lần này, và sau đó sản lượng của nó sẽ đi qua các phần còn lại của đường ống. Nhưng với một từ khóa, Bash có thể xử lý mọi thứ! nó có thể timeđường ống hoàn chỉnh, bao gồm cả các chuyển hướng! Nếu timechỉ là một lệnh, chúng ta không thể làm:

$ time { printf 'hello '; echo world; }

Thử nó:

$ \time { printf 'hello '; echo world; }
bash: syntax error near unexpected token `}'

Cố gắng sửa nó:

$ \time { printf 'hello '; echo world;
time: cannot run {: No such file or directory

Vô vọng.


Từ khóa vs bí danh?

$ alias mytime=time
$ alias myls=ls
$ mytime myls

Bạn nghĩ cái gì xảy ra?


Thực sự, một nội trang giống như một lệnh, ngoại trừ việc nó được xây dựng trong trình bao, trong khi từ khóa là thứ cho phép thực hiện các hành vi tinh vi! chúng ta có thể nói đó là một phần của ngữ pháp của shell.


2
Đồng ý với @JohnyTex, đây là một trong những câu trả lời toàn diện và phù hợp nhất tôi từng thấy trên các trang web Stack. Cảm ơn bạn. Một câu hỏi có lẽ không liên quan: chỉ vì lợi ích của sự tò mò của tôi đang cố gắng để tìm thấy tài liệu cho 'alias tạm thời vô hiệu hoá' chức năng từ trước một lệnh với =\'bằng cách sử dụng man, aproposhelpvà tôi đã không có bất kỳ may mắn. Bất cứ ý tưởng nơi tôi sẽ đi về việc tìm kiếm thông tin này? Chủ yếu là để trong tương lai tôi có thể thấy những gì khác trong đó, bởi vì tôi nghĩ rằng tôi đang thiếu một nguồn tham khảo.
nc.

@nc: bạn sẽ không tìm thấy nó được ghi lại rõ ràng. Lý do nó hoạt động được giải thích trong câu trả lời này. Cách gần nhất bạn sẽ tìm thấy trong hướng dẫn tham khảo trong phần Hoạt động của Shell . Bạn sẽ thấy việc mở rộng bí danh được thực hiện từ rất sớm (điều mà tôi đã cố gắng nhấn mạnh trong câu trả lời này), trong bước 2. Việc loại bỏ trích dẫn, mở rộng tham số, toàn cầu, v.v. được thực hiện sau. Vì vậy, để vô hiệu hóa bí danh, bạn có thể sử dụng một số loại trích dẫn để cấm trình bao hiểu mã thông báo dưới dạng bí danh, ví dụ \ll, "ll"hoặc 'll'.
gniourf_gniourf

1
Tôi thực sự đã nhận được thứ tự, bí danh và từ khóa bị sa thải xảy ra đầu tiên. Vì chúng tôi trích dẫn [[năng suất \[[nên nó không được phân tích thành bí danh. Phải cho đến nay? Nơi tôi bị lạc không nhận ra dấu gạch chéo ngược là một điều trích dẫn trong Bash, và tôi đã cố gắng tìm kiếm nó dưới bí danh và từ khóa và đã hoàn toàn bị mất ... Bây giờ thì tốt rồi. Trong phần Trích dẫn:> Dấu gạch chéo ngược không trích dẫn () là ký tự thoát.
nc.

1
Vì vậy, chúng ta có thể nghĩ về (2) trong danh sách Op đó là Tokenization và \[[là một mã thông báo duy nhất thuộc loại LiteralQuoteToken, trái ngược với [[đó sẽ là OpenTestKeywordToken và yêu cầu một ]]cú pháp đóng / biên dịch chính xác của CloseTestKeywordToken. Sau đó, trong (4), LiteralQuoteToken sẽ được đánh giá [[là tên của lệnh bulitin / lệnh để thực thi và trong (6) Bash sẽ barf vì không có [[lệnh dựng hoặc lệnh. Tốt đẹp. Tôi có thể quên các chi tiết chính xác theo thời gian, nhưng tại thời điểm này, cách Bash điều hành công cụ rõ ràng hơn nhiều đối với tôi; cảm ơn bạn.
nc.

9

man bashgọi họ SHELL BUILTIN COMMANDS. Vì vậy, một "shell dựng" giống như một lệnh thông thường, như grep, v.v., nhưng thay vì được chứa trong một tệp riêng biệt, nó lại được tích hợp vào bash . Điều này làm cho chúng thực hiện hiệu quả hơn các lệnh bên ngoài.

Một từ khóa cũng được "mã hóa cứng vào Bash, nhưng không giống như một nội dung, một từ khóa không phải là một lệnh, mà là một tiểu đơn vị của một cấu trúc lệnh." Tôi giải thích điều này có nghĩa là các từ khóa không có chức năng một mình, nhưng yêu cầu các lệnh để làm bất cứ điều gì. (Từ liên kết, ví dụ khác là for, while, do, và !, và có rất nhiều trong câu trả lời của tôi cho câu hỏi khác của bạn.)


1
Thực tế thú vị: [[ is a shell keywordnhưng [ is a shell builtin. Tôi không biết tại sao.
Sparhawk

1
Có thể vì lý do lịch sử và tiêu chuẩn POSIX phù hợp với vỏ Bourne cũ càng sát càng tốt, vì [đã tồn tại dưới dạng lệnh riêng biệt trở lại trong ngày. [[không được chỉ định bởi tiêu chuẩn, vì vậy các nhà phát triển có thể chọn đưa nó làm từ khóa hoặc dưới dạng tích hợp.
Sergiy Kolodyazhnyy

1

Hướng dẫn sử dụng dòng lệnh đi kèm với Ubuntu không đưa ra định nghĩa về từ khóa, tuy nhiên hướng dẫn trực tuyến (xem sidenote) và thông số kỹ thuật tiêu chuẩn của Ngôn ngữ lệnh POSIX Shell , gọi chúng là "Từ dành riêng" và cả hai đều cung cấp danh sách các từ đó. Từ tiêu chuẩn POSIX:

Sự công nhận này chỉ xảy ra khi không có ký tự nào được trích dẫn và khi từ được sử dụng là:

  • Từ đầu tiên của lệnh

  • Từ đầu tiên theo sau một trong những từ dành riêng ngoài trường hợp, cho hoặc trong

  • Từ thứ ba trong một trường hợp lệnh (chỉ trong trường hợp này là hợp lệ)

  • Từ thứ ba trong lệnh for (chỉ trong và làm là hợp lệ trong trường hợp này)

Chìa khóa ở đây là từ khóa / từ dành riêng có ý nghĩa đặc biệt vì chúng tạo điều kiện cho cú pháp shell, phục vụ để báo hiệu một số khối mã nhất định như vòng lặp, lệnh ghép, câu lệnh phân nhánh (if / case), v.v. Chúng cho phép tạo các câu lệnh, nhưng tự - không làm gì cả, và trong thực tế nếu bạn nhập từ khóa như for, until, case- vỏ sẽ mong đợi một tuyên bố đầy đủ, nếu không - lỗi cú pháp:

$ for
bash: syntax error near unexpected token `newline'
$  

Ở cấp độ mã nguồn, các từ dành riêng cho bash được định nghĩa trong parese.y , trong khi các phần dựng sẵn có toàn bộ thư mục dành riêng cho chúng.

Sidenote

Chỉ mục GNU hiển thị [dưới dạng từ dành riêng, tuy nhiên đây là lệnh tích hợp trong thực tế. [[ngược lại là một từ dành riêng.

Xem thêm: Sự khác nhau giữa từ khóa, từ dành riêng và nội dung?

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.