【问题标题】:Bash test in pipe results in weird behaviour管道中的 Bash 测试导致奇怪的行为
【发布时间】:2017-04-15 03:10:35
【问题描述】:

我正在尝试为 bash 编写一个带有各种样式选项的彩色输出库,允许使用重定向进行着色和样式设置。

例如

echo "Red" | red输出红色文字

echo "Bold" | bold 输出粗体文本

echo "Yellow bold" | yellow | bold 输出粗体黄色文本

目前我写的代码如下:

#shellcheck shell=bash

# set debug
# set -o xtrace

# number of colors supported
__colors=$(tput colors 2> /dev/null)
# colors
__black="$(tput setaf 0)"
__red="$(tput setaf 1)"
__green="$(tput setaf 2)"
__yellow="$(tput setaf 3)"
__blue="$(tput setaf 4)"
__magenta="$(tput setaf 5)"
__cyan="$(tput setaf 6)"
__white="$(tput setaf 7)"
# style
__default="$(tput sgr0)"
__bold="$(tput bold)"
__underline="$(tput smul)"


function has_colors() {
  COLOR=${COLOR:-auto}
  if [[ $COLOR = 'never' ]]; then
    return 1
  elif [[ $COLOR = 'always' ]]; then
    return 0
  else
    # check if stoud is terminal and terminal supports colors
    [[ -t 1 ]] && \
    [[ -n $__colors ]] && \
    [[ $__colors -ge 8 ]]
  fi
}

function __style() {
  read -r input
  if has_colors; then
    echo -e "$1" "$input" "$__default"
  else
    echo -e "$input"
  fi
}

function black() {
  __style "$__black"
}

function red() {
  __style "$__red"
}

function green() {
  __style "$__green"
}

function yellow() {
  __style "$__yellow"
}

function blue() {
  __style "$__blue"
}

function magenta() {
  __style "$__magenta"
}

function cyan() {
  __style "$__cyan"
}

function white() {
  __style "$__white"
}

function bold() {
  __style "$__bold"
}

function underline() {
  __style "$__underline"
}

设置 COLOR=always 始终使用转义码输出。另一方面 COLOR=auto 执行一些检查以确保当前标准输出是终端并且终端支持颜色。

问题是使用多个样式选项似乎不起作用。它总是应用最后一个样式选项。例如:

echo "Yellow bold" | yellow | bold 输出粗体文本,但不是黄色。

另一方面:

echo "Bold yellow" | bold | yellow 输出黄色文本,但不是粗体。

有趣的是;设置 COLOR=always 似乎工作得很好。所以看起来我执行的测试是否 stdout 是终端 [[ -t 1 ]] 导致了这种情况。我不确定是否是因为该测试存在某种延迟。但是当我删除[[ -t 1 ]] 位时,它可以工作。

知道如何实现这一目标吗?不是 Bash 专家,也不是 shell 如何解决这个问题的专家。这里很混乱。

【问题讨论】:

  • 问题是yellow | bold中,yellow的stdout不是tty。您应该在主脚本中检查一次并设置一个所有函数都使用的变量,而不是检查每个颜色函数
  • @那个其他人,这正是我检查它的原因。如果我检查一次并将输出重定向到一个文件,它将带有颜色转义码。我不想要的
  • 查看 set -xv 输出,您的问题似乎来自这样一个事实,即粗体(假设它是第二个)在样式中的 echo -e 行之前被调用并接收原始副本字符串,因此您可以在中间进行任意数量的更改,结果将始终是标准文本加粗。不知何故,您需要让每个管道依次接收数据(不确定最佳方法)。你可以看看wait命令??
  • 当您确实需要某种 路区分(终端 vs 文件 vs 管道)时,您正在进行双向区分(终端与非终端)。

标签: linux bash shell pipe echo


【解决方案1】:

清醒地看待这个问题,我发现了我的方法中的问题。

[[ -t 1 ]] 测试 stdout 是否为终端,当我通过管道传递两个函数时,如 echo "Hello" | yellow | bold[[ -t 1 ]] 在通过函数 yellow 表示输出不是终端时为 false。

这是因为函数(黄色)的输出通过管道传输到第二个函数(粗体)。这就是为什么它不输出黄色的转义码而只输出输入的原因。

因此,如果我继续通过管道连接到另一个函数,例如 echo "Hello" | yellow | bold | underline,它只会在输出下划线。

这似乎是一种很好且简单的颜色输出方法,但现在我必须改变我的方法,除非有办法知道当前正在运行的函数是否正在通过管道传输但未重定向?

编辑

This post 表明有一种方法可以检测命令是被重定向还是被管道传输。

