Uzaktaki kullanıcının giriş kabuğunu bilmeden basit bir komut ssh üzerinden nasıl gerçekleştirilir?


26

ssh çalıştırdığınızda bunun can sıkıcı bir özelliği var:

ssh user@host cmd and "here's" "one arg"

Bunu cmdargümanlarıyla çalıştırmak yerine host, bunu cmdve boşluklarla argümanları birleştirir ve hostsonuçtaki dizgiyi yorumlamak için bir kabuk çalıştırır (sanırım bu yüzden denir sshve çağrılmaz sexec).

Daha da kötüsü, bu ipi yorumlamak için hangi kabuğun kullanılacağını bilmiyorsunuz, çünkü giriş kabuğunun, giriş kabukları userolarak kullanan tcshve hala fishyükselen insanlar olduğu gibi Bourne olduğu garanti edilmiyor .

Bunun bir yolu var mı?

Bir depolanmış bağımsız değişkenler listesi olarak bir komut olduğunu varsayalım bashboş olmayan bayt bir dizisini içerebilir, her biri bir dizi, bu yürütülür için herhangi bir yolu yoktur hostolarak userbunun giriş kabuğunun bağımsız olarak tutarlı bir şekilde userilgili host(Farz edelim ki, belli başlı Unix deniz hayvanlarından biri: Bourne, csh, rc / es, balık)?

Ben yapabilecektir gerektiğini başka makul bir varsayım bir olması yani shüzerinde komut hostmevcut $PATHolduğunu Bourne-uyumludur.

Örnek:

cmd=(
  'printf'
  '<%s>\n'
  'arg with $and spaces'
  '' # empty
  $'even\n* * *\nnewlines'
  "and 'single quotes'"
  '!!'
)

Yerel olarak ksh/ zsh/ bash/ yashas ile çalıştırabilirim :

$ "${cmd[@]}"
<arg with $and spaces>
<>
<even
* * *
newlines>
<and 'single quotes'>
<!!>

veya

env "${cmd[@]}"

veya

xterm -hold -e "${cmd[@]}"
...

İşlevi nasıl aday olacağını hostolarak userüzerinde ssh?

ssh user@host "${cmd[@]}"

Belli ki işe yaramayacak.

ssh user@host "$(printf ' %q' exec "${cmd[@]}")"

yalnızca uzak kullanıcının oturum açma kabuğu yerel kabukla aynıysa (veya yerel kabukta üretilen şekilde alıntı printf %qyapmayı anlarsa) ve aynı yerel ayarda çalışırsa çalışır.


3
Eğer cmdargüman /bin/sh -ctüm vakaların% 99'unda bir posix kabuğu ile sonuçlanırdı, değil mi? Elbette özel karakterlerden kaçmak bu şekilde biraz daha acı vericidir, ancak ilk sorunu çözer mi?
Bananguin

@Bananguin, hayır çalıştırırsanız ssh host sh -c 'some cmd', ssh host 'sh -c some cmd'uzak kullanıcının giriş kabuğuna sahip olduğu gibi aynı sh -c some cmdkomut satırını yorumlar. Komutu, o kabuk için doğru sözdizimine yazmalıyız (ve hangisinin olduğunu bilmiyoruz), böylece shorada -cve some cmdargümanlarıyla çağrılabilir .
Stéphane Chazelas

1
@Otheus, evet, sh -c 'some cmd've some cmdkomut satırları tüm bu kabuklarda aynı şekilde yorumlandı. Peki ya echo \'uzak ana bilgisayarda Bourne komut satırını çalıştırmak istersem ? echo command-string | ssh ... /bin/shcevabımda verdiğim çözümlerden biri, ancak bu uzaktan kumanda komutuna veri besleyemeyeceğiniz anlamına geliyor.
Stéphane Chazelas

1
Daha uzun ömürlü bir çözüm gibi görünüyor ftp eklentisi ala ssh için bir rexec eklentisi olacaktır.
Otheus

1
@ myrdd, hayır, hayır, bir kabuk komut satırındaki argümanları ayırmak için boşluk veya sekmeye ihtiyacınız var. Eğer cmdbir cmd=(echo "foo bar"), kabuk komutu geçirilen ssh` 'eko' 'foo bar' benzer olmalıdır . The *first* space (the one before yankı ) 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'` komut hattı.
Stéphane Chazelas

Yanıtlar:


19

