-1

Edit - 06/08/23 - Original Question Kept Substantially the Same

Thanks to Charles Duffy for providing valuable feedback in comments. The original question occurs in the context of item 1. But based on the feedback it appears that item 2 is the better option for managing system administration tasks. I may open another question or perhaps post some information in my answer here when I finish my research into perl and /etc/profile.d.

  1. Original Question: How to search and replace literal strings in a file, such as PS1=<string> in a .bashrc file, using sed, awk, or perl?
  2. Better question: How to enable desired prompt behavior using script(s) stored in /etc/profile.d that automatically execute whenever a user logs in?

Goal is to automate custom bash prompt on Debian PC and DietPi (ARM) Linux systems. The custom prompt inserts a blank line, to separate commands from terminal output, and looks like this but with different colors:

root@dpiBox /root
#

user@dpiBox /home/user
$

where root@dpiBox is red; user@dpiBox is green; and the present working directory is always shown in blue.

When a new user is created then a copy of /etc/skel/.bashrc is placed in the user's home directory as /home/user/.bashrc. The root user already has a copy made as /root/.bashrc.

My original plan was to edit /etc/skel/.bashrc via script before adding new users and to manually edit the /root/.bashrc. But when root or any user log in this automatically executes scripts properly installed under /etc/profile.d. Charles Duffy seems to be advising the use of this feature for better management of system administration challenges!

Relevant lines in default /etc/skel/.bashrc and /root/.bashrc and /home/user/.bashrc

# /root/bashtest (simulates default .bashrc)
# /etc/skel/.bashrc
# /root/.bashrc
# /home/user/.bashrc

#force_color_prompt=yes

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

Step 1 - Uncomment line to turn on color prompt:

force_color_prompt=yes

Step 2 - Replace old PS1 (above) with new PS1 (shown here) to customize bash prompt:

PS1='${debian_chroot:+($debian_chroot)}\n\[\033[01;32m\]\u@\h\[\033[00m\]\[\033[01;34m\] $(pwd -P) \[\033[00m\]\n\$ '

The code [01;32m\] is green for /home/user/.bashrc. Change this code to [01;31m\] which is red for /root/.bashrc.

Step 1 - sed: good result

/usr/local/bin/bashsed (development script to edit bashtest/.bashrc)

#!/bin/bash
# uncomment to force color prompt on
off="#force_color_prompt=yes"
on="force_color_prompt=yes"
sed -i "s|$off|$on|" /root/bashtest

sed code works with double quotes or single quotes used to define on and off. The double quotes must be used in the sed command.

Step 1 - perl: good result

/usr/local/bin/bashperl (development script to edit bashtest/.bashrc)

#!/bin/bash
# uncomment to force color prompt on
perl -i.bak -pe 's/#force_color_prompt=yes/force_color_prompt=yes/' /root/bashtest

The i.bak switch backs up /root/bashtest as /root/bashtest.bak then edits the file in place. This command can be used on the command line without using the i.bak switch.

Step 2 - sed: Bad result for sed -i "s|$old|$new|" /root/bashtest

/usr/local/bin/bashsed (development script to edit bashtest/.bashrc)

#!/bin/bash
# replace old PS1 with new PS1
# old                                    \[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\
# PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '

old='\\[\\033\[01;32m\\]\\u@\\h\\[\\033[00m\\]:\\[\\033\[01;34m\\]\\w\\[\\033\[00m\\]\\'

# new                                    \n\[\033[01;32m\]\u@\h\[\033[00m\]\[\033[01;34m\] $(pwd -P) \[\033[00m\]\n\
# PS1='${debian_chroot:+($debian_chroot)}\n\[\033[01;32m\]\u@\h\[\033[00m\]\[\033[01;34m\] $(pwd -P) \[\033[00m\]\n\$ '

new='\\n\\[\\033\[01;32m\\]\\u@\\h\\[\\033\[00m\\]\\[\\033\[01;34m\\] \$(pwd -P) \\[\\033\[00m\\]\\n\\'

sed -i "s|$old|$new|" /root/bashtest

Step 2 the code runs without error, but produces no results, when double quotes are used to define old and new. The code throws an error sed: -e expression #1, char 149: unterminated `s' command when single quotes are used to define old and new.

