15

I am trying to set PS1 so that it prints out something just right after login, but preceded with a newline later.

Suppose export PS1="\h:\W \u\$ ", so first time (i.e., right after login) you get:

hostname:~ username$ 

I’ve been trying something like in my ~/.bashrc:

function __ps1_newline_login {
  if [[ -n "${PS1_NEWLINE_LOGIN-}" ]]; then
    PS1_NEWLINE_LOGIN=true
  else
    printf '\n'
  fi
}

export PS1="\$(__ps1_newline_login)\h:\W \u\$ “

expecting to get:

# <empty line>
hostname:~ username$ 

A complete example from the the beginning would be:

hostname:~ username$ ls `# notice: no empty line desired above!`
Desktop      Documents

hostname:~ username$ 
Ali
  • 1,339
  • 14
  • 36
  • 1
    For reference, the reason why your command doesn't work is 1) that you used double quotes, and therefore __ps1_newline_login runs when you do the export rather than every prompt, and 2) that if you had used single quotes, the function would have run in a subshell due to the $(..) so any variables you set would not be visible outside it – that other guy Feb 14 '13 at 01:25
  • 1
    @thatotherguy thank you very much for this explanation. It really helped me understanding various issues of mine. – Ali Feb 14 '13 at 09:07
  • @thatotherguy thinking about this again: are you absolutely sure that `__ps1_newline_login` runs only–once, but not every time? For example `__git_ps1` uses this exact same technique to set every prompt not just initially. – Ali Feb 14 '13 at 09:36
  • Are you sure it uses `"$(__git_ps1)"` and not `'$(__git_ps1)'`? The quotes make all the difference. If it actually does use double quotes, it would have to `echo '$(foo)'` in order to place a literal string `'$(foo)'` in the prompt, which can then be subsequently expanded. – that other guy Feb 14 '13 at 16:58
  • Yes, I am positive. It uses `$(__git_ps1)` with double quotes, and it seems to be working... – Ali Feb 17 '13 at 18:03

3 Answers3

15

Try the following:

function __ps1_newline_login {
  if [[ -z "${PS1_NEWLINE_LOGIN}" ]]; then
    PS1_NEWLINE_LOGIN=true
  else
    printf '\n'
  fi
}

PROMPT_COMMAND='__ps1_newline_login'
export PS1="\h:\W \u\$ "

Explanation:

  • PROMPT_COMMAND is a special bash variable which is executed every time before the prompt is set.
  • You need to use the -z flag to check if the length of a string is 0.
dogbane
  • 242,394
  • 72
  • 372
  • 395
  • further drilling on this subject: are you implying there is no way echoing/printing a newline from a function and using this in `PS1` as if you’d set it containing a `\n` in the first place? – Ali Feb 14 '13 at 09:39
  • I'm not quite sure I get your question, but if you want the `\n` in `PS1`, you could simply update `PS1` and unset `PROMP_COMMAND` instead of `printf "'\n"` – Mark Nov 04 '15 at 14:04
  • This interferes with PROMPT_COMMAND updating the tab name to the current working directory. See my answer for more. – JBallin Jun 06 '18 at 19:55
5

Running with dogbane's answer, you can make PROMPT_COMMAND "self-destruct", preventing the need to run a function after every command.

In your .bashrc or .bash_profile file, do

export PS1='\h:\W \u\$ '
reset_prompt () {
  PS1='\n\h:\W \u\$ '
}
PROMPT_COMMAND='(( PROMPT_CTR-- < 0 )) && { 
  unset PROMPT_COMMAND PROMPT_CTR
  reset_prompt
}'

When the file is processed, PS1 initially does not display a new-line before the prompt. However, PROMPT_CTR is immediately decremented to -1 (it is implicitly 0 before) before the prompt is shown the first time. After the first command, PROMPT_COMMAND clears itself and the counter before resetting the prompt to include the new-line. Subsequently, no PROMPT_COMMAND will execute.

Of course, there is a happy medium, where instead of PROMPT_COMMAND clearing itself, it just resets to a more ordinary function. Something like

export PS1='\h:\W \u\$ '
normal_prompt_cmd () {
   ...
}
reset_prompt () {
  PS1='\n\h:\W \u\$ '
}
PROMPT_COMMAND='(( PROMPT_CTR-- < 0 )) && {
   PROMPT_COMMAND=normal_prompt_cmd
   reset_prompt
   unset PROMPT_CTR
  }'
chepner
  • 389,128
  • 51
  • 403
  • 529
  • How do you `reset_prompt` in the second form please? I may have been confused a little, I must admit. – Ali Feb 14 '13 at 09:11
  • I've updated the answer. The key thing to note is that you can change the value of `PROMPT_COMMAND` inside `PROMPT_COMMAND`; it's not really recursion, since `PROMPT_COMMAND` is just a string that contains a mini-script to execute. One of the things that script could do is change the value of `PROMPT_COMMAND`. – chepner Feb 14 '13 at 12:51
  • why not simply `PROMPT_COMMAND="${PROMPT_COMMAND}__ps1_newline_login;"`? – Ali Feb 17 '13 at 18:06
  • You could do that, but then you are executing the conditional check before *every* prompt. Modifying `PROMPT_COMMAND` gets rid of the check after you know that it is no longer necessary. (Granted, it's an extremely minor optimization that you will probably never notice in practice. This technique is more useful for shedding more expensive checks that you might make.) – chepner Feb 17 '13 at 18:14
  • This interferes with PROMPT_COMMAND updating the tab name to the current working directory. See my answer for more. – JBallin Jun 06 '18 at 19:55
1

2018 Update (inspired by chepner's answer)

UPDATE: Fixed PROMPT_COMMAND issues caused by other answers

Changes:

  1. No need to export PS1
  2. I used "\n$PS1" instead of re-typing.
  3. Other answers interfere with the PROMPT_COMMAND's default behavior (more info below)

Enter the following in ~/.bash_profile (substituting first line with your prompt):

PS1=YOUR_PROMPT_HERE

add_newline_to_prompt() {
  is_new_login="true"
  INIT_PROMPT_COMMAND="$PROMPT_COMMAND"
  DEFAULT_PROMPT_COMMAND=update_terminal_cwd
  PROMPT_COMMAND='{
    if [ $is_new_login = "true" ]; then
      is_new_login="false"
      eval $INIT_PROMPT_COMMAND
    else
      PS1="\n$PS1"
      PROMPT_COMMAND=$DEFAULT_PROMPT_COMMAND
    fi
  }'
}

add_newline_to_prompt

PROMPT_COMMAND

I noticed that my tab name in terminal wasn't updating to my current working directory and did some investigating. I realized that above solutions are messing with PROMPT_COMMAND. Try this out:

  1. Comment out any modifications to PROMPT_COMMAND in your config files (.bash_profile etc.)
  2. Add INIT_PROMPT_COMMAND="$PROMPT_COMMAND" to your config file

Now open a new shell:

$ echo $INIT_PROMPT_COMMAND
shell_session_history_check; update_terminal_cwd
$ echo $PROMPT_COMMAND
update_terminal_cwd

Notice that when you open a new shell, it runs both a "history check" and updates the name of the tab current working directory. Notice that it only runs the "history check" initially, and then never runs it again.

NOTE: I've only tested this on Mac's Terminal. May be different on other systems.

JBallin
  • 4,767
  • 33
  • 38