2

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

...
#export PATH="/usr/local/opt/[email protected]/bin:$PATH"
#export PATH="/usr/local/opt/[email protected]/sbin:$PATH"
##export PATH="/usr/local/opt/[email protected]/bin:$PATH"
##export PATH="/usr/local/opt/[email protected]/sbin:$PATH"
#export PATH="/usr/local/opt/[email protected]/bin:$PATH"
#export PATH="/usr/local/opt/[email protected]/sbin:$PATH"
#export PATH="/usr/local/opt/[email protected]/bin:$PATH"
#export PATH="/usr/local/opt/[email protected]/sbin:$PATH"
export PATH="/usr/local/opt/[email protected]/bin:$PATH"
export PATH="/usr/local/opt/[email protected]/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/[email protected]/bin:$PATH"
#export PATH="/usr/local/opt/[email protected]/sbin:$PATH"

And if you run

mycommand 7.1 uncomment

Will update only the following lines as

export PATH="/usr/local/opt/[email protected]/bin:$PATH"
export PATH="/usr/local/opt/[email protected]/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
  • 66,199
  • 35
  • 114
  • 250
MagePsycho
  • 485
  • 1
  • 6
  • 14

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
  • 28,789
  • 5
  • 20
  • 47
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
    1) 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