Bash có thể ghi vào luồng đầu vào của chính nó?


39

Có thể trong một bash shell tương tác để nhập một lệnh xuất ra một số văn bản để nó xuất hiện ở dấu nhắc lệnh tiếp theo, như thể người dùng đã gõ vào văn bản đó tại dấu nhắc đó?

Tôi muốn có sourcemột tập lệnh sẽ tạo ra một dòng lệnh và xuất nó để nó xuất hiện khi lời nhắc trở lại sau khi tập lệnh kết thúc để người dùng có thể tùy ý chỉnh sửa nó trước khi nhấn enterđể thực thi nó.

Điều này có thể đạt được xdotoolnhưng chỉ hoạt động khi thiết bị đầu cuối nằm trong cửa sổ X và chỉ khi nó được cài đặt.

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

Điều này có thể được thực hiện bằng cách sử dụng chỉ bash?


Tôi nghĩ rằng điều này không nên khó với Expect, nếu bạn có thể chịu đựng được điều đó, và nó sẽ điều khiển nó; nhưng tôi không nhớ đủ để đăng câu trả lời thực tế.
tripleee

Câu trả lời:


40

Với zsh, bạn có thể sử dụng print -zđể đặt một số văn bản vào bộ đệm trình chỉnh sửa dòng cho lời nhắc tiếp theo:

print -z echo test

sẽ ưu tiên trình echo testchỉnh sửa dòng mà bạn có thể chỉnh sửa tại dấu nhắc tiếp theo.

Tôi không nghĩ bashcó một tính năng tương tự, tuy nhiên trên nhiều hệ thống, bạn có thể chọn bộ đệm đầu vào của thiết bị đầu cuối bằng TIOCSTI ioctl():

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

Sẽ chèn echo testvào bộ đệm đầu vào thiết bị đầu cuối, như thể nhận được từ thiết bị đầu cuối.

