TL,DR: don't use bash, you don't need it.
There are two problems. Your biggest one is the quoting of the argument. Another one may be the command line length limit.
The main problem
TL,DR:
- Don't invoke a shell when you don't need one.
- More generally, avoid getting into a situation where you need something parsed multiple times. It's often hard to get right.
You are running a bash command like
./email.sh '[email protected]' 'Something happened' '<html>
<body>
You are heading for trouble.
</body>
</html>'
Bash parses this command and executes the program ./email.sh
with three arguments. The program ./email.sh
happens to run another instance of bash, but this is just a coincidence: you didn't have to use the same shell for both purposes.
But what if you change the text in the HTML to contain a single quote?
./email.sh '[email protected]' 'Something happened' '<html>
<body>
You're heading for trouble.
</body>
</html>'
Now the third argument to passed to ./email.sh
ends with Youre
. There's a fourth argument heading
, a fifth argument for
and a sixth argument trouble.
Then, once ./email.sh
returns, bash parses and tries to run the command </body>
, resulting in the errors
bash: -c: line 4: syntax error near unexpected token `newline'
bash: -c: line 4: `</body>'
With some slightly different HTML, you could have all kinds of different errors. And you could end up running arbitrary commands on your machine, perhaps commands specified by someone malicious who can inject some content into the email. Your code is a giant security vulnerability!
A short solution
That intermediate bash is useless. It doesn't help you at all, and adds a giant bug.
def sendmail(subject, to, body):
subprocess.Popen(["./email.sh", to, subject, body])
This is simpler, and actually works.
A more robust solution
If the email body is too long, you may run into a limit on the length of the command line (command name, arguments, environment variables). The limit is the number of bytes shown by getconf ARG_MAX
.
To avoid this, you can pass the email body as input rather than as a command line parameter.
def sendmail(subject, to, body):
subprocess.Popen(["./email.sh", to, subject], input=body.encode('utf-8'))
(Replace utf-8
by the correct character set if needed.)
#!/bin/bash
# args: 1 - TO address
# 2 - subject
# stdin: body
(
echo "To: $1";
echo "Subject: $2";
echo "Content-Type: text/html";
echo "MIME-Version: 1.0";
echo "";
cat
) | sendmail -t
Why use bash at all?
If that email-sending script is just what you showed, using bash is pointless. You can do this all in Python.
def sendmail(subject, to, body):
mail = f"""\
To: f{to}
Subject: f{subject}
Content-Type: text/html
MIME-Version: 1.0
f{body}"""
subprocess.Popen(["sendmail", "-t"], input=mail.encode('utf-8'))
python
usingsmtplib
?echo "$3"
,cat
smtplib
can connect with any SMTP relay, not just localhost. Set the connection to the same relay thatsendmail
is using and you should be fine.