Edit - 06/08/23 - Comments

Posted my solution to Step 2 using perl call in a bash script.

Based on feedback from Charles Duffy the perl solution for Step 2 can be modified to use literal strings stored as bash environment variables. Relevant information is provided near the top of the page under this link:

https://mywiki.wooledge.org/BashFAQ/021

According to Charles Duffy scripts should run from /etc/profile.d when a user logs in to better manage sysadmin tasks. I need to do more research to understand this feedback. Here are two links that might be helpful:

https://serverfault.com/questions/434321/when-are-scripts-inside-etc-profile-d-executed

https://unix.stackexchange.com/questions/64258/what-do-the-scripts-in-etc-profile-d-do

Links in Original Question

I have searched extensively, tried dozens of experiments in the context below, and I cannot make sense of these references:

https://linuxhint.com/replace_string_in_file_bash/

Why would sed have trouble seeing my PS1 prompt?

Replacement of PS1 variable in .bashrc using Sed or Perl

Regarding sed expression evaluation in shell

So I need to have my "sed" examined! I would not mind using awk if it does the job, but then I need to see the simple command structure, before inserting the literal strings. Otherwise I always mess up the syntax.

SystemTheory
  • 339
  • 3
  • 15
  • See [BashFAQ #21](https://mywiki.wooledge.org/BashFAQ/021), which discusses tools without `sed`'s drawbacks. Both perl and awk are often better choices. – Charles Duffy Jun 07 '23 at 16:32
  • That said, in general, _I don't recommend editing files in-place at all_. Much better to make your code source a bunch of tiny separate source files you can just replace in their entirety. – Charles Duffy Jun 07 '23 at 16:32
  • (That said, this advice above is more coming from lessons I've learned wearing my sysadmin hat, not my developer hat; as such, [unix.se] might be the place to look for practices more than Stack Overflow). – Charles Duffy Jun 07 '23 at 16:34
  • 1
    (you may have seen some distros use `/etc/profile.d` and then have bash scripts source `/etc/profile.d/*.bash`, ksh scripts source `/etc/profile.d/*.ksh`, etc -- that's a solid pattern, because then your scriptlets can be tiny files that just do one thing and are easy to replace with no editing ever needed; and using a shared directory everyone sources files from instead of individual copies off `/etc/skel` also means you don't get divergence between old accounts and new ones). – Charles Duffy Jun 07 '23 at 16:54
  • For a question that covers the same ground as the FAQ I linked above, see [How to search/replace arbitrary literal strings in sed and awk and perl?](https://stackoverflow.com/questions/54059656/how-to-search-replace-arbitrary-literal-strings-in-sed-and-awk-and-perl) – Charles Duffy Jun 07 '23 at 17:01
  • I appreciate the sysadmin advice although some of it goes over my head. I suppose it would be better to automate the script for adding a new user where /etc/skel/.bashrc is untouched. Instead the script alters /home/user/.bashrc after the user is added. So far I am learning Debian sysadmin (basics) on several raspberry pi hosts and other PCs in a home lab. I want to automate the setup tasks for new install of dietpi and the first item is to customize the bash prompt. I prefer to see the user and present working directory when I login via ssh. I also prefer to insert a space between commands. – SystemTheory Jun 07 '23 at 17:14
  • If you *do* want to edit a file in place, prefer `ed` over `sed -i` – chepner Jun 07 '23 at 17:21

1 Answers1

1

Solved using perl instead of sed or awk. Mostly relied on the following reference:

https://www.perl.com/pub/2004/08/09/commandline.html/

old literal string in PS1:

\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\

old string escaped for perl match:

\\\[\\033\[01;32m\\]\\u@\\h\\\[\\033\[00m\\]:\\\[\\033\[01;34m\\]\\w\\\[\\033\[00m\\]\\

new literal string in PS1

\n\[\033[01;32m\]\u@\h\[\033[00m\]\[\033[01;34m\] $(pwd -P) \[\033[00m\]\n\

new string escaped for perl replace operation:

\\n\\[\\033\[01;32m\\]\\u@\\h\\\[\\033\[00m\\]\\\[\\033\[01;34m\\] \$\(pwd -P\) \\[\\033\[00m\\]\\n\\

Used pattern on command line to debug escape sequences for literal strings:

perl 's/old/DummyString/' < ps1old > ps1out; cat ps1out
# ~/ps1old

#force_color_prompt=yes

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
perl 's/new/DummyString/' < ps1new > ps1out; cat ps1out
# ~/ps1new

#force_color_prompt=yes

if [ "$color_prompt" = yes ]; then
    PS1='${debian_chroot:+($debian_chroot)}\n\[\033[01;32m\]\u@\h\[\033[00m\]\[\033[01;34m\] $(pwd -P) \[\033[00m\]\n\$ '
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
perl 's/old/new/' < ps1old > ps1out; cat ps1out

Command line solution with test input file ps1old writes result to ps1out

perl -pe 's/\\\[\\033\[01;32m\\]\\u@\\h\\\[\\033\[00m\\]:\\\[\\033\[01;34m\\]\\w\\\[\\033\[00m\\]\\/\\n\\[\\033\[01;32m\\]\\u@\\h\\\[\\033\[00m\\]\\\[\\033\[01;34m\\] \$\(pwd -P\) \\[\\033\[00m\\]\\n\\/' < ps1old > ps1old; cat ps1out

This method writes results to file ps1out while testing escape sequence patterns from left to right on literal strings represented by placeholder old and placeholder new.

When the escape sequence fails file ps1old or file ps1new is unchanged; file ps1out reverts to a copy of the respective input file; and perl provides valuable debug information.

When the escape sequence succeeds file ps1out shows the dummy string DummyString as replacement for portions of old or new working from left to right to debug escape pattern. This can be done one escape character at a time to isolate syntax errors systematically.

After debugging literal strings for both placeholders old and new then transfer working results to bash script with perl calls as shown below.

An improvement might be to write changes to a new file then move that to target .bashrc and keep .bashrc.bak for backup.

If this script is run more than once it overwrites the backup copy with the edited version!

Bash script calls perl

#!/bin/bash

# Command line test writes result to ps1out without changing ps1old
# perl -pe 's/#force_color_prompt=yes/force_color_prompt=yes/' < ps1old > ps1out; cat ps1out

# Command line test writes result to ps1out without changing ps1old
# perl -pe 's/\\\[\\033\[01;32m\\]\\u@\\h\\\[\\033\[00m\\]:\\\[\\033\[01;34m\\]\\w\\\[\\033\[00m\\]\\/\\n\\[\\033\[01;32m\\]\\u@\\h\\\[\\033\[00m\\]\\\[\\033\[01;34m\\] \$\(pwd -P\) \\[\\033\[00m\\]\\n\\/' < ps1old > ps1old; cat ps1out

# If script is run two or more times it overwrites previous .bashrc.bak!
# Backup target .bashrc using -i.bak switch to uncomment file in place!
perl -i.bak -pe 's/#force_color_prompt=yes/force_color_prompt=yes/' .bashrc

# Replace old PS1 with new PS1 after testing PS1 escape pattern on command line
perl -i -pe 's/\\\[\\033\[01;32m\\]\\u@\\h\\\[\\033\[00m\\]:\\\[\\033\[01;34m\\]\\w\\\[\\033\[00m\\]\\/\\n\\[\\033\[01;32m\\]\\u@\\h\\\[\\033\[00m\\]\\\[\\033\[01;34m\\] \$\(pwd -P\) \\[\\033\[00m\\]\\n\\/' .bashrc
SystemTheory
  • 339
  • 3
  • 15
  • Shouldn't need to escape for perl at all; you can have perl get the literals right out of the environment -- the BashFAQ link demonstrates as much -- and that way you can parameterize to use arbitrary variables without concern about injection attacks. – Charles Duffy Jun 08 '23 at 00:51
  • The downside of editing files in `/etc/skel`? If your distro provides that file from a package then a simple update/reinstall of the said package that contains/provides `/etc/skel` and the files inside it would reset/revert back all the changes, just a heads up though. – Jetchisel Jun 08 '23 at 05:11