7

I'm customizing my zsh PROMPT and calling a function that may or may not echo a string based on the state of an environment variable:

function my_info {
    [[ -n "$ENV_VAR"]] && echo "Some useful information\n"
}

local my_info='$(my_info)'

PROMPT="${my_info}My awesome prompt $>"

I would like the info to end on a trailing newline, so that if it is set, it appears on its own line:

Some useful information
My awesome prompt $>

However, if it's not set, I want the prompt to be on a single line, avoiding an empty line caused by an unconditional newline in my prompt:

PROMPT="${my_info}  # <= Don't want that :)
My awesome prompt $>"

Currently I work around the $(command substitution) removing my newline by suffixing it with a non-printing character, so the newline isn't trailing anymore:

[[ -n "$ENV_VAR"]] && echo "Some useful information\n\r"

This is obviously a hack. Is there a clean way to return a string that ends on a newline?

Edit: I understand what causes the loss of the trailing newline and why that happens, but in this question I would specifically like to know how to prevent that behaviour (and I don't think this workaround applies in my case, since I'm looking for a "conditional" newline).

Edit: I stand corrected: the referenced workaround might actually be a rather nice solution (since prefixing strings in comparisons is a common and somewhat similar pattern), except I can't get it to work properly:

echo "Some useful information\n"x
  [...]
PROMPT="${my_info%x}My awesome prompt $>"

does not strip the trailing x for me.

Edit: Adjusting the proposed workaround for the weirdness that is prompt expansion, this worked for me:

function my_info {
    [[ -n "$ENV_VAR"]] && echo "Some useful information\n"x
}

local my_info='${$(my_info)%x}'

PROMPT="$my_info My awesome prompt $>"

You be the judge if this is a better solution than the original one. It's a tad more explicit, I think, but it also feels a bit less readable.

dtk
  • 193
  • 9
  • Possible duplicate of [Where has the trailing newline char gone from my command substitution?](http://unix.stackexchange.com/q/17732/80216)  Perhaps a better match: [How can I make the bash backtick operator keep newlines in output?](http://superuser.com/q/403800/354511)  (on Super User) – G-Man Says 'Reinstate Monica' May 30 '15 at 02:20
  • @G-Man At first glance it doesn't look like the superuser.com question is a match, since the accepted answer states that the problem is not with the command substitution but with the subsequent `echo`ing. Which isn't the case for this question. – dtk May 30 '15 at 02:57
  • D'oh!  You're right; I read it too fast. – G-Man Says 'Reinstate Monica' May 30 '15 at 02:59
  • 1
    How is this question a duplicate of http://unix.stackexchange.com/questions/17732/where-has-the-trailing-newline-char-gone-from-my-command-substitution? That questions asks why the effect occurs and the accepted answer explains it. As I stated in my first edit, I was aware of that question when posting this one, so I know why it happens, but it doesn't answer how to best prevent it. – dtk May 30 '15 at 09:11

2 Answers2

5

Final newlines are removed from command substitutions. Even zsh doesn't provide an option to avoid this. So if you want to preserve final newlines, you need to arrange for them not to be final newlines.

The easiest way to do this is to print an extra character (other than a newline) after the data that you want to obtain exactly, and remove that final extra character from the result of the command substitution. You can optionally put a newline after that extra character, it'll be removed anyway.

In zsh, you can combine the command substitution with the string manipulation to remove the extra character.

my_info='${$(my_info; echo .)%.}'
PROMPT="${my_info}My awesome prompt $>"

In your scenario, take care that my_info is not the output of the command, it's a shell snippet to get the output, which will be evaluated when the prompt is expanded. PROMPT=${my_info%x}… didn't work because that tries to remove a final x from the value of the my_info variable, but it ends with ).

In other shells, this needs to be done in two steps:

output=$(my_info; echo .)
output=${output%.}

In bash, you wouldn't be able to call my_info directly from PS1; instead you'd need to call it from PROMPT_COMMAND.

PROMPT_COMMAND='my_info=$(my_info; echo .)'
PS1='${my_info%.}…'
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
-1

You can have your function output the escaped escape codes, so that when it is expanded it turns into the newline.

$ function my_info {
>    [[ -n "$ENV_VAR"]] && echo 'Some useful information\\n'
>}
$ echo $(my_info)
Some useful information

$
spelufo
  • 467
  • 4
  • 11
  • 1
    Yeah, I tried that (in different variations, including the [zsh specific `$'\n'` mechanism](http://zsh.sourceforge.net/FAQ/zshfaq03.html#l30)), but it doesn't work, since zsh [doesn't expand](http://unix.stackexchange.com/questions/53789/whats-the-newline-symbol-in-zshs-ps1) the resulting `\n` in the `PROMPT` string :/ – dtk May 30 '15 at 02:37
  • Isn't that because of the single quotes in your variable assignment? `local my_info='$(my_info)'`? Try without them, or just `PROMPT="$(my_info) My awesome prompt $>"` – spelufo May 30 '15 at 11:18
  • Nope, the zsh doesn't even expand `PROMPT="Foo\nBar"`, in contrast to `$ ENV_VAR="Foo\nBar"; echo $ENV_VAR`. The single quotes in the assignment are needed so the expression doesn't get expanded when the file is sourced, as the "reference" the variable contains should be resolved each time the `PROMPT` variable gets evaluated. – dtk May 31 '15 at 20:11