2

I am passing an html string as an input to a bash script:

Script.py


def bash(commandStr):
    subprocess.Popen(commandStr, shell=True, executable="/bin/bash")
  
def sendmail(subject, to, body):
    bash(f"./email.sh '{to}' '{subject}' '{body}'")

email.sh

#!/bin/bash

# args: 1 - TO address
#       2 - subject
#       3 - body

(
echo "To: $1";
echo "Subject: $2";
echo "Content-Type: text/html";
echo "MIME-Version: 1.0";
echo "";
echo "$3";
) | sendmail -t

This successfully sends only a subset of the html, then I get the error: File name too long.

I would like to supply a full HTML string as an argument to my bash script. I am new to bash scripting, so there may be a better way of going about this that I don't know about. My one restriction is that I do need to call the bash script from python. How can I pass a long HTML string as an argument to a bash script?

5
  • Can you send the email directly from python using smtplib?
    – doneal24
    Commented Feb 25, 2022 at 20:02
  • there is no smtp server configured on this machine, so I cannot Commented Feb 25, 2022 at 20:17
  • I don't know the python invocation, but send the email to bash via the stdio channels. Then the bash script can do, instead of echo "$3", cat Commented Feb 25, 2022 at 20:52
  • 1
    smtplib can connect with any SMTP relay, not just localhost. Set the connection to the same relay that sendmail is using and you should be fine.
    – doneal24
    Commented Feb 26, 2022 at 2:06
  • You could also open a python subprocess talking to sendmail directly instead of using bash as an intermediate.
    – doneal24
    Commented Feb 26, 2022 at 2:10

1 Answer 1

2

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:

  1. Don't invoke a shell when you don't need one.
  2. 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!

xkcd 327

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'))
1
  • This is a great answer! I have indeed already encountered the problem of quotations within the html that you mentioned! Thx for getting me on the right path here, I knew something must've been conceptually off with my approach. Commented Feb 25, 2022 at 22:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .