Ubuntu – multiline command chunks in bash history into multiple lines

bashbash-historyhistory

In Bash (as well as Bash in WSL), multiline written commands can call-back with up/down arrows correctly also built-in history command report them correctly (until I'm still logged-in in bash).

But after exiting from bash and press up/down arrows, multiline commands appears each as separate entries and that's because cat ~/.bash_history has chucked the command on multiple separate entries and writing problem here; the history command result messed too.

I have set these options in ~/.bashrc

shopt -s cmdhist lithist
HISTTIMEFORMAT='%F %T '

and now writing problem fixed but history command still report in multiple line and separate entries (reading problem now).

Bash version is 4.3, same issue on v4.4

there is this bug report since a long years but no solution yet.

Update:

it's also to mention that below multiline command as an example is correctly displaying even after exit Bash:

for i in {1..10}
do
echo $i
done

but this is failing:

awk '
{ print $1}' <<<'first last'

Update 2:

the issue mentioned above is fixed in Bash v5.0.0+ that changed to recognize entries between "timestamp as delimiter" as single entry of command (HISTTIMEFORMAT, cmdhist and lithist must be set).

Best Answer

I am using GNU bash, version 5.0.3(1)-release on Ubuntu 19.10. I checked and confirmed the issue described in your question exists.

I then solved it using the following steps. After that I exited bash multiple times and confirmed the problem is solved on my system. Multi-line commands are saved to the ~/.bash_history file, retrieved and executed correctly.


Solution:

Please edit your ~/.bashrc file or create it if it does not exist.

Copy and paste the following three lines into it and save it:

shopt -s cmdhist
shopt -s lithist
HISTTIMEFORMAT='%F %T '

From bash man-pages:

cmdhist:

If set, bash attempts to save all lines of a multiple-line command in the same history entry. This allows easy re-editing of multi-line commands.

lithist:

If set, and the cmdhist option is enabled, multi-line commands are saved to the history with embedded newlines rather than using semicolon separators where possible.

However, cmdhist and lithist are useful while the shell is still open and working from memory. When you exit the shell, the commands are saved into the ~/.bash_history file as single lines without any indication as to where a multi-line command starts or ends and then the working memory is cleared. When you run the shell again these lines in the bash history file (including the multi-line commands) will be read one line at a time and each line will be treated and loaded to working memory as a single command even though it could be a part of a multi-line command because the ~/.bash_history file will look like this:

nano .bashrc 
exit
history 
for i in {1..10}
do
echo $i
done
cat .bash_history 
exit
history 
awk '
{ print $1}' <<<'first last'
history
exit

Hence the need for HISTTIMEFORMAT which will act as a delimiter to separate each command from an other based on its timestamp, be it a single line or a multi-line command. So the ~/.bash_history file will look like this:

nano .bashrc 
#1581635697
exit
#1581635709
history 
#1581635729
for i in {1..10}
do
echo $i
done
#1581635743
cat .bash_history 
#1581635757
exit
#1581635773
history 
#1581635794
awk '
{ print $1}' <<<'first last'
#1581635801
history
#1581635808
exit

From bash man-pages:

HISTTIMEFORMAT:

If this variable is set and not null, its value is used as a format string for strftime(3) to print the time stamp associated with each history entry displayed by the history builtin. If this variable is set, time stamps are written to the history file so they may be preserved across shell sessions. This uses the history comment character to distinguish timestamps from other history lines.

From now on multi-line commands will be retrieved, treated and loaded into working memory as a whole (without breaking them into single lines and treating each line as a separate command) and the history command output will look like this (even after restarting bash):

raffa@Laptop:~$ history
   1  2020-02-14 05:25:19 nano .bashrc 
   2  2020-02-14 05:25:38 exit
   3  2020-02-14 05:25:50 history
   4  2020-02-14 05:27:50 for i in {1..10}
do
echo $i
done
   5  2020-02-14 05:28:05 cat .bash_history
   6  2020-02-14 05:28:14 exit
   7  2020-02-14 05:28:27 history
   8  2020-02-14 05:28:55 awk '
{ print $1}' <<<'first last'
   9  2020-02-14 05:29:06 history
  10  2020-02-14 05:29:16 exit
  11  2020-02-14 05:29:27 history

And multi-line commands will be correctly retrieved, displayed and executed after exit.


Notice:

  • You will need to restart your current session of bash after editing and saving the ~/.bashrc file to activate the changes.
  • If your current ~/.bash_history file or bash working memory already contain history lines with formatting errors, you might need to delete (Please back up first) your current bash history by running history -w; history -c in the terminal which will permanently delete all your current bash history.

Best of luck