Herhangi bir uygulamanın sshbir istemciden sunucuya bir kabuk eklemeden komut iletmenin doğal bir yolu olduğunu sanmıyorum .

Şimdi, uzaktaki kabuğa yalnızca belirli bir tercüman çalıştırmasını söylerseniz ( shbeklenen sözdizimini bildiğimiz gibi ) ve başka bir yöntemle yürütmek için kod vermeniz durumunda işler daha kolay olabilir .

Bu diğer ortalama, örneğin standart girdi veya bir ortam değişkeni olabilir .

Hiçbiri kullanılamadığında, aşağıda üçüncü bir hack çözümü öneririm.

Stdin kullanımı

Herhangi bir veriyi uzak komuta beslemeniz gerekmiyorsa, bu en kolay çözümdür.

Uzak ana makinenin seçeneği xargsdestekleyen bir komutu olduğunu -0ve komutun çok büyük olmadığını biliyorsanız, şunları yapabilirsiniz:

printf '%s\0' "${cmd[@]}" | ssh user@host 'xargs -0 env --'

Bu xargs -0 env --komut satırı, tüm bu kabuk aileleriyle aynı şekilde yorumlanır. xargsstdin'deki boş sınırlandırılmış argümanlar listesini okur ve bunları argümanlar olarak iletir env. Bu, ilk argümanın (komut adı) =karakter içermediğini varsayar .

Veya shher bir öğeyi shalıntı sözdizimini kullanarak alıntı yaptıktan sonra uzak ana bilgisayarda kullanabilirsiniz .

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

Ortam değişkenlerini kullanma

Şimdi, istemciden bazı verileri uzak komutun stdinine beslemeniz gerekiyorsa, yukarıdaki çözüm çalışmaz.

sshAncak bazı sunucu dağıtımları, isteğe bağlı ortam değişkenlerinin istemciden sunucuya iletilmesine izin verir. Örneğin, Debian tabanlı sistemlerde birçok openssh uygulaması, adı ile başlayan değişkenlere izin verir LC_.

Bu durumlarda LC_CODE, örneğin yukarıda belirtilen shquoted sh kodunu içeren bir değişkene sahip olabilir ve sh -c 'eval "$LC_CODE"'istemcinize bu değişkeni iletmesini söyledikten sonra uzak ana bilgisayarda çalıştırılabilir (yine, her kabukta aynı şekilde yorumlanan bir komut satırı):

LC_CODE=$(shquote "${cmd[@]}") ssh -o SendEnv=LC_CODE user@host '
  sh -c '\''eval "$LC_CODE"'\'

Tüm kabuk aileleriyle uyumlu bir komut satırı oluşturma

Yukarıdaki seçeneklerden hiçbiri kabul edilemezse (stdin'e ve sshd'ye herhangi bir değişkeni kabul etmediğinizden veya genel bir çözüme ihtiyaç duyduğunuzdan dolayı), o zaman tümüyle uyumlu olan uzak ana bilgisayar için bir komut satırı hazırlamanız gerekir. desteklenen mermiler.

Bu özellikle aldatıcıdır çünkü tüm bu kabukların (Bourne, csh, rc, es, balık) kendi farklı sözdizimleri vardır ve özellikle farklı alıntılama mekanizmaları vardır ve bazılarının çalışması zor olan sınırlamalara sahiptir.

İşte geldiğim bir çözüm, daha ayrıntılı olarak anlatacağım:

#! /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;

Bu perletrafındaki bir sarmalayıcı betiği ssh. Ben ararım sexec. Buna şöyle diyorsun:

sexec [ssh-options] user@host -- cmd and its args

yani örneğinde:

sexec user@host -- "${cmd[@]}"

Ve sarmalayıcı cmd and its args, tüm kabukların cmd, argümanları ile çağırdığı şeklinde yorum yapar (içeriğinden bağımsız olarak).

