2012-10-08 29 views
5

Mọi người đều nói eval là xấu, và bạn nên sử dụng $() làm người thay thế. Nhưng tôi đã gặp phải tình huống trong đó unquoting không được xử lý giống nhau bên trong $().

Bối cảnh là tôi đã bị đốt cháy quá thường xuyên bởi các đường dẫn tệp có khoảng trống trong chúng và do đó, để trích dẫn tất cả các đường dẫn như vậy. Nhiều hoang tưởng hơn về việc muốn biết tất cả các tập tin thực thi của tôi đến từ đâu. Thậm chí hoang tưởng hơn, không tin tưởng bản thân mình, và vì vậy giống như có thể hiển thị các lệnh đã tạo mà tôi sắp chạy.

Dưới đây tôi cố gắng biến thể về việc sử dụng eval so với $(), và cho dù tên lệnh được trích dẫn (cuz nó thể chứa khoảng trắng)

BIN_LS="/bin/ls" 
    thefile="arf" 
    thecmd="\"${BIN_LS}\" -ld -- \"${thefile}\"" 

    echo -e "\n Running command '${thecmd}'" 
    $($thecmd) 

      Running command '"/bin/ls" -ld -- "arf"' 
     ./foo.sh: line 8: "/bin/ls": No such file or directory 

    echo -e "\n Eval'ing command '${thecmd}'" 
    eval $thecmd 

      Eval'ing command '"/bin/ls" -ld -- "arf"' 
     /bin/ls: cannot access arf: No such file or directory 

    thecmd="${BIN_LS} -ld -- \"${thefile}\"" 

    echo -e "\n Running command '${thecmd}'" 
    $($thecmd) 

      Running command '/bin/ls -ld -- "arf"' 
     /bin/ls: cannot access "arf": No such file or directory 

    echo -e "\n Eval'ing command '${thecmd}'" 
    eval $thecmd 

      Eval'ing command '/bin/ls -ld -- "arf"' 
     /bin/ls: cannot access arf: No such file or directory 

    $("/bin/ls" -ld -- "${thefile}") 

     /bin/ls: cannot access arf: No such file or directory 

Vì vậy ... đây là khó hiểu. Đường dẫn lệnh được trích dẫn là hợp lệ ở mọi nơi ngoại trừ bên trong một cấu trúc $()? Ví dụ ngắn hơn, trực tiếp hơn:

$ c="\"/bin/ls\" arf" 
$ $($c) 
-bash: "/bin/ls": No such file or directory 
$ eval $c 
/bin/ls: cannot access arf: No such file or directory 
$ $("/bin/ls" arf) 
/bin/ls: cannot access arf: No such file or directory 
$ "/bin/ls" arf 
/bin/ls: cannot access arf: No such file or directory 

Làm cách nào giải thích trường hợp đơn giản $($c)?

Trả lời

3

Với thực tế là nó không có ý nghĩa ở nơi đầu tiên. Use an array instead.

$ c=("/bin/ls" arf) 
$ "${c[@]}" 
/bin/ls: cannot access arf: No such file or directory 
+0

Chuỗi không được kiểm duyệt sẽ được đánh giá ngay lập tức. c = ("/ bin/ls"/bin/l *) thực hiện điều gì đó khá khác so với mong muốn. (nhìn với echo "'$ {c [@]}'") c = ("/ bin/ls" "/ bin/l *") thì z = $ ("$ {c [@]}") không thành công. Một mảng các đối số không thể được sử dụng độc đáo với $()? – Shenme

+1

Tuy nhiên, c = ("/ bin/ls" "/ bin/l *") thì z = $ ($ {c [@]}) có vẻ hoạt động tốt. Đây có phải là biểu mẫu thực thi $() mà bạn muốn chỉ cho tôi không? – Shenme

5

Sử dụng " để trích dẫn từ là một phần của sự tương tác của bạn với Bash. Khi bạn gõ

$ "/bin/ls" arf 

tại dấu nhắc, hoặc trong một kịch bản, bạn đang nói Bash rằng lệnh bao gồm các từ /bin/lsarf, và hai dấu ngoặc kép đang thực sự nhấn mạnh rằng /bin/ls là một từ duy nhất.

Khi bạn gõ

$ eval '"/bin/ls" arf' 

bạn đang nói Bash rằng lệnh bao gồm các từ eval"/bin/ls" arf. Vì mục đích của eval là để giả vờ rằng đối số của nó là một lệnh nhân đầu vào thực tế, điều này tương đương với chạy

$ "/bin/ls" arf 

" được xử lý giống như tại dấu nhắc.

Lưu ý rằng giả vờ này cụ thể cho eval; Bash thường không đi ra khỏi con đường của mình để giả vờ rằng một cái gì đó là một lệnh con người thực sự gõ.

Khi bạn gõ

$ c='"/bin/ls" arf' 
$ $c 

các $c bị thay thế, và sau đó trải qua từ tách (xem §3.5.7 "Word Splitting" in the Bash Reference Manual), vì vậy những lời của lệnh là "/bin/ls" (chú ý hai dấu ngoặc kép) và arf. Không cần phải nói, điều này không hoạt động. (Nó cũng không an toàn lắm, vì ngoài việc tách từ, thì $c cũng trải qua việc mở rộng tên tệp và không biết gì. Nói chung, các mở rộng tham số của bạn luôn phải ở trong dấu ngoặc kép và nếu chúng không thể, thì bạn nên viết lại để họ có thể được. Unquoted tham số-mở rộng đang yêu cầu cho sự cố.)

