Bash kendi girdi akışına yazabilir mi?


39

Etkileşimli bir bash kabuğuna, bazı komutları çıkaran bir komut girerek, bir sonraki komut isteminde görünecek şekilde, kullanıcı sanki o komut isteminde o metni girmiş gibi görünmesi mümkün mü?

Ben edebilmek istiyorum sourcegöründüğü o kadar bir komut satırı ve çıktı üretecektir bir komut dosyası zaman komut dosyası sona erdikten sonra istemi döner, böylece kullanıcı basmadan önce isteğe düzeltin enterbunu yürütmek için.

Bu, xdotoolancak terminal sadece bir X penceredeyken ve sadece monte edildiğinde işe yarar.

[me@mybox] 100 $ xdotool type "ls -l"
[me@mybox] 101 $ ls -l  <--- cursor appears here!

Bu sadece bash kullanılarak yapılabilir mi?


Eğer buna tahammül edersen ve bir deniz kabuğu sürdürebilirsen, bunun Expect ile zor olmaması gerektiğini düşünüyorum; ama gerçek bir cevap gönderecek kadarını hatırlamıyorum.
üçlü,

Yanıtlar:


39

İle zsh, bir print -zsonraki istem için bir satır metni editör tamponuna yerleştirmek için kullanabilirsiniz:

print -z echo test

echo testsonraki istemde düzenleyebileceğiniz satır düzenleyiciyi hazırlar.

bashBenzer bir özelliğe sahip olduğunu sanmıyorum , ancak birçok sistemde terminal cihazı giriş arabelleğini şu şekilde hazırlayabilirsiniz TIOCSTI ioctl():

perl -e 'require "sys/ioctl.ph"; ioctl(STDIN, &TIOCSTI, $_)
  for split "", join " ", @ARGV' echo test

Sokmak echo testterminalden alınan gibi, terminal cihazı giriş tamponuna.

