क्या बाश अपनी इनपुट स्ट्रीम को लिख सकता है?


39

क्या यह एक इंटरैक्टिव बैश शेल में एक कमांड दर्ज करना संभव है जो कुछ पाठ को आउटपुट करता है ताकि यह अगले कमांड प्रॉम्प्ट पर प्रकट हो, जैसे कि उपयोगकर्ता ने उस प्रॉम्प्ट पर उस टेक्स्ट में टाइप किया था?

मैं sourceऐसी स्क्रिप्ट के लिए सक्षम होना चाहता हूं जो कमांड-लाइन उत्पन्न करेगी और इसे आउटपुट करेगी ताकि स्क्रिप्ट समाप्त होने के बाद शीघ्र रिटर्न दिखाई दे ताकि उपयोगकर्ता इसे enterनिष्पादित करने के लिए दबाने से पहले इसे वैकल्पिक रूप से संपादित कर सके।

यह प्राप्त किया जा सकता है xdotoolलेकिन यह तभी काम करता है जब टर्मिनल एक एक्स विंडो में होता है और केवल अगर यह स्थापित है।

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

क्या यह केवल बैश का उपयोग करके किया जा सकता है?


मुझे लगता है कि यह एक्सपेक्ट के साथ कठिन नहीं होना चाहिए, अगर आप इसे बर्दाश्त कर सकते हैं, और यह एक सब-ड्राइव ड्राइव कर सकता है; लेकिन मुझे इसका वास्तविक उत्तर पोस्ट करने के लिए पर्याप्त याद नहीं है।
ट्रिपलआई

जवाबों:


40

इसके साथ zsh, आप print -zअगले प्रॉम्प्ट के लिए कुछ टेक्स्ट को लाइन एडिटर बफर में रखने के लिए उपयोग कर सकते हैं :

print -z echo test

पंक्ति संपादक को प्रधान करेगा echo testजिसके साथ आप अगले संकेत पर संपादित कर सकते हैं।

मुझे नहीं लगता bashकि एक समान सुविधा है, हालांकि कई प्रणालियों पर, आप टर्मिनल डिवाइस इनपुट बफर को TIOCSTI ioctl()निम्न के साथ रख सकते हैं :

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

echo testटर्मिनल डिवाइस इनपुट बफर में डालें , जैसे कि टर्मिनल से प्राप्त किया गया हो।

