title: Passing pre-prepared string of input arguments as input arguments to program (mailx)
body: <p>I'm creating a tool to parse an input file to re-construct a mailx command (server that creates the data for mailx cannot send emails, so I need to store data into a file so another server can rebuild the command and send it). I could output the whole command to a file and execute the file on the other server, but that's hardly secure / safe.... anyone could intercept the file and insert malicious stuff that would be run as root - this parsing tool is checking every minute for any files to parse and email using a systemd timer and service.</p>
  <p>I have created the file, using 'markers / separators' with this format:</p>
  <pre><code>-------MESSAGE START-------
  Email Body Text
  Goes Here
  -------MESSAGE END-------
  -------SUBJECT START-------
  Email Subject
  -------SUBJECT END-------
  -------ATTACHEMENT START-------
  path to file to attach if supplied
  -------ATTACHEMENT END-------
  -------S OPTS START-------
  list of mailx '-S' options eg from=EMAILNAME &lt;email@b.c&gt; or sendwait etc each one on a new line
  -------S OPTS END-------
  -------EMAIL LIST START-------
  string of recipient emails comma separated eg. email1,email2,email3 etc..
  -------EMAIL LIST END-------
  </code></pre>
  <p>And I have a program to parse this file and rebuild, and run the mailx command:</p>
  <pre class="lang-bash prettyprint-override"><code>#!/bin/bash
  
  ## Using systemd logging to journal for this as its now being called as part of a service
  ## See: https://serverfault.com/questions/573946/how-can-i-send-a-message-to-the-systemd-journal-froma-the-command-line (kkm answer)
  
  start_time=&quot;$(date +[%c])&quot;
  exec 4&gt;&amp;2 2&gt; &gt;(while read -r REPLY; do printf &gt;&amp;4 '&lt;3&gt;%s\n' &quot;$REPLY&quot;; done)
  echo &gt;&amp;4 &quot;&lt;5&gt;$start_time  -- Started gpfs_flag_email.sh&quot;
  
  trap_exit(){
      exec &gt;2&amp;
  }
  trap 'trap_exit' EXIT 
  
  email_flag_path=&quot;&lt;PATH TO LOCATION&gt;/email_flags/&quot;
  mailx_message_start=&quot;-------MESSAGE START-------&quot;
  mailx_message_end=&quot;-------MESSAGE END-------&quot;
  mailx_subject_start=&quot;-------SUBJECT START-------&quot;
  mailx_subject_end=&quot;-------SUBJECT END-------&quot;
  mailx_attachement_start=&quot;-------ATTACHEMENT START-------&quot;
  mailx_attachement_end=&quot;-------ATTACHEMENT END-------&quot;
  mailx_s_opts_start=&quot;-------S OPTS START-------&quot;
  mailx_s_opts_end=&quot;-------S OPTS END-------&quot;
  mailx_to_email_start=&quot;-------EMAIL LIST START-------&quot;
  mailx_to_email_end=&quot;-------EMAIL LIST END-------&quot;
  no_attachment=false
  no_additional_opts=false
  additional_args_switch=&quot;-S &quot;
  
  
  num_files_in_flag_path=&quot;$(find $email_flag_path -type f | wc -l)&quot;
  if [[ $num_files_in_flag_path -gt 0 ]]; then
      for file in $email_flag_path*; do
          email_message=&quot;$(awk &quot;/$mailx_message_start/,/$mailx_message_end/&quot; $file | egrep -v -- &quot;$mailx_message_start|$mailx_message_end&quot;)&quot;
          email_subject=&quot;$(awk &quot;/$mailx_subject_start/,/$mailx_subject_end/&quot; $file | egrep -v -- &quot;$mailx_subject_start|$mailx_subject_end&quot;)&quot;
          email_attachment=&quot;$(awk &quot;/$mailx_attachement_start/,/$mailx_attachement_end/&quot; $file | egrep -v -- &quot;$mailx_attachement_start|$mailx_attachement_end&quot;)&quot;
          email_additional_opts=&quot;$(awk &quot;/$mailx_s_opts_start/,/$mailx_s_opts_end/&quot; $file | egrep -v -- &quot;$mailx_s_opts_start|$mailx_s_opts_end&quot;)&quot;
          email_addresses=&quot;$(awk &quot;/$mailx_to_email_start/,/$mailx_to_email_end/&quot; $file | egrep -v -- &quot;$mailx_to_email_start|$mailx_to_email_end&quot; | tr -d '\n')&quot;
          if [[ -z &quot;$email_message&quot; || -z &quot;$email_subject&quot; || -z &quot;$email_addresses&quot; ]]; then
              echo &gt;&amp;2 &quot;MISSING DETAILS IN INPUT FILE $file.... Exiting With Error&quot;
              exit 1
          fi
  
          if [[ -z &quot;$email_attachment&quot; ]]; then
              no_attachment=true
          fi
          if [[ -z &quot;$email_additional_opts&quot; ]]; then
              no_additional_opts=true
          else
              additional_opts_string=&quot;&quot;
              while read -r line; do 
                  if [[ ! $line =~ [^[:space:]] ]]; then
                      continue
                  else
                      additional_opts_string=&quot;$additional_opts_string \&quot;${additional_args_switch} '$line'\&quot;&quot;
                  fi
              done &lt;&lt;&lt;&quot;$(echo &quot;$email_additional_opts&quot;)&quot;
              additional_opts_string=&quot;$(echo ${additional_opts_string:1} | tr -d '\n')&quot;
          fi
          if [[ $no_attachment = true ]]; then
              if [[ $no_additional_opts = true ]]; then
                  echo &quot;$email_message&quot; | mailx -s &quot;$email_subject&quot; $email_addresses
              else
                  echo &quot;$email_message&quot; | mailx -s &quot;$email_subject&quot; $additional_opts_string $email_addresses
              fi
          else
              if [[ $no_additional_opts = true ]]; then
                  echo &quot;$email_message&quot; | mailx -s &quot;$email_subject&quot; -a $email_attachment $email_addresses
              else
                  echo &quot;$email_message&quot; | mailx -s &quot;$email_subject&quot; -a $email_attachment $additional_opts_string $email_addresses
              fi
          fi          
      done
  fi
  find $email_flag_path -type f -delete
  exit 0
  </code></pre>
  <p>There is however an issue with the above that I just can work out..... the -S opts completely screw up the email headers and I end up with emails being sent to the wrong people (I have set a reply-to and from options, but the email header is jumbled and the reply-to email ends up in the to: field) like this:</p>
  <pre><code>To: Me &lt;a@b.com&gt;, sendwait@a.lan , -S@a.lan &lt;-s@a.lan&gt;, another-email &lt;another@b.com&gt; 
  </code></pre>
  <p>All I'm trying to do is rebuild the command as if I'd typed it in the CLI:</p>
  <pre class="lang-bash prettyprint-override"><code>echo &quot;EMAIL BODY MESSAGE&quot; | mailx -s &quot;EMAIL SUBJECT&quot; -S &quot;from=EMAILNAME &lt;email@b.c&gt;&quot; -S &quot;replyto=EMAILNAME &lt;email@b.c&gt;&quot; -S sendwait my.email@b.com
  </code></pre>
  <p>I've tried quoting in ' ' and  &quot; &quot;  quoting the other mailx parameters around it etc etc... I have written other tools that pass variables as input arguments so I just cannot understand how I'm screwing this up.....</p>
  <p>Any help would be appreciated...</p>
  <p><em><strong>EDIT</strong></em></p>
  <p>Thanks to Gordon Davisson's really helpful comments I was able to not only fix it but understand the fix as well using an array and appropriately quoting the variables... the tip about using printf was really really helpful in helping me understand what I was doing wrong and how to correct it :P</p>
  <pre class="lang-bash prettyprint-override"><code>declare -a mailx_args_array
  
  ...
  num_files_in_flag_path=&quot;$(find $email_flag_path -type f | wc -l)&quot;
  if [[ $num_files_in_flag_path -gt 0 ]]; then
      for file in $email_flag_path*; do
          ....
          mailx_args_array+=( -s &quot;$email_subject&quot; )
                  
          if [[ ! -z &quot;$email_attachment&quot; ]]; then
              mailx_args_array+=( -a &quot;$email_attachment&quot; )
          fi
          if [[ ! -z &quot;$email_additional_s_opts&quot; ]]; then
              while read -r s_opt_line; do
                  mailx_args_array+=( -S &quot;$s_opt_line&quot; )
              done &lt; &lt;(echo &quot;$email_additional_s_opts&quot;)
          fi
          mailx_args_array+=( &quot;$email_addresses&quot; )
          
          echo &quot;$email_message&quot; | mailx &quot;${mailx_args_array[@]}&quot;
      done
  fi
  </code></pre>