当从脚本中将标准输出/标准错误输出通过管道传输时,使用read -p提示不会显示。

5

我有一个函数,旨在捕获命令输出并缩进每一行:

indent_lines () {
  local line
  local indent="        "

  while IFS= read -r line || [[ -n "$line" ]]; do
      # remove \r and replace with \r$indent
      echo "$indent $(echo "$line" | sed "s/$(printf '\r')/$(printf '\r')$indent /g")"
  done
}

这是如何使用的:

some command 2>&1 | indent_lines
< p >整个some command 2>&1的输出被导入到indent_lines函数中,每一行输出都将进行缩进。除了在some command中调用read -p的情况之外,这种做法都适用,例如:
get_name () {
   echo "this is a line of output 1"
   echo "this is a line of output 2"
   echo "this is a line of output 3"
   read -p "enter your name: " user_input
   echo
   echo "$user_input is your name"
}

输出结果如下:
$ get_name 2>&1 | indent_lines
$        this is a line of output 1
$        this is a line of output 2
$        this is a line of output 3
$

提示符没有显示,而是挂起等待输入。

有没有办法在暂停输入之前显示提示符?


我有点困惑,你讨论了 read -p 但是代码中除了在子shell的讨论中没有看到 read -p 的使用,而且上下文也不太清楚。你能否提供一个最小化、完整化、可复现化的示例([MCVE])?另外,read -p 主要是 Bash 中的命令,对吧?或许你应该加上 [tag:bash] 标签或者无论如何都加上这个标签。 - Jonathan Leffler
@Jonathan read -p 是在子shell "$(some command 2>&1 )" 中调用的,实际上无论是 read -p 还是只有 read 都没有关系(我想)。 - Arctelix
完全没有帮助。当我运行您的“精确示例”时,提示正确地打印到TTY。 - Charles Duffy
另外,为什么要运行 echo "$(get_name)" | indent_lines 而不是 get_name | indent_lines(或者直接将 get_name 放在父进程中,get_name > >(indent_lines))?echo 和子 shell 没有任何有用的作用,如果 get_name 旨在修改进程本地变量,则可能会产生积极的危害。 - Charles Duffy
@CharlesDuffy 如果没有使用 2>&1,则从命令输出的任何错误都不会被传送到 indent_lines 中,提示符也不会缩进。或许您可以修改您的答案以获得正确的输出?请注意我无法修改 get_name - Arctelix
显示剩余13条评论
1个回答

5
while read循环(像许多其他工具一样)在输入端一次处理一行。由于提示符没有在结尾处打印换行符,因此它不会被循环处理。
在高层次上,您有两个选择:
  • 避免使用管道进行提示
  • 添加换行符以刷新内容
由于规范要求无法修改get_name函数,因此我们将在此处修改Shell环境以更改read的工作方式。

避免使用管道

read -p将其提示写入stderr。
如果要重定向提示,则重定向FD 2。
如果要确保其他重定向(例如2>&1,这将导致提示转到被捕获的stdout),则明确指向TTY。
read -p "enter something" 2>/dev/tty

添加一个新行

现在,如果你的目标是运行一个无法修改的shell函数,并将stderr重定向到TTY,但read -p直接打印提示信息,那么可以使用以下方法进行hack:

reading_to_tty() {
  read() { builtin read "$@" 2>/dev/tty; }
  "$@"
  unset -f read
}

因此:

reading_to_tty get_name 2>&1

...将运行get_name,使用read命令(而不是其他命令)将stderr内容直接发送到TTY。


根据深入讨论,确保提示刷新到管道格式的另一种方法是追加换行符。下面就是这样做的,因此现有的管道通过格式化函数即可使用:

reading_with_prompt_newline() { 
  read() {
    if [[ $1 = -p ]]; then 
      set -- "$1" "$2"$'\n' "${@:3}" 
    fi 
    builtin read "$@" 
  } 
  "$@" 
  unset -f read 
}

与上述方式相同,可以使用相同的方法:

reading_with_prompt_newline get_name 

我认为你的解决方案对于一个shell初学者来说有些复杂了!;-) 但总体来说是非常有趣的解决方案! - F. Hauri - Give Up GitHub

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接