在 Bash 中重用最后一个命令的输出

Posted

技术标签:

【中文标题】在 Bash 中重用最后一个命令的输出【英文标题】:Reusing output from last command in Bash 【发布时间】:2014-08-08 14:30:03 【问题描述】:

Bash 命令的输出是否存储在任何寄存器中?例如。类似于$? 捕获输出而不是退出状态的东西。

我可以将输出分配给一个变量:

output=$(command)

但那是更多的打字......

【问题讨论】:

Automatically capture output of last command into a variable using Bash?的可能重复 既然 bash 似乎无法做到这一点,还有其他 shell 可以做到这一点吗? 【参考方案1】:

您可以使用$(!!) 重新计算(不重复使用)最后一个命令的输出。

!! 自己执行最后一条命令。

$ echo pierre
pierre
$ echo my name is $(!!)
echo my name is $(echo pierre)
my name is pierre

【讨论】:

您可以通过使用反引号来避免键入$(your_cmd)`your_cmd` 根据 Gnu Bash 手册,它们在功能上是相同的。当然这也受到@memecs 提出的警告的影响 在实践中,命令的输出是确定性的,所以我不会为重新计算而苦恼。当您想要执行 git diff $(!!) 之类的操作时,它肯定很有用,其中上一个命令是 find 调用。 显然有充分的理由使用$() 而不是反引号。但可能没有什么可以失眠的。 github.com/koalaman/shellcheck/wiki/SC2006 @user2065875 尽管反引号在 Bash 中仍然有效,但它们已被弃用,取而代之的是 $() 重新计算($(!!))和执行(!!)有什么区别?【参考方案2】:

答案是否定的。 Bash 不会将任何输出分配给其内存上的任何参数或任何块。此外,您只能通过其允许的接口操作来访问 Bash。除非您破解 Bash 的私人数据,否则无法访问它。

【讨论】:

是否有可能在每个命令之前运行一些自定义 bash '中间件' 函数,它所做的只是将结果输出保存到充当内部剪贴板的东西(例如,BASH_CLIPBOARD)? @PatNeedham 不确定。检查可加载模块。看看你能不能写一个。【参考方案3】:

非常简单的解决方案

我已经用了很多年了。

