Reusing output from last command in Bash

后端 未结 13 2051
长情又很酷
长情又很酷 2020-12-07 08:35

Is the output of a Bash command stored in any register? E.g. something similar to $? capturing the output instead of the exit status.

I could assign the

相关标签:
13条回答
  • 2020-12-07 08:37

    The answer is no. Bash doesn't allocate any output to any parameter or any block on its memory. Also, you are only allowed to access Bash by its allowed interface operations. Bash's private data is not accessible unless you hack it.

    0 讨论(0)
  • 2020-12-07 08:37

    One way of doing that is by using trap DEBUG:

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

    Now most recently executed command's stdout and stderr will be available in /tmp/out.log

    Only downside is that it will execute a command twice: once to redirect output and error to /tmp/out.log and once normally. Probably there is some way to prevent this behavior as well.

    0 讨论(0)
  • 2020-12-07 08:40

    If you don't want to recompute the previous command you can create a macro that scans the current terminal buffer, tries to guess the -supposed- output of the last command, copies it to the clipboard and finally types it to the terminal.

    It can be used for simple commands that return a single line of output (tested on Ubuntu 18.04 with gnome-terminal).

    Install the following tools: xdootool, xclip , ruby

    In gnome-terminal go to Preferences -> Shortcuts -> Select all and set it to Ctrl+shift+a.

    Create the following ruby script:

    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
    

    In the keyboard settings add the following keyboard shortcut:

    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 '

    The above shortcut:

    • copies the current terminal buffer to the clipboard
    • extracts the output of the last command (only one line)
    • types it into the current terminal
    0 讨论(0)
  • 2020-12-07 08:40

    You can use -exec to run a command on the output of a command. So it will be a reuse of the output as an example given with a find command below:

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

    you are saying here -> find a file called anything.out in the current folder, if found, remove it. If it is not found, the remaining after -exec will be skipped.

    0 讨论(0)
  • 2020-12-07 08:44

    Yeah, why type extra lines each time; agreed. You can redirect the returned from a command to input by pipeline, but redirecting printed output to input (1>&0) is nope, at least not for multiple line outputs. Also you won't want to write a function again and again in each file for the same. So let's try something else.

    A general solution for running the desired command only once and getting multi-line printed output of the command in an array variable line-by-line.

    If you are not exporting the files anywhere and intend to use it locally only, you can have Terminal set-up the function declaration. You have to add the function in ~/.bashrc file or in ~/.profile file. In second case, you need to enable Run command as login shell from Edit>Preferences>yourProfile>Command.

    Make a simple function, say:

    get_prev()
    {
        # 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
        
        # 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<&-
    }
    

    So what we did was print the whole command to a temporary file /tmp/exe and run it and save the output to another file /tmp/out and then read the contents of the /tmp/out file line-by-line to an array.

    In main script:

    #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'"
    

    Now, bash saves the variable even outside previous scope, so the arr variable created in get_prev function is accessible even outside the function in the main script:

    #get previous command outputs in arr
    for((i=0; i<${#arr[@]}; i++)); do echo ${arr[i]}; done
    

    Edit:
    I'm using the following code in my implementation:

    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
        
        echo $* > /tmp/exe
        ERROR=$( { bash /tmp/exe > /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<&-
        print_arr $var
        eval $var=\(\${a[@]}\)
    }
    
    print()
    {
        eval echo \${$1[@]}
    }
    
    print_arr()
    {
        eval local arr=\(\${$1[@]}\)
        for x in ${arr[@]}
        do
            echo $x
        done
    }
    

    Ive been using this to print space-separated outputs of multiple/pipelined/both commands as line separated:

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

    For example:

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

    Now tr command is useful at other places as well, such as

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

    But for multiple/piped commands... you know now. :)

    -Himanshu

    0 讨论(0)
  • 2020-12-07 08:44

    I have an idea that I don't have time to try to implement immediately.

    But what if you do something like the following:

    $ 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!
    

    There might be issues with IO buffering. Also the file might get too huge. One would have to come up with a solution to these problems.

    0 讨论(0)
提交回复
热议问题