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

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

我可以将输出赋值给一个变量:

output=$(command)

但那更多的是打字……

231704 次浏览

答案是否定的。Bash不向内存中的任何参数或块分配任何输出。此外,您只能通过允许的接口操作来访问Bash。Bash的私人数据是无法访问的,除非你黑进去。

你可以使用$(!!) 再计算(不重用)最后一个命令的输出

!!单独执行最后一个命令。

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

一种方法是使用trap DEBUG:

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

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

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

不确定你到底需要这个做什么,所以这个答案可能不相关。你总是可以保存命令的输出:netstat >> 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,它将从命令中创建一个变量并允许你操作它。

如果你在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代表纸板,是剪贴板的同义词。

就像konsolebox说的,你必须黑进bash本身。在这里是一个很好的例子,说明了如何实现这一点。stderred存储库(实际上是为stdout上色)给出了如何构建它的说明。

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

exec 41>/tmp/my_console_log

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

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

受到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中,并且该命令不会被调用两次。

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

但如果你做了下面这样的事情:

$ 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缓冲可能有问题。此外,文件可能会变得太大。人们必须想出解决这些问题的办法。

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

  1. 脚本-c bash -qf fifo_pid . sh

  2. 在解析后使用bash特性进行设置。

Very Simple Solution

.

我已经用了很多年了。

< h2 id = " script-add-to-your.bashrc-or。bash_profile-rlxx">脚本(添加到你的.bashrc.bash_profile中) . sh > . sh
# 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; }
使用< h2 id = " usage-bji3 " > < / h2 >
$ find . -name 'filename' | cap
/path/to/filename


$ ret
/path/to/filename

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

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

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

安装以下工具:xdootoolxclipruby

gnome-terminal中转到Preferences -> Shortcuts -> 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 ) > /tmp/clipboard ) ; (cat /tmp/clipboard | xclip -sel clip ) ; xdotool key ctrl+shift+v '

以上快捷方式:

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

是啊,为什么每次都多打几行;同意了。 您可以通过管道将命令返回的结果重定向到输入,但不能将打印输出重定向到输入(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.

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

如果您不将文件导出到任何地方,而只打算在本地使用它,则可以让Terminal设置函数声明。你必须在~/.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'

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

-Himanshu

可以使用-exec命令在命令的输出信息上执行命令。因此,下面的find命令将重用输出:

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

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

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

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