脚本(添加到您的.bashrc.bash_profile

# capture the output of a command so it can be retrieved with ret
cap ()  tee /tmp/capture.out; 

# return the output of the most recent command that was captured by cap
ret ()  cat /tmp/capture.out; 

用法

$ find . -name 'filename' | cap
/path/to/filename

$ ret
/path/to/filename

我倾向于将| cap 添加到我所有命令的末尾。这样,当我发现我想对运行缓慢的命令的输出进行文本处理时,我总是可以使用 ret 检索它。

【讨论】:

单行函数定义错误。为避免 bash 错误,请使用 cap () tee /tmp/capture.out; ret () cat /tmp/capture.out; 您也可以使用 xclip:xci() xclip -i; xclip -o; ; xco() xclip -o; ; 不知道 xclip!谢谢【参考方案4】:

一种方法是使用trap DEBUG

f()  bash -c "$BASH_COMMAND" >& /tmp/out.log; 
trap 'f' DEBUG

现在最近执行的命令的 stdout 和 stderr 将在 /tmp/out.log 中可用

唯一的缺点是它会执行两次命令:一次将输出和错误重定向到/tmp/out.log,一次正常。可能还有一些方法可以防止这种行为。

【讨论】:

所以当我输入rm -rf * && cd ..时,不仅当前目录,而且父目录也会被删除??这种方法对我来说似乎非常危险 现在如何撤消此操作? @KartikChauhan:就做trap '' DEBUG【参考方案5】:

如果您使用的是 mac,并且不介意将输出存储在剪贴板而不是写入变量,则可以使用 pbcopy 和 pbpaste 作为解决方法。

例如,不要这样做来查找文件并将其内容与另一个文件进行比较:

$ find app -name 'one.php' 
/var/bar/app/one.php

$ diff /var/bar/app/one.php /var/bar/two.php

你可以这样做:

$ find app -name 'one.php' | pbcopy
$ diff $(pbpaste) /var/bar/two.php

当您运行第一个命令时,字符串/var/bar/app/one.php 在剪贴板中。

顺便说一下,pbcopypbpaste 中的 pb 代表粘贴板,剪贴板的同义词。

【讨论】:

在linux上$ find app -name 'one.php' | xclip $ diff $(xclip -o) /var/bar/two.php 有趣,我从没想过将pbpaste 与命令替换一起使用。 如果我做了第一个然后发现它效率低下怎么办,我如何节省计算时间而不用第二个选项重新运行?【参考方案6】:

受 anubhava 的回答启发,我认为这实际上是不可接受的,因为它运行每个命令两次。

save_output()  
   exec 1>&3 
    [ -f /tmp/current ] && mv /tmp/current /tmp/last; 
   exec > >(tee /tmp/current)


exec 3>&1
trap save_output DEBUG

这样最后一条命令的输出在/tmp/last中,并且该命令不会被调用两次。

【讨论】:

我发现的一个缺点是,您的所有命令都不再认为它们在 tty 中运行,这意味着您需要特别注意让颜色代码适用于 grep 或 @ 等实用程序987654323@+1 无论如何 这才是真正的答案【参考方案7】:

就像 konsolebox 所说,您必须侵入 bash 本身。 Here 是一个很好的例子,说明如何实现这一目标。 stderred 存储库(实际上是为标准输出着色)提供了有关如何构建它的说明。

我试了一下:在.bashrc 中定义一些新的文件描述符,比如

exec 41>/tmp/my_console_log

(数字是任意的)并相应地修改stderred.c,以便内容也被写入 fd 41。它有点有效,但包含大量 NUL 字节、奇怪的格式并且基本上是二进制数据,不可读。也许对 C 有很好理解的人可以尝试一下。

如果是这样,获取最后打印行所需的一切都是tail -n 1 [logfile]

【讨论】:

【参考方案8】:

是的,为什么每次都要输入额外的行;同意。 您可以通过管道将命令返回的内容重定向到输入,但是将打印输出重定向到输入 (1>&0) 是不行的,至少对于多行输出不是。 此外,您不希望在每个文件中一次又一次地编写一个函数。所以让我们试试别的吧。

一个简单的解决方法是使用 printf 函数将值存储在变量中。

printf -v myoutput "`cmd`"

比如

printf -v var "`echo ok;
  echo fine;
  echo thankyou`"
echo "$var" # don't forget the backquotes and quotes in either command.

另一种可定制的通用解决方案(我自己使用)只运行一次所需的命令,并在数组变量中逐行获取命令的多行打印输出。

如果您不将文件导出到任何地方并打算仅在本地使用它,您可以让终端设置函数声明。您必须在~/.bashrc 文件或~/.profile 文件中添加函数。在第二种情况下,您需要从Edit>Preferences>yourProfile>Command 启用Run command as login shell

做一个简单的函数,比如:

get_prev() # preferably pass the commands in quotes. Single commands might still work without.

    # option 1: create an executable with the command(s) and run it
    #echo $* > /tmp/exe
    #bash /tmp/exe > /tmp/out
    
    # option 2: if your command is single command (no-pipe, no semi-colons), still it may not run correct in some exceptions.
    #echo `"$*"` > /tmp/out
    
    # option 3: (I actually used below)
    eval "$*" > /tmp/out # or simply "$*" > /tmp/out
    
    # return the command(s) outputs line by line
    IFS=$(echo -en "\n\b")
    arr=()
    exec 3</tmp/out
    while read -u 3 -r line
    do
        arr+=($line)
        echo $line
    done
    exec 3<&-

所以我们在选项 1 中所做的是将整个命令打印到临时文件 /tmp/exe 并运行它并将输出保存到另一个文件 /tmp/out 然后逐行读取 /tmp/out 文件的内容-行到一个数组。 选项 2 和 3 类似,只是命令按原样执行,而不写入要运行的可执行文件。

在主脚本中:

#run your command:

cmd="echo hey ya; echo hey hi; printf `expr 10 + 10`'\n' ; printf $((10 + 20))'\n'"
get_prev $cmd

#or simply
get_prev "echo hey ya; echo hey hi; printf `expr 10 + 10`'\n' ; printf $((10 + 20))'\n'"

现在,bash 甚至将变量保存在先前的范围之外,因此在 get_prev 函数中创建的 arr 变量即使在主脚本中的函数之外也可以访问:

#get previous command outputs in arr
for((i=0; i<$#arr[@]; i++))
do
    echo $arr[i]
done
#if you're sure that your output won't have escape sequences you bother about, you may simply print the array
printf "$arr[*]\n"

编辑:

我在我的实现中使用以下代码:
get_prev()

    usage()
    
        echo "Usage: alphabet [ -h | --help ]
        [ -s | --sep SEP ]
        [ -v | --var VAR ] \"command\""
    
    
    ARGS=$(getopt -a -n alphabet -o hs:v: --long help,sep:,var: -- "$@")
    if [ $? -ne 0 ]; then usage; return 2; fi
    eval set -- $ARGS
    
    local var="arr"
    IFS=$(echo -en '\n\b')
    for arg in $*
    do
        case $arg in
            -h|--help)
                usage
                echo " -h, --help : opens this help"
                echo " -s, --sep  : specify the separator, newline by default"
                echo " -v, --var  : variable name to put result into, arr by default"
                echo "  command   : command to execute. Enclose in quotes if multiple lines or pipelines are used."
                shift
                return 0
                ;;
            -s|--sep)
                shift
                IFS=$(echo -en $1)
                shift
                ;;
            -v|--var)
                shift
                var=$1
                shift
                ;;
            -|--)
                shift
                ;;
            *)
                cmd=$option
                ;;
        esac
    done
    if [ $# -eq 0 ]; then usage; return 1; fi
    
    ERROR=$(  eval "$*" > /tmp/out;  2>&1 )
    if [ $ERROR ]; then echo $ERROR; return 1; fi
    
    local a=()
    exec 3</tmp/out
    while read -u 3 -r line
    do
        a+=($line)
    done
    exec 3<&-
    
    eval $var=\(\$a[@]\)
    print_arr $var # comment this to suppress output


print()

    eval echo \$$1[@]


print_arr()

    eval printf "%s\\\n" "\$$1[@]"

我一直使用它来打印多个/流水线/两个命令的空格分隔输出作为行分隔:

get_prev -s " " -v myarr "cmd1 | cmd2; cmd3 | cmd4"

例如:

get_prev -s ' ' -v myarr whereis python # or "whereis python"
# can also be achieved (in this case) by
whereis python | tr ' ' '\n'

现在tr 命令在其他地方也很有用,比如

echo $PATH | tr ':' '\n'

但是对于多个/管道命令......你现在知道了。 :)

-喜满树

【讨论】:

【参考方案9】:

您可以使用 -exec 在命令的输出上运行命令。因此,这将是一个输出的重用,例如下面的find 命令:

find . -name anything.out -exec rm  \;

你在这里说-> 在当前文件夹中找到一个名为anything.out 的文件,如果找到,将其删除。如果没有找到,-exec 之后的剩余部分会被跳过。

【讨论】:

【参考方案10】:

不确定您到底需要什么,所以这个答案可能不相关。您始终可以保存命令的输出:netstat &gt;&gt; output.txt,但我认为这不是您要查找的内容。

当然也有编程选项;你可以简单地让一个程序在运行该命令后读取上面的文本文件并将其与一个变量相关联,在我选择的语言 Ruby 中,你可以使用“反引号”从命令输出中创建一个变量:

output = `ls`                       #(this is a comment) create variable out of command

if output.include? "Downloads"      #if statement to see if command includes 'Downloads' folder
print "there appears to be a folder named downloads in this directory."
else
print "there is no directory called downloads in this file."
end

将其粘贴在 .rb 文件中并运行它:ruby file.rb,它将从命令中创建一个变量并允许您对其进行操作。

【讨论】:

“不确定你到底需要这个做什么” 好吧,说你(我的意思是 I)刚刚跑了很长时间——持续时间脚本,想要搜索输出,并且愚蠢地忘记在执行之前重定向输出。现在我想尝试在不重新运行脚本的情况下纠正我的愚蠢。【参考方案11】:

我有一个想法,我没有时间尝试立即实施。

但是,如果您执行以下操作会怎样:

$ MY_HISTORY_FILE = `get_temp_filename`
$ MY_HISTORY_FILE=$MY_HISTORY_FILE bash -i 2>&1 | tee $MY_HISTORY_FILE
$ some_command
$ cat $MY_HISTORY_FILE
$ # ^You'll want to filter that down in practice!

IO 缓冲可能存在问题。此外,文件可能会变得太大。人们必须想出解决这些问题的办法。

【讨论】:

【参考方案12】:

我认为使用脚本命令可能会有所帮助。类似的,

    脚本 -c bash -qf fifo_pid

    解析后使用bash特性设置。

【讨论】:

【参考方案13】:

如果您不想重新计算上一个命令,您可以创建一个宏来扫描当前终端缓冲区,尝试猜测最后一个命令的 -supposed- 输出,将其复制到剪贴板,最后将其键入到终端。

它可用于返回单行输出的简单命令(在 Ubuntu 18.04 上使用gnome-terminal 测试)。

安装以下工具:xdootoolxclipruby

gnome-terminal 中转到Preferences -&gt; Shortcuts -&gt; Select all 并将其设置为Ctrl+shift+a

创建以下 ruby​​ 脚本:

cat >$HOME/parse.rb <<EOF
#!/usr/bin/ruby
stdin = STDIN.read
d = stdin.split(/\n/)
e = d.reverse
f = e.drop_while  |item| item == "" 
g = f.drop_while  |item| item.start_with? "$USER@" 
h = g[0] 
print h
EOF

在键盘设置中添加以下键盘快捷键:

bash -c '/bin/sleep 0.3 ; xdotool key ctrl+shift+a ; xdotool key ctrl+shift+c ; ( (xclip -out | $HOME/parse.rb ) &gt; /tmp/clipboard ) ; (cat /tmp/clipboard | xclip -sel clip ) ; xdotool key ctrl+shift+v '

以上快捷方式:

将当前终端缓冲区复制到剪贴板 提取最后一条命令的输出(仅一行) 将其输入到当前终端中

【讨论】:

【参考方案14】:

仅用于非交互式命令的演示:http://asciinema.org/a/395092

为了还支持交互式命令,您必须从 util-linux 破解 script 二进制文件以忽略任何屏幕重绘控制台代码,并从 bashrc 运行它以将登录会话的输出保存到文件中。

【讨论】:

以上是关于在 Bash 中重用最后一个命令的输出的主要内容,如果未能解决你的问题,请参考以下文章

vscode运行c语言终端显示“终端将被任务重用,按任意键关闭。”无预想中的输出内容

如何重用函数的输出? [复制]

循环语句和函数

重用 QRunnable

将插入的输出值存储._ID 到局部变量以在另一个查询中重用它

如何在 Xamarin Forms 的可重用 ViewCell 中绑定命令?