最后,这种方法似乎不太可行,因为如果我在管道时不禁用颜色,它将在管道到另一个不是另一个输出样式函数的命令时输出颜色代码;这可能是一个边缘情况,但解决方案仍然不是 %100 故障证明

编辑解决方案:

改变了方法。而不是像下面这样使用下一个格式选项作为第一个格式的参数一个接一个地管道格式

echo "Hello" | yellow bold underline

最终代码如下:

#shellcheck shell=bash

# set debug
# set -xv

# number of colors supported
__colors=$(tput colors 2> /dev/null)

# foreground colors
__black="$(tput setaf 0)"
__red="$(tput setaf 1)"
__green="$(tput setaf 2)"
__yellow="$(tput setaf 3)"
__blue="$(tput setaf 4)"
__magenta="$(tput setaf 5)"
__cyan="$(tput setaf 6)"
__white="$(tput setaf 7)"

# background colors
__bg_black="$(tput setab 0)"
__bg_red="$(tput setab 1)"
__bg_green="$(tput setab 2)"
__bg_yellow="$(tput setab 3)"
__bg_blue="$(tput setab 4)"
__bg_magenta="$(tput setab 5)"
__bg_cyan="$(tput setab 6)"
__bg_white="$(tput setab 7)"

# style
__reset="$(tput sgr0)"
__bold="$(tput bold)"
__underline="$(tput smul)"

function has_colors() {
  COLOR=${COLOR:-auto}
  if [[ $COLOR = 'never' ]]; then
    return 1
  elif [[ $COLOR = 'always' ]]; then
    return 0
  else
    [[ -t 1 ]] && [[ -n $__colors ]] && [[ $__colors -ge 8 ]]
  fi
}

function __format() {
  local format="$1"
  local next="${2:-}" # next formatting function e.g. underline
  if has_colors; then
    echo -en "$format"
    if [[ -n $next ]]; then
      shift 2
      tee | "$next" "$@"
    else
      tee
      echo -en "$__reset"
    fi
  else
    tee #print output
  fi
}

function black() { __format "$__black" "$@"; }
function red() { __format "$__red" "$@"; }
function green() { __format "$__green" "$@";}
function yellow() { __format "$__yellow" "$@"; }
function blue() { __format "$__blue" "$@"; }
function magenta() { __format "$__magenta" "$@";}
function cyan() { __format "$__cyan"  "$@";}
function white() { __format "$__white"  "$@";}

function bg_black() { __format "$__bg_black" "$@"; }
function bg_red() { __format "$__bg_red" "$@"; }
function bg_green() { __format "$__bg_green" "$@";}
function bg_yellow() { __format "$__bg_yellow" "$@"; }
function bg_blue() { __format "$__bg_blue" "$@"; }
function bg_magenta() { __format "$__bg_magenta" "$@";}
function bg_cyan() { __format "$__bg_cyan"  "$@";}
function bg_white() { __format "$__bg_white"  "$@";}

function bold() { __format "$__bold"  "$@";}
function underline() { __format "$__underline" "$@"; }

【讨论】:

  • 判断管道是被发送到管道还是被重定向的最好方法是检查命令。如果 stdout 是 tty(带有test -t),则在启动时检查主 shell 并设置一个全局变量。在每个函数中,检查该变量。在脚本中,如果您将管道重定向到文件,请不要调用您的着色函数!个人意见:不要在意这些。着色看起来很棒,但是外壳对于这种事情来说太脆弱了,最终你会在凌晨 3:00 看到一个没有足够咖啡的日志文件,并希望它没有被杂物弄得一团糟!
  • @WilliamPursell 我正在写一个可重用的库,我想我会在未来的某个时候将东西重定向到一个文件。我想如果我破解这个一次,这对我来说会非常非常有帮助。现在真的只供个人使用。如果它被证明是有用的并且经过适当的测试,我认为它将节省很多麻烦。或不 ?我更新了我的答案,我想我找到了一个很好的简单解决方案。我知道 ssh 是不同的故事,将有一个剧本来看看它是如何通过 ssh 或套接字工作的。有什么想法吗?
  • 您的解决方案很不错(但它确实会产生额外的 io 并且对于多行输出失败;可能只做red; underline; echo some text; default 就更干净了),但不可避免地这种事情变得比它的价值更大。代码的简单性最终超过了彩色输出的好处。而且“最终”发生的时间比预期的要快!
  • @WilliamPursell 是的,我听到了 :)
猜你喜欢
  • 2021-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-27
  • 1970-01-01
  • 1970-01-01
  • 2017-12-31
相关资源
最近更新 更多