To get the same output you note in your question, all that is needed is this:
PS1='${PS2c##*[$((PS2c=0))-9]}- > '
PS2='$((PS2c=PS2c+1)) > '
You need not contort. Those two lines will do it all in any shell that pretends to anything close to POSIX compatibility.
- > cat <<HD
1 > line 1
2 > line $((PS2c-1))
3 > HD
line 1
line 2
- > echo $PS2c
0
But I liked this. And I wanted to demonstrate the fundamentals of what makes this work a little better. So I edited this a little. I stuck it in /tmp
for now but I think I'm going to keep it for myself, too. It's here:
cat /tmp/prompt
PROMPT SCRIPT:
ps1() { IFS=/
set -- ${PWD%"${last=${PWD##/*/}}"}
printf "${1+%c/}" "$@"
printf "$last > "
}
PS1='$(ps1)${PS2c##*[$((PS2c=0))-9]}'
PS2='$((PS2c=PS2c+1)) > '
Note: having recently learned of yash, I built it yesterday. For whatever reason it doesn't print the first byte of every argument with the %c
string - though the docs were specific about wide-char extensions for that format and so it maybe related - but it does just fine with %.1s
That's the whole thing. There are two main things going on up there. And this is what it looks like:
/u/s/m/man3 > cat <<HERE
1 > line 1
2 > line 2
3 > line $((PS2c-1))
4 > HERE
line 1
line 2
line 3
/u/s/m/man3 >
PARSING $PWD
Every time $PS1
is evaluated it parses and prints $PWD
to add to the prompt. But I don't like the whole $PWD
crowding my screen, so I want just the first letter of every breadcrumb in the current path down to the current directory, which I'd like to see in full. Like this:
/h/mikeserv > cd /etc
/etc > cd /usr/share/man/man3
/u/s/m/man3 > cd /
/ > cd ~
/h/mikeserv >
There are a few steps here:
IFS=/
we're going to have to split the current $PWD
and the most reliable way to do that is with $IFS
split on /
. No need to bother with it at all afterward - all splitting from here on out will be defined by the shell's positional parameter $@
array in the next command like:
set -- ${PWD%"${last=${PWD##/*/}}"}
So this one's a little tricky, but the main thing is that we're splitting $PWD
on /
symbols. I also use parameter expansion to assign to $last
everything after any value occurring between the left-most and right-most /
slash. In this way I know that if I'm just at /
and have only one /
then $last
will still equal the whole $PWD
and $1
will be empty. This matters. I also strip $last
from the tail end of $PWD
before assigning it to $@
.
printf "${1+%c/}" "$@"
So here - as long as ${1+is set}
we printf
the first %c
haracter of each our shell's arguments - which we've just set to each directory in our current $PWD
- less the top directory - split on /
. So we're essentially just printing the first character of every directory in $PWD
but the top one. It's important though to realize this only happens if $1
gets set at all, which will not happen at root /
or at one removed from /
such as in /etc
.
printf "$last > "
$last
is the variable I just assigned to our top directory. So now this is our top directory. It prints whether or not the last statement did. And it takes a neat little >
for good measure.
BUT WHAT ABOUT THE INCREMENT?
And then there's the matter of the $PS2
conditional. I showed earlier how this can be done which you can still find below - this is fundamentally an issue of scope. But there's a little more to it unless you want to start doing a bunch of printf \b
ackspaces and then trying to balance out their character count... ugh. So I do this:
PS1='$(ps1)${PS2c##*[$((PS2c=0))-9]}'
Again, ${parameter##expansion}
saves the day. It's a little strange here though - we actually set the variable while we strip it of itself. We use its new value - set mid-strip - as the glob from which we strip. You see? We ##*
strip all from the head of our increment variable to the last character which can be anything from [$((PS2c=0))-9]
. We're guaranteed in this way not to output the value, and yet we still assign it. It's pretty cool - I've never done that before. But POSIX also guarantees us that this is the most portable way this can be done.
And it's thanks to POSIX-specified ${parameter} $((expansion))
that keeps these definitions in the current shell without requiring that we set them in a separate subshell, regardless of where we evaluate them. And this is why it works in dash
and sh
just as well as it does in bash
and zsh
. We use no shell/terminal dependent escapes and we let the variables test themselves. That's what makes portable code quick.
The rest is fairly simple - just increment our counter for every time $PS2
is evaluated until $PS1
once again resets it. Like this:
PS2='$((PS2c=PS2c+1)) > '
So now I can:
DASH DEMO
ENV=/tmp/prompt dash -i
/h/mikeserv > cd /etc
/etc > cd /usr/share/man/man3
/u/s/m/man3 > cat <<HERE
1 > line 1
2 > line 2
3 > line $((PS2c-1))
4 > HERE
line 1
line 2
line 3
/u/s/m/man3 > printf '\t%s\n' "$PS1" "$PS2" "$PS2c"
$(ps1)${PS2c##*[$((PS2c=0))-9]}
$((PS2c=PS2c+1)) >
0
/u/s/m/man3 > cd ~
/h/mikeserv >
SH DEMO
It works the same in bash
or sh
:
ENV=/tmp/prompt sh -i
/h/mikeserv > cat <<HEREDOC
1 > $( echo $PS2c )
2 > $( echo $PS1 )
3 > $( echo $PS2 )
4 > HEREDOC
4
$(ps1)${PS2c##*[$((PS2c=0))-9]}
$((PS2c=PS2c+1)) >
/h/mikeserv > echo $PS2c ; cd /
0
/ > cd /usr/share
/u/share > cd ~
/h/mikeserv > exit
As I said above, the primary problem is that you need to consider where you do your computation. You don't get the state in the parent shell - so you don't compute there. You get the state in the subshell - so that's where you compute. But you do the definition in the parent shell.
ENV=/dev/fd/3 sh -i 3<<\PROMPT
ps1() { printf '$((PS2c=0)) > ' ; }
ps2() { printf '$((PS2c=PS2c+1)) > ' ; }
PS1=$(ps1)
PS2=$(ps2)
PROMPT
0 > cat <<MULTI_LINE
1 > $(echo this will be line 1)
2 > $(echo and this line 2)
3 > $(echo here is line 3)
4 > MULTI_LINE
this will be line 1
and this line 2
here is line 3
0 >
Best Answer
If I understand your question correctly, you need to
source
or.
your files. For example, within your.bashrc
and taking care of order (only you know):source
can be shortened to.
on the command line for ease of use - it is exactly the same command. The effect ofsource
is to effectively inline the script that is called.