ssh
memiliki fitur yang mengganggu ketika Anda menjalankan:
ssh user@host cmd and "here's" "one arg"
Alih-alih menjalankannya cmd
dengan argumen-argumennya aktif host
, ia menggabungkannya cmd
dan berargumen dengan spasi dan menjalankan shell host
untuk menafsirkan string yang dihasilkan (saya kira itu sebabnya dipanggil ssh
dan tidak sexec
).
Lebih buruk lagi, Anda tidak tahu shell apa yang akan digunakan untuk menafsirkan string itu karena itu shell login user
yang bahkan tidak dijamin menjadi Bourne seperti masih ada orang yang menggunakan tcsh
shell login mereka dan fish
sedang meningkat.
Apakah ada jalan keluarnya?
Misalkan saya memiliki perintah sebagai daftar argumen yang disimpan dalam bash
array, yang masing-masing dapat berisi urutan byte non-null, apakah ada cara untuk menjalankannya host
seperti user
dalam cara yang konsisten terlepas dari shell login yang ada user
di host
(yang akan kita asumsikan adalah salah satu keluarga shell Unix utama: Bourne, csh, rc / es, fish)?
Asumsi lain yang masuk akal bahwa saya harus dapat membuat adalah bahwa ada sh
perintah yang host
tersedia di $PATH
yang kompatibel dengan Bourne.
Contoh:
cmd=(
'printf'
'<%s>\n'
'arg with $and spaces'
'' # empty
$'even\n* * *\nnewlines'
"and 'single quotes'"
'!!'
)
Saya dapat menjalankannya secara lokal dengan ksh
/ zsh
/ bash
/ yash
sebagai:
$ "${cmd[@]}"
<arg with $and spaces>
<>
<even
* * *
newlines>
<and 'single quotes'>
<!!>
atau
env "${cmd[@]}"
atau
xterm -hold -e "${cmd[@]}"
...
Bagaimana saya menjalankannya host
setelah user
selesai ssh
?
ssh user@host "${cmd[@]}"
jelas tidak akan berhasil.
ssh user@host "$(printf ' %q' exec "${cmd[@]}")"
hanya akan berfungsi jika shell login dari pengguna jarak jauh sama dengan shell lokal (atau memahami mengutip dengan cara yang sama seperti printf %q
pada shell lokal yang memproduksinya) dan berjalan di lokal yang sama.
sumber
cmd
argumennya adalah/bin/sh -c
kita akan berakhir dengan shell posix di 99% dari semua kasus, bukan? Tentu saja melarikan diri dari karakter khusus sedikit lebih menyakitkan dengan cara ini, tetapi apakah itu akan menyelesaikan masalah awal?ssh host sh -c 'some cmd'
, sama sepertissh host 'sh -c some cmd'
, yang memiliki shell login dari pengguna jarak jauh menafsirkansh -c some cmd
baris perintah itu. Kita perlu menulis perintah dalam sintaks yang benar untuk shell itu (dan kita tidak tahu yang mana) sehinggash
dipanggil ke sana dengan-c
dansome cmd
argumen.sh -c 'some cmd'
dansome cmd
baris perintah kebetulan diartikan sama di semua shell itu. Sekarang bagaimana jika saya ingin menjalankanecho \'
baris perintah Bourne pada remote host?echo command-string | ssh ... /bin/sh
adalah salah satu solusi yang saya berikan dalam jawaban saya, tetapi itu berarti Anda tidak dapat memberi makan data ke stdin dari perintah jarak jauh itu.cmd
adalahcmd=(echo "foo bar")
, baris perintah shell diteruskan kessh
harus sesuatu seperti ` 'echo' 'foo bar'. The *first* space (the one before
gema) is superflous, but doen't harm. The other one (the ones before
'foo bar') is needed. With
'% q', we'd pass a
'echo''foo bar'` baris perintah.Jawaban:
Saya tidak berpikir implementasi apapun
ssh
memiliki cara asli untuk meneruskan perintah dari klien ke server tanpa melibatkan shell.Sekarang, segala sesuatunya dapat menjadi lebih mudah jika Anda dapat memberi tahu remote shell untuk hanya menjalankan juru bahasa tertentu (seperti
sh
, yang kami tahu sintaks yang diharapkan) dan memberikan kode untuk dieksekusi dengan cara lain.Itu berarti lain dapat misalnya input standar atau variabel lingkungan .
Ketika tidak ada yang dapat digunakan, saya mengusulkan solusi ketiga yang hacky di bawah ini.
Menggunakan stdin
Jika Anda tidak perlu memasukkan data apa pun ke perintah jarak jauh, itu solusi termudah.
Jika Anda tahu host jarak jauh memiliki
xargs
perintah yang mendukung-0
opsi dan perintahnya tidak terlalu besar, Anda dapat melakukannya:Itu
xargs -0 env --
baris perintah ditafsirkan sama dengan semua keluarga shell mereka.xargs
membaca daftar argumen tanpa batas pada stdin dan meneruskannya sebagai argumenenv
. Itu mengasumsikan argumen pertama (nama perintah) tidak mengandung=
karakter.Atau Anda dapat menggunakan
sh
pada host jarak jauh setelah mengutip setiap elemen menggunakansh
sintaks mengutip.Menggunakan variabel lingkungan
Sekarang, jika Anda perlu memberi makan beberapa data dari klien ke stdin perintah jarak jauh, solusi di atas tidak akan berfungsi.
Namun beberapa
ssh
penyebaran server memungkinkan berlalunya variabel lingkungan sewenang-wenang dari klien ke server. Sebagai contoh, banyak penyebaran openssh pada sistem berbasis Debian memungkinkan lewat variabel yang namanya dimulaiLC_
.Dalam kasus tersebut, Anda bisa memiliki
LC_CODE
variabel misalnya berisi kode shquotedsh
seperti di atas dan dijalankansh -c 'eval "$LC_CODE"'
pada host jarak jauh setelah menyuruh klien Anda untuk mengirimkan variabel itu (sekali lagi, itu adalah baris perintah yang diartikan sama di setiap shell):Membangun baris perintah yang kompatibel untuk semua keluarga shell
Jika tidak ada opsi di atas yang dapat diterima (karena Anda perlu stdin dan sshd tidak menerima variabel apa pun, atau karena Anda memerlukan solusi generik), maka Anda harus menyiapkan baris perintah untuk host jarak jauh yang kompatibel dengan semua kerang yang didukung.
Itu sangat sulit karena semua cangkang (Bourne, csh, rc, es, fish) memiliki sintaks yang berbeda, dan khususnya mekanisme kutipan yang berbeda dan beberapa dari mereka memiliki keterbatasan yang sulit untuk dikerjakan.
Berikut adalah solusi yang saya buat, saya jelaskan lebih jauh ke bawah:
Itu
perl
skrip pembungkus di sekitarssh
. Saya menyebutnyasexec
. Anda menyebutnya seperti:jadi dalam contoh Anda:
Dan pembungkusnya berubah
cmd and its args
menjadi baris perintah yang akhirnya ditafsirkan oleh semua shell sebagai panggilancmd
dengan argumennya (tanpa konten).Keterbatasan:
yash
shell login jarak jauh, Anda tidak bisa meneruskan perintah yang argumennya berisi karakter yang tidak valid, tetapi itu adalah batasan diyash
mana Anda tidak bisa menyelesaikannya.sh
itu, ia juga menganggap sistem remote memilikiprintf
perintah.Untuk memahami cara kerjanya, Anda perlu tahu cara mengutip bekerja di shell yang berbeda:
'...'
adalah kutipan kuat tanpa karakter khusus di dalamnya."..."
adalah tanda kutip yang lemah di mana"
bisa lolos dengan backslash.csh
. Sama seperti Bourne kecuali itu"
tidak bisa diloloskan ke dalam"..."
. Juga karakter baris baru harus dimasukkan diawali dengan garis miring terbalik. Dan!
menyebabkan masalah bahkan di dalam tanda kutip tunggal.rc
. Satu-satunya kutipan adalah'...'
(kuat). Kutipan tunggal dalam kutipan tunggal dimasukkan sebagai''
(seperti'...''...'
). Kutipan ganda atau garis miring tidak spesial.es
. Sama seperti rc kecuali kutipan luar, backslash dapat lolos dari penawaran tunggal.fish
: sama seperti Bourne kecuali bahwa backslash lolos'
di dalam'...'
.Dengan semua hambatan itu, mudah untuk melihat bahwa seseorang tidak dapat dengan baik mengutip argumen baris perintah sehingga ia bekerja dengan semua shell.
Menggunakan tanda kutip tunggal seperti pada:
bekerja di semua tetapi:
tidak akan bekerja di
rc
.tidak akan bekerja di
csh
.tidak akan bekerja di
fish
.Namun kita harus dapat mengatasi sebagian besar masalah tersebut jika kita berhasil menyimpan karakter-karakter bermasalah tersebut dalam variabel, seperti backslash in
$b
, single quote in$q
, newline in$n
(dan!
in$x
untuk ekspansi sejarah csh) dengan cara independen shell.akan bekerja di semua kulit. Itu masih tidak akan bekerja untuk baris baru
csh
. Jika$n
berisi baris baru, dalamcsh
, Anda harus menulisnya$n:q
agar bisa meluas ke baris baru dan itu tidak akan berfungsi untuk shell lain. Jadi, apa yang akhirnya kami lakukan di sini adalah meneleponsh
dansh
memperluasnya$n
. Itu juga berarti harus melakukan dua tingkat penawaran, satu untuk shell login jarak jauh, dan satu untuksh
.Di
$preamble
dalam kode itu adalah bagian tersulit. Itu membuat penggunaan berbagai aturan mengutip berbeda dalam semua kerang untuk memiliki beberapa bagian dari kode ditafsirkan oleh hanya satu dari kerang (sementara itu komentar untuk orang lain) yang masing-masing hanya mendefinisikan mereka$b
,$q
,$n
,$x
variabel untuk shell masing-masing.Inilah kode shell yang akan ditafsirkan oleh shell login pengguna jarak jauh
host
untuk contoh Anda:Kode itu akhirnya menjalankan perintah yang sama ketika ditafsirkan oleh salah satu shell yang didukung.
sumber
tl; dr
Untuk solusi yang lebih rumit, baca komentar dan periksa jawaban lainnya .
deskripsi
Nah, solusi saya tidak akan bekerja dengan non-
bash
shell. Tetapi dengan asumsi itubash
di ujung yang lain, segalanya menjadi lebih sederhana. Ide saya adalah menggunakan kembaliprintf "%q"
untuk melarikan diri. Juga umumnya, lebih mudah dibaca untuk memiliki skrip di ujung yang lain, yang menerima argumen. Tetapi jika perintahnya singkat, mungkin tidak apa-apa untuk memasukkannya. Berikut adalah beberapa contoh fungsi yang digunakan dalam skrip:local.sh
:remote.sh
:Hasil:
Atau, Anda dapat melakukan
printf
pekerjaan sendiri, jika Anda tahu apa yang Anda lakukan:sumber
bash
tersedia pada mesin remote. Ada juga beberapa masalah dengan kutipan yang hilang yang akan menyebabkan masalah dengan spasi putih dan wildcard.bash
shell. Tapi semoga orang akan menemukannya berguna. Saya mencoba untuk mengatasi masalah lain. Jangan ragu untuk memberi tahu saya jika ada sesuatu yang saya lewatkan selainbash
hal itu.ssh_run user@host "${cmd[@]}"
). Anda masih memiliki beberapa kutipan yang hilang.printf %q
tidak aman untuk digunakan di lokal yang berbeda (dan juga cukup buggy; misalnya di lokal menggunakan charset BIG5, ia (4.3.48) mengutipε
sebagaiα`
!). Untuk itu, yang terbaik adalah mengutip segala sesuatu dan hanya dengan satu tanda kutip seperti denganshquote()
jawaban saya.