Why Mac Terminal remembers two consecutive commands when they are same?

I started using Mac recently and before I used to work on Ubuntu.

Suppose I run these commands one by one on my terminal:

python3 main.py
python3 main2.py
python3 main2.py
python3 main2.py
python3 main2.py
python3 main2.py
python3 main2.py

Now suppose I want to run python main.py again, so I will click the up key. I will need to click up key only twice on Ubuntu but 7 times on Mac.

If two consecutive commands are same, then terminal should remember only one command instead of remembering two different commands.

How can I make this happen on macOS?


Solution 1:

You need to add the HISTCONTROL environment variable to your .bash_profile. In your .bash_profile add the following line:

export HISTCONTROL=ignoreboth:erasedups

Close and restart your bash session and the dupes should be gone. Alternatively, you could just execute that same line and it will take effect for that session (use it to test it out).


From the manual page for bash(in Terminal):

HISTCONTROL
A colon-separated list of values controlling how commands are saved on the history list. If the list of values includes ignorespace, lines which begin with a space character are not saved in the history list. A value of ignoredups causes lines matching the previous history entry to not be saved. A value of ignoreboth is shorthand for ignorespace and ignoredups. A value of erasedups causes all previous lines matching the current line to be removed from the history list before that line is saved. Any value not in the above list is ignored. If HISTCONTROL is unset, or does not include a valid value, all lines read by the shell parser are saved on the history list, subject to the value of HISTIGNORE. The second and subsequent lines of a multi-line compound command are not tested, and are added to the history regardless of the value of HISTCONTROL.

Solution 2:

Why does this happen?

MacOS and Ubuntu are configured differently out of the box to handle duplicates in bash's command history. These configurations are stored in a number of so-called "dot-files". These take the form of ~/.bash* as well the system wide /etc/profile. All of these files might be customised to your liking and differentiate between interactive shells, login shells, remote shells etc. These files are read in a specific order and serve specific functions.

How to get the same behaviour on macOS?

If you want just this one, single customisation of "ignoring exact duplicates of command lines" you may go with something like Allan's answer, i.e. add a single single line to for example your bash_profile file. There is no "the proper way" but countless options.

In case this is not the only customisation for your bash then this might be not the best option:

  • ~/.bash_profile should be super-simple and just load .profile and .bashrc (in that order)

  • ~/.profile has the stuff NOT specifically related to bash, such as environment variables (PATH and friends)

  • ~/.bashrc has anything you'd want at an interactive command line. Command prompt, EDITOR variable, bash aliases for my use

A few other notes:

  • Anything that should be available to graphical applications OR to sh (or bash invoked as sh) MUST be in ~/.profile
  • ~/.bashrc must not output anything
  • Anything that should be available only to login shells should go in ~/.profile
  • Ensure that ~/.bash_login does not exist.

That means, when things get more complex it is good idea to spread out the customisations into multiple files, each of them specialised and highly ordered in their contents:

All exports may reside in their own file for simplified oversight.

Create a file that is read by bash at the root of your user directory, for example called .exports that contains:

# Omit duplicates and commands that begin with a space from history.
export HISTCONTROL='ignoreboth'; 

This needs to be "sourced" so that the file is read by bash on interactive startup:

Sourcing files
If you have a lot of shell configurations, you may want to split them out into several subfiles and use the source builtin to load them from your .bashrc: with adding source ~/.exports to it.

Alternatively, to ensure the files actually exist before loading

if [ -f ~/.exports ]; then
. ~/.exports
fi

The command . ~/.exports will source ~/.exports in the context of the currently running shell.

This is particularly useful for adding aliases, the separate file makes it easier to re-load them when you make changes.

Solution 3:

To uniqely record every new command is tricky. First you need to add to ~/.profile or similar:

HISTCONTROL=erasedups
PROMPT_COMMAND='history -w'

Then you need to add to ~/.bash_logout:

history -a
history -w