9

Possible Duplicate:
Echo expanded PS1

Is there any way to 'evaluate' PS1, PS2, etc from within a bash script?

Although, I can use alternate means to get all elements of my current PS1, I would really like to be able to reuse its definition instead of using these alternate means.

For example,

=====================================
 PS1 element -->     Alternate means
=====================================
 \u          -->     $USER
 \h          -->     $HOSTNAME
 \w          -->     $PWD
 ...
=====================================

I could very well use the 'alternate means' column in my script, but I don't want to. In my PS1, I, for example, use bold blue color via terminal escape sequences which I'd like to be able to simply reuse by evaluating PS1.

Community
  • 1
  • 1
Harry
  • 3,684
  • 6
  • 39
  • 48
  • Maybe other people know, but I have no idea what you're talking about. What are you trying to accomplish? – Kurt Apr 08 '12 at 05:01
  • Do you wish to eval the $PS1, $PS2 ? Or do you want to attribute values to those variables inside your script? – Gangadhar Apr 08 '12 at 05:02
  • Suppose there's a function or command called `evaluate`. What the result of `evaluate PS1` should be? – n. m. could be an AI Apr 08 '12 at 05:08
  • @Gangadhar Yes, I want to `eval` `$PS1`, `$PS2` etc. No, I do not want to attribute values to those variables from inside my script. – Harry Apr 09 '12 at 06:54
  • @n.m. The result of this hypothetical `evaluate` should be **whatever** I see as my prompt on each `ENTER`. – Harry Apr 09 '12 at 06:55
  • All - though I have marked pax's answer as the final one, my search for a better final answer is not over yet. Would welcome alternate ideas, if any. – Harry Apr 09 '12 at 06:56
  • 2
    Aha, got it. Try this: `myps1=$(echo -e "PROMPT_COMMAND=\n" | bash -i 2>&1 | head -2 | tail -1)` — note this will contain any prompt colorizing escape sequences. – n. m. could be an AI Apr 09 '12 at 21:10
  • @n.m. Not sure what you mean. What is `PROMPT_COMMAND`? You assign it a value but never use it! When I ran just the portion within the `$(...)`, I got a blank output. That is, issuing the whole command (which you wrote above) followed by an `echo ":$myps1:"` gave me `::` as output. Did you try it yourself? My `PS1` is set to `'\[\033[0;34m\]\u@\h $? \w\n$\[\033[0m\]'`; could you try what you're suggesting as a solution on this value of `PS1` and then get back? Many thanks, btw. – Harry Apr 10 '12 at 04:39
  • PROMPT_COMMAND is something that has to be *unset* for this code. Look it up. Assigning an empty string is the simplest way. Yes I tried the code and it works for me. Perhaps different bash versions? Try removing the head and tail commands and see what happens. – n. m. could be an AI Apr 10 '12 at 17:45
  • I don't actually consider this a duplicate. OP specifically stated that the alternate means were not an option which I think makes this unique enough to justify it. Especially since the accepted answer as the other question is to _use_ those alternate means. Voting to reopen but I'll copy (with mods) my answer to the other question for completeness. – paxdiablo Jan 09 '13 at 09:21
  • `echo ${PS1@P}` seems to do the trick – Allan Dec 17 '18 at 20:59

1 Answers1

14

One great advantage of open source software is that the source is, well, open :-)

