Tôi không nghĩ rằng bất kỳ triển khai nào ssh
cũng có cách riêng để truyền lệnh từ máy khách đến máy chủ mà không liên quan đến trình bao.
Bây giờ, mọi thứ có thể trở nên dễ dàng hơn nếu bạn có thể yêu cầu trình điều khiển từ xa chỉ chạy một trình thông dịch cụ thể (như sh
, chúng tôi biết cú pháp dự kiến) và cung cấp mã để thực thi theo nghĩa khác.
Điều đó có nghĩa là có thể là đầu vào tiêu chuẩn hoặc một biến môi trường .
Khi không thể được sử dụng, tôi đề xuất một giải pháp thứ ba hacky dưới đây.
Sử dụng stdin
Nếu bạn không cần cung cấp bất kỳ dữ liệu nào cho lệnh từ xa, đó là giải pháp đơn giản nhất.
Nếu bạn biết máy chủ từ xa có một xargs
lệnh hỗ trợ -0
tùy chọn và lệnh không quá lớn, bạn có thể thực hiện:
printf '%s\0' "${cmd[@]}" | ssh user@host 'xargs -0 env --'
xargs -0 env --
Dòng lệnh đó được giải thích giống nhau với tất cả các họ shell. xargs
đọc danh sách các đối số được phân tách bằng null trên stdin và chuyển chúng làm đối số cho env
. Giả sử đối số đầu tiên (tên lệnh) không chứa =
ký tự.
Hoặc bạn có thể sử dụng sh
trên máy chủ từ xa sau khi đã trích dẫn từng phần tử bằng cách sử dụng sh
cú pháp trích dẫn.
shquote() {
LC_ALL=C awk -v q=\' '
BEGIN{
for (i=1; i<ARGC; i++) {
gsub(q, q "\\" q q, ARGV[i])
printf "%s ", q ARGV[i] q
}
print ""
}' "$@"
}
shquote "${cmd[@]}" | ssh user@host sh
Sử dụng biến môi trường
Bây giờ, nếu bạn cần cung cấp một số dữ liệu từ máy khách đến stdin của lệnh từ xa, giải pháp trên sẽ không hoạt động.
ssh
Tuy nhiên, một số triển khai máy chủ cho phép chuyển các biến môi trường tùy ý từ máy khách sang máy chủ. Chẳng hạn, nhiều triển khai openssh trên các hệ thống dựa trên Debian cho phép truyền các biến có tên bắt đầu bằng LC_
.
Trong những trường hợp, bạn có thể có một LC_CODE
biến ví dụ chứa shquoted sh
mã như ở trên và chạy sh -c 'eval "$LC_CODE"'
trên máy chủ từ xa sau khi đã nói với khách hàng của bạn để vượt qua biến mà (một lần nữa, đó là một dòng lệnh của hiểu như nhau trong mỗi vỏ):
LC_CODE=$(shquote "${cmd[@]}") ssh -o SendEnv=LC_CODE user@host '
sh -c '\''eval "$LC_CODE"'\'
Xây dựng một dòng lệnh tương thích với tất cả các họ shell
Nếu không có tùy chọn nào ở trên chấp nhận được (vì bạn cần stdin và sshd không chấp nhận bất kỳ biến nào hoặc vì bạn cần một giải pháp chung), thì bạn sẽ phải chuẩn bị một dòng lệnh cho máy chủ từ xa tương thích với tất cả hỗ trợ đạn pháo.
Điều đó đặc biệt khó khăn bởi vì tất cả các shell đó (Bourne, csh, rc, es, fish) có cú pháp khác nhau, và đặc biệt là các cơ chế trích dẫn khác nhau và một số trong số chúng có những hạn chế khó khắc phục.
Đây là một giải pháp tôi đã đưa ra, tôi mô tả nó sâu hơn:
#! /usr/bin/perl
my $arg, @ssh, $preamble =
q{printf '%.0s' "'\";set x=\! b=\\\\;setenv n "\
";set q=\';printf %.0s "\""'"';q='''';n=``()echo;x=!;b='\'
printf '%.0s' '\'';set b \\\\;set x !;set -x n \n;set q \'
printf '%.0s' '\'' #'"\"'";export n;x=!;b=\\\\;IFS=.;set `echo;echo \.`;n=$1 IFS= q=\'
};
@ssh = ('ssh');
while ($arg = shift @ARGV and $arg ne '--') {
push @ssh, $arg;
}
if (@ARGV) {
for (@ARGV) {
s/'/'\$q\$b\$q\$q'/g;
s/\n/'\$q'\$n'\$q'/g;
s/!/'\$x'/g;
s/\\/'\$b'/g;
$_ = "\$q'$_'\$q";
}
push @ssh, "${preamble}exec sh -c 'IFS=;exec '" . join "' '", @ARGV;
}
exec @ssh;
Đó là một perl
kịch bản bao bọc xung quanh ssh
. Tôi gọi nó sexec
. Bạn gọi nó như:
sexec [ssh-options] user@host -- cmd and its args
vì vậy trong ví dụ của bạn:
sexec user@host -- "${cmd[@]}"
Và trình bao bọc biến cmd and its args
thành một dòng lệnh mà tất cả các shell kết thúc bằng cách gọi cmd
với các đối số của nó (không có nội dung của chúng).
Hạn chế:
- Lời mở đầu và cách trích dẫn lệnh có nghĩa là dòng lệnh từ xa kết thúc lớn hơn đáng kể, điều đó có nghĩa là giới hạn về kích thước tối đa của một dòng lệnh sẽ đạt được sớm hơn.
- Tôi chỉ thử nghiệm nó với: Bourne shell (từ công cụ gia truyền), dash, bash, zsh, mksh, lksh, yash, ksh93, rc, es, akanga, csh, tcsh, fish như được tìm thấy trên hệ thống Debian gần đây và / bin / sh, / usr / bin / ksh, / bin / csh và / usr / xpg4 / bin / sh trên Solaris 10.
- Nếu
yash
là vỏ đăng nhập từ xa, bạn không thể truyền lệnh có đối số chứa các ký tự không hợp lệ, nhưng đó là hạn chế ở yash
chỗ bạn không thể làm việc xung quanh.
- Một số shell như csh hoặc bash đọc một số tệp khởi động khi được gọi qua ssh. Chúng tôi cho rằng những người không thay đổi hành vi một cách đáng kể để lời mở đầu vẫn hoạt động.
- bên cạnh
sh
, nó cũng giả sử hệ thống từ xa có printf
lệnh.
Để hiểu cách thức hoạt động của nó, bạn cần biết cách trích dẫn hoạt động trong các shell khác nhau:
- Bourne:
'...'
là những trích dẫn mạnh mẽ không có ký tự đặc biệt trong đó. "..."
là những trích dẫn yếu nơi "
có thể thoát được bằng dấu gạch chéo ngược.
csh
. Giống như Bourne ngoại trừ việc "
không thể trốn thoát bên trong "..."
. Ngoài ra, một ký tự dòng mới phải được nhập bằng tiền tố với dấu gạch chéo ngược. Và !
gây ra vấn đề ngay cả trong dấu ngoặc đơn.
rc
. Các trích dẫn duy nhất là '...'
(mạnh). Một trích dẫn trong dấu ngoặc đơn được nhập dưới dạng ''
(như '...''...'
). Dấu ngoặc kép hoặc dấu gạch chéo ngược không đặc biệt.
es
. Giống như RC ngoại trừ các trích dẫn bên ngoài, dấu gạch chéo ngược có thể thoát khỏi một trích dẫn.
fish
: giống như Bourne ngoại trừ dấu gạch chéo ngược thoát ra '
bên trong '...'
.
Với tất cả các mâu thuẫn đó, thật dễ dàng để thấy rằng người ta không thể trích dẫn một cách đáng tin cậy các đối số dòng lệnh để nó hoạt động với tất cả các shell.
Sử dụng dấu ngoặc đơn như trong:
'foo' 'bar'
hoạt động trong tất cả nhưng:
'echo' 'It'\''s'
sẽ không làm việc trong rc
.
'echo' 'foo
bar'
sẽ không làm việc trong csh
.
'echo' 'foo\'
sẽ không làm việc trong fish
.
Tuy nhiên chúng tôi sẽ có thể làm việc xung quanh hầu hết những vấn đề nếu chúng ta quản lý để lưu trữ những ký tự có vấn đề trong các biến, giống như dấu chéo ngược trong $b
, dấu ngoặc đơn trong $q
, xuống dòng trong $n
(và !
trong $x
cho csh mở rộng lịch sử) một cách độc lập vỏ.
'echo' 'It'$q's'
'echo' 'foo'$b
sẽ làm việc trong tất cả các vỏ. Điều đó vẫn sẽ không làm việc cho dòng mới csh
mặc dù. Nếu $n
có dòng mới, trong csh
, bạn phải viết $n:q
nó để mở rộng sang dòng mới và nó sẽ không hoạt động cho các shell khác. Vì vậy, những gì chúng ta cuối cùng làm thay vì ở đây là gọi sh
và đã sh
mở rộng chúng $n
. Điều đó cũng có nghĩa là phải thực hiện hai cấp độ trích dẫn, một cho vỏ đăng nhập từ xa và một cho sh
.
Trong $preamble
mã đó là phần khó nhất. Nó làm cho việc sử dụng quy tắc trích dẫn khác nhau khác nhau trong tất cả các vỏ có một số phần của mã giải thích bởi chỉ một trong những vỏ (trong khi nó đang nhận xét ra cho những người khác) mỗi trong số đó chỉ cần xác định những $b
, $q
, $n
, $x
biến cho vỏ tương ứng của họ.
Đây là mã shell sẽ được giải thích bởi shell đăng nhập của người dùng từ xa trên host
ví dụ của bạn:
printf '%.0s' "'\";set x=\! b=\\;setenv n "\
";set q=\';printf %.0s "\""'"';q='''';n=``()echo;x=!;b='\'
printf '%.0s' '\'';set b \\;set x !;set -x n \n;set q \'
printf '%.0s' '\'' #'"\"'";export n;x=!;b=\\;IFS=.;set `echo;echo \.`;n=$1 IFS= q=\'
exec sh -c 'IFS=;exec '$q'printf'$q' '$q'<%s>'$b'n'$q' '$q'arg with $and spaces'$q' '$q''$q' '$q'even'$q'$n'$q'* * *'$q'$n'$q'newlines'$q' '$q'and '$q$b$q$q'single quotes'$q$b$q$q''$q' '$q''$x''$x''$q
Mã đó kết thúc bằng cách chạy cùng một lệnh khi được giải thích bởi bất kỳ shell nào được hỗ trợ.
cmd
tranh luận là/bin/sh -c
chúng ta sẽ kết thúc với một vỏ posix trong 99% của tất cả các trường hợp, phải không? Tất nhiên thoát khỏi các nhân vật đặc biệt là một chút đau đớn hơn theo cách này, nhưng nó sẽ giải quyết vấn đề ban đầu?