2

I would like to change the PS1 in my ~/.bashrc programmatically with any of the the most popular regexp tools { sed, awk or perl }. However, I have problems with special characters. Note that in the default ~/.bashrc there are three PS1 prompt variables set under different conditions, and that they are all different. I want to change only two of them, by commenting out the original PS1 line and under it modifying what used to be the original.

For illustration let me show you the segment of .bashrc before and after the modification to PS1 variable. (BTW, to increase readability I use my custom concatenation markings "join" (\\j\) and "space" (\\s\) ), where the former means the two parts are joined without any white-space char between them, but the latter allows spaces between the two.

Before:

if [ "$color_prompt" = yes ]; then 
  PS1='${debian_chroot:+($debian_chroot)}                          \\j\
    \[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' 
else 
  PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\n\$> ' 
fi 
  unset color_prompt force_color_prompt 

  # If this is an xterm set the title to user@host:dir 
  case "$TERM" in 
  xterm*|rxvt*) 
    PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" 
  ;; 
  *) 
  ;; 
  esac 

After:

if [ "$color_prompt" = yes ]; then 
#    PS1='${debian_chroot:+($debian_chroot)}                         \\j\ 
    \[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' 
    PS1='${debian_chroot:+($debian_chroot)}                          \\j\ 
    \[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\n\$> ' 
  else 
#    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' 
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\n\$> ' 
  fi 
  unset color_prompt force_color_prompt 

  # If this is an xterm set the title to user@host:dir 
  case "$TERM" in 
  xterm*|rxvt*) 
    PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" 
  ;; 
  *) 
  ;; 
  esac 

I got the following working:

For those of you, who wish to try this out, let me show you what I got working, and what I've also learned is the problem with sed, and where awk and perl are better, though after playing with them, i.e., awk and perl for a day, I actually got only sed to do less than a half of what I wanted!

