我正在尝试以简约的方式为我的项目打印 git clone 进度。
目的
而不是在屏幕上打印整个 git clone 输出
remote: Enumerating objects: 1845678, done.
remote: Counting objects: 100% (503/503), done.
remote: Compressing objects: 100% (79/79), done.
Receiving objects: 28% (54112/1845678), 10.10 MiB | 2.00 MiB/s
我想抽象出冗长的 git 输出行,并仅以这种格式输出克隆的实时进度:
Cloning [$percentage]
我目前所得到的
git clone --progress https://somerepo 2>&1 |tee gitclone.file | tr \\r \\n | total="$(awk '/Receiving objects/{print $3}')" | echo "$total"
注意:由于 git clone 仅返回 stderr 流,我已将其重定向到 stdout 流。即使重定向,我也遇到了一些问题,因此我在 git 命令上使用了进度选项。
我想将输出存储在文件中(用于调试脚本)而不干扰 stdout 流,因此我使用了 tee 命令。由于 git clone 返回的是\r
而不是\n
,因此我将其替换为以正确的方式捕获输出。有关此部分的更多信息,您可以查看此问题及其答案Git 实时将输出生成到文件中,但我无法在 while 循环中直接实时回显它
然后我选择包含关键字的行Receiving objects
并打印/存储该行的第三个关键字段值。
我的问题是什么
如果我不存储 awk 的输出而只是将其打印在屏幕上,我的命令就可以正常工作:
git clone --progress https://somerepo 2>&1 |tee gitclone.file | tr \\r \\n | awk '/Receiving objects/{print $3}'
但是,我无法将 awk 输出存储在 shell 变量中并将其回显:
git clone --progress https://somerepo 2>&1 |tee gitclone.file | tr \\r \\n | total="$(awk '/Receiving objects/{print $3}')" | echo "$total"
那么这个问题可能有什么解决方案呢?
答案1
作为bash 手册说:
管道中的每个命令都作为单独的进程执行(即在子 shell 中)。
因此,当子 shell 退出时,变量中保存的输出total
将丢失。运行以下命令即可看到此信息:
git clone --progress https://somerepo |& tee gitclone.file \
| tr \\r \\n | { total="$(awk '/Receiving objects/{print $3}')" ; \
echo "$total" ; }
total
由于上述命令行(即命令管道)结束后变量就会丢失,因此应该将整行放入“命令替换“像这样的括号:
total=$(git clone --progress https://somerepo |& tee gitclone.file | tr \\r \\n | awk '/Receiving objects/{print $3}')
echo "$total"
但是,如果您希望管道(以命令开始git
)在后台运行,则必须将awk
的输出重定向到文件,然后读取该文件。例如:
tmpfile=$(mktemp)
git ... >"$tmpfile" &
# ...
# Do other stuff...
# ...
wait # for background process to complete.
total=$(cat "$tmpfile")
rm "$tmpfile"
echo "$total"
提示:重定向标准输出和标准错误命令的git
命令tee
可以使用|&
这样的简写:git clone --progress https://somerepo |& tee gitclone.file |
...
答案2
我认为问题出在 git 的输出上。重写“接收对象:”行时,我没有完成新行。
你可以通过查看输出来判断
GIT_FLUSH=1 git clone --progress $repo 2>&1 | cat -bu
在第一次出现“接收”行之后,您将看不到行号。下面是一个例子,我将输出导入“od”,以使 \r 和 \n 可见:
0000200 \n 4 \t R e c e
0000220 i v i n g o b j e c t s :
0000240 0 % ( 1 / 1 1 0 3 8 ) \r R e
0000260 c e i v i n g o b j e c t s :
0000300 0 % ( 4 9 / 1 1 0 3 8 )
0000320 , 8 . 8 8 M i B | 2 . 8
0000340 4 M i B / s \r
逐行读取输入的程序(如 awk)在 git 完成之前将看不到这些行。
答案3
从根本上讲,您遇到了管道缓冲问题。管道中的程序使用的输入和/或输出缓冲区太大。幸运的是,有一种方法可以告诉管道中的每个程序只缓冲一行。
这是您需要的程序: https://manpages.ubuntu.com/manpages/bionic/man1/unbuffer.1.html。
我认为它是在 Ubuntu 桌面中默认安装的,但如果没有:
sudo apt install expect
然后您可以unbuffer
在管道中包含该命令来解决问题:
REPO_URL = https://something or git@something
unbuffer git clone --progress $REPO_URL 2>&1 | \
unbuffer -p tr \\r \\n | \
{ awk '/Receiving objects/{print $3}' ; echo "$total" ; }
它打印 0%、1%、...100%,然后因为“总计”是其中的最后一个,所以再次打印 100%,并且它会随着进度的进行而打印,而不是在最后或大块地打印。