TIL: Slightly over engineered (😅) text replace function (sed multiple replacements)
Tags: shell, bash, sed, cli
Published: 2022-08-21 11:58:00

I recently had to read a lot of long bash scripts. Stumbled about a few things I found interesting and played a little with them. I will explain them later. But first behold the mighty text replace function i created will played around.

```shell
function repl {
    if [[ "$#" -eq 0 ]]; then
        echo "[ERROR]The parameter list needs at least 3 items. The file to work with, the string to replace and the new string! ";
        return
    fi
    local file="$1";
    if ! [[ -f "$file" ]]; then
            echo "[ERROR]The $file doesn't exists!";
            return
    fi
    shift;
	if [[ "$#" -eq 0 ]]; then
        echo "[ERROR]No parameters except the file found!";
        return
    fi
    if [[ $(expr "$#" % 2 ) -eq 1 ]]; then
        echo "[ERROR]Invalid Amount of parameters after the filename!"
        return
    fi
    old=""
    new=""
    replacements=""
    for item in "$@"
    do
        if [[ -z "$old" ]];
        then
            old=$item;
        elif [[ -n "$old" ]] && [[ -z "$new" ]]
        then
            new=$item;  
        fi
        if [[ -n "$old" ]] && [[ -n "$new" ]]
        then
            replacements="s/$old/$new/g; "
            sed -i "$replacements" $file
            unset old;
            unset new;
        fi
    done
    
}
```

It's a convenient wrapper function around a sed call to edit a file. For an example let's assume we have the following data file.

```txt
The Republic is in control of this planet now.
A Jedi will arrive shortly to solve the most important problems.
The local ewok tribes will be rescued.
```

This file needs a few corrections. In the bash shell normally sed can be used to edit this file in place.

```shell
sed -i s/Republic/Empire/g StarWars.txt
sed -i s/Jedi/Inquisitor/g StarWars.txt
sed -i s/rescued/murdered/g StarWars.txt
cat StarWars.txt
```
```txt
The Empire is in control of this planet now.
A Inquisitor will arrive shortly to solve the most important problems.
The local ewok tribes will be murdered.
```

Ah, that's better. 😉

You can even write this in one line.

```shell
sed -i s/Republic/Empire/g; s/Jedi/Inquisitor/g; s/rescued/murdered/g; StarWars.txt
```

After this discovering this, i relealized i might not need my function at all. But as an excersis it was nice and i learned  a few things.

So how does a call of my function look?

```shell
repl StarWars.txt Republic Empire Jedi Inquisitor rescued murdered
```

It's a little bit less verbose. And provides a little bit of additional error handling. But I need to check out sed's error handling. Maybe I can throw out my custom error handling.

**One big takeaway from this (again): What are you trying to do has likely been done before and there likely is a solution for this**.

But anyway, **What else did I learn?**  

**local:** If you put the local keyword in front of a variable. The variable is only visible in the block of code where it is declared.

```shell
function ltest {
    local test="Hello from the ltest function!";
    echo "$test"
}
ltest
echo "-->$test"
```

The result is:

```shell
$ ./test456.sh
Hello from the ltest function!
-->
```

**unset:** With this keyword you are able to clear previously set variables.

```shell
test="Hello!";
echo "-->$test"
unset test
echo "-->$test"
```

The result is:

```shell
$ ./test456.sh
-->Hello!
-->
```

**test command:** To test specific conditions for if statement bash has the test command. With this tool you are able to check file attributes and to string and number comparisons. It doesn't has output. But if it exits with a with 0 that means the supplied expression is true. A 1 stands for false.

```shell
test 1 -lt 2 && echo "true"
# There is an shortcut to write this
[ 1 -lt 2 ] && echo "true"
# You will see a third way of writing this.
[[ 1 -lt 2]] && echo "true"
# This will run another test command that bash has adopted from the kornshell.  
```

The kornshell test command seems to offer more functionality and the general recommendation seems to be to use it instead of the bash one.

**shift:** This shifts every parameter in you function to the left and puts every parameter in a lower position. The first parameter gets dropped.

```shell
echo "$@";
shift;
echo "$@";
shift;
echo "$@";
```

The result:

```shell
$ ./test456.sh 1 2 3
1 2 3
2 3
3
```

**exit/return:** To abort a script you can use the exit keyword. But this will close the shell window as well. If you don't want the window to close, just use the return keyword.