skip to Main Content

I stuck on the following problem…

I have a shell script that is supposed to create docker networks that do not yet exist from a variable file. The challenge is to create the missing network with this associated subnet, which is also grepped from the same file.

Here is the script:

#!/bin/bash

# create tempfiles
TMPFILE=$(mktemp)
TMPFILE2=$(mktemp)
TMPFILE3=$(mktemp)

# search for networks in .env file with VARIABLE_NAME
< .env grep '_NET=' | grep -v '#' | sort | cut -d "=" -f2 > "${TMPFILE}" 2>&1

# search for subnetworks in .env file with VARIABLE_NAME
< .env grep '_NET_SUBNET=' | grep -v '#' | sort | sed 's/[^0-9]*//' > "${TMPFILE2}" 2>&1

# Merge
paste -d ' ' ${TMPFILE2} ${TMPFILE} >> ${TMPFILE3}

mapfile -t lines_array < "${TMPFILE3}"

for line in "${lines_array[@]}"; do
    #echo "$line" | awk '{print $2}'
    docker network ls | grep "$line"
    if [ "$(docker network ls | grep -c "$line" | awk '{print $2}' )" == 0 ]
    then
        echo "network $line not exist"
        echo "docker network create -d bridge --subnet=$line"
    else echo "network $line exists"
    fi

done

variables from .env file:

# Network
## traefik proxy network
TF_PROXY_NET=traefik_proxy_net
## subnet network traefik extern
TF_PROXY_NET_SUBNET=172.30.0.0/16
## crowdsex network
CS_NET=crowdsec_net
## subnet network crowdsec
CS_NET_SUBNET=172.31.0.0/16
## docker-socket-proxy network
DSP_NET=dsp_net
##  subnet network docker-socket-proxy
DSP_NET_SUBNET=172.32.0.0/16

The idea are:

  • grep the complete variable from the network, sort it and put it in a temp file (TMPFILE)
  • grep the complete subnetwork variable from file, sort it and put it in a temp file (TMPFILE2)
    = the merge the output from (TMPFILE2) with (TMPFILE) and put it in a file (TMPFILE3)

output TMPFILE3:

172.31.0.0/16 crowdsec_net
172.32.0.0/16 dsp_net
172.30.0.0/16 traefik_proxy_net

up to this point (maybe a little bit complicated) it’s ok…

output "Docker network ls":

NETWORK ID     NAME                DRIVER    SCOPE
befbc543b385   bridge              bridge    local
34604e449c61   host                host      local
489eb654d1de   none                null      local
b0520be90b41   traefik_proxy_net   bridge    local
1709315fb264   wikijs_default      bridge    local

What I want but doesn’t work in the array is the second field from the variable "line"
example: line = 172.31.0.0/16 crowdsec_net
second field: crowdsec_net

that works:

echo $line" | awk '{print $2}'
traefik_proxy_net
crowdsec_net
dsp_net

that doesn’t work:

if [ "$(docker network ls | grep -c "$line" | awk '{print $2}' )" == 0 ]

because the $line variable doesnt pay attention for the awk command. means that the complete string will used and the docker network cant be found.

Can anyone help me or suggest me a better alternate for this case?
Thanks in advance

EDIT

I’ve found a solution for now 🙂

that works:

mapfile -t lines_array < "${TMPFILE3}"

