6

In the bash reference manual it states:

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

with this hence my question: where/when/how is this possible?

I tried to enable and test the feature on my GNU bash, version 4.4.12(1)-release doing this:

shopts -s cmdhist
shopts -s lithist
echo -n "this is a test for a ";\
echo "multiline command"

I then did a

history | tail 

expecting some ouput akin to this :

101 shopts -s cmdlist
102 shopts -s lithist
103 echo -n "this is a test for a ";\
    echo "multiline command"
104 history

yet instead get this:

101 shopts -s cmdlist
102 shopts -s lithist
103 echo -n "this is a test for a "; echo "multiline command"
104 history

As is obvious the multiline command (the one with bash history number 103) has not been stored with "embedded newlines rathar than using semicolon separators". Why was lithist not possible here? What did I do wrong?

9

A \<new line> is not the correct way to get a <new line> in the history.

Memory

Lets deal only with history lines as they are kept in shell memory (not disk).

Lets type a couple of commands as you did:

$ echo -n "this is a test for a ";\
> echo "two line command"

What was stored in memory as the line just written?

$ history 2
514  echo -n "this is a test for a ";echo "two line command"
515  history 2

As you can see, the "line continuation", a backslash followed by a newline, was removed.
As it should (from man bash):

If a \ pair appears, and the backslash is not itself quoted, the \ is treated as a line continuation (that is, it is removed from the input stream and effectively ignored).

We may get a newline if we quote it:

$ echo " A test of 
> a new line"
A test of 
a new line

And, at this point, the history will reflect that:

$ history 2
  518  echo "A test of
a new line"
  519  history 2

A true multi-line command:

One possible example of a multi-line command is:

$ for a in one two
> do echo "test $a"
> done
test one
test two

Which will be collapsed into one history line if cmdhist is set:

$ shopt -p cmdhist lithist
shopt -s cmdhist                                                                                                       
shopt -u lithist

$ history 3
   24  for a in one two; do echo "test $a"; done                                                                       
   25  shopt -p cmdhist lithist                                                                                        
   26  history 3     

The numbers for each command changed because at some point I cleared the history (in memory) with a history -c.

If you unset the cmdhist, you will get this:

$ shopt -u cmdhist
$ for a in one two
> do echo "test $a"
> done
test one
test two

$ history 5
    5  shopt -u cmdhist
    6  for a in one two
    7  do echo "test $a"
    8  done
    9  history 5

Each line (not a full command) will be at a separate line in the history.

Even if the lithist is set:

$ shopt -s lithist
$ for a in one two
> do echo "test $a"
> done
test one
test two
$ history 5
   12  shopt -s lithist
   13  for a in one two
   14  do echo "test $a"
   15  done
   16  history 5

But if both are set:

$ shopt -s cmdhist lithist
$ for a in one two
> do echo "test $a"
> done

$ history 5
   23  history 15
   24  shopt -p cmdhist lithist
   25  shopt -s cmdhist lithist
   26  for a in one two
do echo "test $a"
done
   27  history 5

The for command was stored as a multiline command with "newlines" instead of semicolons (;). Compare with above where lithist wasn't set.

Disk

All the above was explained using the list of commands kept in the memory of the shell. No commands were written to the disk. The (default) file ~/.bash_history was not changed.

