bash는 자체 입력 스트림에 쓸 수 있습니까?


39

대화식 bash 쉘에서 사용자가 해당 프롬프트에서 해당 텍스트를 입력 한 것처럼 다음 명령 프롬프트에 표시되도록 일부 텍스트를 출력하는 명령을 입력 할 수 있습니까?

source명령 줄을 생성하고 출력하는 스크립트를 사용하여 스크립트가 끝난 후 프롬프트가 표시되면 사용자가 스크립트 enter를 실행 하기 전에 선택적으로 편집하기 전에 스크립트를 편집 할 수 있도록 표시 하고 싶습니다 .

이를 통해 달성 할 수 xdotool있지만 터미널이 X 창에 있고 설치된 경우에만 작동합니다.

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

bash 만 사용 하여이 작업을 수행 할 수 있습니까?


나는 이것이 당신이 그것을 견딜 수 있고 그것이 서브 쉘을 운전하게 할 수 있다면 Expect와 함께 어렵지 않아야한다고 생각합니다. 그러나 실제 답변을 게시하기에는 충분하지 않습니다.
tripleee

답변:


39

을 사용 하면 다음 프롬프트를 위해 텍스트를 라인 편집기 버퍼에 배치 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터미널에서 수신 한 것처럼 터미널 장치 입력 버퍼에 삽입 합니다.

@mike의 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 행 편집기) 터미널 로컬 반향을 비활성화하면 해당 문자열이 쉘 프롬프트에 표시 되지 않고 표시가 약간 엉망이됩니다.

이 문제를 해결하기 위해 readline으로 에코가 비활성화 된 경우 응답이 도착하도록 터미널에 대한 요청 전송을 약간 지연시킬 수 있습니다.

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

(여기 sleep에서 1 초 미만의 해상도를 지원 한다고 가정 ).

이상적으로는 다음과 같은 작업을 원할 것입니다.

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'아마 내가 찾고있는 bash 전용 답변 이라고 생각 합니다. 그것은 나를 위해 작동합니다. 그러나 ^[[0n프롬프트가 표시되기 전에 인쇄됩니다. 나는 이것이 $PS1서브 쉘을 포함 할 때 발생 한다는 것을 발견했다 . PS1='$(:)'바인드 명령 전에 수행하여이를 재현 할 수 있습니다 . 왜 그런 일이 일어나고 그것에 대해 무엇을 할 수 있습니까?
starfry

이 답변의 모든 것이 정확하지만 질문은 zsh가 아닌 bash에 대한 것입니다. 때때로 우리는 사용할 쉘을 선택할 수 없습니다.
거짓 이름

@Falsenames 첫 번째 단락 만 zsh 용입니다. 나머지는 쉘 불가지론 또는 배시 특정입니다. Q & A는 사용자를 강타하기 위해 유용 할 필요는 없습니다.
Stéphane Chazelas

