从脚本管道 stdout/stderr 时不显示 read -p 提示

arc*_*lix 3 bash shell

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

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
}
Run Code Online (Sandbox Code Playgroud)

这是这样使用的:

some command 2>&1 | indent_lines
Run Code Online (Sandbox Code Playgroud)

来自的整个输出通过some command 2>&1管道传输到 indent_lines 函数中,并且每一行输出都将缩进。这适用于read -pin 从 inside 调用的情况some command,例如:

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"
}
Run Code Online (Sandbox Code Playgroud)

输出是:

$ 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
$
Run Code Online (Sandbox Code Playgroud)

不显示提示并挂起等待输入。

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

Cha*_*ffy 5

while read输入端的循环(像许多其他工具一样)一次处理一行。由于提示最后没有打印换行符,因此它不会由循环处理。

概括地说,您有两种选择:

  • 避免提示的管道
  • 添加换行符以便刷新内容

由于get_name函数不能被修改是规范的一部分,我们最终要做的是修改 shell 环境以改变read工作方式。


避免管道

read -p 将其提示写入 stderr。

如果要重定向提示,则重定向 FD 2。

如果您想确保其他重定向(例如 a 2>&1,这会导致提示转到标准输出 - 正在被捕获)不适用,则直接指向 TTY:

read -p "enter something" 2>/dev/tty
Run Code Online (Sandbox Code Playgroud)

添加换行符

现在,如果你的目标是运行一个 shell 函数——你不能修改它——一般read -p会重定向 stderr 但将提示直接打印到 TTY,那么可以使用类似于以下内容的 hack:

reading_to_tty() {
  read() { builtin read "$@" 2>/dev/tty; }
  "$@"
  unset -f read
}
Run Code Online (Sandbox Code Playgroud)

...因此:

reading_to_tty get_name 2>&1
Run Code Online (Sandbox Code Playgroud)

...将运行get_nameread命令(没有其他命令)将 stderr 内容直接发送到 TTY。


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

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

...以与上述相同的方式使用:

reading_with_prompt_newline get_name 
Run Code Online (Sandbox Code Playgroud)