Bir daha taşınabilir üzerinde varyasyon mike en @ Terminologyyaklaşımı bu güvenlik kurban etmez ve terminal emülatörü oldukça standart göndermek olacaktır query status reportkaçış dizisi: <ESC>[5nterminalleri değişmez (böylece girdi olarak) cevap olarak <ESC>[0nve bağlama dizesine eklemek istediğiniz:

bind '"\e[0n": "echo test"'; printf '\e[5n'

GNU dahilindeyse screenşunları da yapabilirsiniz:

screen -X stuff 'echo test'

Şimdi, TIOCSTI ioctl yaklaşımı dışında, terminal emülatöründen bize yazılan bazı stringleri göndermesini istiyoruz. Bu dizge önce gelirse readline( bashsitesindeki düzenleyicide) dizisi görüntülenir o zaman devre dışı uç yerel yankı sahip olmayan hafif ekran karışıklık, hızlı kabuğunda.

Bunu çözmek için, yankı okuma çizgisi tarafından devre dışı bırakıldığında yanıtın geldiğinden emin olmak için isteğin terminale gönderimini biraz geciktirebilirsiniz.

bind '"\e[0n": "echo test"'; ((sleep 0.05;  printf '\e[5n') &)

(burada sleepikinci saniyede çözünürlüğü desteklediğini varsayıyoruz )

İdeal olarak şöyle bir şey yapmak istersiniz:

bind '"\e[0n": "echo test"'
stty -echo
printf '\e[5n'
wait-until-the-response-arrives
stty echo

Ancak bash(aksine zsh) wait-until-the-response-arrivescevabı okumayan bir şeye destek yoktur.

Ancak şunlarla bir has-the-response-arrived-yetözelliğe sahiptir read -t0:

bind '"\e[0n": "echo test"'
saved_settings=$(stty -g)
stty -echo -icanon min 1 time 0
printf '\e[5n'
until read -t0; do
  sleep 0.02
done
stty "$saved_settings"

daha fazla okuma

@ Starfry'nin yanıtına bakın , @mikeserv ve kendim tarafından verilen iki çözüm üzerinde birkaç daha ayrıntılı bilgi edinin.


Galiba bind '"\e[0n": "echo test"'; printf '\e[5n'aradığım tek şey olan cevap. Benim için çalışıyor. Ancak, ^[[0nbenim de isteğimden önce basıldım. Bunun $PS1bir deniz kabuğu içerdiği zaman ortaya çıktığını keşfettim . PS1='$(:)'Ciltleme komutundan önce yaparak bunu çoğaltabilirsiniz . Bu neden olur ve bu konuda bir şeyler yapılabilir mi?
starfry

Her ne kadar bu cevaptaki her şey doğru olsa da, soru zsh için değil, bash içindi. Bazen ne tür bir kabuk kullanacağımızı seçme şansımız yok.
Falsenames

@Falsenames sadece ilk paragraf zsh içindir. Gerisi ya kabuk agnostiği ya da bash'a özgüdür. Sorular ve Cevaplar, yalnızca bash kullanıcıları için faydalı olmak zorunda değildir.
Stéphane Chazelas

1
@starfry, belki sadece \rbaşında bir eturn koymak olabilir gibi görünüyor $PS1? $PS1Yeterince uzunsa işe yaramalı. Eğer değilse o zaman ^[[Moraya koyun .
mikeserv

@mikeserv - rhile yapar. Bu elbette çıktıyı engellemez, göz görmeden önce sadece üzerine yazılır. Sanırım, ^[[Mistemden daha uzun olması durumunda enjekte edilen metni silmek için satırı siliyor. Bu doğru mu (sahip olduğum ANSI kaçış listesinde bulamadım)?
starfry

23

Bu cevap kendi anlayışımın açıklanması olarak verildi ve benden önce @ StéphaneChazelas ve @mikeserv'den ilham aldı.

TL; DR

  • bashdış yardım olmadan bunu yapmak mümkün değildir ;
  • Bunu yapmanın doğru yolu bir gönderme terminal girişi ile ioctl ancak
  • en kolay uygulanabilir bashçözüm kullanır bind.

Kolay çözüm

bind '"\e[0n": "ls -l"'; printf '\e[5n'

Bash, bir bindanahtar dizilim alındığında bir kabuk komutunun yürütülmesine izin veren bir kabuk yerleşkesine sahiptir . Temel olarak, kabuk komutunun çıktısı kabuğun giriş arabelleğine yazılır.

$ bind '"\e[0n": "ls -l"'

Anahtar dizisi \e[0n( <ESC>[0n), bir terminalin normal şekilde çalıştığını belirtmek için gönderdiği bir ANSI Terminali çıkış kodudur . Bunu , olarak gönderilen bir cihaz durum raporu isteğine yanıt olarak gönderir <ESC>[5n.

Yanıtı, echoenjekte edilecek metni çıktısını veren bir şeye bağlayarak, istediğimiz zaman cihazın durumunu istediğimizde enjekte edebiliriz ve bu bir <ESC>[5nkaçış dizisi göndererek yapılır .

printf '\e[5n'

Bu işe yarar ve orijinal soruya cevap vermek için muhtemelen yeterlidir, çünkü başka hiçbir araç yoktur. Saf bashfakat iyi işleyen bir terminale dayanıyor (pratikte hepsi).

Komut satırındaki yankı metnini yazmış gibi kullanılmaya hazır halde bırakır. Eklenebilir, düzenlenebilir ve basılması ENTERçalıştırılmasına neden olur.

\nOtomatik olarak çalıştırılması için ilişkili komuta ekleyin .

Ancak, bu çözüm yalnızca mevcut terminalde çalışır (asıl soru kapsamındadır). Etkileşimli bir istemden veya kaynak kodlu bir komut dosyasından çalışır, ancak bir alt kabuktan kullanıldığında bir hata oluşturur:

bind: warning: line editing not enabled

Daha sonra açıklanan doğru çözüm daha esnektir, ancak harici komutlara dayanır.

Doğru çözüm

Girdiyi enjekte etmenin doğru yolu, giriş enjekte etmek için kullanılabilecek bir komutu olan I / O Denetimi için bir unix sistem çağrısı olan tty_ioctl'yi kullanır TIOCSTI.

TIOC "den T erminal IOC tl " ve STI "adlı S sonunda T erminal I Temel giriş ".

Bunun bashiçin yerleşik bir komut yoktur ; bunu yapmak harici bir komut gerektirir. Tipik GNU / Linux dağıtımında böyle bir komut yoktur, ancak küçük bir programlama ile elde etmek zor değildir. İşte kullanan bir kabuk işlevi perl:

function inject() {
  perl -e 'ioctl(STDIN, 0x5412, $_) for split "", join " ", @ARGV' "$@"
}

İşte, komutun 0x5412kodu TIOCSTI.

TIOCSTIstandart C başlık dosyalarında tanımlanan bir sabittir 0x5412. Deneyin grep -r TIOCSTI /usr/includeya da bakın /usr/include/asm-generic/ioctls.h; C programlarına dolaylı olarak eklenmiştir #include <sys/ioctl.h>.

Sonra yapabilirsiniz:

$ inject ls -l
ls -l$ ls -l <- cursor here

Bazı diğer dillerdeki uygulamalar aşağıda gösterilmiştir (bir dosyaya kaydedin ve ardından chmod +x):

Perl inject.pl

#!/usr/bin/perl
ioctl(STDIN, 0x5412, $_) for split "", join " ", @ARGV

Sayısal değeri kullanmak yerine sys/ioctl.phhangisinin tanımlandığını oluşturabilirsiniz TIOCSTI. Buraya bak

piton inject.py

#!/usr/bin/python
import fcntl, sys, termios
del sys.argv[0]
for c in ' '.join(sys.argv):
  fcntl.ioctl(sys.stdin, termios.TIOCSTI, c)

Yakut inject.rb

#!/usr/bin/ruby
ARGV.join(' ').split('').each { |c| $stdin.ioctl(0x5412,c) }

C inject.c

ile derlemek gcc -o inject inject.c

#include <sys/ioctl.h>
int main(int argc, char *argv[])
{
  int a,c;
  for (a=1, c=0; a< argc; c=0 )
    {
      while (argv[a][c])
        ioctl(0, TIOCSTI, &argv[a][c++]);
      if (++a < argc) ioctl(0, TIOCSTI," ");
    }
  return 0;
}

**! ** Burada başka örnekler var .

Kullanılması ioctlaltkabuklarda bu işleri yapmak. Daha sonra açıklandığı gibi başka terminallere de enjekte edilebilir.

Daha da ileri götürmek (diğer terminalleri kontrol etmek)

Orijinal sorunun kapsamı dışındadır, ancak uygun izinlere tabi olmak kaydıyla karakterleri başka bir terminale enjekte etmek mümkündür. Normalde bunun anlamı root, başka yollarla aşağıya bakınız.

Yukarıda verilen C programının, başka bir terminalin tipini belirten bir komut satırı argümanını kabul etmek üzere genişletilmesi, o terminale enjekte edilmesine izin verir:

#include <stdlib.h>
#include <argp.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>

const char *argp_program_version ="inject - see https://unix.stackexchange.com/q/213799";
static char doc[] = "inject - write to terminal input stream";
static struct argp_option options[] = {
  { "tty",  't', "TTY", 0, "target tty (defaults to current)"},
  { "nonl", 'n', 0,     0, "do not output the trailing newline"},
  { 0 }
};

struct arguments
{
  int fd, nl, next;
};

static error_t parse_opt(int key, char *arg, struct argp_state *state) {
    struct arguments *arguments = state->input;
    switch (key)
      {
        case 't': arguments->fd = open(arg, O_WRONLY|O_NONBLOCK);
                  if (arguments->fd > 0)
                    break;
                  else
                    return EINVAL;
        case 'n': arguments->nl = 0; break;
        case ARGP_KEY_ARGS: arguments->next = state->next; return 0;
        default: return ARGP_ERR_UNKNOWN;
      }
    return 0;
}

static struct argp argp = { options, parse_opt, 0, doc };
static struct arguments arguments;

static void inject(char c)
{
  ioctl(arguments.fd, TIOCSTI, &c);
}

int main(int argc, char *argv[])
{
  arguments.fd=0;
  arguments.nl='\n';
  if (argp_parse (&argp, argc, argv, 0, 0, &arguments))
    {
      perror("Error");
      exit(errno);
    }

  int a,c;
  for (a=arguments.next, c=0; a< argc; c=0 )
    {
      while (argv[a][c])
        inject (argv[a][c++]);
      if (++a < argc) inject(' ');
    }
  if (arguments.nl) inject(arguments.nl);

  return 0;
}  

Ayrıca varsayılan olarak yeni bir satır gönderir, ancak benzer şekilde echoonu -nbastırmak için bir seçenek sunar. --tYa da --ttyseçenek bir argüman gerektirir - ttyterminal enjekte edilecek. Bunun için bu terminalde elde edilebilir:

$ tty
/dev/pts/20

İle derleyin gcc -o inject inject.c. --Ayrıştırıcı komut satırı seçeneklerini yanlış yorumlamayı önlemek için herhangi bir kısa çizgi içeriyorsa , enjekte edilecek metni önekleyin. Bakın ./inject --help. Bu şekilde kullanın:

$ inject --tty /dev/pts/22 -- ls -lrt

ya da sadece

$ inject  -- ls -lrt

mevcut terminali enjekte etmek için.

Başka bir terminale enjekte etmek aşağıdakiler tarafından alınabilecek idari haklar gerektirir:

  • emri vermek root,
  • kullanılarak sudo,
  • sahip olan CAP_SYS_ADMINyeteneği ya da
  • yürütülebilir dosyayı ayarlama setuid

Atamak için CAP_SYS_ADMIN:

$  sudo setcap cap_sys_admin+ep inject

Atamak için setuid:

$ sudo chown root:root inject
$ sudo chmod u+s inject

Temiz çıktı

Enjekte edilen metin, komut isteminde görünmeden önce yazılmış gibi (istemde olduğu gibi) komut isteminin önünde görünür, ancak komut isteminden sonra tekrar görünür.

Bilgi isteminin önünde görünen metni gizlemenin bir yolu, bilgi istemini satır başı ile ( \rsatır besleme değil) hazırlamak ve geçerli satırı ( <ESC>[M) silmek :

$ PS1="\r\e[M$PS1"

Ancak, bu sadece bilgi isteminin göründüğü satırı temizleyecektir. Enjekte edilen metin yeni satırlar içeriyorsa, bu amaçlandığı şekilde çalışmayacaktır.

Başka bir çözüm, enjekte edilen karakterlerin yankılanmasını engeller. Bir sarıcı sttybunu yapmak için kullanır :

saved_settings=$(stty -g)
stty -echo -icanon min 1 time 0
inject echo line one
inject echo line two
until read -t0; do
  sleep 0.02
done
stty "$saved_settings"

burada injectçözümlerden biri, yukarıda tarif edilen ya da ile ikame edilmiştir printf '\e[5n'.

Alternatif yaklaşımlar

Ortamınız belirli ön koşulları karşılıyorsa, girdi enjekte etmek için kullanabileceğiniz başka yöntemler de olabilir. Bir masaüstü ortamındaysanız , xdotool fare ve klavye etkinliğini simüle eden bir X.Org yardımcı programıdır, ancak dağıtımınız bunu varsayılan olarak içermeyebilir. Deneyebilirsin:

$ xdotool type ls

Eğer kullanırsanız tmux terminal çoklayıcı, o zaman bunu yapabilirsiniz:

$ tmux send-key -t session:pane ls

nerede -thangi seçer oturumu ve bölme enjekte etmek. GNU Screenstuff komutuyla benzer bir özelliğe sahiptir :

$ screen -S session -p pane -X stuff ls

Dağıtımınız konsol araçları paketini içeriyorsa, örneklerimiz gibi writevtkullanan bir komutunuz olabilir ioctl. Bununla birlikte çoğu dağıtım bu paketi bu özelliği olmayan kbd lehine kaldırmıştır .

Writevt.c'nin güncellenmiş bir kopyası kullanılarak derlenebilir gcc -o writevt writevt.c.

Bazı kullanım durumlarına daha iyi uyabilecek diğer seçenekler arasında etkileşimli araçların kodlanmasına izin vermek için tasarlanmış bekleyiş ve boş sayılabilir .

Ayrıca zsh, yapılabileceği gibi terminal enjeksiyonunu destekleyen bir kabuk da kullanabilirsiniz print -z ls.

"Vay, bu akıllıca ..." cevabı

Burada tarif edilen yöntem, ayrıca, açıklanan burada ve yöntemi ele oluşturur burada .

' /dev/ptmxDen bir kabuk yönlendirmesi yeni bir sözde-terminal alır:

$ $ ls /dev/pts; ls /dev/pts </dev/ptmx
0  1  2  ptmx
0  1  2  3  ptmx

Psödoterminal masterin (ptm) kilidini açan ve psödoterminal kölenin (pts) adını standart çıktısına veren küçük bir araç.

#include <stdio.h>
int main(int argc, char *argv[]) {
    if(unlockpt(0)) return 2;
    char *ptsname(int fd);
    printf("%s\n",ptsname(0));
    return argc - 1;
}

(farklı kaydet pts.cve derle gcc -o pts pts.c)

Program standart girişi bir ptm'ye ayarlanmış olarak çağrıldığında, ilgili pts'nin kilidini açar ve adını standart çıkışa verir.

$ ./pts </dev/ptmx
/dev/pts/20
  • Unlockpt () işlevi, belirli bir dosya tanıtıcısı tarafından ifade ana uçbirimsiler karşılık gelen ikincil uçbirimsiler cihazı açar. Program bunu, programın standart girişi olan sıfır olarak geçirir .

  • Ptsname () işlev döner ana karşılık gelen ikincil uçbirimsiler aygıtın adı tekrar programın standart giriş sıfır geçen belirli bir dosya tanıtıcısı ile anılacaktır.

Puanlara bir işlem bağlanabilir. İlk önce bir ptm alın (burada dosya tanımlayıcı 3'e atanmış, <>yönlendirme tarafından okuma-yazma açılmıştır ).

 exec 3<>/dev/ptmx

Ardından işlemi başlatın:

$ (setsid -c bash -i 2>&1 | tee log) <>"$(./pts <&3)" 3>&- >&0 &

Bu komut satırının doğduğu süreçler en iyi şekilde gösterilmiştir pstree:

$ pstree -pg -H $(jobs -p %+) $$
bash(5203,5203)─┬─bash(6524,6524)─┬─bash(6527,6527)
                             └─tee(6528,6524)
            └─pstree(6815,6815)

Çıktı mevcut kabuğa ( $$) -pgöredir -gve her bir işlemin PID ( ) ve PGID ( ) parantez içinde gösterilmiştir (PID,PGID).

Ağacın başında, bash(5203,5203)komutları yazdığımız etkileşimli kabuk ve dosya tanımlayıcıları onu onunla etkileşimde bulunmak için kullandığımız terminal uygulamasına bağlar ( xtermveya benzeri).

$ ls -l /dev/fd/
lrwx------ 0 -> /dev/pts/3
lrwx------ 1 -> /dev/pts/3
lrwx------ 2 -> /dev/pts/3

Komuta tekrar bakıldığında, ilk parantez grubu bir alt kabuk başlattı, bash(6524,6524)) dosya tanıtıcısı 0 ( standart girişi ) , kilidini açmak için <>yürütülen başka bir alt kabuk tarafından döndürülen pts'e (okuma-yazma açık olan) atandı ./pts <&3. dosya tanıtıcısı 3 ile ilişkili puanlar (önceki adımda yaratıldı exec 3<>/dev/ptmx).

Subshell'in dosya tanımlayıcısı 3 kapalıdır ( 3>&-), böylece ptm erişilebilir değildir. Okunan / yazılan pts olan standart girişi (fd 0), >&0standart çıkışına (fd 1) yönlendirilir (aslında fd kopyalanır - ).

Bu, standart giriş ve çıkışlara bağlı olarak alt kabuk yaratır. Ptm'ye yazılarak girdi gönderilebilir ve ptm'den okunarak çıktısı görülebilir:

$ echo 'some input' >&3 # write to subshell
$ cat <&3               # read from subshell

Alt kabuk bu komutu çalıştırır:

setsid -c bash -i 2>&1 | tee log

O ishal bash(6527,6527)interaktif (içinde -i(yeni bir oturumda) modunda setsid -c, PID ve pgid aynıdır unutmayın). Standart hatası, standart çıkışına ( 2>&1) yönlendirilir ve tee(6528,6524)bu şekilde aktarılır, böylece bir logdosyaya ve pts'ye yazılır . Bu, subshell'in çıktısını görmenin başka bir yolunu sunar:

$ tail -f log

Alt kabuk bashetkileşimli olarak çalıştığından , alt kabuk dosya tanımlayıcılarını görüntüleyen bu örnekte olduğu gibi yürütmek için komutlar gönderilebilir:

$ echo 'ls -l /dev/fd/' >&3

Subshell'in çıktısını okumak ( tail -f logya da cat <&3) şunu gösterir:

lrwx------ 0 -> /dev/pts/17
l-wx------ 1 -> pipe:[116261]
l-wx------ 2 -> pipe:[116261]

Standart giriş (0 fd) puanlar aynı borusuna bağlanan her iki standart çıkış (fd 1) ve hata (2 fd) birine bağlı olan bağlanır bu tee:

$ (find /proc -type l | xargs ls -l | fgrep 'pipe:[116261]') 2>/dev/null
l-wx------ /proc/6527/fd/1 -> pipe:[116261]
l-wx------ /proc/6527/fd/2 -> pipe:[116261]
lr-x------ /proc/6528/fd/0 -> pipe:[116261]

Ve dosya tanımlayıcılarına bir bakış tee

$ ls -l /proc/6528/fd/
lr-x------ 0 -> pipe:[116261]
lrwx------ 1 -> /dev/pts/17
lrwx------ 2 -> /dev/pts/3
l-wx------ 3 -> /home/myuser/work/log

Standart Çıktı (fd 1) pts: 'tee' nın standart çıktısına yazdığı herhangi bir şey ptm'ye geri gönderilir. Standart Hata (fd 2) kontrol terminaline ait puanlardır.

Sarmalamak

Aşağıdaki komut dosyası yukarıda açıklanan tekniği kullanır. bashBir dosya tanıtıcısına yazılarak enjekte edilebilecek etkileşimli bir oturum ayarlar . Kullanılabilir burada ve açıklamalar ile belgelenmiş.

sh -cm 'cat <&9 &cat >&9|(             ### copy to/from host/slave
        trap "  stty $(stty -g         ### save/restore stty settings on exit
                stty -echo raw)        ### host: no echo and raw-mode
                kill -1 0" EXIT        ### send a -HUP to host pgrp on EXIT
        <>"$($pts <&9)" >&0 2>&1\
        setsid -wc -- bash) <&1        ### point bash <0,1,2> at slave and setsid bash
' --    9<>/dev/ptmx 2>/dev/null       ### open pty master on <>9

20

Bu bashsadece ne demek istediğine bağlı . Tek, etkileşimli bir bashoturum demek istiyorsan , cevap neredeyse kesinlikle hayır demektir . Ve bunun nedeni ls -l, herhangi bir kanonik terminalin komut satırında olduğu gibi bir komutu girseniz bashbile, bunun henüz farkında bile olmamasıdır - ve basho noktada bile dahil değildir.

Aksine, bu noktaya kadar olan şey, çekirdeğin tty line disiplininin arabelleğe stty echoalması ve kullanıcının yalnızca ekrana giriş yapmasıdır. Okuyucusuna girdiyi - bashörneğin sizin durumunuzda - satır satır - ve genel olarak unix sistemlerinde \rewline'ları da \nçevirir - ve bu yüzden de bashöyle değil - ve böylece kaynak kodunuzun hiçbiri var olduğunun farkına varmaz. kullanıcı ENTERtuşa basana kadar giriş yapın .

Şimdi, bazı çalışma ortamları var. En sağlam olanı aslında bir çözüm değil, aslında ve girdi sıralamak, satır disiplinini kullanıcıdan gizlemek -echove ekrana sadece girdi yorumlanırken uygun olanı değerlendirmek için ekrana yazmak için birden fazla işlem veya özel olarak yazılmış programlar kullanmayı içerir. özellikle gerektiğinde. Bunu yapmak zor olabilir, çünkü ortalama bir kullanıcının bu senaryoda ne bekleyeceğini simüle etmek için rastgele girdi karakterlerini karaktere ulaştığı anda isteğe göre girebilecek yorum kuralları yazmak ve aynı anda yanlış yazmak anlamına gelir. Bu nedenle, muhtemelen, etkileşimli terminal g / ç, o kadar nadiren iyi anlaşılır ki, çoğu kişi için daha fazla araştırma yapmak için kendisini zorlaştıran bir ihtimal değildir.

Başka bir çalışma ortamı terminal emülatörünü içerebilir. Sizin için bir problemin X'e ve ona bağımlı olduğunu söylüyorsunuz xdotool. Bu durumda, teklif edeceğim türden bir çalışmanın benzer sorunları olabilir, ancak bununla aynı şekilde ilerleyeceğim.

printf  '\33[22;1t\33]1;%b\33\\\33[20t\33[23;0t' \
        '\025my command'

Bu kaynak setinde bir xtermw / w çalışacak allowwindowOps. Önce simge / pencere adlarını bir yığına kaydeder, ardından terminalin simge dizesini daha ^Umy commandsonra terminalin bu adı giriş kuyruğuna enjekte etmesini ister ve en son kaydedilen değerlere sıfırlar. Doğru konfigürasyonda bashçalışan etkileşimli mermiler için görünmez bir şekilde çalışması gerekir - xtermama muhtemelen kötü bir fikir. Lütfen Stéphane'nin yorumlarına bakınız.

Burada, makinemde printffarklı bir kaçış dizisi kullandıktan sonra Terminoloji terminalimin çektiği bir resim . Komuttaki her yeni satır için daha sonra printfyazdım ve ardından tuşuna basın. Daha sonra hiçbir şey yazmadım, ama gördüğünüz gibi, terminal disiplininin benim için sıraya girdiği terminal :CTRL+VCTRL+JENTERmy command

term_inject

Bunu yapmanın gerçek yolu, iç içe geçmiş bir miktardır. Her ikisi de bu arada bunu sizin için mümkün kılan , nasıl screenve tmuxbenzer bir çalışma. xtermAslında luitbunu da mümkün kılan denilen küçük bir programla gelir . Yine de kolay değil.

İşte yapabileceğiniz bir yol:

sh -cm 'cat <&9 &cat >&9|(             ### copy to/from host/slave
        trap "  stty $(stty -g         ### save/restore stty settings on exit
                stty -echo raw)        ### host: no echo and raw-mode
                kill -1 0" EXIT        ### send a -HUP to host pgrp on EXIT
        <>"$(pts <&9)" >&0 2>&1\       
        setsid -wc -- bash) <&1        ### point bash <0,1,2> at slave and setsid bash
' --    9<>/dev/ptmx 2>/dev/null       ### open pty master on <>9

Bu, hiçbir şekilde taşınabilir değildir, ancak açılış için uygun izinler verilen çoğu Linux sisteminde çalışmalıdır /dev/ptmx. Kullanıcım ttysistemimde yeterli olan grupta. Ayrıca ihtiyacınız olacak ...

<<\C cc -xc - -o pts
#include <stdio.h>
int main(int argc, char *argv[]) {
        if(unlockpt(0)) return 2;
        char *ptsname(int fd);
        printf("%s\n",ptsname(0));
        return argc - 1;
}
C

... bir GNU sisteminde (ya da stdin'den okuyabilen standart bir C derleyicisine sahip başka herhangi biri)pts çalıştırıldığında, unlockpt()fonksiyonunu stdin üzerinde çalıştıracak ve stdout'una yazan küçük bir çalıştırılabilir dosya yazacaktır. Pty cihazın adı henüz açıldı. Üzerinde çalıştığım zaman yazdım ... Bu çukurla nasıl gelebilirim ve bununla ne yapabilirim? .

Her neyse, yukarıdaki kod bitinin yaptığı, bashşu anki tty'nin altındaki bir katmandaki bir katmandır. bashKöle PTY tüm çıktı yazmak için anlatılır ve mevcut Tty için iki yapılandırılmış -echogirişinde ne de tampon, ancak bunun yerine geçmek için (çoğunlukla) raw için cat, bu kopyalar üzerinde için bash. Ve tüm bu süre zarfında, arka plan catbütün köle çıktılarını mevcut tty'ye kopyalar.

Çoğunlukla, yukarıdaki konfigürasyon tamamen işe yaramaz olurdu - sadece gereksiz, temel olarak - kendi usta master fd'sinin bir kopyasıyla başlatmamız dışında . Bu, basit bir yeniden yönlendirmeyle kendi giriş akışına özgürce yazabileceği anlamına gelir . Tek yapması gereken şey:bash<>9bashbash

echo echo hey >&9

... kendi kendine konuşmak için.

İşte başka bir resim:

görüntü tanımını buraya girin


2
Bunun çalışmasını sağlamak için hangi terminalleri başardınız? Bu tür şeyler eski günlerde kötüye kullanılıyordu ve bugünlerde varsayılan olarak devre dışı bırakılmalıdır. Bununla birlikte xterm, simge başlığını sorgulayabilir, \e[20tancak yalnızca yapılandırılmışsa sorgulayabilirsiniz allowWindowOps: true.
Stéphane Chazelas


Terminolojide çalışan @ StéphaneChazelas, ancak gnome terminalinde, kde terminalinde de çalıştığından eminim (adını unuttum ve farklı bir kaçış olduğunu düşünüyorum) ve dediğiniz gibi w / xtermw / uygun yapılandırma. W / a xterm uygun olsa da, kopyala / yapıştır arabelleğini okuyabilir ve yazabilir ve böylece daha basitleşeceğini düşünüyorum. Xterm ayrıca, terimin tanımını değiştirmek / değiştirmek için kaçış dizilerine sahiptir.
mikeserv

Bunun terminolojiden başka bir şeyle çalışmasını sağlayamıyorum (ki bununla benzer güvenlik açıkları var). CVE'in 12 yaşından büyük olması ve nispeten iyi tanınması, ana terminal emülatörlerinden birinin aynı güvenlik açığına sahip olması durumunda şaşırırdım. Xterm ile, bu \e[20t(değil \e]1;?\a)
Stéphane Chazelas


8

Her ne kadar Stéphane Chazelas'ın ioctl(,TIOCSTI,) cevabı elbette doğru cevap olsa da, bazı insanlar bu kısmi ama önemsiz cevabından yeterince mutlu olabilir: basitçe komutu tarih yığınının üzerine getirin, ardından kullanıcı tarihi bulmak için 1 satır ilerletebilir. komut.

$ history -s "ls -l"
$ echo "move up 1 line in history to get command to run"

Bu, kendi 1 satır geçmişine sahip basit bir komut dosyası olabilir:

#!/bin/bash
history -s "ls -l"
read -e -p "move up 1 line: "
eval "$REPLY"

read -eGirişin okuma -psatırının düzenlenmesini sağlar , bir istemidir.


Bu sadece kabuk işlevlerinde veya komut dosyası kaynaklanmışsa ( . foo.shveya bir alt kabukta çalışmak yerine `source foo.sh.) İşe yarayacaktır. Ancak ilginç bir yaklaşım. Çağıran kabuğun içeriğini değiştirmeyi gerektiren benzer bir kesmek, boş satırı bir şeye genişleten ve ardından eski tamamlama işleyicisini geri yükleyen özel bir tamamlama oluşturmaktır.
Peter Cordes,

@Peter haklısın. Ben de soruyu tam anlamıyla alıyordum. Ama işe yarayabilecek basit bir senaryo örneği ekledim.
meuh

@mikeserv Hey, bazı insanlar için yararlı olabilecek basit bir çözüm. evalDüzenlemek için basit komutlar varsa, borular ve yönlendirme vb. Olmadan bile silebilirsiniz .
meuh

1

Oh, söz veriyorum , bash için yerleşik basit bir çözümü özledik : readkomut, -i ...kullanıldığında -emetni giriş arabelleğine iterek bir seçeneğe sahip . Man sayfasından:

-i metin

Satırı okumak için readline kullanılıyorsa, düzenleme başlamadan önce metin düzenleme arabelleğine yerleştirilir.

Bu nedenle, kullanıcıya sunma komutunu alan ve yanıtını çalıştıran veya değerlendiren küçük bir bash işlevi veya kabuk komut dosyası oluşturun:

domycmd(){ read -e -i "$*"; eval "$REPLY"; }

Bu hiç şüphesiz yaklaşık 2,9BSD ioctl.h dosyasında olduğu gibi 32 yıldan beri var olan ioctl (, TIOCSTI) ' yı kullanmaktadır .


1
Benzer bir etkiye sahip ilginç, ancak istemi olsa enjekte değil.
starfry

2. düşüncede haklısın. bash TIOCSTI'ye ihtiyaç duymaz, çünkü tüm giriş / çıkış işlemlerini kendisi yapar.
meuh
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.