tl; dr
Sau đây chương trình con một cách an toàn trích dẫn (thoát) một danh sách các tên tập tin (đường dẫn) trên cả Unix-like và Windows hệ thống:
#!/usr/bin/env perl
sub quoteforshell {
return join ' ', map {
$^O eq 'MSWin32' ?
'"' . s/"/""/gr . '"'
:
"'" . s/'/'\\''/gr . "'"
} @_;
}
#'# Sample invocation
my $shellcmd = ($^O eq 'MSWin32' ? 'echo ' : 'printf "%s\n" ') .
quoteforshell('\\foo/bar', 'I\'m here', '3" of snow', 'bar |&;()<>#!');
print `$shellcmd`;
Output của mẫu lệnh trên các hệ thống giống Unix, cho thấy rằng tất cả các đối số đầu vào đã được chuyển qua chưa sửa đổi:
\foo/bar
I'm here
3" of snow
bar |&;()<>#!
Trên các hệ thống giống Unix, nó sẽ hoạt động với bất kỳ chuỗi nào (ngoại trừ các chuỗi có ký tự NUL được nhúng), không chỉ tên tệp - xem bên dưới để biết chi tiết.
Trên Windows, nhúng "
trường hợp được thoát như ""
, đó là chỉ an toàn cách để làm điều đó, nhưng, thật đáng buồn, có thể không gì chương trình mục tiêu kỳ vọng - xem dưới đây để biết chi tiết; tuy nhiên, lưu ý rằng đây không phải là mối quan ngại nếu bạn chỉ chuyển tên tệp trên Windows, bởi vì "
không phải là một tên tệp pháp lý.
Xem dưới cùng của bài viết này cho một vỏ ít lệnh gọi thay thế không cần qua giai "
-quoting vấn đề trên Windows.
On nền tảng Unix-like, qx//
(dạng tổng quát của `...`
) và các hình thức đơn lập luận của system
và exec
gọi vỏ bằng đi qua các lệnh để /bin/sh -c
. /bin/sh
được giả định là tương thích POSIX (và có thể hoặc không thể là Bash trên một hệ thống nhất định).
Các đơn lập luận hình thức system
và exec
có thể hoặc không thể bao gồm một vỏ - họ quyết định dựa trên các lệnh cụ thể thông qua việc tham gia của một vỏ là cần thiết. Ví dụ: nếu một lệnh có nhúng (theo nghĩa đen) một hoặc hai dấu ngoặc kép, vỏ là được gọi. Vì giải pháp dưới đây dựa trên việc nhúng các mã thông báo đơn trong chuỗi lệnh, nó cũng hoạt động với biểu mẫu một đối số là system
và exec
.
Trong vỏ tương thích POSIX, bạn có thể tận dụng lợi thế của chuỗi được trích dẫn đơn, không nội suy nội dung của chúng theo bất kỳ cách nào.
Thách thức chỉ để thoát khỏi dấu nháy đơn ('
) mình, yêu cầu sự cố, vì, nói đúng, nhúng các dấu nháy đơn trong một chuỗi được trích dẫn không được trình bao hỗ trợ.
Bí quyết là để thay thế mỗi '
dụ với '\''
(sic), mà làm việc xung quanh vấn đề bằng cách tách một cách hiệu quả chuỗi đầu vào nhiều chuỗi đơn trích dẫn, với thoát '
trường - \'
-ghép trong - vỏ sau đó reassembles các phần chuỗi thành một chuỗi duy nhất.
Dưới đây là một chương trình con mà phải mất một danh sách các chuỗi (tên tập tin) và trả về một chuỗi không gian tách biệt của các phiên bản được trích dẫn trong những chuỗi đảm bảo đen sử dụng bằng vỏ:
sub quoteforsh { join ' ', map { "'" . s/'/'\\''/gr . "'" } @_ }
Ví dụ (sử dụng hầu hết các metacharacters POSIX vỏ):
my $shellcmd = 'printf "%s\n" ' .
quoteforsh('\\foo/bar', 'I\'m here', '3" of snow', 'bar |&;()<>#!');
print `$shellcmd`;
này vượt qua sau để /bin/sh -c
(hiển thị ở đây như là một nghĩa đen thuần túy, mà không cần bất kỳ trích dẫn):
printf "%s\n" '\foo/bar' 'I'\''m here' '3" of snow' 'bar |&;()<>#!'
Lưu ý làm thế nào mỗi chuỗi đầu vào được trong kèm theo trong đơn dấu ngoặc kép, và làm thế nào các nhân vật duy nhất mà cần trích dẫn trong tất cả các chuỗi đầu vào là '
, mà , như đã thảo luận, đã được thay thế bằng '\''
.
nên sản lượng này các chuỗi đầu vào như nó vốn có, một trên mỗi dòng:
\foo/bar
I'm here
3" of snow
bar |&;()<>#!
On của Windows, các chương trình con tương tự như thế này:
sub quoteforcmdexe { join ' ', map { '"' . s/"/""/gr . '"' } @_ }
Điều này hoạt động tương tự như quoteforsh()
ở trên, ngoại trừ
- dấu ngoặc kép được sử dụng để đính kèm mã thông báo, vì
cmd.exe
không hỗ trợ một trích dẫn.
- nhân vật duy nhất mà cần thoát là
"
, được thoát như ""
- lưu ý, tuy nhiên, cho tên tập tin này là không thực sự cần thiết, bởi vì Windows không cho phép "
trường hợp trong tên tập tin.
Tuy nhiên, có hạn chế và những cạm bẫy:
- Bạn không thể ngăn chặn sự giải thích của tài liệu tham khảo để hiện biến môi trường, chẳng hạn như
%USERNAME%
; ngược lại, các biến không tồn tại hoặc các trường hợp %
bị cô lập là tốt.
- Lưu ý: Bạn nên có thể thoát khỏi
%
trường hợp như %%
, nhưng trong khi làm việc trong một tập tin thực thi, nó hiểu sao không làm việc từ Perl:
`perl "%%USERNAME%%.pl"`
phàn nàn, ví dụ, khoảng %jdoe%.pl
không được tìm thấy, ngụ ý rằng %USERNAME%
được nội suy, mặc dù số ký tự %
được tăng gấp đôi.
- (Trên Ngược lại, cô lập
%
trường hợp trong chuỗi dụng dấu ngoặc kép không cần thoát theo cách mà họ làm trong các tập tin batch.)
- Thoát nhúng
"
trường hợp như ""
là chỉ SAFE cách để làm điều đó, nhưng nó không phải là những gì hầu hết các chương trình mục tiêu mong đợi.
- Trên Windows, vô cùng, các yêu cầu thoát là cuối cùng lên đến Chương trình mục tiêu - cho nền đầy đủ, xem https://stackoverflow.com/a/31413730/45375
- Tóm lại, tình thế khó khăn là:
- Nếu bạn thoát cho chương trình đích - và hầu hết, bao gồm Perl, mong đợi
\"
- sau đó một phần của đối số danh sách có thể không bao giờ được chuyển đến chương trình đích, phần còn lại hoặc gây ra lỗi, chuyển hướng không mong muốn sang một tệp hoặc, tệ hơn, việc thực thi không mong muốn các lệnh tùy ý.
- Nếu bạn thoát cho
cmd.exe
, bạn có thể phá vỡ phân tích cú pháp của chương trình đích.
- Bạn không thể thoát cho cả hai.
- Bạn có thể khắc phục sự cố nếu lệnh của bạn không cần liên quan đến trình bao - xem bên dưới.
Alternative: vỏ ít lệnh gọi
Nếu lệnh của bạn là một lời gọi của một đơn thực thi với tất cả đối số được truyền như -is, không cần phải liên quan đến trình bao, trong đó:
- không cần trích dẫn của các đối số, trong đó đáng chú ý là bỏ qua
"
-quoting vấn đề trên Windows
- thường hiệu quả hơn
Sau đây chương trình con công trình trên cả Unix-like hệ thống và Windows và là thay thế ít vỏ thành qx//
(`...`
), chấp nhận lệnh cho tôi nvoke như một danh sách các đối số để giải thích như-là:
sub qxnoshell {
use IPC::Cmd;
return unless @_;
my @cmdargs = @_;
if ($^O eq 'MSWin32') { # Windows
# Ensure that the executable name ends in '.exe'
$cmdargs[0] .= '.exe' unless $cmdargs[0] =~ m/\.exe$/i;
unless (IPC::Cmd::can_run $cmdargs[0]) { # executable not found
# Issue warning, as qx// would and open '-|' below does.
my $warnmsg = "Executable '$cmdargs[0]' not found";
scalar(caller) eq 'main' ? warn($warnmsg . "\n") : warnings::warnif('exec', $warnmsg);
return;
}
for (@cmdargs[1..$#cmdargs]) {
if (m'"') {
s/"/\\"/; # \-escape embedded double-quotes
$_ = '"' . $_ . '"'; # enclose as a whole in embedded double-quotes
}
}
}
open my $fh, '-|', @cmdargs or return;
my @lines = <$fh>;
close $fh;
return wantarray ? @lines : join('', @lines);
}
Ví dụ
# Unix: $out should receive literal '$$', which demonstrates that
# /bin/sh is not involved.
my $out = qxnoshell 'printf', '%s', '$$'
# Windows: $out should receive literal '%USERNAME%', which demonstrates
# that cmd.exe is not involved.
my $out = qxnoshell 'perl', '-e', 'print "%USERNAME%"'
- Yêu cầu Perl v5.9.5 + do sử dụng
IPC::Cmd
.
- Lưu ý rằng các thủ tục con làm việc chăm chỉ để làm cho mọi hoạt động trên Windows:
- Mặc dù các đối số được truyền như một danh sách ,
open ..., '-|'
trên Windows vẫn rơi trở lại trên cmd.exe
nếu lần gọi đầu tiên không - tương tự áp dụng cho system()
và exec()
, tình cờ.
- Vì vậy, để ngăn chặn dự phòng này là
cmd.exe
- có thể có hậu quả không mong muốn - chương trình con (a) đảm bảo rằng đối số danh sách đầu tiên là thực thi *.exe
, (b) cố gắng xác định vị trí và (c) chỉ thử để gọi lệnh nếu tập tin thực thi có thể được đặt.
- Trên Windows, thật đáng buồn, bất kỳ đối số có chứa nhúng hai dấu ngoặc kép không được đi qua một cách chính xác đối với chương trình mục tiêu - nó cần thoát bằng cách (a) thêm nhúng hai dấu ngoặc kép để kèm luận rằng và (b) bằng cách thoát khỏi dấu ngoặc kép được nhúng ban đầu là
\"
.
Vâng, tôi nghĩ đó là một .. – aidan
quotemeta quotes for regexes, not bash. –
'quotemeta' được sử dụng để thực hiện thoát' \ Q' trong các regex. –