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

时间:2014-06-18 10:22:10

标签: bash terminal stdout

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

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

output=$(command)

但这更像打字......

14 个答案:

答案 0 :(得分:134)

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

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

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

答案 1 :(得分:75)

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

答案 2 :(得分:8)

这样做的一种方法是使用trap DEBUG

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

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

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

答案 3 :(得分:4)

如果您使用的是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 代表剪贴板的同义词 - 粘贴板。

答案 4 :(得分:3)

灵感来自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

这样,last命令的输出在/ tmp / last中,命令不会被调用两次。

答案 5 :(得分:2)

是的,为什么每次都要输入多余的行?同意 您可以将命令返回的内容通过管道重定向到输入,但是可以将打印的输出重定向到输入(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文件中添加该功能。在第二种情况下,您需要从Run command as login shell中启用Edit>Preferences>yourProfile>Command

做一个简单的功能,说:

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中,然后读取{{1}的内容}文件逐行移至数组。 与选项2和3相似,不同之处在于命令是照这样执行的,而无需写入要运行的可执行文件。

在主脚本中:

/tmp/out

现在,bash甚至将变量保存在先前的作用域之外,因此即使在主脚本中的函数之外,也可以访问在#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'" 函数中创建的arr变量:

get_prev
#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

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

- Himanshu

答案 6 :(得分:1)

就像konsolebox所说,你必须自己攻击bash。 Here是一个很好的例子,说明如何实现这一目标。 stderred存储库(实际上用于着色stdout)提供了有关如何构建它的说明。

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

exec 41>/tmp/my_console_log

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

如果是这样,获得最后一行的所有内容都是tail -n 1 [logfile]

答案 7 :(得分:0)

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

当然有编程选项;在运行该命令后,你可以简单地得到一个程序来读取上面的文本文件,并将它与变量相关联,而在我选择的语言Ruby中,你可以使用&#39;反引号&#39创建一个变量命令输出。 ;:

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它将在命令之外创建一个变量并允许您对其进行操作。

答案 8 :(得分:0)

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

但是,如果您执行以下操作怎么办:

$ 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缓冲可能存在问题。文件也可能变得太大。必须解决这些问题。

答案 9 :(得分:0)

我认为使用脚本命令可能会有所帮助。像

  1. 脚本-c bash -qf fifo_pid

  2. 使用bash功能在解析后进行设置。

答案 10 :(得分:0)

因为我发现记得将命令的输出通过管道传输到特定文件中有点烦人,所以我的解决方案是.bash_profile中的一个函数,它将函数捕获到文件中并在需要时返回结果它。

足够简单:

脚本

catch () { cat - | tee /tmp/catch.out}
res () { cat /tmp/catch.out }

用法

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

$ res
/path/to/filename

在这一点上,我倾向于将| catch添加到所有命令的末尾,因为这样做没有实际成本,并且省去了重新运行需要很长时间才能完成的命令的麻烦。

答案 11 :(得分:0)

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

它可用于返回单行输出的简单命令(在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 '

上面的快捷方式:

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

答案 12 :(得分:0)

您可以使用-exec在命令的输出上运行命令。因此,它将作为下面的find命令给出的示例的输出重用:

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

您在这里说->在当前文件夹中找到一个名为any.out的文件,如果找到,请将其删除。如果找不到,将跳过-exec之后的其余部分。

答案 13 :(得分:0)

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

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