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

mem*_*ecs 147 bash terminal stdout

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

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

output=$(command)
Run Code Online (Sandbox Code Playgroud)

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

lin*_*ing 154

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

在!!自身执行的最后一个命令.

$ echo pierre
pierre
$ echo my name is $(!!)
echo my name is $(echo pierre)
my name is pierre
Run Code Online (Sandbox Code Playgroud)

  • 你可以通过使用反引号来输入`$(your_cmd)来保存自己:`\`your_cmd \``根据Gnu Bash手册,它们在功能上完全相同.当然这也受到@memecs提出的警告的影响 (7认同)
  • 显然有充分的理由使用“$()”而不是反引号。但也许没有什么可以让我失眠的。https://github.com/koalaman/shellcheck/wiki/SC2006 (2认同)
  • @user2065875 尽管反引号在 Bash 中仍然有效,但它们已被弃用,取而代之的是 `$()`。 (2认同)

kon*_*box 80

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

  • 是否可以有一些自定义的bash“中间件”功能在每个命令之前运行,它所做的只是将结果输出保存到充当内部剪贴板的东西(例如BASH_CLIPBOARD)上? (2认同)

Con*_*nor 38

非常简单的解决方案

一个我用了很多年的。

脚本(添加到您的.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; }
Run Code Online (Sandbox Code Playgroud)

用法

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

$ ret
/path/to/filename
Run Code Online (Sandbox Code Playgroud)

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

  • oneline 函数定义是错误的。为了避免 bash 错误,请使用 `cap () { tee /tmp/capture.out; }` 和 `ret () { cat /tmp/capture.out; }` (2认同)

anu*_*ava 11

一种方法是使用trap DEBUG:

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

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

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

  • 所以当我输入`rm -rf*&& cd ..`时,不仅当前目录,而且父目录也将被删除?这种做法对我来说似乎非常危险 (8认同)
  • @KartikChauhan:要做个陷阱调试 (4认同)
  • 现在我该如何撤消此操作? (2认同)

Rag*_*dda 6

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

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

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

$ diff /var/bar/app/one.php /var/bar/two.php
Run Code Online (Sandbox Code Playgroud)

您可以这样做:

$ find app -name 'one.php' | pbcopy
$ diff $(pbpaste) /var/bar/two.php
Run Code Online (Sandbox Code Playgroud)

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

顺便说一句,pb进入pbcopy并pbpaste代表粘贴板(剪贴板的同义词)。

  • 在 Linux 上 `$ find app -name 'one.php' | xclip $ diff $(xclip -o) /var/bar/two.php` (2认同)

oli*_*der 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
Run Code Online (Sandbox Code Playgroud)

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

  • 我发现的一个缺点是,你的命令都认为它们不再在 tty 中运行,这意味着你需要特别注意让颜色代码适用于诸如 `grep` 或 `git diff` +1 之类的实用程序 (2认同)

Him*_*war 6

是啊,为什么每次都要输入额外的行?同意。您可以将命令返回的结果重定向到管道输入,但是将打印输出重定向到输入(1>&0)是不行的,至少对于多行输出来说不行。此外,您也不想在每个文件中一次又一次地编写相同的函数。那么让我们尝试一些其他的东西。

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

printf -v myoutput "`cmd`"
Run Code Online (Sandbox Code Playgroud)

例如

printf -v var "`echo ok;
  echo fine;
  echo thankyou`"
echo "$var" # don't forget the backquotes and quotes in either command.
Run Code Online (Sandbox Code Playgroud)

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

如果您不将文件导出到任何地方并且打算仅在本地使用它,则可以让终端设置函数声明。您必须在~/.bashrc文件或~/.profile文件中添加该功能。在第二种情况下,您需要启用Run command as login shellfrom 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<&-
}
Run Code Online (Sandbox Code Playgroud)

因此,我们在选项 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'"
Run Code Online (Sandbox Code Playgroud)

现在,bash 即使在先前的作用域之外也会保存变量,因此即使在主脚本中的函数之外也可以访问在函数中arr创建的变量:get_prev

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

编辑:

我在实现中使用以下代码:
#if you're sure that your output won't have escape sequences you bother about, you may simply print the array
printf "${arr[*]}\n"
Run Code Online (Sandbox Code Playgroud)

我一直在使用它来打印多个/管道/两个命令的空格分隔输出,以行分隔:

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[@]}"
}
Run Code Online (Sandbox Code Playgroud)

例如:

get_prev -s " " -v myarr "cmd1 | cmd2; cmd3 | cmd4"
Run Code Online (Sandbox Code Playgroud)

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

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

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

-希曼舒