Khi bạn gõ

$ c='"/bin/ls" arf' 
$ $($c) 

này cũng giống như trước đây, ngoại trừ việc bây giờ bạn cũng đang cố gắng sử dụng đầu ra của lệnh nonworking như một mới lệnh. Không cần phải nói, điều đó không gây ra lệnh không hoạt động đột ngột hoạt động.

Như Ignacio Vazquez-Abrams nói trong câu trả lời của mình, giải pháp đúng là sử dụng một mảng, và xử lý các trích dẫn đúng cách:

$ c=("/bin/ls" arf) 
$ "${c[@]}" 

mà đặt c đến một mảng với hai yếu tố, /bin/lsarf, và sử dụng hai yếu tố đó làm từ của lệnh.

+0

(ghét giới hạn 5 phút) Dòng cuối cùng không hiển thị lệnh được đóng gói trong một biểu mẫu nơi đầu ra có thể được chụp (mặc dù không được nói trong câu hỏi ban đầu) Hãy thử c = ("/ bin/ls" "/ bin/l * ") và c = ("/bin/ls "/ bin/l *) với echo" '$ {c [@]}' ". Phương pháp này sẽ không cho phép tôi xây dựng một mảng với các đối số được trích dẫn để sử dụng sau này với $() ...? – Shenme

+1

@Shenme: Theo như tôi có thể nói, những gì bạn đang nói là, "Tôi muốn sử dụng' eval', bởi vì tôi muốn xử lý một chuỗi tùy ý như một lệnh Bash. " Những gì mọi người khác đang nói là, "' eval' là điều ác, bởi vì nó xử lý một chuỗi tùy ý như một lệnh Bash."Bạn đang cố gắng hòa giải các quan điểm này bằng cách hỏi," Có một số cách không * không phải để làm điều ác này không? ", Nhưng điều đó không hiệu quả. Quan điểm cơ bản là không thể hòa giải. một công cụ độc ác, hoặc, để tránh các công cụ ác, viết lại kịch bản của bạn để tránh làm điều ác. – ruakh

+0

Không, không có lệnh tùy ý, không có ý định xấu. :-) Chỉ muốn xây dựng một lệnh và thực hiện nó một cách có kiểm soát Tôi sẽ kiểm soát các yếu tố đầu vào, và thậm chí còn hoang tưởng đủ để sử dụng '-'. Mọi người nhìn thấy câu trả lời đơn giản cho các câu hỏi phức tạp, và chỉ trích bạn nếu bạn 'làm' sử dụng eval, vì vậy tôi muốn tránh những lời chỉ trích. - (Dù sao, z = $ ($ {c [@]}) chỉ hoạt động tốt – Shenme

2

Từ man page for bash, về eval:

eval [arg ...]: Các args được đọc và nối với nhau thành một lệnh duy nhất. Lệnh này sau đó được đọc và thực hiện bởi trình bao, và trạng thái thoát được trả về dưới dạng giá trị của eval.

Khi c được định nghĩa là "\"/bin/ls\" arf", có dấu ngoặc kép bên ngoài sẽ gây toàn bộ điều để được xử lý như là đối số đầu tiên eval, mà dự kiến ​​sẽ là một lệnh hoặc chương trình. Bạn cần chuyển các đối số eval của mình theo cách mà lệnh mục tiêu và các đối số của nó được liệt kê riêng.

Cấu trúc $(...) hoạt động khác với eval vì nó không phải là lệnh lấy đối số. Nó có thể xử lý toàn bộ lệnh cùng một lúc thay vì xử lý từng đối số một.

Ghi chú trên tiền đề ban đầu của bạn: Lý do chính khiến mọi người nói rằng eval là điều xấu vì nó thường được sử dụng bởi tập lệnh để thực thi chuỗi do người dùng cung cấp dưới dạng lệnh trình bao. Mặc dù hữu ích vào những thời điểm, đây là vấn đề bảo mật chính bảo mật (thường không có cách nào thực tế để kiểm tra chuỗi an toàn trước khi thực thi). Vấn đề bảo mật không áp dụng nếu bạn đang sử dụng eval trên các chuỗi được mã hóa cứng bên trong tập lệnh của bạn, như bạn đang làm. Tuy nhiên, thường dễ sử dụng hơn $(...) hoặc `...` bên trong tập lệnh để thay thế lệnh, không để lại trường hợp sử dụng thực nào cho eval.

+0

Mẹo bổ sung: khi cố gắng xem một lệnh mở rộng được trình bao thấy như thế nào, sử dụng 'set -v' đôi khi có thể cho kết quả chính xác hơn' echo'. – bta

+0

về cơ bản, 'eval' là an toàn nếu chuỗi được đánh giá không được hiển thị theo bất kỳ cách nào để người dùng nhập vào; nhưng nếu chúng ta thất bại tại bất kỳ điểm nào trong việc ngăn chặn điều đó, nó có thể bị khai thác; do đó, việc sử dụng '' ... '' hoặc '$ (...)' yêu cầu ít cảnh báo hơn so với 'eval'; thx man! đây là mẹo cụ thể mà tôi đang tìm kiếm trước khi thay đổi kịch bản của mình hehe! :) –

Các vấn đề liên quan