Bisakah bash menulis ke aliran inputnya sendiri?


39

Apakah mungkin dalam bash shell interaktif untuk memasukkan perintah yang menampilkan beberapa teks sehingga muncul di prompt perintah berikutnya, seolah-olah pengguna telah mengetikkan teks itu di prompt itu?

Saya ingin sourceskrip yang akan menghasilkan baris perintah dan output sehingga muncul ketika prompt kembali setelah skrip berakhir sehingga pengguna dapat mengeditnya sebelum menekan enteruntuk menjalankannya.

Ini dapat dicapai dengan xdotooltetapi itu hanya bekerja ketika terminal berada di jendela X dan hanya jika itu diinstal.

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

Bisakah ini dilakukan hanya dengan menggunakan bash?


Saya pikir ini seharusnya tidak sulit dengan Harapkan, jika Anda bisa mentolerir itu, dan minta subshell; tapi saya tidak cukup ingat untuk mengirim jawaban yang sebenarnya.
tripleee

Jawaban:


40

Dengan zsh, Anda dapat menggunakan print -zuntuk menempatkan beberapa teks ke dalam buffer editor baris untuk prompt berikutnya:

print -z echo test

akan perdana editor baris echo testyang dapat Anda edit pada prompt berikutnya.

Saya tidak berpikir bashmemiliki fitur serupa, namun pada banyak sistem, Anda dapat menggunakan buffer input perangkat terminal dengan TIOCSTI ioctl():

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

Akan memasukkan echo testke dalam buffer input perangkat terminal, seolah-olah diterima dari terminal.