for line in "${lines_array[@]}"; do

    docker network ls | grep "$line"
    if [ "$(docker network ls --format "{{.Name}}" | while read i; do echo $i $(docker network inspect $i | grep Subnet | tr -d '"' | sed -e 's/<Subnet>//g ' | tr -d ":" ); done | grep "$line" | wc -l )" == 0 ]
    then
        echo "network $line not exist"
        echo "docker network create -d bridge --subnet=$line"
    else echo "network $line exists"
    fi

the command searching for docker networks and the associates subnet with the same string/output as in the TMPFILE5

output:

network crowdsec_net 172.31.0.0/16 not exist
docker network create -d bridge --subnet=crowdsec_net 172.31.0.0/16
network dsp_net 172.32.0.0/16 not exist
docker network create -d bridge --subnet=dsp_net 172.32.0.0/16
network traefik_proxy_net 172.30.0.0/16 exists

EDIT 2

Thanks @Gilles Quénot & @markp-fuso

finally, this is the working script (look at the post from @markp-fuso):

#!/bin/bash

while read -r status line
do
    printf "n########## %s : %sn" "${status}" "${line}"

    if [[ "${status}" = 'create' ]]
    then
        echo "network '${line} not exist"
        echo "docker network create -d bridge --subnet=${line}"
    else
        echo "network ${line} exists"
    fi
done < <( awk -f env.awk <(docker network ls) .env )

great work, thanks @all 🙂

2

Answers


  1. Instead of:

    if [ "$(docker network ls | grep -c "$line" | awk '{print $2}' )" == 0 ]
    

    you could do with bash’s arithmetic operator:

    count=$(docker network ls | awk -v line=$line '$2 ~ line{c++}END{print c}')
    if ((count == 0)); then
        echo "0"
    else
        echo "> 0"
    fi
    

    ((...)) and $((...)) are arithmetic commands, which returns an exit status of 0 if the expression is nonzero, or 1 if the expression is zero. Also used as a synonym for "let", if side effects (assignments) are needed.

    See http://mywiki.wooledge.org/ArithmeticExpression

    Login or Signup to reply.
  2. Once you bring awk into the mix there’s a good chance you can eliminate calls to grep, cut and sed, not to mention reduce the number of times .env has to be parsed. In this case a single awk script also allows us to improve overall performance by eliminating a lot of subshell invocations.

    To simulate OP’s docker network ls call:

    $ cat docker.out
    NETWORK ID     NAME                DRIVER    SCOPE
    befbc543b385   bridge              bridge    local
    34604e449c61   host                host      local
    489eb654d1de   none                null      local
    b0520be90b41   traefik_proxy_net   bridge    local
    1709315fb264   wikijs_default      bridge    local
    

    One awk idea that replaces all of OP’s current code (except for the final sets of echo calls):

    $ cat env.awk
    FNR==NR        { if (FNR>1)                          # "docker network ls" output; skip header then ...
                        net[$2]                          # save 2nd field as index in net[] array
                     next
                   }
    FNR==1         { FS = "=" }                          # 2nd file: redefine input field separator
    /#/            { next     }                          # skip lines with comments
                   { id = $1    }                        # first "=" delimited field; bogus assignment for lines we are not interested in 
    /_NET=/        { sub(/_NET/,"",id)                   # line contains "_NET="? strip "_NET" to get id then ...
                     names[id] = $2                      # save name
                   }
    /_NET_SUBNET=/ { sub(/_NET_SUBNET/,"",id)            # same as for "_NET="; strip down to id then ...
                     ips[id] = $2                        # save ip address
                   }
    END            { for (id in names)                   # loop through array indices
                        
                          # if names[i] is an index in the net[] array then prepend "exists" else "create"
                          print (names[id] in net ? "exists" : "create"), ips[id], names[id]
                   }
    

    Taking for a test drive:

    $ awk -f env.awk <(cat docker.out) .env
    exists 172.30.0.0/16 traefik_proxy_net
    create 172.31.0.0/16 crowdsec_net
    create 172.32.0.0/16 dsp_net
    

    NOTES:

    • OP would replace <(cat docker.out) with <(docket network ls)
    • if OP needs the data sorted in a particular order the output can be piped to sort as needed

    Replacing OP’s for loop with a comparable while loop, and feeding the while loop from the awk output:

    while read -r status line
    do
        printf "n########## %s : %sn" "${status}" "${line}"
    
        if [[ "${status}" = 'create' ]]
        then
            echo "network '${line} not exist"
            echo "docker network create -d bridge --subnet=${line}"
        else
            echo "network ${line} exists"
        fi
    done < <( awk -f env.awk <(cat docker.out) .env )
    

    Taking for a test drive:

    ########## exists : 172.30.0.0/16 traefik_proxy_net
    network 172.30.0.0/16 traefik_proxy_net exists
    
    ########## create : 172.31.0.0/16 crowdsec_net
    network '172.31.0.0/16 crowdsec_net not exist
    docker network create -d bridge --subnet=172.31.0.0/16 crowdsec_net
    
    ########## create : 172.32.0.0/16 dsp_net
    network '172.32.0.0/16 dsp_net not exist
    docker network create -d bridge --subnet=172.32.0.0/16 dsp_net
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search