1

I'm trying to combine two lists containing names (if available) and emails with a standard email text in bash (shell) (I had to delete the irrelevant code as it contains some private info, so some of the code might look unusal.)

The first half of the code checks if there is a name list along with the email list. The second half combines only the email address and text if no name is available, if the name list is available it also 'tries' to combine the name, email and text.

f1 = email list and f2 = name list. As you can see in the first half of the code below, $f2 should show the names if the list is available but it does not show anything in the log file.

I been trying to sort this problem out for two days but nothing has worked. When names are available it always outputs as "Hello ..." when it should be "Hello John D..."


#FIRST HALF
if [ "$names" = "no" ]
then
    text="Hello..."

elif [ "$names" = "yes" ]
then
    text="Hello $f2..."
fi

#SECOND HALF
if [ "$names" = "no" ]
then
for i in $(cat $emaillist); do
    echo "$text" >> /root/log
    echo "$i" >> /root/log
done

elif [ "$names" = "yes" ]
then
    paste $emaillist $namelist | while IFS="$(printf '\t')" read -r f1 f2
do
    echo "$text" >> /root/log
    echo "$f1" >> /root/log
done
fi
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • `"$(printf '\t')"` is just `$'\t'`. Don't `for i in cat file`, using `while IFS= read -r i; do echo "$text"; echo "$i"; done <"$emailling" >>/root/log` is better – KamilCuk Sep 21 '18 at 01:28
  • Can you post example of the lists? I feel like this is XYproblem, while you should use `paste` on those lists with `grep` filtering. What is the value of "$names" $f1" "$f2" "$text" ? You set `f2` in while read below in a subshell, but use `"$f2"` above, don't know if it's relevant. Maybe try below: `while IFS=$'\t' read -r f1 f2; do echo "$text" >> /root/log; echo "$f1" >> /root/log; done < <(paste $emaillist $namelist)` ? – KamilCuk Sep 21 '18 at 01:32
  • BTW, an alternate answer (using `envsubst`) is given in https://stackoverflow.com/questions/10683349/forcing-bash-to-expand-variables-in-a-string-loaded-from-a-file. There are also some `eval`-based answers there, but those pose serious security risks and should be avoided. – Charles Duffy Sep 21 '18 at 01:44

1 Answers1

0

When you run text="Hello $f2", $f2 is looked up at the time of the assignment; an exact string is assigned to text, and only that exact string is used later, on echo "$text".

This is very desirable behavior: If shell variables' values could run arbitrary code, it would be impossible to write shell scripts that handled untrusted data safely... but it does mean that implementing your program requires some changes.


If you want to defer evaluation (looking up the value of $f2 at expansion time rather than assignment), don't use a shell variable at all: Use a function instead.

case $names in
  yes) write_greeting() { echo "Hello $name..."; };;
  *)   write_greeting() { echo "Hello..."; };;
esac

while read -r name <&3 && read -r email <&4; do
    write_greeting
    echo "$email"
done 3<"$namelist" 4<"$emaillist" >>/root/log

Some enhancements in the code above:

  • You don't need paste to read from two streams in lockstep; you can simply open them on different file descriptors (above, FDs 3 and 4 are chosen; only 0, 1 and 2 are reserved, so larger numbers could have been selected as well) with a separate read command for each.
  • Opening your output sink only once for the entire loop (by putting the redirection after the done) is far more efficient than re-opening it every time you want to write a single line.
  • Expansions, such as "$namelist" and "$emaillist", are always quoted; this makes code more reliable if dealing with filenames with unusual characters (including spaces and glob expressions), or if IFS is at a non-default value.
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441