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