2

Background

I am writing a function to append to a file in my $HOME directory called .bash.local.

➡️ .bash.local is sourced via .bash_profile.

However, I want to conditionally append to .bash.local if and only if the file does not already contain the contents of $BASH_CONFIGS.

Things to Keep in Mind

My operating system of choice is MacOS Mojave, thus certain versions of command line applications will be different (e.g. grep on a Mac is BSD grep and not GNU grep).

⚠️ append_to_bash_local()

append_to_bash_local() {
    local LOCAL_BASH_CONFIG_FILE="$HOME"/.bash.local

    declare -r BASH_CONFIGS="
# TOOL_NAME - TOOL_DESCRIPTION.
# Add tool configurations here
"

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    # If needed, add the necessary configs in the
    # local shell configuration file.

    if ! grep "^$BASH_CONFIGS" < "$LOCAL_BASH_CONFIG_FILE" &> /dev/null; then
        # this block never runs, even if the contents of $BASH_CONFIG
        # are NOT present in $HOME/.bash.local
    fi

}
Nicholas Adamou
  • 711
  • 10
  • 23

2 Answers2

3

You are close. Anyway, small fixes:

  • use zero separated grep -Z to match patterns with newlines. If you think of it, it is a bit of hacky, but works every time.
  • use -q to grep to make it silent.
  • and don't input the file to stdin to grep, just tell grep the filename.
  • check if a file does exists

append_to_bash_local() {
     local LOCAL_BASH_CONFIG_FILE="$HOME"/.bash.local
     declare -r BASH_CONFIGS="
# TOOL_NAME - TOOL_DESCRIPTION.
# Add tool configurations here
"

if [[ "$(uname)" == "Linux" ]]; then
    if
           # if a file does not exists
           [ ! -e "$LOCAL_BASH_CONFIG_FILE" ] ||
           # if it doesn't have the content of BASH_CONFIGS
           ! grep -q -z "$BASH_CONFIGS" "$LOCAL_BASH_CONFIG_FILE"; then
        echo appending
        printf '%s\n' "$BASH_CONFIGS" >> "$LOCAL_BASH_CONFIG_FILE"
    else
        echo not appending
    fi

elif [[ "$(uname)" == "Darwin" ]]; then
    if 
            [ ! -e "$LOCAL_BASH_CONFIG_FILE" ] || 
            ! grep -q "$(<<<"$BASH_CONFIGS" tr '\n' '\01')" < <(
                less "$LOCAL_BASH_CONFIG_FILE" | tr '\n' '\01'
            ); then
        echo appending
        printf '%s\n' "$BASH_CONFIGS" >> "$LOCAL_BASH_CONFIG_FILE"
    else
       echo not appending
    fi
fi

}
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Okay, so after some initial testing, the `grep -z` command returns `grep: invalid option -- z`. However, according to `grep`'s man page, `-z` & `-Z` **do** exist and have the following behavior: `Force grep to behave as zgrep`. Moreover, however, when adding `zgrep -q` or `grep -q -Z` instead of `grep -q -z` in your code block above, it still returns `not appending` even if the contents of `$BASH_CONFIGS` **do not** exist within `$HOME/.bash.local`. I am on a Mac running MacOS Mojave & the results after running `grep -V` is: `grep (BSD grep) 2.5.1-FreeBSD`. Any thoughts? – Nicholas Adamou Dec 23 '18 at 03:25
  • According [How do I grep for multiple patterns on multiple lines?](https://askubuntu.com/q/551338/552275), it appears that Linux-based OS's such as `ubuntu` may have a different `grep` version installed because the man page results for `grep -z` is different than the man page for `grep` on Mojave. As seen on the page [How do I grep for multiple patterns on multiple lines?](https://askubuntu.com/q/551338/552275): `Treat the input as a set of lines, each terminated by a zero byte (the ASCII NUL character) instead of a newline.` While on Mojave: `Force grep to behave as zgrep`. – Nicholas Adamou Dec 23 '18 at 03:38
  • `GNU grep` is different than `BSD grep` which they both have a different set of options for `-z` or `-Z`, thus, since I'm on a Mac and am without `GNU grep` installed by default, _is there a different solution available that you know of that will work on a Mac?_ See: https://stackoverflow.com/a/49805153/5290011 or https://stackoverflow.com/a/152755/5290011. – Nicholas Adamou Dec 23 '18 at 03:53
  • 2
    Well, then tricky, you can substitute newlines for an unreadable character in the file, then grep for the pattern `grep -q "$(<<<"$BASH_CONFIGS" tr '\n' '\01')" < <(tr '\n' '\01' "$LOCAL_BASH_CONFIG_FILE")`. You could `sed -z`, but you would need to omit the `/` character unless you program some escaping. `sed -z -n "/${BASH_CONFIGS}/p" "$LOCAL_BASH_CONFIG_FILE"` – KamilCuk Dec 23 '18 at 07:04
0

With a little help from @kamil-cuk, I was able to create a more improved version of append_to_bash_local().

⚠️ Please note that because I am on MacOS Mojave & have both BSD-grep & BSD-sed installed by default & neither GNU-grep nor GNU-sed installed, I was forced to find a different solution for truncating new-line characters with the ASCII NUL character. Thankfully, @kamil-cuk had a hacky solution using tr.

Below is the improved version of append_to_bash_local().

⚠️ append_to_bash_local()

  1. First, check if the file does not exist with [ ! -e "$LOCAL_BASH_CONFIG_FILE" ].
  2. Second, use -q with grep to make it silent.
    • ➡️ Make sure to put a ! (not) in the front of grep -q to make sure the status of the output is inverted since we are checking if the file does not contain the contents of $BASH_CONFIGS.
  3. Lastly, use "$(<<<"$BASH_CONFIGS" tr '\n' '\01')" < <(less "$LOCAL_BASH_CONFIG_FILE" | tr '\n' '\01') in order to truncate new-line characters with the ASCII NUL character in both $BASH_CONFIGS & $LOCAL_BASH_CONFIG_FILE.

    append_to_bash_local() {
    
        local LOCAL_BASH_CONFIG_FILE="$HOME"/.bash.local
    
        declare -r BASH_CONFIGS="
        # TOOL_NAME - TOOL_DESCRIPTION.
        # Add tool configurations here
        "
    
        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    
        # If needed, add the necessary configs in the
        # local shell configuration file.
    
        if [ ! -e "$LOCAL_BASH_CONFIG_FILE" ] || ! grep -q "$(<<<"$BASH_CONFIGS" tr '\n' '\01')" < <(less "$LOCAL_BASH_CONFIG_FILE" | tr '\n' '\01'); then
            printf '%s\n' "$BASH_CONFIGS" >> "$LOCAL_BASH_CONFIG_FILE"
        fi
    
    }
    
Nicholas Adamou
  • 711
  • 10
  • 23