यह उत्तर मेरी अपनी समझ के स्पष्टीकरण के रूप में प्रदान किया गया है और मेरे सामने @ StéphaneChazelas और @mikeserv से प्रेरित है।
टी एल; डॉ
bashबाहरी मदद के बिना ऐसा करना संभव नहीं है ;
- ऐसा करने का सही तरीका एक टर्मिनल टर्मिनल इनपुट है
ioctl लेकिन
- सबसे आसान व्यावहारिक
bashसमाधान का उपयोग करता है bind।
आसान उपाय
bind '"\e[0n": "ls -l"'; printf '\e[5n'
बैश में एक शेल बिल्डिन होता है, bindजो एक प्रमुख अनुक्रम प्राप्त होने पर शेल कमांड को निष्पादित करने की अनुमति देता है। संक्षेप में, शेल कमांड का आउटपुट शेल के इनपुट बफर को लिखा जाता है।
$ bind '"\e[0n": "ls -l"'
मुख्य अनुक्रम \e[0n( <ESC>[0n) एक एएनएसआई टर्मिनल एस्केप कोड है जो एक टर्मिनल यह इंगित करने के लिए भेजता है कि यह सामान्य रूप से कार्य कर रहा है। यह एक डिवाइस स्थिति रिपोर्ट अनुरोध के जवाब में भेजता है जो कि भेजा जाता है <ESC>[5n।
किसी प्रतिक्रिया को बाध्य करके echo, जो पाठ को इंजेक्ट करने के लिए आउटपुट करता है, हम उस पाठ को जब भी हम डिवाइस स्थिति का अनुरोध करके इंजेक्ट कर सकते हैं और यह एक <ESC>[5nएस्केप अनुक्रम भेजकर किया जाता है ।
printf '\e[5n'
यह काम करता है, और शायद मूल प्रश्न का उत्तर देने के लिए पर्याप्त है क्योंकि कोई अन्य उपकरण शामिल नहीं है। यह शुद्ध है bashलेकिन एक अच्छी तरह से व्यवहार करने वाले टर्मिनल पर निर्भर करता है (व्यावहारिक रूप से सभी हैं)।
यह प्रयोग किए जाने के लिए तैयार कमांड लाइन पर प्रतिध्वनित पाठ को छोड़ता है जैसे कि इसे टाइप किया गया हो। इसे लागू किया जा सकता है, संपादित किया जा सकता है, और ENTERइसे दबाने के कारण इसे निष्पादित किया जा सकता है।
\nइसे स्वचालित रूप से निष्पादित करने के लिए बाध्य आदेश में जोड़ें ।
हालांकि, यह समाधान केवल वर्तमान टर्मिनल (जो मूल प्रश्न के दायरे में है) में काम करता है। यह एक इंटरेक्टिव प्रॉम्प्ट से या एक सॉर्ट की गई स्क्रिप्ट से काम करता है, लेकिन यदि यह सबस्क्रिप्शन से उपयोग किया जाता है, तो यह एक त्रुटि उत्पन्न करता है:
bind: warning: line editing not enabled
अगला वर्णित सही समाधान अधिक लचीला है लेकिन यह बाहरी आदेशों पर निर्भर करता है।
सही समाधान
इनपुट इंजेक्षन करने का उचित तरीका tty_ioctl का उपयोग करता है , I / O नियंत्रण के लिए एक यूनिक्स सिस्टम कॉल जिसमें एक TIOCSTIकमांड है जिसका उपयोग इनपुट को इंजेक्ट करने के लिए किया जा सकता है।
टीआईओसी " टी ऑर्मिनल आईओसी टीएल " और एसटीआई से " एस एंड टी र्मिनल आई एनपुट "।
इसके लिए कोई कमांड नहीं बनाई गई bashहै; ऐसा करने के लिए एक बाहरी आदेश की आवश्यकता होती है। विशिष्ट GNU / Linux वितरण में ऐसा कोई आदेश नहीं है, लेकिन थोड़ी प्रोग्रामिंग के साथ इसे प्राप्त करना मुश्किल नहीं है। यहाँ एक शेल फ़ंक्शन है जो उपयोग करता है perl:
function inject() {
perl -e 'ioctl(STDIN, 0x5412, $_) for split "", join " ", @ARGV' "$@"
}
यहाँ, कमांड 0x5412के लिए कोड है TIOCSTI।
TIOCSTIमान के साथ मानक C हेडर फ़ाइलों में एक निरंतर परिभाषित है 0x5412। कोशिश करो grep -r TIOCSTI /usr/include, या में देखो /usr/include/asm-generic/ioctls.h; यह परोक्ष रूप से सी कार्यक्रमों में शामिल है #include <sys/ioctl.h>।
आप तब कर सकते हैं:
$ inject ls -l
ls -l$ ls -l <- cursor here
कुछ अन्य भाषाओं में कार्यान्वयन नीचे दिखाए गए हैं (एक फ़ाइल में सहेजें और फिर chmod +xइसे):
पर्ल inject.pl
#!/usr/bin/perl
ioctl(STDIN, 0x5412, $_) for split "", join " ", @ARGV
आप संख्यात्मक मान का उपयोग करने के बजाय sys/ioctl.phकौन सा परिभाषित कर सकते हैं TIOCSTI। यहाँ देखें
अजगर 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)
माणिक inject.rb
#!/usr/bin/ruby
ARGV.join(' ').split('').each { |c| $stdin.ioctl(0x5412,c) }
सी inject.c
संकलन 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;
}
**! ** यहाँ और भी उदाहरण हैं ।
इसका उपयोग ioctlकरने के लिए उपधाराओं में काम करता है। यह भी अन्य टर्मिनलों में इंजेक्ट किया जा सकता है जैसा कि आगे बताया गया है।
इसे और आगे ले जाना (अन्य टर्मिनलों को नियंत्रित करना)
यह मूल प्रश्न के दायरे से परे है, लेकिन उपयुक्त अनुमति होने के अधीन वर्णों को किसी अन्य टर्मिनल में इंजेक्ट करना संभव है। आम तौर पर इसका मतलब है root, लेकिन अन्य तरीकों के लिए नीचे देखें।
एक कमांड-लाइन तर्क को स्वीकार करने के लिए ऊपर दिए गए C प्रोग्राम को विस्तारित करने से दूसरे टर्मिनल के ट्टी को निर्दिष्ट करने से उस टर्मिनल पर इंजेक्शन लगाने की अनुमति मिलती है:
#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;
}
यह डिफ़ॉल्ट रूप से एक नई पंक्ति भी भेजता है, लेकिन इसके समान echo, यह इसे -nदबाने का विकल्प प्रदान करता है। --tया --ttyविकल्प एक बहस की आवश्यकता है - ttyटर्मिनल के इंजेक्ट किया जा करने के लिए। इसके लिए मान उस टर्मिनल में प्राप्त किया जा सकता है:
$ tty
/dev/pts/20
इसके साथ संकलित करें gcc -o inject inject.c। --कमांड-लाइन विकल्पों को गलत तरीके से तर्क पार्सर को रोकने के लिए किसी भी हाइफ़न को शामिल करने के लिए पाठ को उपसर्ग करें। देख लो ./inject --help। इसे इस तरह उपयोग करें:
$ inject --tty /dev/pts/22 -- ls -lrt
या केवल
$ inject -- ls -lrt
वर्तमान टर्मिनल को इंजेक्ट करने के लिए।
किसी अन्य टर्मिनल में इंजेक्शन लगाने के लिए प्रशासनिक अधिकारों की आवश्यकता होती है जो निम्न द्वारा प्राप्त किए जा सकते हैं:
- आदेश जारी करते हुए
root,
- का उपयोग कर
sudo,
- होने
CAP_SYS_ADMINक्षमता या
- निष्पादन योग्य स्थापित करना
setuid
असाइन करने के लिए CAP_SYS_ADMIN:
$ sudo setcap cap_sys_admin+ep inject
असाइन करने के लिए setuid:
$ sudo chown root:root inject
$ sudo chmod u+s inject
स्वच्छ उत्पादन
इंजेक्ट किया गया टेक्स्ट प्रॉम्प्ट से आगे दिखाई देता है जैसे कि प्रॉम्प्ट दिखाई देने से पहले टाइप किया गया था (जो कि, वास्तव में, यह था) लेकिन यह प्रॉम्प्ट के बाद फिर से दिखाई देता है।
प्रॉम्प्ट के आगे दिखाई देने वाले टेक्स्ट को छिपाने का एक तरीका यह है कि प्रॉम्प्ट को कैरिज रिटर्न ( \rलाइन-फीड नहीं) से प्रीपेंड करें और करेंट लाइन ( <ESC>[M) को क्लियर करें :
$ PS1="\r\e[M$PS1"
हालाँकि, यह केवल उस रेखा को साफ़ करेगा जिस पर संकेत दिखाई देता है। यदि इंजेक्ट किए गए पाठ में newlines शामिल हैं, तो यह उद्देश्य के अनुसार काम नहीं करेगा।
एक अन्य समाधान इंजेक्शन वाले पात्रों की गूंज को निष्क्रिय करता है। एक आवरण sttyऐसा करने के लिए उपयोग करता है:
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"
जहां injectऊपर वर्णित समाधानों में से एक है, या इसके द्वारा प्रतिस्थापित किया गया है printf '\e[5n'।
वैकल्पिक दृष्टिकोण
यदि आपका वातावरण कुछ आवश्यक शर्तों को पूरा करता है तो आपके पास अन्य तरीके उपलब्ध हो सकते हैं जिनका उपयोग आप इनपुट को इंजेक्ट करने के लिए कर सकते हैं। आप एक डेस्कटॉप वातावरण में हैं, तो उसके बाद xdotool एक है X.Org उपयोगिता है कि माउस और कीबोर्ड गतिविधि simulates लेकिन अपने distro डिफ़ॉल्ट रूप से यह शामिल नहीं हो सकता। तुम कोशिश कर सकते हो:
$ xdotool type ls
यदि आप tmux , टर्मिनल मल्टीप्लेक्सर का उपयोग करते हैं, तो आप यह कर सकते हैं:
$ tmux send-key -t session:pane ls
जहां सत्र और -tचयन करने के लिए फलक इंजेक्ट करता है। GNU स्क्रीन की कमांड के साथ समान क्षमता है :stuff
$ screen -S session -p pane -X stuff ls
यदि आपके डिस्ट्रो में कंसोल-टूल्स पैकेज शामिल है, तो आपके पास एक writevtकमांड हो सकती है ioctlजो हमारे उदाहरणों की तरह उपयोग करती है। हालांकि, अधिकांश डिस्ट्रोस ने इस पैकेज को kbd के पक्ष में चित्रित किया है जिसमें इस सुविधा का अभाव है।
Writevt.c की एक अद्यतन प्रति का उपयोग करके संकलित किया जा सकता है gcc -o writevt writevt.c।
अन्य विकल्प जो कुछ उपयोग-मामलों को बेहतर ढंग से फिट कर सकते हैं उनमें अपेक्षा और रिक्त शामिल हैं जो कि इंटरैक्टिव टूल को स्क्रिप्ट करने की अनुमति देने के लिए डिज़ाइन किए गए हैं।
आप ऐसे शेल का भी उपयोग zshकर सकते हैं जो टर्मिनल इंजेक्शन का समर्थन करता है जैसे कि कर सकते हैं print -z ls।
"वाह, यह चतुर ..." जवाब
यहाँ वर्णित विधि पर भी चर्चा की गई है और यहाँ पर चर्चा की गई विधि पर आधारित है ।
एक शेल पुनर्निर्देशित से /dev/ptmxएक नया छद्म टर्मिनल प्राप्त होता है:
$ $ ls /dev/pts; ls /dev/pts </dev/ptmx
0 1 2 ptmx
0 1 2 3 ptmx
C में लिखा गया एक छोटा उपकरण जो स्यूडोटर्मिनल मास्टर (ptm) को अनलॉक करता है और स्यूडोटर्मिनल स्लेव (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;
}
(के रूप में बचाने के लिए pts.cऔर के साथ संकलन gcc -o pts pts.c)
जब प्रोग्राम को इसके मानक इनपुट के साथ एक ptm पर सेट किया जाता है, तो यह संबंधित पीटी को अनलॉक करता है और मानक आउटपुट के लिए इसका नाम आउटपुट करता है।
$ ./pts </dev/ptmx
/dev/pts/20
Unlockpt () समारोह दास pseudoterminal डिवाइस मास्टर pseudoterminal दिया फ़ाइल वर्णनकर्ता से संबोधित किया जाता करने के लिए इसी बातें बताता है। कार्यक्रम इसे शून्य के रूप में पास करता है जो कार्यक्रम के मानक इनपुट है ।
Ptsname () समारोह रिटर्न गुलाम pseudoterminal उपकरण मास्टर करने के लिए इसी का नाम दिया फ़ाइल वर्णनकर्ता से संबोधित किया जाता है, फिर से कार्यक्रम के मानक इनपुट के लिए शून्य से गुजर रहा।
एक प्रक्रिया को pts से जोड़ा जा सकता है। सबसे पहले एक ptm प्राप्त करें (यहाँ यह डिस्क्रिप्टर 3 फाइल करने के लिए दिया गया है, <>रीडायरेक्ट द्वारा रीड-राइट खोला गया है)।
exec 3<>/dev/ptmx
फिर प्रक्रिया शुरू करें:
$ (setsid -c bash -i 2>&1 | tee log) <>"$(./pts <&3)" 3>&- >&0 &
इस कमांड-लाइन द्वारा बताई गई प्रक्रियाओं को सबसे अच्छी तरह से चित्रित किया गया है pstree:
$ pstree -pg -H $(jobs -p %+) $$
bash(5203,5203)─┬─bash(6524,6524)─┬─bash(6527,6527)
│ └─tee(6528,6524)
└─pstree(6815,6815)
आउटपुट वर्तमान शेल ( $$) के सापेक्ष है -pऔर -gप्रत्येक प्रक्रिया के पीआईडी ( ) और पीजीआईडी ( ) कोष्ठक में दिखाए गए हैं (PID,PGID)।
पेड़ के सिर पर bash(5203,5203), वह इंटरेक्टिव शेल है जिसे हम कमांड टाइप कर रहे हैं, और इसके फाइल डिस्क्रिप्टर इसे उस टर्मिनल एप्लिकेशन से जोड़ते हैं जिसका उपयोग हम इसके साथ बातचीत करने के लिए कर रहे हैं ( xterm, या समान)।
$ ls -l /dev/fd/
lrwx------ 0 -> /dev/pts/3
lrwx------ 1 -> /dev/pts/3
lrwx------ 2 -> /dev/pts/3
कमांड को फिर से देखते हुए, कोष्ठक का पहला सेट एक सबशेल शुरू हुआ, bash(6524,6524)) इसकी फ़ाइल डिस्क्रिप्टर 0 (इसका मानक इनपुट ) को पीटीएस (जिसे रीड-राइट खोला जाता है) को सौंपा जा रहा है, <>जैसा कि एक अन्य सबस्क्रिप्शन द्वारा लौटाया गया है जिसे ./pts <&3अनलॉक करने के लिए निष्पादित किया गया है फ़ाइल डिस्क्रिप्टर 3 (पूर्ववर्ती चरण में निर्मित exec 3<>/dev/ptmx) के साथ जुड़े पीटी ।
सबस्क्रिप्शन की फ़ाइल डिस्क्रिप्टर 3 बंद है ( 3>&-) ताकि ptm उसके लिए सुलभ न हो। इसका मानक इनपुट (fd 0), जो कि पढ़ा / लिखा हुआ खोला गया पीटीएस है, को रीडायरेक्ट किया गया है (वास्तव में fd कॉपी किया गया है - >&0) इसके मानक आउटपुट (fd 1) के लिए।
यह अपने मानक इनपुट और आउटपुट से जुड़ा हुआ है जो पीटीएस से जुड़ा है। इसे ptm पर लिखकर इनपुट भेजा जा सकता है और इसका आउटपुट ptm से पढ़कर देखा जा सकता है:
$ echo 'some input' >&3 # write to subshell
$ cat <&3 # read from subshell
इस आदेश को उपधारा निष्पादित करती है:
setsid -c bash -i 2>&1 | tee log
यह नए सत्र bash(6527,6527)में इंटरैक्टिव ( -i) मोड में चलता है ( setsid -cध्यान दें, पीआईडी और पीजीआईडी समान हैं)। इसकी मानक त्रुटि को इसके मानक आउटपुट ( 2>&1) पर पुनर्निर्देशित किया गया है और इसके माध्यम से पाइप को फ़ाइल के साथ-साथ पीटीएस पर tee(6528,6524)लिखा गया है log। यह सबस्क्रिप्शन के आउटपुट को देखने का एक और तरीका देता है:
$ tail -f log
क्योंकि सबस्क्रिप्शन इंटरली चल रहा bashहै, इसे निष्पादित करने के लिए कमांड भेजा जा सकता है, जैसे कि यह उदाहरण जो सब्स्क्रिप्शन की फाइल डिस्क्रिप्शन को प्रदर्शित करता है:
$ echo 'ls -l /dev/fd/' >&3
सब्स्क्रिप्शन के आउटपुट ( tail -f logया cat <&3) को पढ़ने से पता चलता है:
lrwx------ 0 -> /dev/pts/17
l-wx------ 1 -> pipe:[116261]
l-wx------ 2 -> pipe:[116261]
मानक इनपुट (fd 0) pts से जुड़ा है और दोनों मानक आउटपुट (fd 1) और त्रुटि (fd 2) एक ही पाइप से जुड़े हुए हैं, जो इससे कनेक्ट होता है 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]
और फ़ाइल के विवरणों पर एक नज़र डालें 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
मानक आउटपुट (fd 1) pts है: कुछ भी जो 'टी' अपने मानक आउटपुट को लिखता है उसे वापस पीटीएम में भेजा जाता है। मानक त्रुटि (fd 2) नियंत्रण टर्मिनल से संबंधित pts है।
इसे लपेट रहा है
निम्न स्क्रिप्ट ऊपर वर्णित तकनीक का उपयोग करती है। यह एक इंटरैक्टिव bashसत्र सेट करता है जिसे फ़ाइल डिस्क्रिप्टर पर लिखकर इंजेक्ट किया जा सकता है। यह यहाँ उपलब्ध है और स्पष्टीकरण के साथ प्रलेखित है।
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