Chỉ cần một ghi chú thêm trên đầu câu trả lời hay của @ Kusalananda .
echo run after_bundle
là tốt vì không có ký tự nào trong 3 đối số đó được truyền để echo
chứa các ký tự đặc biệt cho trình bao.
Và (điểm bổ sung tôi muốn thực hiện ở đây) không có ngôn ngữ hệ thống nơi các byte đó có thể dịch sang các ký tự đặc biệt cho trình bao.
Tất cả những ký tự đó nằm trong cái mà POSIX gọi là bộ ký tự di động . Các ký tự đó phải có mặt và được mã hóa giống nhau trong tất cả các bộ ký tự trên hệ thống POSIX².
Vì vậy, dòng lệnh đó sẽ được giải thích giống nhau bất kể miền địa phương.
Bây giờ, nếu chúng ta bắt đầu sử dụng các ký tự bên ngoài bộ ký tự di động đó, thì nên trích dẫn chúng ngay cả khi chúng không đặc biệt với vỏ, bởi vì trong một miền địa phương khác, các byte tạo thành chúng có thể được hiểu là các ký tự khác nhau có thể trở thành đặc biệt cho vỏ. Lưu ý rằng đó là cho dù bạn đang sử dụng echo
hay bất kỳ lệnh nào khác, vấn đề không nằm ở chỗ vấn đề là echo
trình bao phân tách mã của nó như thế nào.
Ví dụ trong UTF-8:
echo voilà | iconv -f UTF-8 -t //TRANSLIT
Đó à
được mã hóa như 0xc3 0xa0. Bây giờ, nếu bạn có dòng mã đó trong tập lệnh shell và tập lệnh shell được gọi bởi người dùng sử dụng miền địa phương có bộ ký tự không phải là UTF-8, hai byte đó có thể tạo ra các ký tự rất khác nhau.
Chẳng hạn, tại một fr_FR.ISO8859-15
miền địa phương, một miền địa phương điển hình của Pháp sử dụng bộ ký tự byte đơn tiêu chuẩn bao gồm ngôn ngữ tiếng Pháp (giống với hầu hết các ngôn ngữ Tây Âu bao gồm tiếng Anh), byte 0xc3 được hiểu là Ã
ký tự và 0xa0 là không phải là ký tự phá vỡ không gian nhân vật.
Và trên một vài hệ thống như NetBSD³, không gian không phá vỡ đó được coi là một ký tự trống ( isblank()
trên đó trả về đúng, nó được khớp bởi [[:blank:]]
) và các shell như bash
do đó coi nó như một dấu phân cách mã thông báo trong cú pháp của chúng.
Điều đó có nghĩa là thay vì chạy echo
với $'voil\xc3\xa0'
tư cách là đối số, họ chạy nó với $'voil\xc3'
tư cách là đối số, có nghĩa là nó sẽ không được in voilà
chính xác.
Nó sẽ nặng hơn rất nhiều với bộ ký tự Trung Quốc như BIG5, BIG5-HKSCS, GB18030, GBK đó có nhiều nhân vật có mã hóa chứa các mã hóa giống như |
, `
, \
(tên tồi tệ nhất) (cũng là SJIS lố bịch, hay còn gọi là Microsoft Kanji, ngoại trừ rằng nó ¥
thay vì \
, nhưng vẫn được xử lý như \
hầu hết các công cụ vì nó được mã hóa thành 0x5c ở đó).
Chẳng hạn, nếu ở một zh_CN.gb18030
địa phương Trung Quốc, bạn viết một đoạn script như:
echo 詜 reboot
Tập lệnh đó sẽ xuất ra 詜 reboot
một ngôn ngữ sử dụng GB18030 hoặc GBK, 唰 reboot
trong một ngôn ngữ sử dụng BIG5 hoặc BIG5-HKSCS, nhưng trong một ngôn ngữ C sử dụng ASCII hoặc một miền địa phương sử dụng ISO8859-15 hoặc UTF-8, sẽ reboot
bị chạy vì mã hóa GB18030 của 詜
là 0xd4 0x7c và 0x7c là mã hóa |
trong ASCII nên cuối cùng chúng tôi sẽ chạy:
echo �| reboot
(tuy nhiên, đại diện cho byte 0xd4 được hiển thị ở miền địa phương). Ví dụ sử dụng ít gây hại uname
thay vì reboot
:
$ echo $'echo \u8a5c uname' | iconv -t gb18030 > myscript
$ LC_ALL=zh_CN.gb18030 bash ./myscript | sed -n l
\324| uname$
$ LC_ALL=C bash ./myscript | sed -n l
Linux$
( uname
đã chạy).
Vì vậy, lời khuyên của tôi sẽ là trích dẫn tất cả các chuỗi có chứa các ký tự bên ngoài bộ ký tự di động.
Tuy nhiên lưu ý rằng kể từ khi mã hóa \
và `
được tìm thấy trong mã hóa của một số những nhân vật, đó là tốt hơn không sử dụng \
hoặc "..."
hoặc $'...'
(bên trong đó `
và / hoặc \
vẫn còn đặc biệt), nhưng '...'
thay vì để trích dẫn các nhân vật bên ngoài bộ ký tự cầm tay.
Tôi không biết bất kỳ hệ thống nào có miền địa phương nơi bộ ký tự có bất kỳ ký tự nào (không phải là '
chính nó) có mã hóa chứa mã hóa '
, vì vậy chúng '...'
chắc chắn là an toàn nhất.
Lưu ý rằng một số shell cũng hỗ trợ $'\uXXXX'
ký hiệu để thể hiện các ký tự dựa trên điểm mã Unicode của chúng. Trong các shell như zsh
và bash
, ký tự được chèn được mã hóa trong bộ ký tự của miền địa phương (mặc dù có thể gây ra các hành vi không mong muốn nếu bộ ký tự đó không có ký tự đó). Điều đó cho phép bạn tránh chèn các ký tự không phải ASCII vào mã shell của mình.
Vì vậy, ở trên:
echo 'voilà' | iconv -f UTF-8 -t //TRANSLIT
echo '詜 reboot'
Hoặc là:
echo $'voil\u00e0'
echo $'\u8a5c reboot'
(với sự cảnh báo, nó có thể phá vỡ tập lệnh khi chạy ở các địa phương không có các ký tự đó).
Hoặc tốt hơn, vì \
cũng đặc biệt đối với echo
(hoặc ít nhất là một số echo
triển khai, ít nhất là các triển khai tuân thủ Unix):
printf '%s\n' 'voilà' | iconv -f UTF-8 -t //TRANSLIT
printf '%s\n' '詜 reboot'
(lưu ý rằng \
cũng đặc biệt trong đối số đầu tiên printf
, vì vậy các ký tự không phải ASCII cũng tốt hơn nên tránh ở đó trong trường hợp chúng có thể chứa mã hóa \
).
Lưu ý rằng bạn cũng có thể làm:
'echo' 'voilà' | 'iconv' '-f' 'UTF-8' '-t' '//TRANSLIT'
(đó sẽ là quá mức cần thiết nhưng có thể giúp bạn yên tâm hơn nếu bạn không chắc chắn nhân vật nào có trong bộ ký tự di động)
Cũng đảm bảo không bao giờ sử dụng `...`
hình thức thay thế lệnh cổ (giới thiệu một mức xử lý dấu gạch chéo ngược khác), nhưng sử dụng $(...)
thay thế.
Về mặt kỹ thuật, echo
cũng được chuyển làm đối số cho echo
tiện ích (để cho nó biết nó được gọi như thế nào), nó là argv[0]
và argc
3, mặc dù trong hầu hết các shell hiện nay echo
đều được dựng sẵn, do đó, exec()
một /bin/echo
tệp có danh sách 3 đối số được mô phỏng bởi vỏ. Cũng thường thấy danh sách các đối số bắt đầu bằng đối số thứ hai ( argv[1]
đến argv[argc - 1]
) vì đó là các đối số mà các lệnh chủ yếu hành động theo.
² một ngoại lệ đáng chú ý đó là lố bịch ja_JP.SJIS
locale của hệ thống FreeBSD có charset không có \
cũng không ~
nhân vật!
³ lưu ý rằng mặc dù nhiều hệ thống (FreeBSD, Solaris, chứ không phải GNU) coi U + 00A0 là một [[:blank:]]
ngôn ngữ UTF-8, nhưng rất ít người làm ở các địa phương khác như những người sử dụng ISO8859-15, có thể để tránh loại vấn đề này.