이 답변은 내 자신의 이해를 명확히하기 위해 제공되며 @ 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' "$@"
}
다음 0x5412
은 TIOCSTI
명령 코드입니다 .
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 Screen 은 stuff
명령 과 비슷한 기능을 가지고 있습니다 :
$ screen -S session -p pane -X stuff ls
배포판에 console-tools 패키지가 포함되어 있다면 예제와 같은 writevt
명령을 사용할 수 있습니다 ioctl
. 그러나 대부분의 배포판에서는 이 기능이없는 kbd 를 위해이 패키지를 더 이상 사용하지 않습니다 .
writevt.c 의 업데이트 된 사본은을 사용하여 컴파일 할 수 있습니다 gcc -o writevt writevt.c
.
일부 사용 사례에 더 적합한 다른 옵션에는 대화식 도구를 스크립팅 할 수 있도록 설계된 expect 및 empty 가 있습니다.
당신은 또한 같은 터미널 주입 지원하는 쉘 사용할 수 있습니다 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
프로세스는 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