Các POSIX tiêu chuẩn mở rộng áp đặt từ để được thực hiện theo thứ tự sau (nhấn mạnh là của tôi):
Mở rộng Tilde (xem Mở rộng Tilde), mở rộng tham số (xem Mở rộng tham số), thay thế lệnh (xem Thay thế lệnh) và mở rộng số học (xem Mở rộng số học) sẽ được thực hiện, bắt đầu kết thúc. Xem mục 5 trong Nhận dạng mã thông báo.
Việc tách trường (xem Chia tách trường) sẽ được thực hiện trên các phần của các trường được tạo bởi bước 1, trừ khi IFS là null.
Mở rộng tên đường dẫn (xem Mở rộng tên đường dẫn) sẽ được thực hiện, trừ khi tập -f có hiệu lực.
Trích dẫn loại bỏ (xem Trích dẫn loại bỏ) sẽ luôn luôn được thực hiện cuối cùng.
Điểm duy nhất khiến chúng ta quan tâm ở đây là điểm đầu tiên: như bạn có thể thấy mở rộng dấu ngã được xử lý trước khi mở rộng tham số:
- Shell cố gắng mở rộng dấu ngã
echo $x
, không có dấu ngã nào được tìm thấy, vì vậy nó tiến hành.
- Shell cố gắng mở rộng tham số
echo $x
, $x
được tìm thấy và mở rộng và dòng lệnh trở thành echo ~/someDirectory
.
- Quá trình xử lý tiếp tục, mở rộng dấu ngã đã được xử lý
~
nhân vật vẫn như cũ.
Bằng cách sử dụng dấu ngoặc kép trong khi gán $x
, bạn rõ ràng yêu cầu không mở rộng dấu ngã và coi nó như một ký tự bình thường. Một điều thường bị bỏ lỡ là trong các lệnh shell, bạn không phải trích dẫn toàn bộ chuỗi, vì vậy bạn có thể thực hiện mở rộng ngay trong khi gán biến:
user@host:~$ set -o xtrace
user@host:~$ x=~/'someDirectory'
+ x=/home/user/someDirectory
user@host:~$ echo $x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
Và bạn cũng có thể thực hiện việc mở rộng xảy ra trên dòng echo
lệnh miễn là nó có thể xảy ra trước khi mở rộng tham số:
user@host:~$ x='someDirectory'
+ x=someDirectory
user@host:~$ echo ~/$x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
Nếu vì một lý do nào đó bạn thực sự cần ảnh hưởng đến dấu ngã $x
mà không cần mở rộng và có thể mở rộng nó theo echo
lệnh, bạn phải tiến hành hai lần để buộc hai lần mở rộng của $x
biến xảy ra:
user@host:~$ x='~/someDirectory'
+ x='~/someDirectory'
user@host:~$ echo "$( eval echo $x )"
++ eval echo '~/someDirectory'
+++ echo /home/user/someDirectory
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
Tuy nhiên, lưu ý rằng tùy thuộc vào bối cảnh bạn sử dụng cấu trúc như vậy, nó có thể có tác dụng phụ không mong muốn. Theo nguyên tắc thông thường, thích tránh sử dụng bất cứ điều gì cần thiết eval
khi bạn có một cách khác.
Nếu bạn muốn giải quyết cụ thể vấn đề dấu ngã trái ngược với bất kỳ loại mở rộng nào khác, cấu trúc như vậy sẽ an toàn và di động hơn:
user@host:~$ x='~/someDirectory'
+ x='~/someDirectory'
user@host:~$ case "$x" in "~/"*)
> x="${HOME}/${x#"~/"}"
> esac
+ case "$x" in
+ x=/home/user/someDirectory
user@host:~$ echo $x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
Cấu trúc này kiểm tra rõ ràng sự hiện diện của một hàng đầu ~
và thay thế nó bằng thư mục nhà của người dùng nếu nó được tìm thấy.
Theo nhận xét của bạn, điều x="${HOME}/${x#"~/"}"
này thực sự có thể gây ngạc nhiên cho một người không được sử dụng trong lập trình shell, nhưng thực tế lại được liên kết với cùng một quy tắc POSIX mà tôi đã trích dẫn ở trên.
Theo tiêu chuẩn POSIX, việc loại bỏ trích dẫn xảy ra sau cùng và việc mở rộng tham số xảy ra rất sớm. Do đó, ${#"~"}
được đánh giá và mở rộng xa trước khi đánh giá các trích dẫn bên ngoài. Lần lượt, như được định nghĩa trong quy tắc mở rộng Thông số :
Trong mỗi trường hợp cần một giá trị của từ (dựa trên trạng thái của tham số, như được mô tả bên dưới), từ phải chịu sự mở rộng dấu ngã, mở rộng tham số, thay thế lệnh và mở rộng số học.
Do đó, phía bên phải của #
toán tử phải được trích dẫn hoặc thoát đúng để tránh mở rộng dấu ngã.
Vì vậy, để nói khác đi, khi người phiên dịch vỏ nhìn vào x="${HOME}/${x#"~/"}"
, anh ta thấy:
${HOME}
và ${x#"~/"}
phải được mở rộng.
${HOME}
được mở rộng đến nội dung của $HOME
biến.
${x#"~/"}
kích hoạt mở rộng lồng nhau: "~/"
được phân tích cú pháp nhưng, được trích dẫn, được coi là nghĩa đen 1 . Bạn có thể đã sử dụng dấu ngoặc đơn ở đây với cùng kết quả.
${x#"~/"}
bản thân biểu thức hiện được mở rộng, dẫn đến tiền tố ~/
bị xóa khỏi giá trị của $x
.
- Kết quả của những điều trên bây giờ được nối lại: sự mở rộng
${HOME}
, nghĩa đen /
, sự mở rộng ${x#"~/"}
.
- Kết quả cuối cùng được đặt trong dấu ngoặc kép, có chức năng ngăn chặn việc tách từ. Tôi nói một cách chức năng ở đây bởi vì những trích dẫn kép này không bắt buộc về mặt kỹ thuật ( ví dụ ở đây và ở đó ), nhưng như một phong cách cá nhân ngay khi một bài tập nhận được bất cứ điều gì ngoài
a=$b
tôi thường thấy nó rõ ràng hơn khi thêm dấu ngoặc kép.
Nhân tiện, nếu nhìn kỹ hơn vào case
cú pháp, bạn sẽ thấy "~/"*
cấu trúc dựa trên cùng một khái niệm như x=~/'someDirectory'
tôi đã giải thích ở trên (ở đây một lần nữa, các dấu ngoặc kép và đơn giản có thể được sử dụng thay thế cho nhau).
Đừng lo lắng nếu những điều này có vẻ mơ hồ ngay từ cái nhìn đầu tiên (thậm chí có thể ở tầm nhìn thứ hai hoặc sau đó!). Theo tôi, việc mở rộng tham số, với các lớp con, là một trong những khái niệm phức tạp nhất cần nắm bắt khi lập trình bằng ngôn ngữ shell.
Tôi biết rằng một số người có thể không đồng ý mạnh mẽ, nhưng nếu bạn muốn học lập trình shell sâu hơn, tôi khuyến khích bạn đọc Hướng dẫn về Script Bash nâng cao : nó dạy Bash-scripting, vì vậy với rất nhiều phần mở rộng và chuông - và- huýt sáo so với kịch bản shell POSIX, nhưng tôi thấy nó được viết tốt với vô số ví dụ thực tế. Khi bạn quản lý điều này, thật dễ dàng để hạn chế các tính năng POSIX khi bạn cần, cá nhân tôi nghĩ rằng việc nhập trực tiếp vào vương quốc POSIX là một đường cong học tập dốc không cần thiết cho người mới bắt đầu (so sánh thay thế dấu ngã POSIX của tôi với biểu thức chính quy của @ m0dular tương đương để có được một ý tưởng về những gì tôi muốn nói;)!).
1 : Điều này dẫn tôi đến việc tìm ra một lỗi trong Dash không thực hiện mở rộng dấu ngã ở đây một cách chính xác (có thể kiểm chứng bằng cách sử dụng x='~/foo'; echo "${x#~/}"
). Mở rộng tham số là một lĩnh vực phức tạp cho cả người dùng và chính các nhà phát triển hệ vỏ!
x='~'; print -l ${x} ${~x}
. Tôi đã bỏ cuộc sau khi đào quabash
hướng dẫn một lúc.