How to save terminal history to a file from a bash file?

Solution 1:

Short Answer

Run the script with source or .:

source ./script_name.sh

or

. ./script_name.sh

The latter is slightly more compatible across different shells.

Long Answer

This question highlights an important point, which is that shell scripts are run in their own context. To see what this means, consider the following shell script:

#!/bin/bash

cd /
ls

If you run this, you will get an output something like this:

bin  boot  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

But you will notice that after you run the script, you are still in whatever directory you were in before you ran it: the cd / inside the script hasn't actually affected your session - it only affected the context which the script was running in, which is created for the script to run in, and destroyed once it returns.

The source command will 'read and execute commands from the filename argument in the current shell context', so any commands like cd inside it will affect your current session. If you were to run the script above by passing it to source you would find that you end up inside the root directory after it ran.

In this case, the problem is that the history command gives you the history for the current shell context; the shell context that your script runs in without using source has no history, so it doesn't write anything to the output file. If you use source it will execute in the correct context, and will work as expected.

N.B.: source is a shell built-in, not a program per se - in Bash, source is synonymous with ., but in some shells only . will work - I've used source in this answer because it's easier to read than ., but for maximum compatibility, . should be used.

Solution 2:

First of all, note that your history is already in a file. If you're running bash, its name is usually ~/.bash_history. More specifically, it is whatever you have set the variable HISTFILE to. If you want to copy it to another file, just run cat "$HISTFILE" > hist.txt

Now, as to why the history command doesn't work in a bash shell script, that's because scripts are run in a non-interactive child shell of your current shell session. Child shells don't inherit all of the parent's environment (so not all of the set variables), only the variables that have been exported. To illustrate, the script below will echo the value of the variable $var:

#!/bin/bash
echo "$var"

Now, set $var to something and run the script:

$ var="foo"
$ foo.sh
VAR: 

Next, export the variable first:

$ var="foo"
$ export var
$ foo.sh
VAR: foo

As you can see, when the variable has been exported, it is available to child shells.

As I mentioned before, the history is stored in the file pointed to by the variable $HISTFILENAME. Because that isn't exported by default, it is not set when running a script:

$ cat foo.sh
#!/bin/bash
echo "HISTFILE: $HISTFILE"
$ ./foo.sh
HISTFILE:
$ echo $HISTFILE
/home/terdon/.bash_history

As you can see in the example above, the variable HISTFILE is set in my normal shell session, but is empty when running the script.

So, to get the history, you have a few options:

  1. The the default HISTFILE value is $HOME/.bash_history. If you haven't changed that, you can simply run this command in your script:

    cat "$HOME/.bash_history" > history
    
  2. You can pass the $HISTFILE variable to your script and cat that:

    #!/bin/bash
    cat "$1" > history
    

    Save the above as foo.sh and run like this:

    ./foo.sh "$HISTORY"
    
  3. Make sure the variable is exported. Add this line to your ~/.bash_profile (if it exists) or ~/.profile (if ~/.bash_profile doesn't exist) files:

    export HISTFILE
    

    Then, log out and log back in again and you should be able to run history > hist.txt from a script as expected. This is because export VAR means "make $VAR available to child shells". In practical terms, this means that the value of HISTFILE will be inherited by the non-interactive shell you use to run your script.

    Now, while the HISTFILE will be set, it hasn't been read by the shell running the script. So, to get it to work, you'd need to read it with history -r first. The whole script would look like this:

    $!/bin/bash
    history -r
    history > hist.txt
    

    Alternatively, just export it manually before running the script:

    $ export HISTFILE
    

    But you'll still need to history -r in the script.

  4. You can source it as suggested by @p0llard's answer.