axiac@web

sh: 1: -t: not found

The problem

Recently I had to deal with a PHP script that was designed to run as a cron job. It does a simple processing: gets some data, creates a CSV file and sends the file using email.

When I manually ran it on the development environment everything went well except for the email not being sent and this strange line of text sneaked in the script’s output:

sh: 1: -t: not found

The email not being sent wasn’t a surprise, I knew that for some (technical or business) reason, the PHP code is unable to send emails from that particular machine. The strange error message (strange for me) is a different story. It was not generated by my code or by any of its dependencies.

Sure, a quick search on Google could bring some light over the mystery; this is how you reached this page, isn’t it? Eventually I discovered the root cause of the problem by getting PHP info from the command line.

$ php -i | grep sendmail
sendmail_from => no value => no value
sendmail_path => -t -i => -t -i
Path to sendmail => -t -i

The email is not sent because the property sendmail_from is not set in php.ini.

The solution

The fix is really easy: open /etc/php.ini, search for key sendmail_path and make sure it reads:

sendmail_path=sendmail -t -i

Additional or different parameters can be added after the command name (sendmail). If you use a different mail program on your system (qmail, postfix or something else) then replace sendmail with the name of the mail program you use. This is usually not necessary because most of the alternative mail programs create a link called sendmail that points to the actual mail program (for compatibility.)

The mystery

However, what is the meaning of each component of the error message?

I peeked into the source code of PHP (version 5.4.16) on GitHub and this is what I found. The code that handles a call to the mail() function is located in file ext/standard/mail.c. On line 324 it reads:

mail.cSee the complete file on GitHub
324
sendmail = popen(sendmail_cmd, "w");

Several lines above, the variable sendmail_cmd is initialized with the value of sendmail_path defined in php.ini followed by the value of the 5th parameter of function mail(), if provided (its name is $additional_parameters.)
In my case, the first parameter of popen() was -t -i (no extra parameters were provided on the call to mail().)

Who is popen() and what does it do for a living?

popen() is a system function from the standard C library. Details can be found by running man popen in an Unix/Linux shell. See some relevant fragmens below:

SYNOPSIS
  FILE *
  popen(const char *command, const char *mode);

DESCRIPTION
  The popen() function ``opens'' a process by creating a bidirectional pipe, forking,
  and invoking the shell.
  ...
  The `command` argument is a (...) string containing a shell command line. This command
  is passed to `/bin/sh`, using the `-c` flag; interpretation, if any, is performed
  by the shell.

This means popen() creates an instance of the classic Bourne shell /bin/sh and tells it to run the command -t -i. I tried this on the devel machine:

axiac:~$ /bin/sh
$ -t -i
sh: 1: -t: not found

Et voilĂ ! The error message is there!

Further investigation

I ran this command on several other Linux/Unix systems (including a Mac OS X Lion) and some of them provided the same output while on the others the 1: part was missing. Further investigation revealed that on some of the systems that produce the error message containing the 1: fragment, /bin/sh is, in fact, dash.

I run several other commands on dash’s command line and I found out that 1 from 1: is the number of the command it (attempted to) ran since it started.

I never worked with dash before (except when I thought /bin/sh is a symlink to bash and in fact it was dash.) I cannot tell why dash numbers the commands it runs and how can it be configured to do (or to not do) it so.

Conclusion

Investigating this cryptic error message we found some interesting things about the implementation of PHP. On a Linux/Unix system there are at least three ways to launch another program: fork()+exec(), system(), popen(). By default, sendmail reads the message to send from STDIN. From the three methods above, popen() is the easiest (and the most natural) way to launch sendmail and pass some text to it.

More than that, because popen() doesn’t run the command by itself but passes it to a shell instance, this approach provides higher flexibility on defining the command used to send emails from PHP. The first token of sendmail_path configuration value can be a shell alias, a shell function, a pipe of commands, a list of commands or even a small script.

Comments