In particular pay attention to concatenation trick with a single-quote inside a single-quoted string replaced with {{ ' " ' " ' }}: Namely:

                           internal                    internal
                         single-quote                single-quote
                             vvvvv                       vvvvv
  aString=' this is unquoted '"'"' this is single-quoted '"'"' '
  echo "[$aString]"
  [ this is unquoted ' this is single-quoted ' ]

Internal single-quote is actually two single quotes and a string that happens to be a double-quoted single quote {{ '   " ' "   ' }}. Which when put together inside $aString, makes that string made up of five concatenated strings:

    ' this is unquoted '  + "'"  + ' this is single-quoted ' + "'" + ' '

Another mine you'll step on in {{ sed }} is that {{ sed }} for some reason does not handle octal representation of <Esc> character (\033) correctly, apparently it'd be fine with hex, namely, {{ \x1b }} or {{ \x1B }}, but that's not what's in the .bashrc file, because the four characters {{ \033 }} are stored literally as ASCII {{ \, 0, 3 and 3 }} though I believe the shell (Bash, Bourne or Perl for that matter) should convert these occurrences into appropriate single character they represent: the <Esc> character in this case. This looks to me like a can of worms! I spent entire weekend testing different permutations of escaped, double escaped, octal, and hex combinations with {{ sed, awk & perl }} and the only thing I got working is the following without colour-codes in {{ sed }}. Adding colour codes in the mix, just kills everything I know and and everything I tried.

This sed code works:

 29 
 30  ps1_2='    PS1='"'"'${debian_chroot:+($debian_chroot)}\\u\@\\h:\\w\\$ '"'"''
 31 ps1_2n='    PS1='"'"'${debian_chroot:+($debian_chroot)}\\u\@\\h:\\w\\n\\$> '"'"'' 
 32 
 33 sed "s/$ps1_2/#&\n$ps1_2n/" ps1-tFILE.txt 

Solved

Scott, thank you very much for your prompt reply, that inspired me exactly in the right direction. I solved my problem by handling smaller pieces first and then merging it all back together. Here's how I did it:

  1 #!/usr/bin/env bash 
  2 # $Log$
  3
  4 # -- The following are the contents of the {{ ps1-tFILE.txt }} file: 
  5
  6 #^   PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]     \\j\
                \u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' 
  8 #^ else 
  9 #^   PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\n\$> ' 
 10 
 11 P1='    PS1='"'"'${debian_chroot:+($debian_chroot)}' 
 12 P2='\\\[\\033\[01;32m\\]' 
 13 P3='\\u@\\h' 
 14 P4='\\\[\\033\[00m\\]:' 
 15 P5='\\\[\\033\[01;34m\\]\\w' 
 16 P6='\\\[\\033\[00m\\]' 
 17 P7='\\$ '"'"'' 
 18 
 19 srchPatt_wCtrls="\($P1$P2$P3$P4$P5$P6\)\($P7\)" 
 20 replPatt_wCtrls='#&\n\1\\n\\$> '"'"''
 21
 22 # -- For the convenience I also print out all individual parts  \\s\
            of the match and replace patterns. 
 23 echo "P1=$P1" 
 24 echo "P2=$P2" 
 25 echo "P3=$P3" 
 26 echo "P4=$P4" 
 27 echo "P5=$P5" 
 28 echo "P6=$P6" 
 29 echo "P7=$P7" 
 30 echo "srchPatt_wCtrls=$srchPatt_wCtrls" 
 31 echo "replPatt_wCtrls=$replPatt_wCtrls" 
 32 
 33 ps1_o='    PS1='"'"'${debian_chroot:+($debian_chroot)}\\u\@     \\j\
                    \\h:\\w\\$ '"'"'' 
 34 ps1_r='    PS1='"'"'${debian_chroot:+($debian_chroot)}\\u\@     \\j\
                    \\h:\\w\\n\\$> '"'"'' 
 35 
 36 sed -e "s/$srchPatt_wCtrls/$replPatt_wCtrls/"   \ 
 37     -e "s/$ps1_o/#&\n$ps1_r/" ps1-tFILE.txt 
 38 
 39 exit 

My test file

For those of you who have trouble with my ASCII text concatenation conventions I'm also including the contents of my test file (ps1-tFILE.txt) in a scrollable window:

$> cat ps1-tFILE.txt 
    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '

Thanks again, it helped me to sleep it over, and get inspirations from Scott:)

ubixy
  • 21
  • 2
  • *printf -v var '%q' "$var"* https://stackoverflow.com/a/62922701 – alecxs Jan 19 '21 at 08:14
  • (1) Your “question” feels like like it’s half question, half rant.  Are you asking a question about `awk` and/or `perl`?  (Seriously — are you? I believe that you aren’t, but I’m not sure.)  You have a lot to say about them, but you appear not to be asking about them. (2) There’s a concept of [MCVE (minimal, complete, verifiable example)](https://stackoverflow.com/help/minimal-reproducible-example) that embodies one of the basic principles of debugging: narrow down the problem as far as possible. (2a) You didn’t really need to give us all 16 lines of your `.bashrc`, and  … (Cont’d) – Scott - Слава Україні Jan 19 '21 at 23:54
  • (Cont’d) …  (2b) you should have tried to solve your problem on shorter lines. (3) And if you *had* presented shorter lines, you wouldn’t have had to use your ``\\j\`` notation. (3b) It’s confusing that you explain your ``\\s\`` notation even though you don’t use it. (4) If you’re going to ask a question about a command that doesn’t work, you should include *the command that doesn’t work* in your question. (5) I should thank you for providing *some* data, but (for reasons discussed in my answer), your files don’t provide a good *illustration* of your problem. … (Cont’d) – Scott - Слава Україні Jan 19 '21 at 23:54
  • (Cont’d) …  It would help if you would *describe* your objective; i.e., *what change* do you want to make?  It seems to be “For every line that assigns a value to `PS1` that includes `\$`, comment it out and replace it with an assignment that inserts a `\n` before the `\$`.”  Or is adding a `>` also part of your objective? (6) If you would describe the problem that you’re having with **``\033``**, somebody might be able to help you with it. – Scott - Слава Україні Jan 19 '21 at 23:54
  • **alecxs**, what are you saying? **Scott**, you have no reason to think the way you do, perhaps a second, and may be more readings are in order. And yes, I tested all the three tools, awk looked most promising at first. And I was open to any solution with either of these tools, since one may be better or easier than the other for certain approaches. I just didn't want to clutter the post with all the unsuccessful tests I performed. – ubixy Jan 20 '21 at 15:16
  • And, **Scott**, as for Esc (\033) character incompatibilities in these gegexp processing capable tools, see the post https://superuser.com/questions/380772/removing-ansi-color-codes-from-text-stream, namely the very 1st answer under the title: "**Using GNU sed**". I explained, not in so many words, that the four characters (backslash zero, three, and three) are to be treated as individual ASCII characters in the file, and that its the job of the enclosing shell (be it Bourne, Bash, ... or an interpreter) to interpret them as a single octal character due to the syntax rules. – ubixy Jan 20 '21 at 15:17

1 Answers1

0

I don’t know where to begin.  I might begin with the question you asked, if you had asked one, but I don’t see one.

You say that the sed code you presented works.  It doesn’t work for me on the file you provided, for these reasons:

  • The Before file that you show already has \n before the \$ (in the second assignment — the one without the color codes), but your ps1_2 string contains “…\\w\\$…” (without \n), so it won’t match the \w\n\$ in the file.
  • Likewise, your Before file already has > after the \$ (again, in the second assignment), but your ps1_2 string ends with “\\$ ” (without >), so it won’t match the \$>  in the file.

Additionally, your script uses \\$ (in ps1_2) to match \$ (in .bashrc).  That will probably work, in the context of your script.  But it might be safer to use \\\$, since \ and $ are both special characters.  See more below.

If you change your Before file to say

  if [ "$color_prompt" = yes ]; then
    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
  else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$> '
  fi

(i.e., remove the \n in the second assignment), and change your script to

 ps1_2='    PS1='"'"'${debian_chroot:+($debian_chroot)}\\u@\\h:\\w\\$> '"'"''
ps1_2n='    PS1='"'"'${debian_chroot:+($debian_chroot)}\\u@\\h:\\w\\n\\$> '"'"''

sed "s/$ps1_2/#&\n$ps1_2n/" ...

(i.e., add > to ps1_2), then it will work.

Obviously, if your Before file doesn’t already have > after \$, then don’t add it to ps1_2.


But consider,

The quick brown fax jumps over the lazy dog.

Well, that’s wrong.  We can fix it with

s/The quick brown fax jumps over the lazy dog./The quick brown fox jumps over the lazy dog./

(optionally ending the “old” string with \., to be sure), but why, when

s/a/o/

will work?

By the same token, if your objective is “For every line that assigns a value to PS1 that includes \$, comment it out and replace it with an assignment that inserts a \n before the \$.”, then you can do that with

sed 's/\(.*PS1=.*\)\(\\$.*\)/#\1\2\n\1\\n\2/'

This fixes both line 2 and line 4, while leaving line 11 alone (because it doesn’t contain \$).  If your objective is “For every line that assigns a value to PS1 that includes \$, comment it out and replace it with an assignment that inserts a \n before the \$ and a > after the \$.”, it’s only a little more complicated:

sed 's/\(.*PS1=.*\)\(\\\$\)\(.*\)/#\1\2\3\n\1\\n\2>\3/'

Note that I had to use \\\$ here.  I guess that it’s because, when sed sees something$.*, it assumes that the $ means a literal <dollar sign> ($), and not the end of the line, because the $ isn’t at the end of the regular expression, but when it sees \(…something$\)\(.*\), it gets confused, because the $ is at the end of a subexpression.  This might be a bug in sed.

  • **Scott**, thank you for your inspiration, except for the insult about ranting, and rather uncalled call to close the thread! :) – ubixy Jan 20 '21 at 12:13
  • **Scott**, your quote **"You say that the sed code you presented works. It doesn’t work for me on the file you provided, ..."** is not true! I've implemented it in my solution as well. You are doing something wrong! But, do we really care? In other words, is it really a patients job to try to cure his doctor? Though, I'm glad to help if you are interested! – ubixy Jan 20 '21 at 15:40
  • (1) I’m glad you got a solution working. (2) No, it’s not the patient’s job to “cure the doctor”, but it is the patient’s duty to tell the doctor the (whole) truth. I guess you posted the code you use, but garbled the “Before” file. But that’s unimportant now.  (3) I believe that you’re confused about `\033`. The question you linked to is about handling ***actual characters**,* not the four-character octal notation. As your answer proves, `sed 's/\\033//'` works. (4) Why do you believe that I called for your question to be closed? Everything I posted was after the question was closed. – Scott - Слава Україні Jan 21 '21 at 04:09