1

have anybody a smarter way to prepare template files via bash script?

This is the current script:

#!/bin/bash
projectDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
echo "Project root: $projectDir"
echo "> Is this the project root directory? (y/n)"
read validProjectPath
#if [ "$isProjectDir" != "y" ] then exit 1 fi
echo "> Your OS user name (e.g. peter):"
read userName
echo "> VM IPv4 address (e.g. 192.168.178.2):"
read ipv4

# setup
echo "prepare configuration files..."
for f in $(find "$projectDir/vagrant" -name "*.template" -type f); do 
    toFile=${f/.template/}
    cp $f $f.bak
    sed "s/_CHANGE_PRODIR_/$projectDir/g" $f.bak > $f.bak1
    sed "s/_CHANGE_USERNAME_/$userName/g" $f.bak1 > $f.bak2
    sed "s/_CHANGE_IPV4_/$ipv4/g" $f.bak2 > $toFile
    rm -f $f.bak
    rm -f $f.bak1
    rm -f $f.bak2
done
echo "... done!"

Thanks for reply. Jim

1
  • oh yeah, the template files are redundant (sed -i -e). I've currently the error "prepare configuration files... sed: 1: "s/_CHANGE_PRODIR_//User ...": bad flag in substitute command: 'U' sed: 1: "s/_CHANGE_PRODIR_//User ...": bad flag in substitute command: 'U' ... done!" What's wrong and why is it printed two times (foreach case)? Commented Sep 1, 2014 at 6:38

4 Answers 4

3

This whole temporary file business is entirely unnecessary.

for f in $(find "$projectDir/vagrant" -name "*.template" -type f); do
    sed -e "s%_CHANGE_PRODIR_%$projectDir%g" \
        -e "s%_CHANGE_USERNAME_%$userName%g" \
        -e "s%_CHANGE_IPV4_%$ipv4%g" "$f"> "${f/.template/}"
done

If your sed dialect doesn't like multiple -e options, try putting the entire script between double quotes with newlines or semicolons between the commands.

    sed "s%_CHANGE_PRODIR_%$projectDir%g
         s%_CHANGE_USERNAME_%$userName%g
         s%_CHANGE_IPV4_%$ipv4%g" "$f"> "${f/.template/}"

(Yes, that's a long double-quoted string spanning three physical lines.)

Update: I also changed the delimiter to % so that the substitution value can safely contain a slash. If you want to permit percent signs as well, you'll obviously have to come up with another delimiter still (colon, exclamation mark, semicolon are all popular).

Sign up to request clarification or add additional context in comments.

Comments

2

Well, I recommend you run the sed commands with -i for in-place, and surround your output file with "" so it supports files with spaces in the path and combining it into one sed invocation. That is something like,

cp "$f" "$toFile"
sed -i -e "s/_CHANGE_PRODIR_/$projectDir/g" \
       -e "s/_CHANGE_USERNAME_/$userName/g" \
       -e "s/_CHANGE_IPV4_/$ipv4/g" "$toFile"
# rm -f $f.bak
# rm -f $f.bak1
# rm -f $f.bak2

Also,

echo "> Your OS user name (e.g. peter):"
read userName

Might be

userName=$(whoami)

Depending on what you're using it for.

Comments

0

In addition to the other suggestions, one way to test/verify input in a more reliable manner is to convert the string data to lower-case and then limit the test to the substring you need:

echo "> Is this the project root directory? (y/n)"
read validProjectPath
ans="${validProjectPath,,}"         # conversion to lower-case
[ "${ans:0:1}" != "y" ] && exit 1   # test using compound-command

You can eliminate your .bakX file by using sed to 'edit-in-place' -i:

# setup
echo "prepare configuration files..."
for f in $(find "$projectDir/vagrant" -name "*.template" -type f); do 
    toFile=${f/.template/}
    cp "$f" "$toFile"
    sed -i "s/_CHANGE_PRODIR_/$projectDir/g" "$toFile"
    sed -i "s/_CHANGE_USERNAME_/$userName/g" "$toFile"
    sed -i "s/_CHANGE_IPV4_/$ipv4/g" "$toFile"
done

The only other thought (not recommendation) would be use readlink -f ${0} to return the calling script name and then parse for the path with either dirname or simple substring extraction ${fullname%/*}. What you have seem to work fine using BASH_SOURCE.

1 Comment

case $ans in [Yy]*) *;; *) exit 1;; esac avoids the case conversion. Matter of taste I suppose.
0

Instead of creating unnecessary temporary files, you can use awk with gsub to do global replacement of a file.

for f in $(find "$projectDir/vagrant" -name "*.template" -type f); do
    toFile=${f%.template}
    awk -v p="$projectDir" -v u="$userName" -v ip=$ipv4 '{
        gsub("_CHANGE_PRODIR_", p)
        gsub("_CHANGE_USERNAME_", u)
        gsub("_CHANGE_IPV4_", ip)
    }' "$f" > "$toFile"
done

It might be better not to rely on the user having the VM IP address memorized. An array of possible IP addresses can be created using awk:

ips=($(awk '{
        m=match($0,/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/,a)
        if(m && a[0] !~ /^127./)
            print a[0]
    }' < <(ifconfig)
))

Then, a select loop can be used for the user to select the IP from a list.

echo "Select the VM IPv4 address:"
select opt in ${ips[@]}; do
    case $opt in
        ${ips[0]})
            ipv4=$opt
            break
            ;;
        ${ips[1]})
            ipv4=$opt
            break
            ;;
        ${ips[2]})
            ipv4=$opt
            break
            ;;
       *)
           echo invalid option
           ;;
    esac
done

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.