1
@starfry는 아마도 당신 \r의 머리에 돈을 넣을 수있는 것처럼 보입니다 $PS1. $PS1충분히 길면 작동해야합니다 . 그렇지 않으면 ^[[M거기에 두십시오 .
mikeserv

@ mikeserv- r트릭을 수행합니다. 이것은 물론 출력을 막는 것이 아니라, 눈으로보기 전에 덮어 쓰기됩니다. 입력 ^[[M된 텍스트가 프롬프트보다 길 경우 줄을 지우면 삽입 된 텍스트가 지워집니다. 맞습니까? (내가 가지고있는 ANSI 이스케이프 목록에서 찾을 수 없었습니다)?
starfry

23

이 답변은 내 자신의 이해를 명확히하기 위해 제공되며 @ StéphaneChazelas 및 @mikeserv에서 영감을 얻었습니다.

TL; DR

  • bash외부 도움 없이는 이 작업을 수행 할 수 없습니다 .
  • 올바른 방법은 송신 터미널 입력 ioctl 이지만
  • 가장 쉬운 실행 가능한 bash솔루션이 사용합니다 bind.

쉬운 솔루션

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

Bash에는 bind키 시퀀스가 ​​수신 될 때 쉘 명령을 실행할 수 있는 쉘 내장 기능 이 있습니다. 본질적으로 쉘 명령의 출력은 쉘의 입력 버퍼에 기록됩니다.

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

키 시퀀스 \e[0n( <ESC>[0n)는 터미널 이 정상적으로 작동하고 있음을 나타 내기 위해 전송 하는 ANSI 터미널 이스케이프 코드 입니다. 그것은에 응답이 송신 장치의 상태보고 요청 으로 전송된다 <ESC>[5n.

echo삽입 할 텍스트를 출력 하는 응답을 바인딩하면 장치 상태를 요청하여 원하는 때마다 <ESC>[5n이스케이프 시퀀스 를 전송하여 해당 텍스트를 삽입 할 수 있습니다 .

printf '\e[5n'

이것은 효과가 있으며 다른 도구가 없기 때문에 원래 질문에 대답하기에 충분합니다. 순수 bash하지만 잘 작동하는 터미널에 의존합니다 (실제로는 모두 있습니다).

입력 된 것처럼 사용할 수 있도록 명령 행에 반향 된 텍스트를 남겨 둡니다. 추가, 편집 및을 누르면 ENTER실행됩니다.

\n바인딩 된 명령에 추가 하여 자동으로 실행되도록합니다.

그러나이 솔루션은 현재 터미널에서만 작동합니다 (원래 질문의 범위 내에 있음). 대화식 프롬프트 또는 소스 스크립트 에서 작동 하지만 서브 쉘에서 사용하면 오류가 발생합니다.

bind: warning: line editing not enabled

다음에 설명 된 올바른 솔루션 은 더 유연하지만 외부 명령에 의존합니다.

올바른 해결책

입력을 주입하는 올바른 방법은 입력 주입에 사용할 수 있는 명령 이있는 I / O 제어 를 위한 유닉스 시스템 호출 인 tty_ioctl을 사용합니다.TIOCSTI

TIOC "에서 T erminal IOC의 TL "및 STI "에서 S의T erminal I nput ".

이를 bash위해 내장 된 명령이 없습니다 . 그렇게하려면 외부 명령이 필요합니다. 일반적인 GNU / Linux 배포판에는 그러한 명령이 없지만 약간의 프로그래밍만으로는 어렵지 않습니다. 다음은 사용하는 쉘 함수입니다 perl.

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

다음 0x5412TIOCSTI명령 코드입니다 .

TIOCSTI표준 C 헤더 파일에 값이 정의 된 상수 0x5412입니다. 시도해 grep -r TIOCSTI /usr/include보거나 /usr/include/asm-generic/ioctls.h; C 프로그램에 의해 간접적으로 포함되어 있습니다 #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하지만 다른 방법은 아래를 참조하십시오.

다른 터미널의 tty를 지정하는 명령 행 인수를 허용하도록 위에 제공된 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"

그러나 이렇게하면 프롬프트가 나타나는 줄만 지워집니다. 삽입 된 텍스트에 줄 바꿈이 포함되어 있으면 의도 한대로 작동하지 않습니다.

또 다른 솔루션은 주입 된 문자의 에코를 비활성화합니다. 랩퍼는 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 유틸리티이지만 기본적으로 배포판에 포함되지 않을 수 있습니다. 당신은 시도 할 수 있습니다:

$ xdotool type ls

터미널 멀티플렉서 인 tmux 를 사용하면 다음을 수행 할 수 있습니다.

$ tmux send-key -t session:pane ls

여기서 주입 -t세션분할 창 을 선택합니다 . GNU Screenstuff명령 과 비슷한 기능을 가지고 있습니다 :

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

배포판에 console-tools 패키지가 포함되어 있다면 예제와 같은 writevt명령을 사용할 수 있습니다 ioctl. 그러나 대부분의 배포판에서는 이 기능이없는 kbd 를 위해이 패키지를 더 이상 사용하지 않습니다 .

writevt.c 의 업데이트 된 사본은을 사용하여 컴파일 할 수 있습니다 gcc -o writevt writevt.c.

일부 사용 사례에 더 적합한 다른 옵션에는 대화식 도구를 스크립팅 할 수 있도록 설계된 expectempty 가 있습니다.

당신은 또한 같은 터미널 주입 지원하는 쉘 사용할 수 있습니다 zsh할 수있는가 print -z ls.

"와우, 영리하다 ..."답변

여기에 기재된 방법도 논의되고 여기서 상기 방법은 설명을 토대로 여기 .

쉘 리다이렉트 /dev/ptmx는 새로운 의사 터미널 을 얻는다 :

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

의사 터미널 마스터 (ptm)를 잠금 해제하고 의사 터미널 슬레이브 (pts)의 이름을 표준 출력으로 출력하는 C로 작성된 작은 도구입니다.

#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으로 설정된 상태에서 프로그램을 호출하면 해당 pt가 잠금 해제되고 해당 이름이 표준 출력으로 출력됩니다.

$ ./pts </dev/ptmx
/dev/pts/20
  • unlockpt () 함수는 주어진 파일 디스크립터에 의해 참조되는 마스터 의사 터에 대응하는 상기 슬레이브 장치를 잠금 해제 의사 터. 프로그램은 이것을 프로그램의 표준 입력 인 0으로 전달합니다 .

  • ptsname () 함수의 리턴에 대응 마스터 슬레이브 의사 터 장치의 이름이 다시 프로그램의 표준 입력을 제로 전달 지정된 파일 디스크립터에 의해 참조.

프로세스는 pt에 연결될 수 있습니다. 먼저 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)

출력은 현재 쉘 ( $$)에 상대적 이며 각 프로세스 의 PID ( -p) 및 PGID ( -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 (읽기-쓰기로 열린 상태)에 할당 <>되어 다른 서브 쉘 ./pts <&3에서 잠금을 해제하기 위해 실행 된 서브 쉘과 함께 서브 쉘을 시작 했습니다. 파일 디스크립터 3과 연관된 pt (이전 단계에서 작성 됨 exec 3<>/dev/ptmx).

3>&-ptm에 액세스 할 수 없도록 서브 쉘의 파일 설명자 3이 닫힙니다 ( ). 읽기 / 쓰기로 열린 pts 인 표준 입력 (fd 0) >&0은 표준 출력 (fd 1)으로 리디렉션됩니다 (실제로 fd가 복사 됨- ).

그러면 표준 입력 및 출력이 pt에 연결된 서브 쉘이 작성됩니다. 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, PID와 PGID가 동일 함). 표준 오류가 표준 출력 (로 리디렉션 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입니다. 'tee'가 표준 출력에 쓰는 것은 ptm으로 다시 전송됩니다. 표준 오류 (fd 2)는 제어 터미널에 속하는 pt입니다.

그것을 마무리

다음 스크립트는 위에서 설명한 기술을 사용합니다. 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

20

그것은 당신이 무슨 뜻인지에 따라 달라집니다 bash . 하나의 대화식 bash세션 을 의미한다면 대답은 거의 아니오 입니다. 그리고 이것은 ls -l정식 터미널의 명령 줄에서 와 같은 명령을 입력 bash하더라도 아직 인식 bash하지 못하기 때문에 그 시점에도 관여하지 않기 때문입니다.

오히려, 그 시점까지 일어난 일은 커널의 tty line-discipline이 버퍼링되어 stty echo사용자 입력이 화면에 표시 되었다는 것 입니다. 그것은 입력을 리더로 플러시 bash합니다 (예를 들어 한 줄씩)-일반적으로 유닉스 시스템 \r에서 \neturn 을 ewline 으로 변환 합니다. 그렇지 bash않습니다. 사용자가 ENTER키를 누를 때까지 입력하십시오 .

이제 몇 가지 해결 방법이 있습니다. 가장 강력한 방법은 전혀 해결 방법이 아니며 실제로 여러 프로세스 또는 특수하게 작성된 프로그램을 사용하여 입력을 시퀀싱 -echo하고 사용자 에게 라인 분야를 숨기고 입력을 해석하는 동안 적절하다고 판단되는 내용 만 화면에 쓰는 것입니다 특별히 필요할 때. 이는 임의의 입력 문자가 도착할 때 문자로 처리하고 실수로 동시에 쓸 수있는 해석 규칙을 작성하여 해당 시나리오에서 일반 사용자가 기대하는 것을 시뮬레이션하기 때문에 잘 수행하기 어려울 수 있습니다. 이런 이유로 대화 형 터미널 I / O는 거의 잘 이해되지 않는 것입니다. 어려운 전망은 대부분의 추가 조사에 적합하지 않습니다.

다른 해결 방법은 터미널 에뮬레이터와 관련 될 수 있습니다. 당신은 당신에게 문제가 X와에 대한 의존성이라고 말합니다 xdotool. 이 경우 제공하려는 해결 방법과 비슷한 문제가있을 수 있지만 계속 똑같이 진행할 것입니다.

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

그건에서 작동합니다 xtermw /를 allowwindowOps자원 세트. 먼저 아이콘 / 창 이름을 스택에 저장 한 다음 터미널의 아이콘 문자열을 설정하여 터미널이 ^Umy command해당 이름을 입력 대기열에 주입하도록 요청한 다음 마지막으로 저장된 값으로 재설정합니다. 대화식 bashxterm 이 올바른 구성으로 실행되면 보이지 않게 작동 하지만 아마도 나쁜 생각 일 것입니다. 아래 Stéphane의 의견을 참조하십시오.

그러나 여기 printf내 컴퓨터에서 다른 이스케이프 시퀀스가 있는 비트를 실행 한 후 내 터미널 터미널을 찍은 사진이 있습니다. 의 각 줄 바꿈을 위해 printf내가 입력 한 명령을 CTRL+V다음 CTRL+J과 이후에 누를 ENTER키를 누릅니다. 나중에 아무것도 입력하지 않았지만, 알 수 있듯이 터미널 my command은 회선 분야의 입력 대기열에 주입 되었습니다.

term_inject

이 작업을 수행하는 실제 방법은 중첩 pty입니다. 그것은 방법 screentmux유사한 작업입니다-두 가지 모두 당신을 위해 이것을 가능하게 할 수 있습니다. 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

이것은 이식성이있는 것은 아니지만 개봉에 대한 적절한 권한이 부여 된 대부분의 Linux 시스템에서 작동해야합니다 /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

... GNU 시스템 (또는 stdin에서 읽을 수있는 표준 C 컴파일러를 사용하는 다른 시스템 ) 에서 pts실행될 때 unlockpt()stdin 에서 함수를 실행 하고 stdout에 쓸 작은 실행 가능한 바이너리를 작성합니다. 잠금 해제 한 pty 장치의 이름입니다. 나는 일할 때 그것을 썼다. 나는 어떻게이 pty에 의해오고 어떻게 나는 그것을 할 수 있는가? .

어쨌든, 위의 코드 비트 bash는 현재 tty 아래의 계층에서 셸을 실행합니다 . bash슬레이브 PTY 모든 출력을 작성하라고, 전류가 TTY에 둘 구성된 -echo입력 없으며 버퍼링하는 대신이를 통과 (주로) rawcat, 어떤 카피를 통해로 bash. 그리고 다른 배경 cat은 모든 슬레이브 출력을 현재 tty에 복사합니다.

대부분의 경우 위의 구성은 기본적으로 중복되지 않습니다. 기본적으로 자체 pty 마스터 fd의 복사본으로 시작한다는 제외하고 는 중복되지 않습니다 . 즉 , 간단한 리디렉션으로 자체 입력 스트림에 자유롭게 쓸 수 있습니다. 그 모든 상관이있다 :bash<>9bashbash

echo echo hey >&9

... 자신과 대화하기.

다른 사진이 있습니다 :

여기에 이미지 설명을 입력하십시오


2
그 터미널을 작동시키기 위해 어떤 터미널을 관리 했습니까? 예전에는 그런 종류의 일이 남용되었으므로 현재는 기본적으로 사용하지 않도록 설정해야합니다. 로도 xterm아이콘 제목을 쿼리 할 수 \e[20t있지만로 구성된 경우에만 아이콘 제목을 쿼리 할 수 ​​있습니다 allowWindowOps: true.
Stéphane Chazelas


@ StéphaneChazelas 그 용어의 작품,하지만 난 확신 그것은 또한 KDE 터미널에서, 그놈 터미널에서 작동 해요 (난 그 이름을 잊지 내가 다른 탈출이 있다고 생각) 당신이 말한대로 / W, 및 xterm적절한 / w 구성. xterm이 적절하지만 복사 / 붙여 넣기 버퍼를 읽고 쓸 수 있으므로 그 외에도 더 간단 해집니다. Xterm에는 용어 설명 자체를 변경 / 영향하기위한 이스케이프 시퀀스도 있습니다.
mikeserv

용어 이외의 다른 곳에서는 작동하지 않습니다 (btw에는 다른 유사한 취약점이 있습니다). CVE가 12 세 이상이고 비교적 잘 알려져 있다는 사실은 주요 터미널 에뮬레이터 중 하나가 동일한 취약점을 가지고 있다면 놀랄 것입니다. xterm을 사용하면 \e[20t( \e]1;?\a
아니요


8

Stéphane Chazelas 의 ioctl(,TIOCSTI,) 답변 은 물론 정답이지만 일부 사람들은이 부분적이지만 사소한 답변에 만족할 것입니다. 명령을 기록 스택에 푸시하면 사용자가 기록을 찾기 위해 1 줄의 기록을 이동할 수 있습니다 명령.

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

이것은 간단한 스크립트가 될 수 있으며, 고유 한 한 줄의 히스토리가 있습니다.

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

read -e입력의 리드 라인 편집을 활성화 -p하고 프롬프트입니다.


쉘 함수에서만, 또는 스크립트가 소스 된 경우 ( . foo.sh또는 서브 쉘에서 실행되는 대신`source foo.sh) 에만 작동 합니다. 호출 쉘의 컨텍스트를 수정해야하는 유사한 해킹은 빈 행을 무언가로 확장 한 다음 이전 완료 핸들러를 복원하는 사용자 정의 완료를 설정하는 것입니다.
Peter Cordes

@PeterCordes 당신이 맞아요. 나는 문자 그대로 너무 질문을하고 있었다. 그러나 나는 작동 할 수있는 간단한 스크립트의 예를 추가했습니다.
meuh

@mikeserv Hey는 일부 사람들에게 유용 할 수있는 간단한 솔루션입니다. eval파이프 나 리다이렉션 등없이 간단한 편집 명령이
있다면를

1

오, 내 말은, 우리는 bash에 내장 된 간단한 솔루션 을 놓쳤다 . read명령에는 옵션 -i ...과 함께 사용할 때 -e텍스트를 입력 버퍼로 푸시 하는 옵션이 있습니다. 매뉴얼 페이지에서 :

-i 텍스트

readline을 사용하여 행을 읽는 경우 편집을 시작하기 전에 텍스트가 편집 버퍼에 배치됩니다.

따라서 명령을 사용자에게 제공하고 응답을 실행하거나 평가하는 작은 bash 함수 또는 쉘 스크립트를 작성하십시오.

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

이것은 2.9BSD ioctl.h에 이미 존재했던 것처럼 32 년 이상 지속 된 ioctl (, TIOCSTI,)을 사용합니다 .


1
비슷한 효과가 있지만 흥미있는 것은 아닙니다.
starfry

두 번째 생각에 당신이 맞습니다. bash는 모든 I / O 자체를 수행하기 때문에 TIOCSTI가 필요하지 않습니다.
meuh
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.