That file will be changed when the running shell exits. At that point in time the history will overwrite the file (if histappend isn't set), or will be appended otherwise.

If you want the commands to be committed to disk you need to have this set:

export PROMPT_COMMAND='history -a'

That will make each command line to be appended to file on each new command line.

Now, lets get down to business with cmdhist and lithist. It is not so simple as it may seem. But don't worry, all will be clear in a moment.

Let's say that you take the time to type all the commands below (there is no shortcut, no alias, no function, you need the actual commands, sorry).
To first clear all history in memory (history -c) and in disk (make a backup) (history -w) and then to try three times:

  • With the default values of cmdhist (set) and lithist (unset).
  • With both set
  • With both un-set
  • Setting lithist with an unset cmdhist makes no sense (you can test it).

List of commands to execute:

$ history -c ; history -w           # Clear all history ( Please backup).

$ shopt -s cmdhist; shopt -u lithist
$ for a in one two
> do echo "test $a"
> done

$ shopt -s cmdhist; shopt -s lithist
$ for a in one two
> do echo "test $a"
> done

$ shopt -u cmdhist; shopt -u lithist
$ for a in one two
> do echo "test $a"
> done

You will end with this (in memory):

$ history
    1  shopt -s cmdhist; shopt -u lithist
    2  for a in one two; do echo "test $a"; done
    3  shopt -s cmdhist; shopt -s lithist
    4  for a in one two
do echo "test $a"
done
    5  shopt -u cmdhist; shopt -u lithist
    6  for a in one two
    7  do echo "test $a"
    8  done
    9  history

The three multiline commands end as follows:

  • one in line numbered 2 (one single line, one command).
  • one in a multiline numbered 4 (one command in several lines)
  • one in several lines numbered from 6 to 8

Ok, but what happen in file? say it alredy ....

finally, in file:

Simple, write to file and cat it to see this:

$ history -w ; cat "$HISTFILE"
shopt -s cmdhist; shopt -u lithist
for a in one two; do echo "test $a"; done
shopt -s cmdhist; shopt -s lithist
for a in one two
do echo "test $a"
done
shopt -u cmdhist; shopt -u lithist
for a in one two
do echo "test $a"
done
history
history -w ; cat "$HISTFILE"

No line numbers, only commands, there is no way to tell where a multiline starts and where it ends. There is no way to tell even if there is a multiline.

In fact, that is exactly what happens, if the commands are written to file as above, when the file is read back, any information about multilines gets lost.

There is only one delimiter (the newline), each lines is read back as one command.

Is there a solution to this, yes, to use an additional delimter.
The HISTTIMEFORMAT kind of does that.

HISTTIMEFORMAT

When this variable is set to some value, the time at which each command was executed gets stored in file as the seconds since epoch (yes, always seconds) after a comment (#) character.

If we set the variable and re-write the ~/.bash_history file, we get this:

$ HISTTIMEFORMAT='%F'
$ history -w ; cat "$HISTFILE"
#1490321397
shopt -s cmdhist; shopt -u lithist
#1490321397
for a in one two; do echo "test $a"; done
#1490321406
shopt -s cmdhist; shopt -s lithist
#1490321406
for a in one two
do echo "test $a"
done
#1490321418
shopt -u cmdhist; shopt -u lithist
#1490321418
for a in one two
#1490321418
do echo "test $a"
#1490321420
done
#1490321429
history
#1490321439
history -w ; cat "$HISTFILE"
#1490321530
HISTTIMEFORMAT='%FT%T '
#1490321571
history -w ; cat "$HISTFILE"

Now you can tell where and which line is a multiline.

The format '%FT%T ' shows the time but only when using the history command:

$ history
    1  2017-03-23T22:09:57 shopt -s cmdhist; shopt -u lithist
    2  2017-03-23T22:09:57 for a in one two; do echo "test $a"; done
    3  2017-03-23T22:10:06 shopt -s cmdhist; shopt -s lithist
    4  2017-03-23T22:10:06 for a in one two
do echo "test $a"
done
    5  2017-03-23T22:10:18 shopt -u cmdhist; shopt -u lithist
    6  2017-03-23T22:10:18 for a in one two
    7  2017-03-23T22:10:18 do echo "test $a"
    8  2017-03-23T22:10:20 done
    9  2017-03-23T22:10:29 history
   10  2017-03-23T22:10:39 history -w ; cat "$HISTFILE"
   11  2017-03-23T22:12:10 HISTTIMEFORMAT='%F'
   12  2017-03-23T22:12:51 history -w ; cat "$HISTFILE"
   13  2017-03-23T22:15:30 history
   14  2017-03-23T22:16:29 HISTTIMEFORMAT='%FT%T'
   15  2017-03-23T22:16:31 history
   16  2017-03-23T22:16:35 HISTTIMEFORMAT='%FT%T '
   17  2017-03-23T22:16:37 history
  • this answer explains the concepts nicely, I appreciate the separate paragraphs for memory and disk. I now see, as also by the other answer, that in parts my question was provoked by my lacking concept of what is a "multi-line command" and, for which you provided even a reference/quote. I wanted to use this comment to add to the "disk" part, that once written to the .bash_history file (even with both shopts -s lithist and shopts -s cmdlist) the command will repear as single lines when reloaded. The listhist hence does not seem to conserve in bash_history, at least that was what I saw – humanityANDpeace Mar 23 '17 at 19:30
  • @humanityANDpeace Ok, completed the part about disk. Sorry but writing a detailed and correct answer just takes time (and effort). Now (I think) it is complete. – sorontar Mar 24 '17 at 2:22
  • thank you very much. I am sorry to might have caused you effort, that was not my intention. Your answer is really comprising and good and helped my understanding a lot. great work. – humanityANDpeace Mar 24 '17 at 9:16
  • Unfortunately almost all my rep is on SO instead of here, because this is the first time I’ve seen an answer so good and with so few upvotes that I wanted to use the bounty system to reward an existing answer. – Daniel H Mar 20 at 21:08
3

That doesn't appear to be a multiline commmand (because you escaped the return). If you do one with real returns there is a difference.

$ for i in /tmp/g*
> do echo $i
> done
/tmp/gigabyte
$ shopt -s lithist
$ for i in /tmp/g*
> do echo $i
> done
/tmp/gigabyte
$ history 
[...]
  517  for i in /tmp/g* ; do echo $i ; done
  518  shopt -s lithist
  519  for i in /tmp/g*
do echo $i
done
  520  history
  • Yes, I was about to add to my question, the doubt about what constitues a multi-line command, yet you already resolved the question. – humanityANDpeace Mar 23 '17 at 18:19
0

iTerm handles multiple-line command perfectly, it saves multiple-lines command as one command, then we can use Cmd+Shift+; to navigate the history. Check more iTerm tips at Working effectively with iTerm

0

While continuation lines (the \ at the end of line) seem to be stripped no matter what. I've realized there are two alternative ways to do continuation:

  1. Backquotes. Command substitution. I rather prefer this to latter

    $ echo "1st line";`
    > `echo "2nd line"
    1st line
    2nd line
    
    $ history 2
      507  echo "1st line";`
    `echo "2nd line"
      508  history 2
    
  2. Dollar signs. Still abusing command substitution.

    $ echo "1st line";$(
    > ) echo "2nd line"
    1st line
    2nd line
    
    $ history 2
      501  echo "1st line";$(
    ) echo "2nd line"
      502  history 2
    

Yeah. Not very charming. Though if I think about it, when did we think that slash at EOL was pretty?

Initially was thinking of including :, but as it appears, the substitution works without it, so I'll go with that.

The neat thing is, it even works with shopt -u lithist. Though starting a new bash session, which loads history from ~/.bash_history, will cleave them.

0

I tried shopt -s lithist cmdhist.

But after logout and login, history recover from ~/.bash_history was still split into multi line:

 1953  2019-05-23 16:57:53 shopt -s cmdhist; shopt -s lithist
 1954  2019-05-23 16:57:58 for i in 1 2 3
 1955  2019-05-23 16:58:34 do echo $i
 1956  2019-05-23 16:58:34 done
 1957  2019-05-23 16:58:08 history 5
 1958  2019-05-23 16:58:27 history -a
 1959  2019-05-23 16:54:12 history 10 | less 

Is this behavior expected?

Following is my content of ~/.bash_history:

#1558601873
shopt -s cmdhist; shopt -s lithist
#1558601878
for i in 1 2 3
do echo $i
done
#1558601888
history 5
#1558601907
history -a
#1558601652
history 10 | less 

solution

i solve this problem by removing old ~/.bash_history , then multiline history work fine; even after logout and login.

    1  2019-06-12 13:53:11 \rm .bash_history
    2  2019-06-12 13:53:27 fx mcd
    3  2019-06-12 13:53:28 mcd () 
{ 
    mkdir $@;
    cd $1
}
    4  2019-06-12 14:55:14 nmcli-restart-hotspot -s
    5  2019-06-12 21:34:35 ssh lab
    6  2019-06-12 23:38:45 history  | less 

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.