Sınırlamalar:

  • Giriş ve komutun alıntı şekli, uzak komut satırının önemli ölçüde daha büyük olduğu anlamına gelir, bu da komut satırının maksimum büyüklüğündeki sınıra daha erken ulaşılması anlamına gelir.
  • Sadece şunu test ettim: Bourne kabuğu (yadigarı toolchest'ten), kısa çizgi, bash, zsh, mksh, lksh, yash, ksh93, rc, es, akanga, csh, tcsh, yeni bir Debian sisteminde bulunan balık ve / Solaris 10'daki bin / sh, / usr / bin / ksh, / bin / csh ve / usr / xpg4 / bin / sh.
  • Eğer yashuzaktan giriş kabuğu, kimin argümanlar geçersiz karakterler içeren bir komut geçemez, ancak bunda bir sınırlama var yashzaten geçici bir çözüm olamayacağını.
  • Csh veya bash gibi bazı kabuklar, ssh üzerinden çağrıldığında bazı başlangıç ​​dosyalarını okurlar. Bunların davranışı dramatik biçimde değiştirmediğini ve giriş kısmın hala çalışacağını varsayıyoruz.
  • yanında sh, uzaktaki sistemin de printfkomutu olduğunu varsayar .

Nasıl çalıştığını anlamak için, farklı kabuklarda alıntılamanın nasıl çalıştığını bilmeniz gerekir:

  • Bourne: İçinde özel bir karakter bulunmayan'...' güçlü tırnaklardır . ters eğik çizgiyle kaçılabilecek zayıf tırnaklardır ."...""
  • csh. Bourne ile aynı, ancak "içeri giremez "...". Ayrıca bir ters eğik çizgi ile önceden eklenmiş bir yeni satır karakteri girilmelidir. Ve !tek tırnak içinde bile sorunlara neden olur.
  • rc. Tek tırnak '...'(güçlü). Tek tırnak içinde tek bir alıntı ''(gibi '...''...') olarak girilir . Çift tırnak veya ters eğik çizgi özel değildir.
  • es. Dış tırnak işaretleri dışında rc ile aynı ters eğik çizgi tek bir alıntıdan kaçabilir.
  • fish: 'içeride ters eğik çizgi kaçması dışında Bourne ile aynı '...'.

Tüm bu çelişkilerde, birinin tüm satırlarda çalışabilmesi için komut satırı argümanlarını güvenilir bir şekilde alıntılayamayacağını görmek kolaydır.

Tek tırnakların aşağıdaki gibi kullanılması:

'foo' 'bar'

hepsinde çalışır ama:

'echo' 'It'\''s'

işe yaramazdı rc.

'echo' 'foo
bar'

işe yaramazdı csh.

'echo' 'foo\'

işe yaramazdı fish.

Biz ters eğik çizgi gibi değişkenlerde bu sorunlu karakterleri depolamak için yönetmek Ancak biz etrafındaki en bu sorunların çalışmak gerekir $b, tek alıntı $qiçinde, yeni satır $n(ve !de $xbir kabuk bağımsız bir şekilde csh geçmişi genişleme).

'echo' 'It'$q's'
'echo' 'foo'$b

her mermide çalışırdı. Yine de bu newline için işe yaramaz csh. Eğer $nnewline içeriyorsa, içine newline genişletecek şekilde cshyazmanız gerekir $n:qve bu, diğer mermiler için işe yaramaz. Öyleyse, burada yaptığımız şey, bunun yerine çağırmak shve shgenişletmektir $n. Bu aynı zamanda, bir tanesi uzak oturum açma kabuğu için diğeri için olmak üzere iki alıntı seviyesinin yapılması gerektiği anlamına gelir sh.

Bu $preamblekodun en zor kısmı. Sadece bu tanımlayan her biri (diğerleri için dışarı yorumladı esnada) kabukları sadece biri tarafından yorumlanır kod bazı bölümleri için tüm kabuklarda çeşitli alıntı kurallardan yararlanır $b, $q, $n, $xkendi kabuğunda için değişkenler.

hostÖrnek olarak uzaktaki kullanıcının giriş kabuğu tarafından yorumlanacak olan kabuk kodu :

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

Bu kod, desteklenen mermilerden herhangi biri tarafından yorumlandığında aynı komutu çalıştırır.


1
SSH protokolü ( RFC 4254 §6.5 ) uzak komutu dizge olarak tanımlar. Bu dizenin nasıl yorumlanacağına karar vermek sunucuya bağlıdır. Unix sistemlerinde normal yorum, dizeyi kullanıcının giriş kabuğuna geçirmektir. Kısıtlı bir hesap için, rssh veya rush gibi bir şey olabilir, isteğe bağlı komutları kabul etmiyor olabilir. Hesapta veya istemcinin gönderdiği komut dizesinin yok sayılmasına neden olan anahtarda zorunlu bir komut bile olabilir.
Gilles 'SO- kötülük'

1
@Gilles, RFC referansı için teşekkürler. Evet, bu soru-cevap için varsayım, uzak kullanıcının oturum açma kabuğunun (çalıştırmak istediğim uzak komutu çalıştırabilirim gibi) ve POSIX sistemlerindeki ana kabuk ailelerinden birinin kullanılabilir olmasıdır. Kısıtlanmış mermiler veya mermiler olmayanlar veya zorlama komutları veya bu uzak komutu zaten çalıştırmama izin vermeyecek herhangi bir şeyle ilgilenmiyorum.
Stéphane Chazelas

1
Bazı ortak mermiler arasındaki sözdizimindeki ana farklar hakkında faydalı bir referans Hyperpolyglot'ta bulunabilir .
lcd047

0

tl; Dr.

ssh USER@HOST -p PORT $(printf "%q" "cmd") $(printf "%q" "arg1") \
    $(printf "%q" "arg2")

Daha ayrıntılı bir çözüm için yorumları okuyun ve diğer cevabı inceleyin .

açıklama

Benim çözümüm bashmermilerle çalışmaz . Ancak bashdiğer tarafta olduğunu varsayarsak işler daha da basitleşir. Benim fikrim printf "%q"kaçmak için yeniden kullanmak . Ayrıca, genel olarak, diğer ucunda, argümanları kabul eden bir komut dosyası olması daha kolay okunur. Fakat eğer komut kısaysa, muhtemelen satır içi olması tamam. İşte komut dosyalarında kullanmak için bazı fonksiyonlar:

local.sh:

#!/usr/bin/env bash
set -eu

ssh_run() {
    local user_host_port=($(echo "$1" | tr '@:' ' '))
    local user=${user_host_port[0]}
    local host=${user_host_port[1]}
    local port=${user_host_port[2]-22}
    shift 1
    local cmd=("$@")
    local a qcmd=()
    for a in ${cmd[@]+"${cmd[@]}"}; do
        qcmd+=("$(printf "%q" "$a")")
    done
    ssh "$user"@"$host" -p "$port" ${qcmd[@]+"${qcmd[@]}"}
}

ssh_cmd() {
    local user_host_port=$1
    local cmd=$2
    shift 2
    local args=("$@")
    ssh_run "$user_host_port" bash -lc "$cmd" - ${args[@]+"${args[@]}"}
}

ssh_run USER@HOST ./remote.sh "1  '  \"  2" '3  '\''  "  4'
ssh_cmd USER@HOST:22 "for a; do echo \"'\$a'\"; done" "1  '  \"  2" '3  '\''  "  4'
ssh_cmd USER@HOST:22 'for a; do echo "$a"; done' '1  "2' "3'  4"

remote.sh:

#!/usr/bin/env bash
set -eu
for a; do
    echo "'$a'"
done

Çıktı:

'1  '  "  2'
'3  '  "  4'
'1  '  "  2'
'3  '  "  4'
1  "2
3'  4

Alternatif olarak, printfne yaptığınızı biliyorsanız, kendiniz de iş yapabilirsiniz:

ssh USER@HOST ./1.sh '"1  '\''  \"  2"' '"3  '\''  \"  4"'

1
Uzak kullanıcının oturum açma kabuğunun bash olduğunu (bash baskısının% q bashifadelerinin bash şeklinde olduğunu) ve uzak makinede kullanılabilir olduğunu varsayar . Eksik alıntılarla, boşluk ve joker karakterlerle sorunlara neden olacak birkaç sorun da var.
Stéphane Chazelas,

@ StéphaneChazelas Gerçekten, benim çözümüm muhtemelen sadece bashkabukları hedef alıyor . Ama umarım insanlar onu kullanacaktır. Yine de diğer sorunları çözmeye çalıştım. Şeyden başka bir bashşey kaçırdığımı bana söylemekten çekinmeyin .
x-yuri

1
Yine de sorudaki ( ssh_run user@host "${cmd[@]}") örnek komutuyla çalışmadığını unutmayın . Hala bazı tekliflerin eksik.
Stéphane Chazelas

1
Bu daha iyi. Bash en çıktısı o Not printf %qfarklı bir yerel ayarda kullanmak güvenli değildir (; bıg5 charset kullanarak yerel örneğin, o (4.3.48) tırnak ve ayrıca oldukça adamcağız εolarak α`!). Bunun için en iyisi her şeyi ve tek tırnak işareti ile sadece cevabımdaki gibi shquote().
Stéphane Chazelas
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.