@ माइक के Terminologyदृष्टिकोण पर एक अधिक पोर्टेबल बदलाव और जो सुरक्षा का त्याग नहीं करता है, वह टर्मिनल एमुलेटर को एक मानक मानक query status reportअनुक्रम भेजने के लिए होगा : <ESC>[5nजो कि अनौपचारिक रूप से (इसलिए इनपुट) के रूप में उत्तर देते हैं <ESC>[0nऔर उस स्ट्रिंग से बाँधते हैं जिसे आप सम्मिलित करना चाहते हैं:

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

यदि GNU के भीतर screen, आप यह भी कर सकते हैं:

screen -X stuff 'echo test'

अब, TIOCSTI ioctl दृष्टिकोण को छोड़कर, हम टर्मिनल एमुलेटर से पूछ रहे हैं कि हमें कुछ स्ट्रिंग भेजें जैसे कि टाइप किया गया। यदि वह स्ट्रिंग पहले आती है readline( bash's लाइन एडिटर') में टर्मिनल स्थानीय इको अक्षम है, तो उस स्ट्रिंग को शेल प्रॉम्प्ट में प्रदर्शित नहीं किया जाएगा , डिस्प्ले को थोड़ा गड़बड़ कर देगा।

उस के आसपास काम करने के लिए, आप या तो टर्मिनल को अनुरोध भेजने में थोड़ी देरी कर सकते हैं, यह सुनिश्चित करने के लिए कि प्रतिक्रिया तब आती है जब गूंज को रीडलाइन द्वारा अक्षम कर दिया गया हो।

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

(यहाँ अपने sleepसमर्थन उप सेकंड संकल्प मान )।

आदर्श रूप से आप कुछ करना चाहेंगे:

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

हालांकि bash(इसके विपरीत zsh) के पास ऐसे समर्थन के लिए समर्थन wait-until-the-response-arrivesनहीं है जो प्रतिक्रिया नहीं पढ़ता है।

हालाँकि इसकी एक has-the-response-arrived-yetविशेषता है 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"

आगे की पढाई

@ Starfry का उत्तर देखें जो @mikeserv द्वारा दिए गए दो समाधानों पर और स्वयं कुछ और विस्तृत जानकारी के साथ विस्तृत है।


मुझे लगता है कि bind '"\e[0n": "echo test"'; printf '\e[5n'शायद मैं केवल जवाब की तलाश में हूं। इससे मेरा काम बनता है। हालाँकि, मैं ^[[0nअपने प्रॉम्प्ट से पहले ही प्रिंट करवा लेता हूं। मुझे पता चला कि यह तब होता है जब $PS1एक सबशेल होता है। आप इसे PS1='$(:)'बाइंड कमांड से पहले कर सकते हैं । ऐसा क्यों होगा और इसके बारे में कुछ भी किया जा सकता है?
स्टार

हालाँकि इस उत्तर में सब कुछ सही है, सवाल बश के लिए था, न कि ज़ीश के लिए। कभी-कभी हमारे पास कोई विकल्प नहीं होता है कि हम किस शेल का उपयोग करें।
फर्ल्सनम्स

@Falsenames केवल पहला पैराग्राफ zsh के लिए है। बाकी या तो खोल अज्ञेयवादी या बैश विशिष्ट है। क्यू एंड ए केवल उपयोगकर्ताओं को कोसने के लिए उपयोगी नहीं है।
स्टीफन चेजलस

1
@ स्टारफ्री ऐसा लगता है कि शायद आप केवल \rसिर के बल बैठ सकते हैं $PS1? अगर $PS1लंबे समय तक काम करना चाहिए । अगर नहीं तो ^[[Mवहाँ डाल दिया ।
23

@mikeserv - rचाल करता है। यह निश्चित रूप से आउटपुट को रोकता नहीं है, यह आंख को देखने से पहले ही इसे ओवरराइट कर दिया जाता है। मुझे लगता है कि ^[[Mअगर यह प्रॉम्प्ट की तुलना में लंबा है तो इंजेक्ट किए गए टेक्स्ट को साफ़ करने के लिए लाइन को मिटा देता है। क्या यह सही है (मैं इसे ANSI भागने की सूची में नहीं पा सकता हूं)?
स्टारफ्री

24

यह उत्तर मेरी अपनी समझ के स्पष्टीकरण के रूप में प्रदान किया गया है और मेरे सामने @ 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

सबसे आसान bind '"\e[0n": "ls -l"'; printf '\e[5n'समाधान के साथ, टर्मिनल के आउटपुट के बाद ls -lभी ^[[0nटर्मिनल पर आउटपुट किया जाएगा एक बार जब मैं इस तरह से रन कुंजी दर्ज करता हूं ls -l। किसी भी विचार यह कैसे "छिपाने" के लिए कृपया? धन्यवाद।
अली

1
मैंने एक समाधान प्रस्तुत किया जो आपके बाद के प्रभाव को देता है - मेरे उत्तर के स्वच्छ आउटपुट खंड में मैं सर्पफ्लस पाठ को छिपाने के लिए प्रॉम्प्ट पर एक रिटर्न जोड़ने का सुझाव देता हूं। मैंने करने PS1="\r\e[M$PS1"से पहले कोशिश की bind '"\e[0n": "ls -l"'; printf '\e[5n'और उस प्रभाव का वर्णन किया जिसे आपने वर्णित किया है।
starfry

धन्यवाद! मैं पूरी तरह से उस बिंदु से चूक गया।
अली

20

यह आप से क्या मतलब है पर निर्भर करता है bashकेवल । यदि आप एकल, संवादात्मक bashसत्र से मतलब रखते हैं , तो उत्तर लगभग निश्चित रूप से नहीं है । और इसका कारण यह है कि जब आप ls -lकिसी भी कैनोनिकल टर्मिनल पर कमांड-लाइन की तरह एक कमांड दर्ज करते हैं, तो bashअभी तक इसके बारे में पता bashनहीं है - और उस बिंदु पर भी शामिल नहीं है।

बल्कि, उस बिंदु तक क्या हुआ है कि कर्नेल की tty लाइन-डिसिप्लिन बफ़र हो गई है और stty echoकेवल स्क्रीन पर उपयोगकर्ता के इनपुट को d है। यह उस इनपुट को अपने पाठक तक पहुंचाता है - bash, आपके उदाहरण के मामले में - लाइन बाय लाइन - और आमतौर पर यूनिक्स को यूनिक्स के सिस्टम में अनुवाद \rकरता है \n- और इसलिए bashऐसा नहीं है - और इसलिए न तो आपकी खट्टी स्क्रिप्ट को समझा जा सकता है - कोई भी ऐसा नहीं है उपयोगकर्ता की ENTERकुंजी दबाने तक सभी पर इनपुट ।

अब, वहाँ कुछ काम कर रहे हैं। सबसे मजबूत वास्तव में एक काम के आसपास नहीं है, वास्तव में, और इनपुट करने के लिए कई प्रक्रियाओं या विशेष रूप से लिखित कार्यक्रमों का उपयोग करना शामिल -echoहै, उपयोगकर्ता से लाइन-अनुशासन को छिपाएं , और केवल स्क्रीन को लिखें जो इनपुट की व्याख्या करते समय उपयुक्त है। विशेष रूप से जब आवश्यक हो। यह अच्छी तरह से करना मुश्किल हो सकता है क्योंकि इसका मतलब है कि व्याख्या नियम लिखना जो मनमाने ढंग से इनपुट चार को चार के रूप में संभाल सकता है जैसे कि यह आता है और गलती से बिना एक साथ इसे लिखने के क्रम में यह समझने के लिए कि औसत उपयोगकर्ता उस परिदृश्य में क्या उम्मीद करेगा। यह इस कारण से है, शायद, कि इंटरैक्टिव टर्मिनल i / o को शायद ही कभी अच्छी तरह से समझा जाता है - एक संभावना यह है कि मुश्किल वह नहीं है जो खुद को सबसे अधिक जांच के लिए उधार देता है।

एक अन्य कार्य-क्षेत्र में टर्मिनल एमुलेटर शामिल हो सकता है। आप कहते हैं कि आपके लिए एक समस्या एक्स और ऑन पर निर्भरता है xdotool। उस मामले में इस तरह के एक काम के आसपास के रूप में मैं पेश करने के लिए कर रहा हूँ के समान मुद्दे हो सकते हैं, लेकिन मैं इसके साथ ही आगे जाऊँगा।

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

वह xtermw / allowwindowOpsसंसाधन सेट में काम करेगा । यह पहले एक स्टैक पर आइकन / विंडो नामों को सहेजता है, फिर टर्मिनल के आइकन-स्ट्रिंग को ^Umy commandफिर से अनुरोध करता है कि टर्मिनल उस नाम को इनपुट कतार में इंजेक्ट करता है, और अंतिम रूप से सहेजे गए मानों पर रीसेट करता है। यह bashएक xterm w / सही विन्यास में चलने वाले इंटरैक्टिव गोले के लिए अदृश्य रूप से काम करना चाहिए- लेकिन यह शायद एक बुरा विचार है। कृपया स्टीफन की टिप्पणियों को नीचे देखें।

यहाँ, हालांकि, एक तस्वीर है जो मैंने printfअपने मशीन पर अलग-अलग भागने के क्रम में बिट w / चलाने के बाद अपने टर्मिनोलॉजी टर्मिनल के लिए ली थी । printfकमांड में प्रत्येक newline के लिए मैंने CTRL+Vफिर टाइप किया CTRL+Jऔर बाद में ENTERकुंजी दबाया । मैंने बाद में कुछ भी नहीं लिखा, लेकिन, जैसा कि आप देख सकते हैं, टर्मिनल ने my commandमेरे लिए लाइन-डिसिप्लिन की इनपुट कतार में इंजेक्शन लगाया :

term_inject

ऐसा करने का वास्तविक तरीका w / एक नेस्टेड पैटी है। यह कैसे screenऔर कैसे tmuxसमान काम है - दोनों, वैसे, यह आपके लिए संभव बना सकता है। xtermवास्तव में एक छोटे से कार्यक्रम के साथ आता है जिसे बुलाया जाता है luitजिससे यह संभव हो सके। हालांकि यह आसान नहीं है।

यहाँ एक तरीका है जो आप कर सकते हैं:

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

यह किसी भी तरह से पोर्टेबल नहीं है, लेकिन इसे खोलने के लिए उचित अनुमति दिए गए अधिकांश लिनक्स सिस्टम पर काम करना चाहिए /dev/ptmx। मेरा उपयोगकर्ता उस ttyसमूह में है जो मेरे सिस्टम पर पर्याप्त है। आपको भी आवश्यकता होगी ...

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

... जो, जब एक ग्नू प्रणाली (या मानक सी कंपाइलर जिसे स्टड से भी पढ़ सकते हैं) के साथ चलाया जा सकता है , तो एक छोटे से निष्पादन योग्य बाइनरी का नाम लिखेगा, ptsजो unlockpt()स्टड पर फ़ंक्शन को चलाएगा और इसके स्टडआउट को लिखेगा। Pty डिवाइस का नाम जिसे उसने अभी अनलॉक किया है। मैंने इसे लिखा था जब मैं काम कर रहा था ... मैं इस दर्द से कैसे आता हूं और मैं इसके साथ क्या कर सकता हूं?

वैसे भी, कोड के ऊपर जो कुछ भी होता है bashवह मौजूदा ट्टी के नीचे एक परत में एक खोल को चलाता है । bashगुलाम पेंटी को सभी आउटपुट लिखने के लिए कहा जाता है, और वर्तमान tty को -echoइसके इनपुट में न तो कॉन्फ़िगर किया जाता है और न ही इसे बफर करने के लिए, बल्कि इसे (ज्यादातर) पास raw करने के लिए cat, जो इसे कॉपी करता है bash। और सभी जबकि अन्य, पृष्ठभूमि वाले catसभी गुलाम आउटपुट को वर्तमान tty में कॉपी करते हैं।

अधिकांश भाग के लिए उपरोक्त कॉन्फ़िगरेशन पूरी तरह से बेकार होगा - बस अनावश्यक, मूल रूप से - सिवाय इसके कि हम bashअपने स्वयं के पीटी मास्टर fd की एक प्रति के साथ लॉन्च करें <>9। इसका मतलब यह है कि bashएक सरल पुनर्निर्देशन के साथ स्वतंत्र रूप से अपने स्वयं के इनपुट स्ट्रीम को लिख सकते हैं। सब bashकरने के लिए है:

echo echo hey >&9

... खुद से बात करने के लिए।

यहाँ एक और तस्वीर है:

यहाँ छवि विवरण दर्ज करें


2
उस कार्य को प्राप्त करने के लिए आपने किन टर्मिनलों का प्रबंधन किया? पुराने समय में इस तरह की चीजों का दुरुपयोग किया जा रहा था और आजकल डिफ़ॉल्ट रूप से अक्षम होना चाहिए। के साथ xterm, आप अभी भी आइकन शीर्षक के साथ क्वेरी कर सकते हैं, \e[20tलेकिन केवल अगर इसके साथ कॉन्फ़िगर किया गया है allowWindowOps: true
स्टीफन चेज़लस


@ StéphaneChazelas जो शब्दावली में काम करता है, लेकिन मुझे पूरा यकीन है कि यह kde टर्मिनल में ( gnome टर्मिनल में भी काम करता है, (मैं इसका नाम भूल जाता हूं, और मुझे लगता है कि वहाँ एक अलग पलायन है) , और जैसा कि आप कहते हैं, w / xtermw / उचित config। W / a xterm उचित है, हालांकि, आप कॉपी / पेस्ट बफर को पढ़ और लिख सकते हैं और इसलिए यह इसके अलावा अधिक सरल हो जाता है, मुझे लगता है। Xterm में शब्द विवरण को बदलने / प्रभावित करने के लिए एस्केप सीक्वेंस भी हैं।
mikeserv

मुझे ऐसा कुछ भी नहीं मिल सकता है लेकिन शब्दावली में काम करना (जिसमें btw की कई अन्य समान कमजोरियाँ हैं)। सीवीई 12 साल से अधिक उम्र का है और अपेक्षाकृत अच्छी तरह से जाना जाता है, मुझे आश्चर्य होगा कि मुख्य टर्मिनल एमुलेटर में से कोई भी समान भेद्यता थी। ध्यान दें कि xterm के साथ, वह \e[20t(नहीं \e]1;?\a)
स्टीफन चेज़लस


8

हालांकि ioctl(,TIOCSTI,) इस सवाल का जवाब स्टीफन Chazelas द्वारा किया जाता है, ज़ाहिर है, सही जवाब, कुछ लोगों को खुश करने के लिए पर्याप्त इस आंशिक लेकिन तुच्छ जवाब के साथ हो सकता है: बस इतिहास ढेर पर आदेश धक्का है, तो उपयोगकर्ता 1 लाइन इतिहास को खोजने के लिए ले जा सकते हैं आदेश।

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

यह एक साधारण स्क्रिप्ट बन सकती है, जिसका अपना 1 लाइन इतिहास है:

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

read -eइनपुट की रीडलाइन संपादन को सक्षम करता है, -pएक त्वरित है।


यह केवल शेल फंक्शंस में काम करेगा, या यदि स्क्रिप्ट को . foo.shउप-भाग में चलाने के बजाय ( या `स्रोत foo.sh; एक समान हैक जिसे कॉलिंग शेल के संदर्भ को संशोधित करने की आवश्यकता होती है, एक कस्टम समापन को सेट करना होगा जो किसी चीज़ के लिए खाली लाइन का विस्तार करता है, और फिर पुराने पूरा करने वाले हैंडलर को पुनर्स्थापित करता है।
पीटर कॉर्ड्स

@PeterCordes आप सही हैं। मैं प्रश्न को भी शाब्दिक रूप से ले रहा था। लेकिन मैंने एक साधारण स्क्रिप्ट का उदाहरण जोड़ा है जो काम कर सकती है।
मयूह जूल

@mikeserv अरे, इसका सिर्फ एक सरल समाधान है जो कुछ लोगों के लिए उपयोगी हो सकता है। evalयदि आप पाइप और पुनर्निर्देशन आदि के बिना संपादित करने के लिए सरल आदेश हैं , तो आप भी निकाल सकते हैं
meuh

1

ओह, मेरा शब्द, हम एक सरल समाधान बनाने में चूक गए , जो कि readएक विकल्प है : कमांड में एक विकल्प होता है -i ..., जिसका उपयोग -eपाठ को इनपुट बफर में धकेल देता है। आदमी पृष्ठ से:

-ई पाठ

यदि लाइन को पढ़ने के लिए रीडलाइन का उपयोग किया जा रहा है, तो संपादन शुरू होने से पहले टेक्स्ट को संपादन बफर में रखा जाता है।

इसलिए एक छोटा सा बैश फ़ंक्शन या शेल स्क्रिप्ट बनाएं जो उपयोगकर्ता को प्रस्तुत करने के लिए कमांड लेता है, और उनके उत्तर को चलाता या मूल्यांकन करता है:

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

यह कोई संदेह ioctl (, TIOCSTI) का उपयोग करता है, जो लगभग 32 वर्षों से है, क्योंकि यह पहले से ही 2.9BSD ioctl.h में मौजूद था ।


1
एक समान प्रभाव के साथ दिलचस्प है, लेकिन यह शीघ्र संकेत के लिए इंजेक्ट नहीं करता है।
स्टार

दूसरे विचार पर आप सही हैं। क्योंकि यह सभी i / o स्वयं कर रहा है, bash को TIOCSTI की आवश्यकता नहीं है।
मयूह
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.