扩展 bash 函数以添加输入重定向 and/or 管道

Expanding a bash function to add input redirection and/or pipes

我正在尝试设置一个 bash 函数,它使用一些前缀来注释命令的输出。目前,我有一堆看起来像这样的代码行:

git pull              2>&1 | sed "s/^/   [git pull] /"
clean_cmake_fortests  2>&1 | sed "s/^/   [cmake] /"
make -j 2             2>&1 | sed "s/^/   [make] /"
docker rmi $(docker images -a -q) 2>&1 | sed "s/^/   [docker rmi] /" | grep "removed" || true

我的目标是用函数替换 2>&1 | sed "s/^/$ [] /" 位,这样我就可以让上面的行看起来像:

git pull              `annotate "git pull"`
clean_cmake_fortests  `annotate "cmake"`
make -j 2             `annotate "make"`
docker rmi $(docker images -a -q) `annotate "docker rmi"` | grep "removed" || true

我将函数annotate定义为

function annotate {
   2>&1 | sed "s/^/    [] /"
}

但是在执行时,它没有任何影响,所有命令都只是原封不动地转储它们的标准输出。我怎样才能实现我在这里的意图?我正在寻找类似于 C 内联宏扩展的东西。


如果有人好奇,这里的重点是让我生成这样的日志:

04: Getting proto images
    [docker_get_proto_images] Fetching proto docker images...
    ...
    [docker_get_proto_images] Status: Image is up to date for api:dev-proto
    [docker_get_proto_images] ... Done.
05: Building local docker containers
    [docker_local_build] [sh] Building local docker images...
    [docker_local_build] [sh] NOTE: Odd behaviour may result if using outdated bases...
    [docker_local_build] [sh] Local docker image build complete.
    ...
    [docker_local_build] [sh] For advanced usage, see $ARE_TOP/deployment/docker/README
    [docker_local_build]
06: Running docker-compose
    [docker-compose] Starting docker_datacachedisk_1
    [docker-compose] Starting docker_djangodisk_1
    ...

而不是这个:

04: Getting proto images
Fetching proto docker images...
...
Status: Image is up to date for api:dev-proto
... Done.
05: Building local docker containers
[sh] Building local docker images...
[sh] NOTE: Odd behaviour may result if using outdated bases...
[sh] Local docker image build complete.
...
[sh] For advanced usage, see $ARE_TOP/deployment/docker/README

06: Running docker-compose
Starting docker_datacachedisk_1
Starting docker_djangodisk_1
...

一段时间后很难阅读。

执行函数中的命令。例如:

annotate () {
    "$@" |& sed "s/^/    [] /"
}

这里按照正常写命令,在开头加上annotate即可:

$ annotate ls /
    [ls] bin
    [ls] boot
    [ls] dev
    [ls] etc
    [ls] home
    ...

Awk 可能是更好的选择。如果您的 IFS 以 space 开头(默认情况下确实如此),您可以使用 $*:

获取整个命令
annotate () {
    "$@" |& awk -v tag="$*" '{printf "\t[%s] | %s\n", tag, [=12=]}'
}

再次:

$ annotate ls /
    [ls /] | bin
    [ls /] | boot
    [ls /] | dev
    [ls /] | etc
    [ls /] | home

或者,您可以将第一个参数保留为标签,这样您就可以传递任意标签:

annotate () {
    "${@:2}" |& awk -v tag="" '{printf "\t[%s] | %s\n", tag, [=14=]}'
}

$ annotate "foo bar" ls /
    [foo bar] | bin
    [foo bar] | boot
    [foo bar] | dev
    [foo bar] | etc
    [foo bar] | home
    [foo bar] | lib

重定向与命令相关联,因此您不能将 2>&1 与其影响的命令分开。不过,您可以像现在这样定义 annotate。 (不过不要使用 sed,因为在不知道使用什么定界符的情况下在其命令中包含一个变量是非常困难的。)

annotate ()
while IFS= read -r line; do
       printf '    [%s] %s\n' "" "$line"
done

(是的,没有大括号是有意的,但不是必需的。函数体可以是任何复合命令,而不仅仅是大括号组。)

然后称之为

git pull &> >( annotate "git pull" )

您可以使用一个简单的管道,git pull |& annotate "git pull",但它在子 shell 中运行命令,这可能是不可取的。