Variasi yang lebih portabel pada pendekatan @ mikeTerminology dan yang tidak mengorbankan keamanan adalah mengirimkan emulator terminal query status reporturutan pelarian yang cukup standar : <ESC>[5nterminal mana yang selalu membalas (jadi input) seperti <ESC>[0ndan mengikatnya ke string yang ingin Anda masukkan:

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

Jika dalam GNU screen, Anda juga dapat melakukan:

screen -X stuff 'echo test'

Sekarang, kecuali untuk pendekatan TIOCSTI ioctl, kami meminta emulator terminal untuk mengirim kami beberapa string seolah-olah diketik. Jika string itu muncul sebelum readline( basheditor baris) telah menonaktifkan gema lokal terminal, maka string itu akan ditampilkan bukan pada prompt shell, sedikit mengacaukan tampilan.

Untuk mengatasinya, Anda bisa sedikit menunda pengiriman permintaan ke terminal sedikit untuk memastikan respons datang ketika gema telah dinonaktifkan oleh readline.

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

(di sini dengan asumsi Anda sleepmendukung resolusi sub-detik).

Idealnya Anda ingin melakukan sesuatu seperti:

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

Namun bash(bertentangan dengan zsh) tidak memiliki dukungan untuk wait-until-the-response-arrivesyang tidak membaca tanggapan.

Namun memiliki has-the-response-arrived-yetfitur dengan 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"

Bacaan lebih lanjut

Lihat jawaban @ starfry yang diperluas pada dua solusi yang diberikan oleh @mikeserv dan saya sendiri dengan beberapa informasi lebih rinci.


Saya pikir bind '"\e[0n": "echo test"'; printf '\e[5n'mungkin jawaban bash-only yang saya cari. Ini bekerja untuk saya. Namun, saya ^[[0ndicetak sebelum prompt saya juga. Saya menemukan ini disebabkan ketika $PS1berisi subkulit. Anda dapat mereproduksinya dengan melakukan PS1='$(:)'sebelum perintah bind. Mengapa itu bisa terjadi dan apa pun bisa dilakukan?
Starfry

Meskipun semua jawaban ini benar, pertanyaannya adalah untuk bash, bukan zsh. Terkadang kami tidak memiliki pilihan shell apa yang akan digunakan.
Nama samaran

@ Nama samaran hanya paragraf pertama untuk zsh. Sisanya adalah shell agnostik atau spesifik bash. T&J tidak harus bermanfaat hanya untuk mem-bash pengguna.
Stéphane Chazelas

1
@ Starfry sepertinya mungkin Anda bisa meletakkan \return di kepala $PS1? Itu harus bekerja jika $PS1cukup lama. Jika tidak maka letakkan di ^[[Msana.
mikeserv

@ mikeserv - rmelakukan trik. Ini tentu saja tidak mencegah output, itu hanya ditimpa sebelum mata melihatnya. Saya kira ^[[Mmenghapus baris untuk menghapus teks yang disuntikkan seandainya lebih lama dari prompt. Apakah itu benar (saya tidak dapat menemukannya di daftar pelarian ANSI yang saya miliki)?
Starfry

25

Jawaban ini diberikan sebagai klarifikasi dari pemahaman saya sendiri dan terinspirasi oleh @ StéphaneChazelas dan @mikeserv sebelum saya.

TL; DR

  • tidak mungkin melakukan ini bashtanpa bantuan eksternal;
  • cara yang benar untuk melakukan ini adalah dengan input terminal kirim ioctl tetapi
  • bashmenggunakan solusi termudah yang bisa diterapkan bind.

Solusi mudah

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

Bash memiliki shell builtin yang disebut bindyang memungkinkan perintah shell dieksekusi ketika urutan kunci diterima. Intinya, output dari perintah shell ditulis ke buffer input shell.

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

Urutan kunci \e[0n( <ESC>[0n) adalah kode pelarian Terminal ANSI yang dikirim terminal untuk menunjukkan bahwa ia berfungsi secara normal. Ini mengirimkan ini sebagai tanggapan terhadap permintaan laporan status perangkat yang dikirim sebagai <ESC>[5n.

Dengan mengikat respons ke echooutput teks yang akan disuntikkan, kita dapat menyuntikkan teks itu kapan pun kita inginkan dengan meminta status perangkat dan itu dilakukan dengan mengirimkan <ESC>[5nurutan pelarian.

printf '\e[5n'

Ini berfungsi, dan mungkin cukup untuk menjawab pertanyaan awal karena tidak ada alat lain yang terlibat. Ini murni bashtetapi bergantung pada terminal yang berperilaku baik (praktis semuanya).

Ini meninggalkan teks yang digaungkan pada baris perintah siap digunakan seolah-olah telah diketik. Itu dapat ditambahkan, diedit, dan ditekan ENTERmenyebabkannya dieksekusi.

Tambahkan \nke perintah terikat untuk menjalankannya secara otomatis.

Namun, solusi ini hanya berfungsi di terminal saat ini (yang berada dalam ruang lingkup pertanyaan awal). Ini berfungsi dari prompt interaktif atau dari skrip bersumber tetapi itu menimbulkan kesalahan jika digunakan dari subkulit:

bind: warning: line editing not enabled

Solusi yang benar yang dijelaskan selanjutnya lebih fleksibel tetapi bergantung pada perintah eksternal.

Solusi yang benar

Cara yang tepat untuk menyuntikkan input menggunakan tty_ioctl , panggilan sistem unix untuk I / O Control yang memiliki TIOCSTIperintah yang dapat digunakan untuk menyuntikkan input.

TIOC dari " T erminal IOC tl " dan STI dari " S end T erminal I nput ".

Tidak ada perintah bashuntuk ini; melakukannya memerlukan perintah eksternal. Tidak ada perintah seperti itu dalam distribusi GNU / Linux yang khas tetapi tidak sulit untuk dicapai dengan sedikit pemrograman. Inilah fungsi shell yang menggunakan perl:

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

Di sini, 0x5412adalah kode untuk TIOCSTIperintah.

TIOCSTIadalah konstanta yang didefinisikan dalam file header C standar dengan nilai 0x5412. Coba grep -r TIOCSTI /usr/include, atau lihat /usr/include/asm-generic/ioctls.h; itu termasuk dalam program C secara tidak langsung oleh #include <sys/ioctl.h>.

Anda kemudian dapat melakukan:

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

Implementasi dalam beberapa bahasa lain ditunjukkan di bawah ini (simpan dalam file dan kemudian chmod +x):

Perl inject.pl

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

Anda dapat membuat sys/ioctl.phyang menentukan TIOCSTIalih-alih menggunakan nilai numerik. Lihat di sini

Python 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)

Rubi inject.rb

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

C inject.c

kompilasi dengan 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;
}

**! ** Ada contoh lebih lanjut di sini .

Menggunakan ioctluntuk melakukan ini bekerja dalam subkulit. Itu juga dapat menyuntikkan ke terminal lain seperti yang dijelaskan selanjutnya.

Melangkah lebih jauh (mengendalikan terminal lain)

Itu di luar ruang lingkup pertanyaan asli tetapi dimungkinkan untuk menyuntikkan karakter ke terminal lain, tunduk pada izin yang sesuai. Biasanya ini berarti ada root, tetapi lihat di bawah untuk cara lain.

Memperluas program C yang diberikan di atas untuk menerima argumen baris perintah yang menentukan tty terminal lain memungkinkan injeksi ke terminal itu:

#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;
}  

Ini juga mengirimkan baris baru secara default tetapi, mirip dengan echo, itu memberikan -nopsi untuk menekannya. The --tatau --ttyopsi membutuhkan sebuah argumen - yang ttydari terminal harus disuntikkan. Nilai untuk ini dapat diperoleh di terminal itu:

$ tty
/dev/pts/20

Kompilasi dengan gcc -o inject inject.c. Awali teks yang akan disuntikkan --jika berisi tanda hubung untuk mencegah parser argumen salah mengartikan opsi baris perintah. Lihat ./inject --help. Gunakan seperti ini:

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

atau hanya

$ inject  -- ls -lrt

untuk menyuntikkan terminal saat ini.

Menyuntikkan ke terminal lain membutuhkan hak administratif yang dapat diperoleh dengan:

  • mengeluarkan perintah sebagai root,
  • menggunakan sudo,
  • memiliki CAP_SYS_ADMINkemampuan atau
  • mengatur executable setuid

Untuk menetapkan CAP_SYS_ADMIN:

$  sudo setcap cap_sys_admin+ep inject

Untuk menetapkan setuid:

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

Bersihkan keluaran

Teks yang disuntikkan muncul di depan prompt seolah-olah itu diketik sebelum prompt muncul (yang, pada dasarnya, itu) tetapi kemudian muncul lagi setelah prompt.

Salah satu cara untuk menyembunyikan teks yang muncul di depan prompt adalah dengan mengawali prompt dengan carriage return ( \rbukan feed baris) dan menghapus baris saat ini ( <ESC>[M):

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

Namun, ini hanya akan menghapus garis di mana prompt muncul. Jika teks yang disuntikkan termasuk baris baru maka ini tidak akan berfungsi sebagaimana dimaksud.

Solusi lain menonaktifkan gema karakter yang disuntikkan. Pembungkus menggunakan sttyuntuk melakukan ini:

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"

di mana injectsalah satu solusi yang dijelaskan di atas, atau diganti oleh printf '\e[5n'.

Pendekatan alternatif

Jika lingkungan Anda memenuhi prasyarat tertentu maka Anda mungkin memiliki metode lain yang tersedia yang dapat Anda gunakan untuk menyuntikkan input. Jika Anda berada di lingkungan desktop maka xdotool adalah utilitas X.Org yang mensimulasikan aktivitas mouse dan keyboard tetapi distro Anda mungkin tidak memasukkannya secara default. Anda dapat mencoba:

$ xdotool type ls

Jika Anda menggunakan tmux , terminal multiplexer, maka Anda dapat melakukan ini:

$ tmux send-key -t session:pane ls

di mana -tmemilih sesi dan panel mana yang akan disuntikkan. Layar GNU memiliki kemampuan serupa dengan stuffperintahnya:

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

Jika distro Anda menyertakan paket konsol-alat maka Anda mungkin memiliki writevtperintah yang menggunakan ioctlseperti contoh kami. Sebagian besar distro telah menolak paket ini karena mendukung kbd yang tidak memiliki fitur ini.

Salinan writevt.c yang diperbarui dapat dikompilasi menggunakan gcc -o writevt writevt.c.

Opsi lain yang mungkin cocok dengan beberapa kasus penggunaan lebih baik termasuk harapan dan kosong yang dirancang untuk memungkinkan alat interaktif yang akan dituliskan.

Anda juga bisa menggunakan shell yang mendukung injeksi terminal seperti zshyang bisa dilakukan print -z ls.

Jawaban "Wow, itu pintar ..."

Metode yang dijelaskan di sini juga dibahas di sini dan didasarkan pada metode yang dibahas di sini .

Pengalihan shell dari /dev/ptmxmendapatkan terminal semu baru:

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

Sebuah alat kecil yang ditulis dalam C yang membuka master pseudoterminal (ptm) dan menampilkan nama pseudoterminal slave (pts) ke output standarnya.

#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;
}

(simpan sebagai pts.cdan kompilasi dengan gcc -o pts pts.c)

Ketika program dipanggil dengan input standarnya diatur ke ptm, ia membuka kunci yang sesuai dan menampilkan namanya menjadi output standar.

$ ./pts </dev/ptmx
/dev/pts/20
  • Fungsi unlockpt () membuka perangkat pseudoterminal slave yang sesuai dengan master pseudoterminal yang dirujuk oleh deskriptor file yang diberikan. Program melewatkan ini sebagai nol yang merupakan input standar program .

  • Fungsi ptsname () mengembalikan nama perangkat pseudoterminal slave yang sesuai dengan master yang dirujuk oleh deskriptor file yang diberikan, sekali lagi melewati nol untuk input standar program.

Suatu proses dapat dihubungkan ke Poin. Pertama mendapatkan ptm (di sini ditugaskan untuk file deskriptor 3, dibuka baca-tulis oleh <>redirect).

 exec 3<>/dev/ptmx

Kemudian mulailah prosesnya:

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

Proses yang dihasilkan oleh command-line ini paling baik digambarkan dengan pstree:

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

Outputnya relatif terhadap shell saat ini ( $$) dan PID ( -p) dan PGID ( -g) dari setiap proses ditunjukkan dalam tanda kurung (PID,PGID).

Di kepala pohon adalah bash(5203,5203), shell interaktif yang sedang kita ketikkan perintah, dan deskriptor file menghubungkannya ke aplikasi terminal yang kita gunakan untuk berinteraksi dengannya ( xterm, atau serupa).

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

Melihat perintah lagi, set kurung pertama memulai subkulit, bash(6524,6524)) dengan deskriptor file 0 ( input standarnya ) ditugaskan ke pts (yang dibuka baca-tulis, <>) seperti yang dikembalikan oleh subkulit lain yang dijalankan ./pts <&3untuk membuka kunci Poin yang terkait dengan deskriptor file 3 (dibuat pada langkah sebelumnya, exec 3<>/dev/ptmx).

Deskriptor file subshell 3 ditutup ( 3>&-) sehingga ptm tidak dapat diakses. Input standarnya (fd 0), yang merupakan pts yang dibuka baca / tulis, dialihkan (sebenarnya fd disalin - >&0) ke output standarnya (fd 1).

Ini menciptakan subkulit dengan input dan output standar yang terhubung ke Poin. Ini dapat dikirim input dengan menulis ke ptm dan hasilnya dapat dilihat dengan membaca dari ptm:

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

Subshell menjalankan perintah ini:

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

Ini berjalan bash(6527,6527)dalam -imode interaktif ( ) dalam sesi baru ( setsid -c, perhatikan PID dan PGID adalah sama). Kesalahan standarnya dialihkan ke output standar ( 2>&1) dan disalurkan melalui tee(6528,6524)sehingga ditulis ke logfile dan juga ke pts. Ini memberi cara lain untuk melihat output subshell:

$ tail -f log

Karena subkulit berjalan secara bashinteraktif, maka dapat dikirim perintah untuk dieksekusi, seperti contoh ini yang menampilkan deskriptor file subkulit:

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

Membaca output subkulit ( tail -f logatau cat <&3) mengungkapkan:

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

Input standar (fd 0) terhubung ke pts dan output standar (fd 1) dan kesalahan (fd 2) terhubung ke pipa yang sama, yang terhubung ke 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]

Dan lihat file deskriptor dari 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

Output Standar (fd 1) adalah pts: apa pun yang 'tee' tulis ke output standarnya dikirim kembali ke ptm. Kesalahan Standar (fd 2) adalah poin yang dimiliki oleh terminal pengendali.

Membungkusnya

Script berikut menggunakan teknik yang dijelaskan di atas. Ini mengatur bashsesi interaktif yang dapat disuntikkan dengan menulis ke deskriptor file. Ini tersedia di sini dan didokumentasikan dengan penjelasan.

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

Dengan bind '"\e[0n": "ls -l"'; printf '\e[5n'solusi termudah , setelah semua output ls -ljuga ^[[0nakan dikeluarkan di Terminal begitu saya menekan tombol enter sehingga dijalankan ls -l. Ada ide bagaimana "menyembunyikan" itu? Terima kasih.
Ali

1
Saya menyajikan satu solusi yang memberikan efek yang Anda cari - di bagian keluaran bersih dari jawaban saya, saya sarankan menambahkan kembali ke prompt untuk menyembunyikan teks surpflouous. Saya mencoba PS1="\r\e[M$PS1"sebelum melakukan bind '"\e[0n": "ls -l"'; printf '\e[5n'dan itu memberikan efek yang Anda gambarkan.
Starfry

Terima kasih! Saya benar-benar merindukan hal itu.
Ali

20

Itu tergantung pada apa yang Anda maksudkan bashsaja . Jika yang Anda maksud adalah bashsesi interaktif tunggal , maka jawabannya hampir pasti tidak . Dan ini karena bahkan ketika Anda memasukkan perintah seperti ls -lpada baris perintah pada terminal kanonik apa pun maka bashbahkan belum menyadarinya - dan bashbahkan tidak terlibat pada saat itu.

Sebaliknya, apa yang terjadi sampai saat itu adalah bahwa disiplin baris tty kernel telah buffered dan stty echoinput pengguna hanya ke layar. Itu memerah input itu ke pembacanya - bash, dalam contoh kasus Anda - baris demi baris - dan umumnya menerjemahkan \returns ke \newlines pada sistem Unix juga - dan bashtidak - dan begitu juga skrip sumber Anda - dibuat sadar bahwa ada masukan sama sekali sampai pengguna menekan ENTERtombol.

Sekarang, ada beberapa solusi. Yang paling kuat bukanlah penyelesaian sama sekali, sebenarnya, dan melibatkan penggunaan berbagai proses atau program yang ditulis secara khusus untuk mengurutkan input, menyembunyikan garis-disiplin -echodari pengguna, dan hanya menulis ke layar apa yang dinilai sesuai saat menginterpretasikan input khusus bila perlu. Ini bisa sulit dilakukan dengan baik karena itu berarti menulis aturan interpretasi yang dapat menangani char sewenang-wenang dari char saat tiba dan menuliskannya secara bersamaan tanpa kesalahan untuk mensimulasikan apa yang diharapkan oleh pengguna rata-rata dalam skenario itu. Karena alasan inilah, mungkin, terminal interaktif i / o sangat jarang dipahami dengan baik - suatu prospek yang sulit bukanlah sesuatu yang cocok untuk penyelidikan lebih lanjut bagi kebanyakan orang.

Penanganan lain dapat melibatkan emulator terminal. Anda mengatakan bahwa masalah bagi Anda adalah ketergantungan pada X dan terus xdotool. Dalam hal ini penyelesaian seperti yang akan saya tawarkan mungkin memiliki masalah yang sama, tapi saya akan melanjutkan dengan hal yang sama.

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

Itu akan bekerja di xtermw / allowwindowOpsset sumber daya. Pertama-tama ia menyimpan ikon / nama jendela pada tumpukan, kemudian mengatur ikon-string terminal untuk ^Umy commandkemudian meminta terminal memasukkan nama itu ke dalam antrian input, dan terakhir mengatur ulang ke nilai yang disimpan. Ini seharusnya bekerja tanpa terlihat untuk bashshell interaktif yang dijalankan xterm dengan konfigurasi yang benar, tetapi itu mungkin ide yang buruk. Silakan lihat komentar Stéphane di bawah ini.

Namun, di sini adalah gambar yang saya ambil dari terminal Terminology saya setelah menjalankan printfbit dengan urutan escape yang berbeda pada mesin saya. Untuk setiap baris baru dalam printfperintah, saya mengetik CTRL+Vlalu CTRL+Jdan sesudahnya menekan ENTERtombol. Saya tidak mengetik apa pun setelahnya, tetapi, seperti yang Anda lihat, terminal menyuntikkan my commandke antrian masukan garis-disiplin untuk saya:

term_inject

Cara sebenarnya untuk melakukan ini adalah dengan pty bersarang. Ini adalah bagaimana screendan tmuxdan pekerjaan serupa - yang keduanya, dengan cara, dapat membuat ini mungkin bagi Anda. xtermsebenarnya dilengkapi dengan program kecil yang disebut luityang juga dapat membuat ini mungkin. Itu tidak mudah.

Inilah satu cara yang mungkin Anda lakukan:

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

Itu tidak berarti portabel, tetapi harus bekerja pada sebagian besar sistem Linux yang diberi izin untuk membuka /dev/ptmx. Pengguna saya berada di ttygrup yang cukup di sistem saya. Anda juga akan membutuhkan ...

<<\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

... yang, ketika dijalankan pada sistem GNU (atau yang lainnya dengan kompiler C standar yang juga dapat membaca dari stdin) , akan menulis biner kecil yang dapat dieksekusi yang bernama ptsyang akan menjalankan unlockpt()fungsi pada stdinnya dan menulis ke stdoutnya. nama perangkat pty itu baru saja dibuka. Saya menulisnya ketika mengerjakan ... Bagaimana saya bisa mendapatkan pty ini dan apa yang bisa saya lakukan dengannya? .

Ngomong-ngomong, apa yang dilakukan oleh sedikit kode di atas adalah menjalankan bashshell dalam pty a layer di bawah tty saat ini. bashdiperintahkan untuk menulis semua output ke slave pty, dan tty saat ini dikonfigurasi baik untuk -echoinput maupun untuk buffer, tetapi sebaliknya untuk meneruskannya (kebanyakan) raw ke cat, yang menyalinnya ke bash. Dan sementara itu, catsalinan latar belakang semua output budak ke tty saat ini.

Sebagian besar konfigurasi di atas akan sepenuhnya tidak berguna - hanya berlebihan, pada dasarnya - kecuali bahwa kita memulai bashdengan salinan master pty sendiri pada <>9. Ini berarti bahwa bashdapat secara bebas menulis ke input stream sendiri dengan pengalihan sederhana. Yang bashharus dilakukan adalah:

echo echo hey >&9

... untuk berbicara dengan dirinya sendiri.

Ini gambar lain:

masukkan deskripsi gambar di sini


2
Terminal apa yang Anda kelola agar bisa berfungsi? Hal semacam itu sedang dilecehkan di masa lalu dan harus dinonaktifkan secara default saat ini. Dengan xterm, Anda masih dapat menanyakan judul ikon dengan \e[20ttetapi hanya jika dikonfigurasi dengan allowWindowOps: true.
Stéphane Chazelas


@ StéphaneChazelas yang bekerja di Terminology, tapi saya cukup yakin itu juga bekerja di terminal gnome, di terminal KDE (saya lupa namanya, dan saya pikir ada jalan keluar yang berbeda) , dan seperti yang Anda katakan, b / xtermb / tepat konfigurasi W / xterm yang tepat, meskipun, Anda dapat membaca dan menulis buffer copy / paste dan jadi itu menjadi lebih sederhana, saya pikir. Xterm juga memiliki urutan pelarian untuk mengubah / memengaruhi deskripsi istilah itu sendiri.
mikeserv

Saya tidak bisa membuatnya bekerja selain terminologi (yang mana btw memiliki beberapa kerentanan serupa lainnya). Bahwa CVE berusia di atas 12 tahun dan relatif terkenal saya akan terkejut jika ada emulator terminal utama yang memiliki kerentanan yang sama. Perhatikan bahwa dengan xterm, itu \e[20t(tidak \e]1;?\a)
Stéphane Chazelas


8

Meskipun ioctl(,TIOCSTI,) jawaban oleh Stéphane Chazelas adalah, tentu saja, jawaban yang tepat, beberapa orang mungkin cukup senang dengan jawaban parsial tetapi sepele ini: cukup dorong perintah ke tumpukan riwayat, maka pengguna dapat memindahkan 1 baris ke atas riwayat untuk menemukan perintah.

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

Ini dapat menjadi skrip sederhana, yang memiliki riwayat 1 baris sendiri:

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

read -ememungkinkan pengeditan readline dari input, -padalah prompt.


Itu hanya akan berfungsi dalam fungsi shell, atau jika skrip tersebut bersumber ( . foo.shatau `source foo.sh, alih-alih dijalankan dalam subkulit.) Pendekatan yang menarik. Retasan serupa yang memerlukan modifikasi konteks shell panggilan adalah untuk mengatur penyelesaian kustom yang memperluas baris kosong ke sesuatu, dan kemudian mengembalikan pengendali penyelesaian lama.
Peter Cordes

@PeterCordes Anda benar. Saya mengambil pertanyaan itu terlalu harfiah. Tetapi saya telah menambahkan contoh skrip sederhana yang bisa berfungsi.
meuh

@ mikeserv Hai, ini hanya solusi sederhana yang mungkin berguna bagi sebagian orang. Anda bahkan dapat menghapus evaljika Anda memiliki perintah sederhana untuk diedit, tanpa pipa dan redirection dll.
meuh

1

Ya Tuhan, kami melewatkan solusi sederhana bawaan untuk bash : readperintah memiliki opsi -i ..., yang ketika digunakan dengan -e, mendorong teks ke buffer input. Dari halaman manual:

-Saya teks

Jika readline digunakan untuk membaca baris, teks ditempatkan ke dalam buffer pengeditan sebelum pengeditan dimulai.

Jadi buat fungsi bash kecil atau skrip shell yang mengambil perintah untuk disajikan kepada pengguna, dan jalankan atau evaluasi jawaban mereka:

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

Ini tidak diragukan lagi menggunakan ioctl (, TIOCSTI,) yang telah ada selama lebih dari 32 tahun, seperti yang sudah ada di 2.9BSD ioctl.h .


1
Yang menarik dengan efek yang serupa, tetapi tidak menyuntikkan ke prompt sekalipun.
starfry

pada pikiran ke-2 Anda benar. bash tidak perlu TIOCSTI karena melakukan semua i / o itu sendiri.
meuh
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.