2009-08-12 17 views
7

Tôi có một tập lệnh Perl xử lý một loạt tên tệp và sử dụng các tên tệp đó trong các dấu gạch chéo ngược. Nhưng tên tệp chứa dấu cách, dấu nháy đơn và các ký tự funky khác.Làm thế nào tôi có thể chuyển một tên tập tin bằng dấu cách vào một lệnh ngoài trong Perl một cách an toàn?

Tôi muốn có thể thoát chúng đúng cách (nghĩa là không sử dụng regex ngẫu nhiên ngoài đầu của tôi). Có một mô-đun CPAN thoát đúng chuỗi để sử dụng trong các lệnh bash không? Tôi biết tôi đã giải quyết vấn đề này trong quá khứ, nhưng tôi không thể tìm thấy bất cứ điều gì về nó lần này. Dường như có ít thông tin đáng ngạc nhiên về nó.

Trả lời

3

Bạn đang tìm kiếm quotemeta?

Trả về giá trị của EXPR với tất cả các ký tự không phải "từ" bị gạch chéo ngược.

Cập nhật: Như Hobbs chỉ ra trong các ý kiến, quotemeta không dành cho mục đích này và sau khi suy nghĩ nhiều hơn một chút về nó, có thể có vấn đề với nhúng nul s. Mặt khác, String::ShellQuote bị bẻ cong khi gặp phải null s được nhúng.

Cách an toàn nhất là tránh hoàn toàn trình bao. Sử dụng hình thức danh sách 'system' có thể đi một chặng đường dài hướng tới điều đó (tôi phát hiện ra sự mất tinh thần của tôi một vài tháng trước rằng cmd.exe vẫn có thể tham gia vào Windows), tôi muốn giới thiệu điều đó.

Nếu bạn cần đầu ra của lệnh, bạn là tốt nhất tắt (an toàn-khôn ngoan) mở một đường ống chính mình như trong hobbs' answer

+0

Vâng, tôi nghĩ đó là một .. – aidan

+0

quotemeta quotes for regexes, not bash. –

+0

'quotemeta' được sử dụng để thực hiện thoát' \ Q' trong các regex. –

6

Nếu bạn có thể quản lý nó (tức là nếu bạn đang gọi một số lệnh trực tiếp, mà không có bất kỳ kịch bản shell hoặc shenanigans chuyển hướng nâng cao nào), điều an toàn nhất cần làm là tránh truyền dữ liệu hoàn toàn thông qua trình bao.

Trong perl 5.8+:

my @output_lines = do { 
    open my $fh, "-|", $command, @args or die "Failed spawning $command: $!"; 
    <$fh>; 
}; 

Nếu đó là cần thiết để hỗ trợ 5.6:

my @output_lines = do { 
    my $pid = open my $fh, "-|"; 
    die "Couldn't fork: $!" unless defined $pid; 
    if (!$pid) { 
     exec $command, @args or die "Eek, exec failed: $!"; 
    } else { 
     <$fh>; # This is the value of the C<do> 
    } 
}; 

Xem perldoc perlipc để biết thêm thông tin về loại hình kinh doanh, và xem thêm IPC::Open2IPC::Open3.

+0

Điều này hoạt động tốt trên các hệ thống giống Unix (là tất cả các OP được yêu cầu) và là một giải pháp thay thế ít tiện dụng hơn để sử dụng 'qx //' (backticks). Cẩn thận cho người dùng Windows: trình bao, 'cmd.exe', có thể _still_ được gọi, cụ thể là _fallback_ nếu trình gọi shell-less thất bại. Hơn nữa, bất kỳ đối số nào chứa _embedded_ double-quotes phải được _escaped_ để được truyền qua một cách chính xác, với các đối số đầu vào được giữ nguyên); bạn làm điều đó bằng _enclosing_ giá trị trong _embedded_ dấu ngoặc kép và bằng cách thoát khỏi dấu nháy kép ban đầu theo yêu cầu của chương trình đích, thường là '\" '. – mklement0

1

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 systemexec 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 systemexeccó 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ỏ đượ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à systemexec.

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()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à \".
+0

Viết lớn. Bằng cách này, tôi đang thực hiện một số nghiên cứu về Shell trích dẫn cho các cuộc gọi đệ quy 'system' (ví dụ' system 'bash -c' bash -c '\' 'echo "hello";' \ '' '; ') .. loại cuộc gọi này không được xử lý đúng bởi [' String :: ShellQuote'] (https://metacpan.org/pod/String::ShellQuote) .. Tôi đã liên lạc với người bảo trì cách đây hai tuần, nhưng anh ấy dường như đã rời khỏi hiện trường. Tôi tự hỏi liệu bạn có thể quan tâm đến (có vẻ như nó cũng thiếu sự hỗ trợ của Windows.) –

+1

Cảm ơn, @ HåkonHægland Tôi đánh giá cao đề xuất, nhưng không thể dành thời gian vào lúc này (tôi đã gửi email cho người duy trì liên kết đến câu trả lời này, nhưng kinh nghiệm cho thấy rằng có thể đi không nghe) – mklement0

+1

Tôi gặp phải nhiều trường hợp sử dụng .. phổ biến nhất là 'ssh' comman ds, xem ví dụ [Trích dẫn trong bash và perl trong lệnh ssh đệ quy] (http://stackoverflow.com/questions/23597777/quoting-in-bash-and-perl-in-recursive-ssh-command). Các trường hợp sử dụng khác là khi bạn cần tải '~/.bashrc', xem ví dụ [Chạy lệnh hệ thống trong trình bash tương tác] (http://stackoverflow.com/questions/27581085/running-system-command-under-interactive -bash-shell) –

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