0

I have a file like this

www       IN       A       192.168.10.1
webmail   IN       A       192.168.10.2
mail      IN       A       192.168.10.3

I want to write a bash script that get 3 input like this

./script.sh network.com www 192.168.10.10

and script is

project=$1
server=$2
ip=$3
h=$(awk -F "$server       IN       A" '{print $2}' /home/forward.$project)
sed -i "s/$h/$ip/"

I want to find line that start with second input and replace (ip) with third input but my script doesn't work.

James Brown
  • 36,089
  • 7
  • 43
  • 59
Sara Azizi
  • 33
  • 6

3 Answers3

3

You can select the line in sed and do regexp replace.

project=$1
server=${2//./\\.}  # escape '.' to avoid problems with sed if $server contains some
ip=$3
sed  -E "/^$server /s/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/$ip/" "$1"
Alain Merigot
  • 10,667
  • 3
  • 18
  • 31
  • You can use the -i option to edit in place. `sed -i -E "/$server/s/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/$ip/" "$1"` – Alain Merigot Jan 27 '19 at 11:12
  • 1
    @AlainMerigot Note: `awk` isn't useless here because it comes with the nice advantage that it can perform exact string comparisons in contrast to sed which can only perform regex matches. Take `server=foo.bar.com` for example. Your sed command would match `foo1bar_com` and so on because the `.` has a special meaning in the regex. – hek2mgl Jan 27 '19 at 11:27
  • @hek2mgl A workaround would be to escape dots: `server="${2//./\.}"`. – SLePort Jan 27 '19 at 12:17
  • @hek2mgl I never meant that awk was useless in general. It is a great tool, but for such a simple problem, easier solutions can be found. As SLeport suggest, dots can be just backslashed by the shell. Edited answer accordingly. – Alain Merigot Jan 27 '19 at 12:24
  • 1
    You have more work to do on that regexp if you want to avoid false matches. See https://stackoverflow.com/q/29613304/1745001. You should also mention that it'll only work on seds that support `-E` for EREs (e.g. GNU sed and OSX/BSD sed), not in POSIX seds in general. – Ed Morton Jan 27 '19 at 13:22
  • 1
    As one example of a failure waiting to happen - try to change the IP address of the server `mail`. Notice the effect that has on the server `webmail`. – Ed Morton Jan 27 '19 at 13:37
  • I updated the script to differentiate mail and webmail (or whatever name which is a substring of another name). It is sufficient to say the the server name shoud be matched when it is a the start of a line and ends with a space. I do not understand what you want to do if the servername do not exist. Add `servername IN A $ip` at the end of your file ? – Alain Merigot Jan 28 '19 at 08:07
  • yes, if servername not find, script should add it at the end of file.but i don't know how! i can use if [[ $server="www" || $server=mail ...... ]], but it's a bad way to check servername because if a new servername create at next time,it is not in the if condition! how i can do it? – Sara Azizi Jan 28 '19 at 08:41
  • You can add at the end of the script `if ! grep "$server" "$project" >/dev/null ; then echo $server IN A $ip >> "$project" ; fi` The script test if server name exist in file and if not append whatever is require at the end. The `if` test the exit status of `grep` which is 1 if no line is found and 0 otherwise. `grep` output is redirected to `/devnull` to avoid spurious output if server name is present. – Alain Merigot Jan 28 '19 at 08:57
  • That grep suggestion would fail if the new server name was `mail` and `webmail` existed in the file, for example. It also would expose the shell variables to word splitting, globbing, and file name expansion so YMMV. – Ed Morton Jan 28 '19 at 13:44
2

Using awk:

$ echo network.com www 192.168.10.10 | 
awk '
NR==FNR {
    a=$2   # store hostname
    b=$3   # and ip
    next   # .
}
$1==a {    # if hostname matches
    sub(/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/,b)  # replace ip looking string
}1' - a_file                                                 # output

Output:

www       IN       A       192.168.10.10
webmail   IN       A       192.168.10.2
mail      IN       A       192.168.10.3

Edit:

A version which adds non-matching record to the end:

$ echo network.com www2 192.168.10.10 |
awk '
NR==FNR {
    a=$2           # store hostname
    b=$3           # and ip
    next           # not needed for this input but good practise
} 
FNR==1 { t=$0 }    # store a template record for later use
$1==a {            # if hostname matches
    sub(/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/,b)  # replace ip looking string
    f=1            # flag up when there was replace
}
1;                 # output
END {              # in the end 
    if(!f) {       # if there was no replace
        sub(/^[^ \t]+/,a,t)  # replace the template
        sub(/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/,b,t)
    print t        # and output it
    }
}' - a_file

Output:

www       IN       A       192.168.10.1
webmail   IN       A       192.168.10.2
mail      IN       A       192.168.10.3
www2       IN       A       192.168.10.10
James Brown
  • 36,089
  • 7
  • 43
  • 59
  • 1
    I would recommend to use `awk -v server=...` instead of using echo. – hek2mgl Jan 27 '19 at 11:29
  • 1
    Are you **sure** there can't be a server name like `foo_version_300.001.001.001`? That'd also produce very odd results if the caller accidentally(?) passed in an IP address that contained a `&` and it'd fail cryptically if they passed in something like `\1`. – Ed Morton Jan 27 '19 at 13:45
  • 1
    Whoops, forgot the `$` from the end of the regex. That should ensure that ip is in the last field. Better yet, there should probably be `_*$` (`_` denotes space) to deal with trailing space. – James Brown Jan 27 '19 at 15:43
  • it just replace ip. but when servername not found,it should add a line at the end of file like "server IN A IP"!!! – Sara Azizi Jan 28 '19 at 06:57
  • if servername not found,what happen to awk? – Sara Azizi Jan 28 '19 at 07:12
  • Updated with such a version. It's quick and dirty but should work. – James Brown Jan 28 '19 at 07:18
  • but i can not run awk here 'awk -f foo.awk - a_file' Because the inputs are taken from sms,and the script does not run directly. a script get input and send to this script! – Sara Azizi Jan 28 '19 at 07:41
  • Basically you can replace the `awk -f script` with `awk 'whatever was in the script'` but I updated. – James Brown Jan 28 '19 at 08:12
2
$ cat script.sh
#!/bin/env bash
project=$1
server=$2
ip=$3
file="file"     # change to "/home/forward.$project"

awk -v server="$server" -v ip="$ip" '
    $1 == server {
        sub(/[^[:space:]]+[[:space:]]*$/,"")
        $0 = $0 ip
        found = 1
    }
    { print; lastLine=$0 }
    END {
        if ( !found ) {
            match(lastLine,/^[^[:space:]]+[[:space:]]+/)
            gsub(/^[^[:space:]]+[[:space:]]+|[^[:space:]]+[[:space:]]*$/,"",lastLine)
            printf "%-*s %s%s\n", RLENGTH-1, server, lastLine, ip
        }
    }
' "$file"

.

$ ./script.sh network.com www 192.168.10.10
www       IN       A       192.168.10.10
webmail   IN       A       192.168.10.2
mail      IN       A       192.168.10.3

$ ./script.sh network.com fluffy 192.168.10.10
www       IN       A       192.168.10.1
webmail   IN       A       192.168.10.2
mail      IN       A       192.168.10.3
fluffy    IN       A       192.168.10.10

$ ./script.sh network.com super_long_server 192.168.10.10
www       IN       A       192.168.10.1
webmail   IN       A       192.168.10.2
mail      IN       A       192.168.10.3
super_long_server IN       A       192.168.10.10

The above will work portably and robustly (i.e. no chance of false matches or other failures due to partial matching or regexp characters or delimiters like / appearing in the input or arguments) using any awk in any shell on any UNIX box

To write back to the original file you could add -i inplace at the front of the awk script if you're using GNU awk, or add > tmp && mv tmp "$file" after the end of it otherwise.

Ed Morton
  • 188,023
  • 17
  • 78
  • 185
  • thank you i try this, how i can use if here?if the server not found?then add new line like another? – Sara Azizi Jan 28 '19 at 07:52
  • I updated it to add a modified version of the last line in the file if the server isn't found. It retains the spacing from the last line if possible (i.e. if the new server name is short enough to fit in the same space as the existing ones), otherwise it just adds the new server name with a blank after it and then the rest of the line is the original spacing. – Ed Morton Jan 28 '19 at 13:55