How to set a conditional newline in PS1?
Asked Answered
H

4

16

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$ 
Hairstyle answered 13/2, 2013 at 17:17 Comment(5)
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 itRobertoroberts
@thatotherguy thank you very much for this explanation. It really helped me understanding various issues of mine.Hairstyle
@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.Hairstyle
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.Robertoroberts
Yes, I am positive. It uses $(__git_ps1) with double quotes, and it seems to be working...Hairstyle
R
16

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.
Regicide answered 13/2, 2013 at 17:40 Comment(3)
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?Hairstyle
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"Gilmore
This interferes with PROMPT_COMMAND updating the tab name to the current working directory. See my answer for more.Banff
H
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
  }'
Hebetude answered 13/2, 2013 at 18:39 Comment(5)
How do you reset_prompt in the second form please? I may have been confused a little, I must admit.Hairstyle
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.Hebetude
why not simply PROMPT_COMMAND="${PROMPT_COMMAND}__ps1_newline_login;"?Hairstyle
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.)Hebetude
This interferes with PROMPT_COMMAND updating the tab name to the current working directory. See my answer for more.Banff
B
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.

Banff answered 21/2, 2018 at 20:38 Comment(3)
I like this! 👏Hairstyle
@Hairstyle made an update (PROMPT_COMMAND) which you may be interested inBanff
Thank you @BanffHairstyle
R
0

Insert this in your .bashrc:

PROMPT_COMMAND="export PROMPT_COMMAND=echo"
alias clear="clear; export PROMPT_COMMAND='export PROMPT_COMMAND='echo''"

This achieves exactly what you want. No need for \n in PS1 or any functions.

Ruling answered 9/11, 2021 at 16:39 Comment(1)
Thanks @Andreas! It seems to be working. Would you care to explain how is it working though?Hairstyle

© 2022 - 2024 — McMap. All rights reserved.