在 bash 管道中工作的提示命令
prompt command that works in bash pipe
我正在寻找一个提示命令,它允许您在 bash 管道的中间使用 readline 绑定,如下所示:
$ prompt -i 'prefill' -p 'prompt> ' | cat
然后我开始使用预填充文本的 readline 绑定进行编辑:
prompt> prefill|
然后您输入的任何内容的输出都会发送到 cat
。
我写了一个 python 程序几乎可以完成所有这些工作:
#!/usr/bin/env python3
import readline
import sys
def input_with_prefill(prompt, text):
def hook():
readline.insert_text(text)
readline.redisplay()
readline.set_pre_input_hook(hook)
result = input(prompt)
readline.set_pre_input_hook()
return result
def arg(flag):
arg = ""
if flag in sys.argv:
tuple_matches_flag = lambda x: x[1] == flag
# select first instance of "<flag>",
arg_ind = next(filter(tuple_matches_flag, enumerate(sys.argv)))[0]
# add one to the index for the value of the arg
arg = sys.argv[arg_ind + 1]
return arg
def main():
prompt = arg("-p")
prefill = arg("-i")
response = input_with_prefill(prompt, prefill)
print(response)
main()
这在单独使用时效果很好。 prompt -i 'prefill' -p 'prompt> '
完美地按照我想要的方式工作。
但是,当我把它放在这样的管道中时 prompt -i 'prefill' -p 'prompt> ' | cat
它不会起作用。我知道这是因为提示正在将它的标准输出发送到 FIFO 管道到 cat
,所以 readline 库无法正常工作,因为它通常与标准输出接口。所以我认为解决方案是像这样重定向文件描述符:
prompt -i 'prefill' -p 'p> ' 3>&1 1>&2 2>&3 3>&- | cat
这将创建第三个文件描述符,将其指向 stdout 曾经所在的位置(到 cat 的 FIFO 管道),将 stdout 指向 stderr(终端),将 stderr 指向 FIFO 管道,并关闭第三个文件描述符.因此,在这结束时,我们将 prompt
的 stderr 发送到 FIFO,将 prompt
的 stdout 发送到控制台。如果我们将 python 程序的最后一行调整为 print(response, file=sys.stderr)
,那么这应该可以工作。但是,事实并非如此。打印了 2 个提示,没有预填充。怎么了?
这似乎是 python 的 readline 实现的问题:
Python module "readline" can't handle output redirection
https://bugs.python.org/issue24829
我写了这个 C 程序,它可以做我想做的事。您可以使用 clang file.c -L/opt/homebrew/opt/readline/lib -lreadline -o prompt
在 mac 上编译它
#include <string.h>
#include <unistd.h>
#include <readline/readline.h>
char *prefill = "";
int startup_hook() {
if (prefill) {
return rl_insert_text(prefill);
}
return 0;
}
// get value for flag
char *arg(int argc, char **argv, char *flag) {
for (int i = 0; i < argc; i++) {
if (!strcmp(argv[i], flag)) {
if (i < argc) {
return argv[i+1];
}
}
}
return "";
}
int main(int argc, char** argv)
{
// prompt and prefill from commandline
char *prompt = arg(argc, argv, "-p");
prefill = arg(argc, argv, "-i");
// cast because functions hooks are supposed to take (char *, int)
rl_startup_hook = (Function *)startup_hook;
// use stderr if stdout redirected
if(!isatty(STDIN_FILENO)) {
rl_outstream = stderr;
}
char *line = readline(prompt);
puts(line);
return 0;
}
我正在寻找一个提示命令,它允许您在 bash 管道的中间使用 readline 绑定,如下所示:
$ prompt -i 'prefill' -p 'prompt> ' | cat
然后我开始使用预填充文本的 readline 绑定进行编辑:
prompt> prefill|
然后您输入的任何内容的输出都会发送到 cat
。
我写了一个 python 程序几乎可以完成所有这些工作:
#!/usr/bin/env python3
import readline
import sys
def input_with_prefill(prompt, text):
def hook():
readline.insert_text(text)
readline.redisplay()
readline.set_pre_input_hook(hook)
result = input(prompt)
readline.set_pre_input_hook()
return result
def arg(flag):
arg = ""
if flag in sys.argv:
tuple_matches_flag = lambda x: x[1] == flag
# select first instance of "<flag>",
arg_ind = next(filter(tuple_matches_flag, enumerate(sys.argv)))[0]
# add one to the index for the value of the arg
arg = sys.argv[arg_ind + 1]
return arg
def main():
prompt = arg("-p")
prefill = arg("-i")
response = input_with_prefill(prompt, prefill)
print(response)
main()
这在单独使用时效果很好。 prompt -i 'prefill' -p 'prompt> '
完美地按照我想要的方式工作。
但是,当我把它放在这样的管道中时 prompt -i 'prefill' -p 'prompt> ' | cat
它不会起作用。我知道这是因为提示正在将它的标准输出发送到 FIFO 管道到 cat
,所以 readline 库无法正常工作,因为它通常与标准输出接口。所以我认为解决方案是像这样重定向文件描述符:
prompt -i 'prefill' -p 'p> ' 3>&1 1>&2 2>&3 3>&- | cat
这将创建第三个文件描述符,将其指向 stdout 曾经所在的位置(到 cat 的 FIFO 管道),将 stdout 指向 stderr(终端),将 stderr 指向 FIFO 管道,并关闭第三个文件描述符.因此,在这结束时,我们将 prompt
的 stderr 发送到 FIFO,将 prompt
的 stdout 发送到控制台。如果我们将 python 程序的最后一行调整为 print(response, file=sys.stderr)
,那么这应该可以工作。但是,事实并非如此。打印了 2 个提示,没有预填充。怎么了?
这似乎是 python 的 readline 实现的问题: Python module "readline" can't handle output redirection https://bugs.python.org/issue24829
我写了这个 C 程序,它可以做我想做的事。您可以使用 clang file.c -L/opt/homebrew/opt/readline/lib -lreadline -o prompt
#include <string.h>
#include <unistd.h>
#include <readline/readline.h>
char *prefill = "";
int startup_hook() {
if (prefill) {
return rl_insert_text(prefill);
}
return 0;
}
// get value for flag
char *arg(int argc, char **argv, char *flag) {
for (int i = 0; i < argc; i++) {
if (!strcmp(argv[i], flag)) {
if (i < argc) {
return argv[i+1];
}
}
}
return "";
}
int main(int argc, char** argv)
{
// prompt and prefill from commandline
char *prompt = arg(argc, argv, "-p");
prefill = arg(argc, argv, "-i");
// cast because functions hooks are supposed to take (char *, int)
rl_startup_hook = (Function *)startup_hook;
// use stderr if stdout redirected
if(!isatty(STDIN_FILENO)) {
rl_outstream = stderr;
}
char *line = readline(prompt);
puts(line);
return 0;
}