2

I have a file ~/.zshrc with the following lines

...
#export PATH="/usr/local/opt/php@7.0/bin:$PATH"
#export PATH="/usr/local/opt/php@7.0/sbin:$PATH"
##export PATH="/usr/local/opt/php@7.1/bin:$PATH"
##export PATH="/usr/local/opt/php@7.1/sbin:$PATH"
#export PATH="/usr/local/opt/php@7.3/bin:$PATH"
#export PATH="/usr/local/opt/php@7.3/sbin:$PATH"
#export PATH="/usr/local/opt/php@7.2/bin:$PATH"
#export PATH="/usr/local/opt/php@7.2/sbin:$PATH"
export PATH="/usr/local/opt/php@7.4/bin:$PATH"
export PATH="/usr/local/opt/php@7.4/sbin:$PATH"
...

I am preparing a small bash script, which accepts the PHP version (first arg) and action (second arg). For example dummy command may look like:

mycommand 7.4 comment

Which will only comment out the following lines form the file as

#export PATH="/usr/local/opt/php@7.4/bin:$PATH"
#export PATH="/usr/local/opt/php@7.4/sbin:$PATH"

And if you run

mycommand 7.1 uncomment

Will update only the following lines as

export PATH="/usr/local/opt/php@7.1/bin:$PATH"
export PATH="/usr/local/opt/php@7.1/sbin:$PATH"

So my question is how to use sed or any other command in .sh script which will parse the file.
My bash script looks like below (not working)

# File mycommand.sh
# ...
if [[ ! -z "$1" && ! -z "$2" && "$2" = "comment" ]]; then
    # remove multi # comments if there's any
    sed -i '' 's/#*export PATH="\/usr\/local\/opt\/php@$1/export PATH="\/usr\/local\/opt\/php@$1/g'   
    # Finally add a single # comment
    sed -i '' 's/*export PATH="\/usr\/local\/opt\/php@$1/#export PATH="\/usr\/local\/opt\/php@$1/g'    
fi
if [[ ! -z "$1" && ! -z "$2" && "$2" = "uncomment" ]]; then
    # remove one or more # comments
    sed -i '' 's/#*export PATH="\/usr\/local\/opt\/php@$1/export PATH="\/usr\/local\/opt\/php@$1/g'   
fi
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255

2 Answers2

3

This will work robustly, efficiently, and portably:

#!/usr/bin/env bash

infile=~/.zshrc tmp=$(mktemp) || exit 1

awk -v ver="$1" -v cmd="$2" ' index($0,"php@"ver"/") { if ( cmd == "comment" ) { sub(/^#*/,"#") } else if ( cmd == "uncomment" ) { sub(/^#+/,"") } else { printf "bad cmd: %s\n", cmd | "cat>&2" } } { print } ' "$infile" > "$tmp" && mv "$tmp" "$infile"

Change "php@"ver"/" to "export PATH=\"/usr/local/opt/php@"ver"/" if needed to avoid false matches with other lines that exist in your real input but you didn't show in your example.

If you have GNU awk you could use -i inplace instead of manually creating a tmp file.

Ed Morton
  • 31,617
1

The script that you use should be changed as follows:

if [[ ! -z "$1" && ! -z "$2" && "$2" = "comment" ]]; then
    # remove multi # comments if there's any
    sed -i "s/#*export PATH=\"\/usr\/local\/opt\/php@$1/export PATH=\"\/usr\/local\/opt\/php@$1/g" ~/.zshrc
    # Finally add a single # comment
    sed -i "s/export PATH=\"\/usr\/local\/opt\/php@$1/#export PATH=\"\/usr\/local\/opt\/php@$1/g" ~/.zshrc
fi
if [[ ! -z "$1" && ! -z "$2" && "$2" = "uncomment" ]]; then
    # remove one or more # comments
   sed -i "s/#*export PATH=\"\/usr\/local\/opt\/php@$1/export PATH=\"\/usr\/local\/opt\/php@$1/g" ~/.zshrc
fi

The problems were:

  1. The empty single quotes ('') after sed -i.

  2. You hadn't specified the sed input file (~/.zshrc).

  3. You needed to use double quotes (") instead of single ones (') for the shell to expand the $1 variable first. See this and this relevant questions.

  • 2
  • If you use a delimiter char other than /, e.g. :, then you won't have to escape every / in the regexp or replacement text. 2) There's no need to test for ! -z "$2" when you're explicitly testing for "$2" = "[un]comment". 3) You're using $1 in an unanchored regexp context so 1.2 will match the 142 in ...php@142yeehaw.
  • – Ed Morton Sep 09 '20 at 13:31