If you download the code for bash (I'm looking at version 4.2), there's a y.tab.c file which contains the decode_prompt_string() function:

char *decode_prompt_string (string) char *string; { ... }

You can try to extract that (along with any needed support routines and build an executable which did the job for you. Although, from a cursory try, those support routines seem to be a lot, so this may be a hard task.


Other than that, you can probably "trick" bash into expanding it for you with something like:

expPS1=$(echo xyzzyplughtwisty | bash -i 2>&1
         | grep xyzzyplughtwisty
         | head -1
         | sed 's/xyzzyplughtwisty//g')

Now I've put that across multiple lines for readability but it was done on one line.

What this does is run an interactive instance of bash, passing (what hopefully is) an invalid command.

Because it's interactive, it prints the prompt so I grab the first line with the command string on it and remove that command string. What's left over should be the prompt.

On my system, this is what I get:

pax> expPS1=$(echo xyzzyplughtwisty | bash -i 2>&1 | grep xyzzyplughtwisty | head -1 | sed 's/xyzzyplughtwisty//g')

pax> echo "[$expPS1]"
[pax> ]

pax> 

However, this has problems with multi-line prompts and will actually give you your regular prompt rather than the current shell one.


If you want to do it properly, it may involve adding a little bit to bash itself. Here are the steps to add an internal command evalps1.

First, change support/mkversion.sh so that you won't confuse it with a "real" bash, and so that the FSF can deny all knowledge for warranty purposes :-) Simply change one line (I added the -pax bit):

echo "#define DISTVERSION \"${float_dist}-pax\""

Second, change `builtins/Makefile.in to add a new source file. This entails a number of steps.

(a) Add $(srcdir)/evalps1.def to the end of DEFSRC.

(b) Add evalps1.o to the end of OFILES.

(c) Add the required dependencies:

evalps1.o: evalps1.def $(topdir)/bashtypes.h $(topdir)/config.h \
           $(topdir)/bashintl.h $(topdir)/shell.h common.h

Third, add the builtins/evalps1.def file itself, this is the code that gets executed when you run the evalps1 command:

This file is evalps1.def, from which is created evalps1.c.
It implements the builtin "evalps1" in Bash.

Copyright (C) 1987-2009 Free Software Foundation, Inc.

This file is part of GNU Bash, the Bourne Again SHell.

Bash is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Bash is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Bash.  If not, see <http://www.gnu.org/licenses/>.

$PRODUCES evalps1.c

$BUILTIN evalps1
$FUNCTION evalps1_builtin
$SHORT_DOC evalps1
Outputs the fully interpreted PS1 prompt.

Outputs the PS1 prompt, fully evaluated, for whatever nefarious purposes
you require.
$END

#include <config.h>
#include "../bashtypes.h"
#include <stdio.h>
#include "../bashintl.h"
#include "../shell.h"
#include "common.h"

int
evalps1_builtin (list)
     WORD_LIST *list;
{
  char *ps1 = get_string_value ("PS1");
  if (ps1 != 0)
  {
    ps1 = decode_prompt_string (ps1);
    if (ps1 != 0)
    {
      printf ("%s", ps1);
    }
  }
  return 0;
}

The bulk of that is the GPL licence (since I modified it from exit.def) with a very simple function at the end to get and decode PS1.

Lastly, just build the thing in the top level directory:

./configure
make

The bash that appears can be renamed to paxsh, though I doubt it will ever become as prevalent as its ancestor :-)

And running it, you can see it in action:

pax> mv bash paxsh

pax> ./paxsh --version
GNU bash, version 4.2-pax.0(1)-release (i686-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

pax> ./paxsh

pax> echo $BASH_VERSION
4.2-pax.0(1)-release

pax> echo "[$PS1]"
[pax> ]

pax> echo "[$(evalps1)]"
[pax> ]

pax> PS1="\h: "

paxbox01: echo "[$PS1]"
[\h: ]

paxbox01: echo "[$(evalps1)]"
[paxbox01: ]

Now, granted, making code changes to bash to add an internal command may be considered overkill by some but, if you want an accurate evaluation of PS1, it's certainly an option.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Are you sure you tried it on your system? When I tried and echoed $expPS1, I got "bash: : command not found..." as the value. Note also that head -1 may not work when you have a multi-line prompt. Also, I'm printing my prompt in bold-blue color. This is the very reason I posted this question: I would like to retain my PS1 fully without duplicating its elements in any shape or form. Fyi, my PS1 is set to: '`\[\033[0;34m\]\u@\h $? \w\n$\[\033[0m\]`'. While I definitely like the ingenuity of your solution, it is very slow: there's 1-2 second gap before I see any result at all. – Harry Apr 08 '12 at 09:21
  • That's quite a laundry list of problems you have :-) (1) Yes, I tried in on my system, that last bit is an actual transcript. (2) Yes, `head -1` won't work for multiline prompts, you'll have to be a bit more ingenious for that and it'll probably require foreknowledge of the line count or pre-processing of `PS1` counting `\n` sections. (3) I'm not sure what the problem is with color although you may have to force your `TERM` setting. (4) I can't vouch for the speed, that depends on how long it takes to start the shell. If the second solution is inadequate, you may need to look at the first. – paxdiablo Apr 08 '12 at 09:31
  • But I suspect it may be _more_ effort, though at least it should solve those problems. You can even write a script which does similar things to the C code to translate an arbitrary `PS1`. – paxdiablo Apr 08 '12 at 09:32
  • "That's quite a laundry list of problems you have :-)" Was just providing you feedback, not expecting you to work on this 'laundry list'. Have +1'ed your answer already, appreciate your taking the time to write instead of (quite justifiably) working on your own laundry list of things which people usually have :-) especially on weekends :-)) – Harry Apr 08 '12 at 11:42