Một biến thể di động hơn theo cách tiếp cận của @ mikeTerminology và không hy sinh tính bảo mật sẽ gửi cho trình giả lập thiết bị đầu cuối một query status reportchuỗi thoát khá chuẩn : <ESC>[5nthiết bị đầu cuối nào luôn trả lời (như đầu vào) <ESC>[0nvà liên kết chuỗi đó với chuỗi bạn muốn chèn:

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

Nếu trong GNU screen, bạn cũng có thể làm:

screen -X stuff 'echo test'

Bây giờ, ngoại trừ cách tiếp cận TIOCSTI ioctl, chúng tôi đang yêu cầu trình giả lập thiết bị đầu cuối gửi cho chúng tôi một chuỗi như thể được gõ. Nếu chuỗi đó xuất hiện trước readline( bashtrình chỉnh sửa dòng) đã tắt tiếng vang cục bộ của thiết bị đầu cuối, thì chuỗi đó sẽ được hiển thị không tại dấu nhắc trình bao, làm rối màn hình một chút.

Để giải quyết vấn đề đó, bạn có thể trì hoãn việc gửi yêu cầu đến thiết bị đầu cuối một chút để đảm bảo phản hồi đến khi tiếng vang đã bị vô hiệu hóa bởi đường đọc.

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

(ở đây giả sử bạn sleephỗ trợ độ phân giải dưới giây).

Lý tưởng nhất là bạn muốn làm một cái gì đó như:

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

Tuy nhiên bash(trái với zsh) không có hỗ trợ cho wait-until-the-response-arrivesviệc không đọc phản hồi.

Tuy nhiên, nó có một has-the-response-arrived-yettính năng với 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"

đọc thêm

Xem câu trả lời của @ starfry mở rộng về hai giải pháp được đưa ra bởi @mikeerv và bản thân tôi với một vài thông tin chi tiết hơn.


Tôi nghĩ bind '"\e[0n": "echo test"'; printf '\e[5n'có lẽ câu trả lời chỉ bash tôi đang tìm kiếm. Nó làm việc cho tôi. Tuy nhiên, tôi cũng được ^[[0nin trước lời nhắc của mình. Tôi phát hiện ra điều này được gây ra khi $PS1chứa một subshell. Bạn có thể tái tạo nó bằng cách thực hiện PS1='$(:)'trước khi lệnh ràng buộc. Tại sao điều đó sẽ xảy ra và có thể làm bất cứ điều gì về nó?
starfry 04/07/2015

Mặc dù tất cả mọi thứ trong câu trả lời này là chính xác, câu hỏi là dành cho bash, không phải zsh. Đôi khi chúng ta không có lựa chọn sử dụng vỏ nào.
Falsenames 04/07/2015

@Falsenames chỉ đoạn đầu tiên là dành cho zsh. Phần còn lại là vỏ bất khả tri hoặc bash cụ thể. Q & A không phải chỉ hữu ích đối với người dùng bash.
Stéphane Chazelas

1
@starfry có vẻ như có thể bạn chỉ cần đặt một \return ở đầu $PS1? Điều đó sẽ làm việc nếu $PS1đủ dài. Nếu không thì đặt ^[[Mở đó.
mikeerv

@mikeerv - rkhông lừa. Điều này tất nhiên không ngăn chặn đầu ra, nó chỉ bị ghi đè trước khi mắt nhìn thấy nó. Tôi đoán ^[[Mxóa dòng để xóa văn bản được chèn trong trường hợp nó dài hơn dấu nhắc. Điều đó có đúng không (tôi không thể tìm thấy nó trong danh sách thoát ANSI mà tôi có)?
starfry 7/07/2015

24

Câu trả lời này được cung cấp để làm rõ sự hiểu biết của riêng tôi và được truyền cảm hứng bởi @ StéphaneChazelas và @mikeerv trước tôi.

TL; DR

  • không thể làm điều này bashmà không cần sự trợ giúp từ bên ngoài;
  • cách chính xác để làm điều này là với một đầu vào gửi ioctl nhưng
  • bashgiải pháp khả thi nhất sử dụng bind.

Giải pháp dễ dàng

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

Bash có hàm dựng sẵn được gọi là bindcho phép thực thi lệnh shell khi nhận được chuỗi khóa. Về bản chất, đầu ra của lệnh shell được ghi vào bộ đệm đầu vào của shell.

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

Chuỗi khóa \e[0n( <ESC>[0n) là mã thoát ANSI Terminal mà thiết bị đầu cuối gửi để chỉ ra rằng nó đang hoạt động bình thường. Nó sẽ gửi cái này để đáp lại yêu cầu báo cáo trạng thái thiết bị được gửi dưới dạng <ESC>[5n.

Bằng cách ràng buộc phản hồi với một echođầu ra văn bản cần tiêm, chúng ta có thể tiêm văn bản đó bất cứ khi nào chúng ta muốn bằng cách yêu cầu trạng thái thiết bị và điều đó được thực hiện bằng cách gửi một <ESC>[5nchuỗi thoát.

printf '\e[5n'

Điều này hoạt động, và có lẽ là đủ để trả lời câu hỏi ban đầu vì không có công cụ nào khác được tham gia. Đó là thuần túy bashnhưng dựa vào một thiết bị đầu cuối hoạt động tốt (thực tế tất cả là).

Nó để lại văn bản lặp lại trên dòng lệnh sẵn sàng để được sử dụng như thể nó đã được gõ. Nó có thể được nối thêm, chỉnh sửa và nhấn ENTERkhiến nó được thực thi.

Thêm vào \nlệnh bị ràng buộc để nó được thực thi tự động.

Tuy nhiên, giải pháp này chỉ hoạt động trong thiết bị đầu cuối hiện tại (nằm trong phạm vi của câu hỏi ban đầu). Nó hoạt động từ một dấu nhắc tương tác hoặc từ một tập lệnh có nguồn gốc nhưng nó sẽ phát sinh lỗi nếu được sử dụng từ một khung con:

bind: warning: line editing not enabled

Giải pháp chính xác được mô tả tiếp theo là linh hoạt hơn nhưng nó dựa vào các lệnh bên ngoài.

Giải pháp đúng

Cách thích hợp để nhập dữ liệu đầu vào sử dụng tty_ioctl , một lệnh gọi hệ thống unix cho Điều khiển I / O có một TIOCSTIlệnh có thể được sử dụng để tiêm đầu vào.

TIOC từ " T erminal IOC tl " và STI từ " S cuối T erminal tôi nput ".

Không có lệnh nào được xây dựng bashcho việc này; làm như vậy đòi hỏi một lệnh bên ngoài. Không có lệnh như vậy trong bản phân phối GNU / Linux điển hình nhưng không khó để đạt được với một lập trình nhỏ. Đây là một hàm shell sử dụng perl:

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

Đây 0x5412là mã cho TIOCSTIlệnh.

TIOCSTIlà một hằng số được xác định trong các tệp tiêu đề C tiêu chuẩn có giá trị 0x5412. Hãy thử grep -r TIOCSTI /usr/include, hoặc nhìn vào /usr/include/asm-generic/ioctls.h; nó được bao gồm trong các chương trình C gián tiếp bởi #include <sys/ioctl.h>.

Sau đó bạn có thể làm:

$ inject ls -l
ls -l$ ls -l <- cursor here

Việc triển khai trong một số ngôn ngữ khác được hiển thị bên dưới (lưu trong một tệp và sau đó là chmod +xnó):

Perl inject.pl

#!/usr/bin/perl
ioctl(STDIN, 0x5412, $_) for split "", join " ", @ARGV

Bạn có thể tạo sys/ioctl.phđịnh nghĩa TIOCSTIthay vì sử dụng giá trị số. Xem tại đây

Con trăn 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)

Hồng ngọc inject.rb

#!/usr/bin/ruby
ARGV.join(' ').split('').each { |c| $stdin.ioctl(0x5412,c) }

C inject.c

biên dịch với 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;
}

**! ** Có thêm ví dụ ở đây .

Sử dụng ioctlđể làm điều này hoạt động trong subshells. Nó cũng có thể tiêm vào các thiết bị đầu cuối khác như được giải thích tiếp theo.

Đưa nó đi xa hơn (kiểm soát các thiết bị đầu cuối khác)

Nó vượt quá phạm vi của câu hỏi ban đầu nhưng có thể đưa các ký tự vào một thiết bị đầu cuối khác, có các quyền phù hợp. Thông thường điều này có nghĩa là root, nhưng xem bên dưới để biết những cách khác.

Mở rộng chương trình C được đưa ra ở trên để chấp nhận đối số dòng lệnh chỉ định tty của thiết bị đầu cuối khác cho phép tiêm vào thiết bị đầu cuối đó:

#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;
}  

Nó cũng gửi một dòng mới theo mặc định, nhưng tương tự echo, nó cung cấp một -ntùy chọn để loại bỏ nó. Các --thoặc --ttytùy chọn đòi hỏi một cuộc tranh cãi - sự ttycủa nhà ga để được tiêm. Giá trị này có thể đạt được trong thiết bị đầu cuối đó:

$ tty
/dev/pts/20

Biên dịch nó với gcc -o inject inject.c. Tiền tố văn bản được thêm vào --nếu nó chứa bất kỳ dấu gạch nối nào để ngăn trình phân tích cú pháp đối số diễn giải sai các tùy chọn dòng lệnh. Xem ./inject --help. Sử dụng nó như thế này:

$ inject --tty /dev/pts/22 -- ls -lrt

hoặc chỉ

$ inject  -- ls -lrt

để tiêm thiết bị đầu cuối hiện tại.

Tiêm vào một thiết bị đầu cuối khác đòi hỏi các quyền hành chính có thể có được bằng cách:

  • ban hành lệnh như root,
  • sử dụng sudo,
  • CAP_SYS_ADMINkhả năng hoặc
  • thiết lập thực thi setuid

Để gán CAP_SYS_ADMIN:

$  sudo setcap cap_sys_admin+ep inject

Để gán setuid:

$ sudo chown root:root inject
$ sudo chmod u+s inject

Đầu ra sạch

Văn bản được chèn xuất hiện trước dấu nhắc như thể nó được gõ trước khi dấu nhắc xuất hiện (mà thực tế là nó) nhưng sau đó nó lại xuất hiện sau dấu nhắc.

Một cách để ẩn văn bản xuất hiện trước dấu nhắc là trả trước lời nhắc bằng trả về vận chuyển ( \rkhông phải nguồn cấp dữ liệu) và xóa dòng hiện tại ( <ESC>[M):

$ PS1="\r\e[M$PS1"

Tuy nhiên, điều này sẽ chỉ xóa dòng mà lời nhắc xuất hiện. Nếu văn bản được chèn bao gồm các dòng mới thì điều này sẽ không hoạt động như dự định.

Một giải pháp khác vô hiệu hóa tiếng vang của các ký tự được tiêm. Một trình bao bọc sử dụng sttyđể làm điều này:

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"

đâu injectlà một trong những giải pháp được mô tả ở trên, hoặc được thay thế bởi printf '\e[5n'.

Cách tiếp cận khác

Nếu môi trường của bạn đáp ứng các điều kiện tiên quyết nhất định thì bạn có thể có sẵn các phương pháp khác mà bạn có thể sử dụng để tiêm đầu vào. Nếu bạn đang ở trong môi trường máy tính để bàn thì xdotool là tiện ích X.Org mô phỏng hoạt động của chuột và bàn phím nhưng bản phân phối của bạn có thể không bao gồm nó theo mặc định. Bạn co thể thử:

$ xdotool type ls

Nếu bạn sử dụng tmux , bộ ghép kênh đầu cuối, thì bạn có thể làm điều này:

$ tmux send-key -t session:pane ls

trong đó -tchọn phiênkhung để tiêm. GNU Screen có khả năng tương tự với stufflệnh của nó :

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

Nếu bản phân phối của bạn bao gồm gói công cụ bảng điều khiển thì bạn có thể có một writevtlệnh sử dụng ioctlnhư ví dụ của chúng tôi. Tuy nhiên, hầu hết các bản phân phối đều không dùng gói này để ủng hộ kbd mà thiếu tính năng này.

Một bản sao cập nhật của writevt.c có thể được biên dịch bằng cách sử dụng gcc -o writevt writevt.c.

Các tùy chọn khác có thể phù hợp với một số trường hợp sử dụng tốt hơn bao gồm cả mong đợisản phẩm nào được thiết kế để cho phép các công cụ tương tác được viết kịch bản.

Bạn cũng có thể sử dụng một vỏ hỗ trợ tiêm đầu cuối như zshcó thể làm được print -z ls.

Câu trả lời "Wow, thật thông minh ..."

Phương pháp được mô tả ở đây cũng được thảo luận ở đây và dựa trên phương pháp được thảo luận ở đây .

Chuyển hướng shell từ /dev/ptmxmột thiết bị đầu cuối giả mới:

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

Một công cụ nhỏ được viết bằng C sẽ mở khóa pseudoterminal master (ptm) và đưa ra tên của nô lệ giả (pts) cho đầu ra tiêu chuẩn của nó.

#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;
}

(lưu dưới dạng pts.cvà biên dịch với gcc -o pts pts.c)

Khi chương trình được gọi với đầu vào tiêu chuẩn được đặt thành ptm, nó sẽ mở khóa các pts tương ứng và xuất tên của nó thành đầu ra tiêu chuẩn.

$ ./pts </dev/ptmx
/dev/pts/20
  • Hàm Unlockpt () mở khóa thiết bị giả phụ nô lệ tương ứng với mã giả chính được gọi bởi bộ mô tả tệp đã cho. Chương trình vượt qua mức 0 này là đầu vào tiêu chuẩn của chương trình .

  • Hàm ptsname () trả về tên của thiết bị giả phụ nô lệ tương ứng với bản gốc được mô tả bởi tệp mô tả tệp đã cho, một lần nữa chuyển về 0 cho đầu vào tiêu chuẩn của chương trình.

Một quá trình có thể được kết nối với pts. Trước tiên hãy lấy ptm (ở đây được gán cho mô tả tệp 3, mở đọc-ghi bởi <>chuyển hướng).

 exec 3<>/dev/ptmx

Sau đó bắt đầu quá trình:

$ (setsid -c bash -i 2>&1 | tee log) <>"$(./pts <&3)" 3>&- >&0 &

Các quy trình được sinh ra bởi dòng lệnh này được minh họa rõ nhất với pstree:

$ pstree -pg -H $(jobs -p %+) $$
bash(5203,5203)─┬─bash(6524,6524)─┬─bash(6527,6527)
                             └─tee(6528,6524)
            └─pstree(6815,6815)

Đầu ra liên quan đến shell hiện tại ( $$) và PID ( -p) và PGID ( -g) của mỗi quá trình được hiển thị trong ngoặc đơn (PID,PGID).

Ở phần đầu của cây là bash(5203,5203)lớp vỏ tương tác mà chúng ta đang gõ lệnh và các bộ mô tả tệp của nó kết nối nó với ứng dụng đầu cuối mà chúng ta đang sử dụng để tương tác với nó ( xtermhoặc tương tự).

$ ls -l /dev/fd/
lrwx------ 0 -> /dev/pts/3
lrwx------ 1 -> /dev/pts/3
lrwx------ 2 -> /dev/pts/3

Nhìn lại lệnh một lần nữa, tập hợp dấu ngoặc đơn đầu tiên đã khởi động một lớp con, bash(6524,6524)) với bộ mô tả tệp 0 ( đầu vào tiêu chuẩn của nó ) được gán cho pts (được mở đọc-ghi <>) , được trả về bởi một lớp con khác được thực thi ./pts <&3để mở khóa pts liên kết với mô tả tập tin 3 (được tạo ở bước trước, exec 3<>/dev/ptmx).

Bộ mô tả tập tin của subshell 3 được đóng ( 3>&-) để ptm không thể truy cập được. Đầu vào tiêu chuẩn của nó (fd 0), là pts đã được mở đọc / ghi, được chuyển hướng (thực tế là fd được sao chép - >&0) thành đầu ra tiêu chuẩn của nó (fd 1).

Điều này tạo ra một subshell với đầu vào và đầu ra tiêu chuẩn của nó được kết nối với pts. Nó có thể được gửi đầu vào bằng cách viết vào ptm và đầu ra của nó có thể được nhìn thấy bằng cách đọc từ ptm:

$ echo 'some input' >&3 # write to subshell
$ cat <&3               # read from subshell

Subshell thực thi lệnh này:

setsid -c bash -i 2>&1 | tee log

Nó chạy bash(6527,6527)-ichế độ tương tác ( ) trong một phiên mới ( setsid -clưu ý rằng PID và PGID giống nhau). Lỗi tiêu chuẩn của nó được chuyển hướng đến đầu ra tiêu chuẩn của nó ( 2>&1) và được truyền qua tee(6528,6524)để nó được ghi vào một logtệp cũng như các pts. Điều này đưa ra một cách khác để xem đầu ra của subshell:

$ tail -f log

Vì lớp con đang chạy bashtương tác, nên nó có thể được gửi các lệnh để thực thi, như ví dụ này hiển thị các mô tả tệp của lớp con:

$ echo 'ls -l /dev/fd/' >&3

Đọc kết quả đầu ra của subshell ( tail -f loghoặc cat <&3) cho thấy:

lrwx------ 0 -> /dev/pts/17
l-wx------ 1 -> pipe:[116261]
l-wx------ 2 -> pipe:[116261]

Đầu vào tiêu chuẩn (fd 0) được kết nối với pts và cả đầu ra tiêu chuẩn (fd 1) và lỗi (fd 2) được kết nối với cùng một đường ống, kết nối với 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]

Và xem xét các mô tả tập tin của 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

Đầu ra tiêu chuẩn (fd 1) là pts: mọi thứ mà 'tee' ghi vào đầu ra tiêu chuẩn của nó được gửi lại cho ptm. Lỗi tiêu chuẩn (fd 2) là các điểm thuộc về thiết bị đầu cuối điều khiển.

Gói nó lên

Kịch bản sau đây sử dụng kỹ thuật được mô tả ở trên. Nó thiết lập một bashphiên tương tác có thể được chèn bằng cách viết vào một mô tả tập tin. Nó có sẵn ở đây và tài liệu với lời giải thích.

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

Với bind '"\e[0n": "ls -l"'; printf '\e[5n'giải pháp đơn giản nhất , sau khi tất cả đầu ra của ls -lcũng ^[[0nsẽ được xuất ra trên Terminal sau khi tôi nhấn phím enter do đó chạy ls -l. Bất cứ ý tưởng làm thế nào để "ẩn" nó xin vui lòng? Cảm ơn bạn.
Ali

1
Tôi đã trình bày một giải pháp mang lại hiệu quả cho bạn sau - trong phần đầu ra sạch trong câu trả lời của tôi, tôi khuyên bạn nên thêm một trả về lời nhắc để ẩn văn bản surpflouous. Tôi đã thử PS1="\r\e[M$PS1"trước khi làm bind '"\e[0n": "ls -l"'; printf '\e[5n'và điều đó mang lại hiệu quả mà bạn mô tả.
starfry

Cảm ơn bạn! Tôi hoàn toàn bỏ lỡ điểm đó.
Ali

20

Nó phụ thuộc vào những gì bạn có nghĩa là bashchỉ . Nếu bạn có nghĩa là một bashphiên tương tác duy nhất , thì câu trả lời gần như chắc chắn là không . Và điều này là do ngay cả khi bạn nhập một lệnh như ls -ltại dòng lệnh trên bất kỳ thiết bị đầu cuối chính tắc nào thì bashthậm chí còn không biết về nó - và bashthậm chí không tham gia vào thời điểm đó.

Thay vào đó, điều đã xảy ra cho đến thời điểm đó là kỷ luật dòng tty của kernel đã được đệm và stty echod chỉ nhập vào màn hình. Nó tuôn ra đầu vào cho trình đọc của nó - bash, trong trường hợp ví dụ của bạn - theo từng dòng - và thường dịch chuyển từ \rewlines sang \ncác hệ thống Unix - và vì vậy bashkhông - và vì vậy, tập lệnh có nguồn gốc của bạn cũng không thể nhận ra nhập tất cả cho đến khi người dùng nhấn ENTERphím.

Bây giờ, có một số công việc xung quanh. Thực tế, mạnh mẽ nhất hoàn toàn không phải là một công việc xung quanh và liên quan đến việc sử dụng nhiều quy trình hoặc chương trình được viết đặc biệt để nhập chuỗi, ẩn dòng kỷ luật -echokhỏi người dùng và chỉ ghi vào màn hình những gì được đánh giá phù hợp trong khi diễn giải đầu vào đặc biệt khi cần thiết Điều này có thể khó thực hiện tốt bởi vì nó có nghĩa là viết các quy tắc diễn giải có thể xử lý char đầu vào tùy ý khi nó đến và viết ra đồng thời mà không nhầm lẫn để mô phỏng những gì người dùng trung bình mong đợi trong kịch bản đó. Có lẽ vì lý do này, có lẽ, thiết bị đầu cuối tương tác rất hiếm khi được hiểu rõ - một viễn cảnh khó khăn không phải là thứ cho vay để điều tra thêm cho hầu hết.

Một công việc khác có thể liên quan đến trình giả lập thiết bị đầu cuối. Bạn nói rằng một vấn đề đối với bạn là sự phụ thuộc vào X và xdotool. Trong trường hợp đó, một công việc như tôi sắp đưa ra có thể có vấn đề tương tự, nhưng tôi sẽ tiếp tục với nó.

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

Điều đó sẽ làm việc trong một xtermbộ allowwindowOpstài nguyên. Đầu tiên, nó lưu tên biểu tượng / cửa sổ trên một ngăn xếp, sau đó đặt chuỗi biểu tượng của thiết bị đầu cuối để ^Umy commandsau đó yêu cầu thiết bị đầu cuối đưa tên đó vào hàng đợi đầu vào và cuối cùng đặt lại nó vào các giá trị đã lưu. Nó sẽ hoạt động vô hình đối với các bashshell tương tác chạy trong một xterm cấu hình đúng / nhưng có lẽ đó là một ý tưởng tồi. Xin vui lòng xem ý kiến ​​của Stéphane dưới đây.

Tuy nhiên, đây là một bức ảnh tôi chụp về thiết bị đầu cuối Thuật ngữ của mình sau khi chạy printfbit với một chuỗi thoát khác trên máy của tôi. Đối với mỗi dòng mới trong printflệnh tôi đã gõ CTRL+Vsau đó CTRL+Jvà sau đó nhấn ENTERphím. Sau đó tôi đã gõ không có gì, nhưng, như bạn có thể thấy, thiết bị đầu cuối được đưa my commandvào hàng đợi đầu vào của dòng-kỷ luật cho tôi:

hạn_inject

Cách thực sự để làm điều này là w / a pty lồng nhau. Đó là cách thức screentmuxcông việc tương tự - cả hai đều có thể làm điều này cho bạn. xtermthực sự đi kèm với một chương trình nhỏ được gọi là luitcũng có thể làm cho điều này có thể. Nó không phải là dễ dàng, mặc dù.

Đây là một cách bạn có thể:

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

Điều đó không có nghĩa là di động, nhưng sẽ hoạt động trên hầu hết các hệ thống Linux được cấp quyền thích hợp để mở /dev/ptmx. Người dùng của tôi nằm trong ttynhóm đủ trên hệ thống của tôi. Bạn cũng sẽ cần ...

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

... mà khi chạy trên hệ thống GNU (hoặc bất kỳ hệ thống nào khác có trình biên dịch C tiêu chuẩn cũng có thể đọc từ stdin) , sẽ viết ra một nhị phân thực thi nhỏ có tên ptssẽ chạy unlockpt()hàm trên stdin của nó và ghi vào thiết bị xuất chuẩn của nó Tên của thiết bị pty nó vừa mở khóa. Tôi đã viết nó khi làm việc trên ... Làm thế nào để tôi đến đây và tôi có thể làm gì với nó? .

Dù sao, những gì đoạn mã trên thực hiện là chạy một bashlớp vỏ trong một lớp bên dưới lớp tty hiện tại. bashđược yêu cầu ghi tất cả đầu ra cho pty nô lệ, và tty hiện tại được cấu hình cả không phải -echođầu vào của nó cũng như để đệm nó, mà thay vào đó để chuyển nó (chủ yếu) raw sang cat, sao chép nó sang bash. Và trong khi khác, catsao chép nền tất cả đầu ra nô lệ cho tty hiện tại.

Đối với hầu hết các phần, cấu hình trên sẽ hoàn toàn vô dụng - về cơ bản chỉ là dư thừa - ngoại trừ việc chúng tôi khởi chạy bashvới một bản sao của fd master pty của chính nó <>9. Điều này có nghĩa là bashcó thể tự do ghi vào luồng đầu vào của chính nó với một chuyển hướng đơn giản. Tất cả những gì bashphải làm là:

echo echo hey >&9

... Nói chuyện với chính nó.

Đây là một hình ảnh khác:

nhập mô tả hình ảnh ở đây


2
Những thiết bị đầu cuối nào bạn quản lý để làm việc đó? Loại điều đó đã bị lạm dụng trong thời xa xưa và ngày nay nên bị vô hiệu hóa. Với xterm, bạn vẫn có thể truy vấn tiêu đề biểu tượng \e[20tnhưng chỉ khi được định cấu hình allowWindowOps: true.
Stéphane Chazelas


@ StéphaneChazelas hoạt động trong Thuật ngữ, nhưng tôi khá chắc chắn rằng nó cũng hoạt động trong thiết bị đầu cuối gnome, trong thiết bị đầu cuối kde (tôi quên tên của nó và tôi nghĩ rằng có một lối thoát khác) , và như bạn nói, w / xtermw / đúng cấu hình. Mặc dù vậy, với một xterm thích hợp, bạn có thể đọc và viết bộ đệm sao chép / dán và vì vậy nó trở nên đơn giản hơn bên cạnh đó, tôi nghĩ vậy. Xterm cũng có các chuỗi thoát để thay đổi / ảnh hưởng đến chính mô tả thuật ngữ.
mikeerv

Tôi không thể làm điều đó để làm việc trong bất cứ điều gì ngoại trừ thuật ngữ (mà btw có một số lỗ hổng tương tự khác). CVE đó đã hơn 12 tuổi và tương đối nổi tiếng Tôi sẽ ngạc nhiên nếu bất kỳ trình giả lập thiết bị đầu cuối chính nào có cùng lỗ hổng. Lưu ý rằng với xterm, đó là \e[20t(không \e]1;?\a)
Stéphane Chazelas


8

Mặc dù ioctl(,TIOCSTI,) câu trả lời của Stéphane Chazelas là, tất nhiên, câu trả lời đúng, một số người có thể hài lòng với câu trả lời một phần nhưng tầm thường này: chỉ cần đẩy lệnh lên ngăn xếp lịch sử, sau đó người dùng có thể di chuyển 1 dòng lịch sử để tìm chỉ huy.

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

Điều này có thể trở thành một tập lệnh đơn giản, có lịch sử 1 dòng riêng:

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

read -echo phép chỉnh sửa dòng đầu vào, -plà một dấu nhắc.


Điều đó sẽ chỉ hoạt động trong các hàm shell hoặc nếu tập lệnh có nguồn gốc ( . foo.shhoặc `nguồn foo.sh, thay vì chạy trong một lớp con.) Tuy nhiên, cách tiếp cận thú vị. Một hack tương tự yêu cầu sửa đổi bối cảnh của vỏ gọi sẽ là thiết lập một hoàn thành tùy chỉnh mở rộng dòng trống thành một cái gì đó, và sau đó khôi phục trình xử lý hoàn thành cũ.
Peter Cordes

@PeterCordes bạn đúng. Tôi đã nhận câu hỏi quá đúng theo nghĩa đen. Nhưng tôi đã thêm một ví dụ về một kịch bản đơn giản có thể hoạt động.
meuh

@mikeerv Này, nó chỉ là một giải pháp đơn giản có thể hữu ích với một số người. Bạn thậm chí có thể xóa evalnếu bạn có các lệnh đơn giản để chỉnh sửa, không có đường ống và chuyển hướng, v.v.
meuh

1

Ôi trời, chúng ta đã bỏ lỡ một giải pháp đơn giản tích hợp sẵn để bash : readlệnh có một tùy chọn -i ..., khi được sử dụng -e, sẽ đẩy văn bản vào bộ đệm đầu vào. Từ trang người đàn ông:

-i văn bản

Nếu đường đọc đang được sử dụng để đọc dòng, văn bản sẽ được đặt vào bộ đệm chỉnh sửa trước khi bắt đầu chỉnh sửa.

Vì vậy, hãy tạo một hàm bash nhỏ hoặc tập lệnh shell dùng lệnh để trình bày cho người dùng và chạy hoặc đánh giá câu trả lời của họ:

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

Điều này chắc chắn sử dụng ioctl (, TIOCSTI,) đã tồn tại hơn 32 năm, vì nó đã tồn tại trong 2.9BSD ioctl.h .


1
Thú vị với một hiệu ứng tương tự, nhưng nó không tiêm vào dấu nhắc.
starfry 24/07/2015

Suy nghĩ thứ 2 bạn đúng. bash không cần TIOCSTI vì nó tự làm tất cả các i / o.
meuh 24/07/2015
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.