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 source
skrip yang akan menghasilkan baris perintah dan output sehingga muncul ketika prompt kembali setelah skrip berakhir sehingga pengguna dapat mengeditnya sebelum menekan enter
untuk menjalankannya.
Ini dapat dicapai dengan xdotool
tetapi 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?
Jawaban:
Dengan
zsh
, Anda dapat menggunakanprint -z
untuk menempatkan beberapa teks ke dalam buffer editor baris untuk prompt berikutnya:akan perdana editor baris
echo test
yang dapat Anda edit pada prompt berikutnya.Saya tidak berpikir
bash
memiliki fitur serupa, namun pada banyak sistem, Anda dapat menggunakan buffer input perangkat terminal denganTIOCSTI
ioctl()
:Akan memasukkan
echo test
ke dalam buffer input perangkat terminal, seolah-olah diterima dari terminal.Variasi yang lebih portabel pada pendekatan @ mike
Terminology
dan yang tidak mengorbankan keamanan adalah mengirimkan emulator terminalquery status report
urutan pelarian yang cukup standar :<ESC>[5n
terminal mana yang selalu membalas (jadi input) seperti<ESC>[0n
dan mengikatnya ke string yang ingin Anda masukkan:Jika dalam GNU
screen
, Anda juga dapat melakukan:Sekarang, kecuali untuk pendekatan TIOCSTI ioctl, kami meminta emulator terminal untuk mengirim kami beberapa string seolah-olah diketik. Jika string itu muncul sebelum
readline
(bash
editor 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.
(di sini dengan asumsi Anda
sleep
mendukung resolusi sub-detik).Idealnya Anda ingin melakukan sesuatu seperti:
Namun
bash
(bertentangan denganzsh
) tidak memiliki dukungan untukwait-until-the-response-arrives
yang tidak membaca tanggapan.Namun memiliki
has-the-response-arrived-yet
fitur denganread -t0
:Bacaan lebih lanjut
Lihat jawaban @ starfry yang diperluas pada dua solusi yang diberikan oleh @mikeserv dan saya sendiri dengan beberapa informasi lebih rinci.
sumber
bind '"\e[0n": "echo test"'; printf '\e[5n'
mungkin jawaban bash-only yang saya cari. Ini bekerja untuk saya. Namun, saya^[[0n
dicetak sebelum prompt saya juga. Saya menemukan ini disebabkan ketika$PS1
berisi subkulit. Anda dapat mereproduksinya dengan melakukanPS1='$(:)'
sebelum perintah bind. Mengapa itu bisa terjadi dan apa pun bisa dilakukan?\r
eturn di kepala$PS1
? Itu harus bekerja jika$PS1
cukup lama. Jika tidak maka letakkan di^[[M
sana.r
melakukan trik. Ini tentu saja tidak mencegah output, itu hanya ditimpa sebelum mata melihatnya. Saya kira^[[M
menghapus 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)?Jawaban ini diberikan sebagai klarifikasi dari pemahaman saya sendiri dan terinspirasi oleh @ StéphaneChazelas dan @mikeserv sebelum saya.
TL; DR
bash
tanpa bantuan eksternal;ioctl
tetapibash
menggunakan solusi termudah yang bisa diterapkanbind
.Solusi mudah
Bash memiliki shell builtin yang disebut
bind
yang memungkinkan perintah shell dieksekusi ketika urutan kunci diterima. Intinya, output dari perintah shell ditulis ke buffer input shell.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
echo
output teks yang akan disuntikkan, kita dapat menyuntikkan teks itu kapan pun kita inginkan dengan meminta status perangkat dan itu dilakukan dengan mengirimkan<ESC>[5n
urutan pelarian.Ini berfungsi, dan mungkin cukup untuk menjawab pertanyaan awal karena tidak ada alat lain yang terlibat. Ini murni
bash
tetapi 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
ENTER
menyebabkannya dieksekusi.Tambahkan
\n
ke 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:
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
TIOCSTI
perintah yang dapat digunakan untuk menyuntikkan input.TIOC dari " T erminal IOC tl " dan STI dari " S end T erminal I nput ".
Tidak ada perintah
bash
untuk 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 menggunakanperl
:Di sini,
0x5412
adalah kode untukTIOCSTI
perintah.TIOCSTI
adalah konstanta yang didefinisikan dalam file header C standar dengan nilai0x5412
. Cobagrep -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:
Implementasi dalam beberapa bahasa lain ditunjukkan di bawah ini (simpan dalam file dan kemudian
chmod +x
):Perl
inject.pl
Anda dapat membuat
sys/ioctl.ph
yang menentukanTIOCSTI
alih-alih menggunakan nilai numerik. Lihat di siniPython
inject.py
Rubi
inject.rb
C
inject.c
kompilasi dengan
gcc -o inject inject.c
**! ** Ada contoh lebih lanjut di sini .
Menggunakan
ioctl
untuk 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:
Ini juga mengirimkan baris baru secara default tetapi, mirip dengan
echo
, itu memberikan-n
opsi untuk menekannya. The--t
atau--tty
opsi membutuhkan sebuah argumen - yangtty
dari terminal harus disuntikkan. Nilai untuk ini dapat diperoleh di terminal itu: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:atau hanya
untuk menyuntikkan terminal saat ini.
Menyuntikkan ke terminal lain membutuhkan hak administratif yang dapat diperoleh dengan:
root
,sudo
,CAP_SYS_ADMIN
kemampuan atausetuid
Untuk menetapkan
CAP_SYS_ADMIN
:Untuk menetapkan
setuid
: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 (
\r
bukan feed baris) dan menghapus baris saat ini (<ESC>[M
):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
stty
untuk melakukan ini:di mana
inject
salah satu solusi yang dijelaskan di atas, atau diganti olehprintf '\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:
Jika Anda menggunakan tmux , terminal multiplexer, maka Anda dapat melakukan ini:
di mana
-t
memilih sesi dan panel mana yang akan disuntikkan. Layar GNU memiliki kemampuan serupa denganstuff
perintahnya:Jika distro Anda menyertakan paket konsol-alat maka Anda mungkin memiliki
writevt
perintah yang menggunakanioctl
seperti 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
zsh
yang bisa dilakukanprint -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/ptmx
mendapatkan terminal semu baru:Sebuah alat kecil yang ditulis dalam C yang membuka master pseudoterminal (ptm) dan menampilkan nama pseudoterminal slave (pts) ke output standarnya.
(simpan sebagai
pts.c
dan kompilasi dengangcc -o pts pts.c
)Ketika program dipanggil dengan input standarnya diatur ke ptm, ia membuka kunci yang sesuai dan menampilkan namanya menjadi output standar.
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).Kemudian mulailah prosesnya:
Proses yang dihasilkan oleh command-line ini paling baik digambarkan dengan
pstree
: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).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 <&3
untuk 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:
Subshell menjalankan perintah ini:
Ini berjalan
bash(6527,6527)
dalam-i
mode interaktif ( ) dalam sesi baru (setsid -c
, perhatikan PID dan PGID adalah sama). Kesalahan standarnya dialihkan ke output standar (2>&1
) dan disalurkan melaluitee(6528,6524)
sehingga ditulis kelog
file dan juga ke pts. Ini memberi cara lain untuk melihat output subshell:Karena subkulit berjalan secara
bash
interaktif, maka dapat dikirim perintah untuk dieksekusi, seperti contoh ini yang menampilkan deskriptor file subkulit:Membaca output subkulit (
tail -f log
ataucat <&3
) mengungkapkan: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
:Dan lihat file deskriptor dari
tee
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
bash
sesi interaktif yang dapat disuntikkan dengan menulis ke deskriptor file. Ini tersedia di sini dan didokumentasikan dengan penjelasan.sumber
bind '"\e[0n": "ls -l"'; printf '\e[5n'
solusi termudah , setelah semua outputls -l
juga^[[0n
akan dikeluarkan di Terminal begitu saya menekan tombol enter sehingga dijalankanls -l
. Ada ide bagaimana "menyembunyikan" itu? Terima kasih.PS1="\r\e[M$PS1"
sebelum melakukanbind '"\e[0n": "ls -l"'; printf '\e[5n'
dan itu memberikan efek yang Anda gambarkan.Itu tergantung pada apa yang Anda maksudkan
bash
saja . Jika yang Anda maksud adalahbash
sesi interaktif tunggal , maka jawabannya hampir pasti tidak . Dan ini karena bahkan ketika Anda memasukkan perintah sepertils -l
pada baris perintah pada terminal kanonik apa pun makabash
bahkan belum menyadarinya - danbash
bahkan tidak terlibat pada saat itu.Sebaliknya, apa yang terjadi sampai saat itu adalah bahwa disiplin baris tty kernel telah buffered dan
stty echo
input pengguna hanya ke layar. Itu memerah input itu ke pembacanya -bash
, dalam contoh kasus Anda - baris demi baris - dan umumnya menerjemahkan\r
eturns ke\n
ewlines pada sistem Unix juga - danbash
tidak - dan begitu juga skrip sumber Anda - dibuat sadar bahwa ada masukan sama sekali sampai pengguna menekanENTER
tombol.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
-echo
dari 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.Itu akan bekerja di
xterm
w /allowwindowOps
set sumber daya. Pertama-tama ia menyimpan ikon / nama jendela pada tumpukan, kemudian mengatur ikon-string terminal untuk^Umy command
kemudian meminta terminal memasukkan nama itu ke dalam antrian input, dan terakhir mengatur ulang ke nilai yang disimpan. Ini seharusnya bekerja tanpa terlihat untukbash
shell interaktif yang dijalankanxterm
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
printf
bit dengan urutan escape yang berbeda pada mesin saya. Untuk setiap baris baru dalamprintf
perintah, saya mengetikCTRL+V
laluCTRL+J
dan sesudahnya menekanENTER
tombol. Saya tidak mengetik apa pun setelahnya, tetapi, seperti yang Anda lihat, terminal menyuntikkanmy command
ke antrian masukan garis-disiplin untuk saya:Cara sebenarnya untuk melakukan ini adalah dengan pty bersarang. Ini adalah bagaimana
screen
dantmux
dan pekerjaan serupa - yang keduanya, dengan cara, dapat membuat ini mungkin bagi Anda.xterm
sebenarnya dilengkapi dengan program kecil yang disebutluit
yang juga dapat membuat ini mungkin. Itu tidak mudah.Inilah satu cara yang mungkin Anda lakukan:
Itu tidak berarti portabel, tetapi harus bekerja pada sebagian besar sistem Linux yang diberi izin untuk membuka
/dev/ptmx
. Pengguna saya berada ditty
grup yang cukup di sistem saya. Anda juga akan membutuhkan ...... 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
pts
yang akan menjalankanunlockpt()
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
bash
shell dalam pty a layer di bawah tty saat ini.bash
diperintahkan untuk menulis semua output ke slave pty, dan tty saat ini dikonfigurasi baik untuk-echo
input maupun untuk buffer, tetapi sebaliknya untuk meneruskannya (kebanyakan)raw
kecat
, yang menyalinnya kebash
. Dan sementara itu,cat
salinan 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
bash
dengan salinan master pty sendiri pada<>9
. Ini berarti bahwabash
dapat secara bebas menulis ke input stream sendiri dengan pengalihan sederhana. Yangbash
harus dilakukan adalah:... untuk berbicara dengan dirinya sendiri.
Ini gambar lain:
sumber
xterm
, Anda masih dapat menanyakan judul ikon dengan\e[20t
tetapi hanya jika dikonfigurasi denganallowWindowOps: true
.xterm
b / 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.\e[20t
(tidak\e]1;?\a
)?
untuk hanya untuk font dan warna di sana , bukan judulMeskipun
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.Ini dapat menjadi skrip sederhana, yang memiliki riwayat 1 baris sendiri:
read -e
memungkinkan pengeditan readline dari input,-p
adalah prompt.sumber
. foo.sh
atau `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.eval
jika Anda memiliki perintah sederhana untuk diedit, tanpa pipa dan redirection dll.Ya Tuhan, kami melewatkan solusi sederhana bawaan untuk bash :
read
perintah memiliki opsi-i ...
, yang ketika digunakan dengan-e
, mendorong teks ke buffer input. Dari halaman manual:Jadi buat fungsi bash kecil atau skrip shell yang mengambil perintah untuk disajikan kepada pengguna, dan jalankan atau evaluasi jawaban mereka:
Ini tidak diragukan lagi menggunakan ioctl (, TIOCSTI,) yang telah ada selama lebih dari 32 tahun, seperti yang sudah ada di 2.9BSD ioctl.h .
sumber