Có nguy hiểm khi chạy echo mà không có dấu ngoặc kép?


11

Tôi đã thấy một vài chủ đề tương tự, nhưng chúng đề cập đến việc không trích dẫn các biến, mà tôi biết có thể dẫn đến kết quả không mong muốn.

Tôi đã thấy mã này và tự hỏi liệu có thể tiêm thứ gì đó để chạy khi dòng mã này thực thi không:

echo run after_bundle


Tôi gặp phải vấn đề này khi tôi có: target = "*** LIVE SERVER ***"; mục tiêu tiếng vang: $ mục tiêu; và *** được mở rộng thành một danh sách thư mục ...
Matt Parkins

Câu trả lời:


17

Đối với trường hợp cụ thể

echo run after_bundle

trích dẫn là không cần thiết. Không cần trích dẫn bởi vì đối số echolà các chuỗi tĩnh không chứa mở rộng biến đổi hoặc thay thế lệnh, v.v. Chúng chỉ là "hai từ" (và như Stéphane chỉ ra , chúng được xây dựng thêm từ bộ ký tự di động ).

"Nguy hiểm" xuất hiện khi bạn xử lý dữ liệu biến đổi mà vỏ có thể mở rộng hoặc diễn giải. Trong những trường hợp như vậy, phải cẩn thận rằng vỏ làm đúng và kết quả là những gì dự định.

Hai câu hỏi sau đây chứa thông tin liên quan về điều đó:


echođôi khi được sử dụng để "bảo vệ" các lệnh có hại trong câu trả lời trên trang web này. Ví dụ: tôi có thể chỉ ra cách xóa tệp hoặc di chuyển tệp đến đích mới bằng cách sử dụng

echo rm "${name##*/}.txt"

hoặc là

echo mv "$name" "/new_dir/$newname"

Điều này sẽ xuất các lệnh trên thiết bị đầu cuối thay vì thực sự loại bỏ hoặc đổi tên các tập tin. Sau đó, người dùng có thể kiểm tra các lệnh, quyết định rằng chúng trông ổn, loại bỏ echovà chạy lại.

Lệnh của bạn echo run after_bundlecó thể là một hướng dẫn cho người dùng hoặc nó có thể là một đoạn mã "nhận xét" quá nguy hiểm để chạy mà không biết hậu quả.

Sử dụng echonhư thế này, người ta phải biết những gì lệnh sửa đổi không và một người phải đảm bảo rằng lệnh sửa đổi thực sự an toàn (nó sẽ có khả năng không được nếu nó chứa chuyển hướng, và sử dụng nó trên một đường ống không làm việc, vv)


Tuy nhiên, việc thêm dấu ngoặc kép là không đủ để biết shell sẽ làm gì - tuy nhiên, giống như bạn không thể nói điều đó echo rm "first file.txt" "second file.txt"khác với bất kỳ cách nào khác echo rm "first" "file.txt" "second" "file.txt", đầu ra từ cả hai đều giống nhau. Nếu bạn muốn tạo một lệnh shell làm đầu ra, người ta phải sử dụng printf '%q ' rm "first file.txt" "second file.txt"; echohoặc một cái gì đó tương đương để tạo lại trích dẫn cú pháp đánh giá cho argvthông qua.
Charles Duffy

@CharlesDuffy Tôi thực sự hy vọng không ai sao chép-dán đầu ra gỡ lỗi và chạy nó trong trình bao!
Kusalananda

1
Tạo các lệnh shell và sau đó chuyển chúng thành shmột mẫu không phổ biến và thấy mọi người hỏi "tại sao tôi foolàm việc khi tôi chạy nó trên một dòng lệnh, nhưng tập lệnh này phát ra chuỗi chính xác đó echoở phía trước dòng không? " xảy ra tất cả thời gian ở đây. Hơn nữa, đầu ra gỡ lỗi không hữu ích nếu nó che giấu các lỗi của bạn - và nếu các lỗi của bạn có liên quan đến trích dẫn, thì echosẽ không tiết lộ chúng.
Charles Duffy

27

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 để echochứ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 echohay bất kỳ lệnh nào khác, vấn đề không nằm ở chỗ vấn đề là echotrì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-15miề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ư bashdo đó 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 echovớ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 詜 rebootmột ngôn ngữ sử dụng GB18030 hoặc GBK, 唰 reboottrong 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ẽ rebootbị 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 unamethay 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 \`đượ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ư zshbash, 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, echocũng được chuyển làm đối số cho echotiện ích (để cho nó biết nó được gọi như thế nào), nó là argv[0]argc3, 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/echotệ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.SJISlocale 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.


Trong đoạn đầu tiên của bạn, bạn nói với chúng tôi "... về các ký tự trong 3 đối số được truyền cho echo...", tôi chỉ đếm 2 đối số được truyền cho lệnh echo, các đối số tôi có thể đếm là runafter_bundlequan tâm giải thích cách bạn đếm và có tới 3 đối số?
Ferrybig

1
@ViktorFonic, xem chỉnh sửa về số lượng đối số (và vấn đề chính không phải là với echo). Xem (exec -a foo /bin/echo --help)trên hệ thống GNU và với trình bao GNU để biết cách truyền đối số đầu tiên tùy ý cho /bin/echotiện ích.
Stéphane Chazelas

@Ferrybig Xem phần chỉnh sửa của Stephane, chú thích 1. Các đối số để ra lệnh theo kiểu C thông thường là một loạt các đối số, với argv [0] là tên thực thi. Kinda tương tự $0và tham số vị trí trong shell.
Sergiy Kolodyazhnyy

Có 373 mã hóa iconvtrong đó ESCđược chuyển đổi thành '. Hãy thử (làm ví dụ):printf '\x1b'|iconv -f utf8 -t IBM-937|xxd
NotAnUnixNazi

Có 173 mã hóa trong đó một số tiền mã hóa (không phải ESC) được chuyển đổi thành a '. Hãy thử printf '\u2804' | iconv -f utf8 -t BRF | xxd. Có những bảng mã trong đó có rất nhiều tiền mã hóa trở thành '. Khoảng 8695 điểm mã trong UCS-4 trở thành '. Hãy thử printf '\U627' | iconv -cf utf-8 -t UCS-4. Một số (37) mã hóa chuyển đổi ký tự 0x127 thành a '. Hãy thửprintf '\U127' | iconv -cf utf8 -t UCS2 |xxd
NotAnUnixNazi
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.