6
\$\begingroup\$

(See the next iteration.)

From time to time, while working with a command line in *nix family of operating systems, we have to write those scripts doing a rather specific task. Usually we use bash + utility programs to do the job. However, I had to ask myself: how to implement such a script in C? After an invocation of such a script, it should perform the following:

  1. Create a temporary source file \$S\$,
  2. Dump the C code to \$S\$,
  3. Compile \$S\$ to the program \$P\$,
  4. Run \$P\$ passing the arguments to it and caching its exit status,
  5. Remove \$S\$ and \$P\$,
  6. Return the cached exit status of \$P\$.

Code

#! /bin/bash

# Below, XXXX asks for random characters in order to make a unique file name. 
# Mac OSX seems to ignore XXXX, yet Ubuntu requires them.

# Create a temporary file name for the source code:
TMP_SOURCE_FILE="$(mktemp -t sourceXXXX).c"
# Create a temporary file name for the executable file:
TMP_PROGRAM_FILE="$(mktemp -t programXXXX)"

# Write the source code into the temporary source file:
cat > $TMP_SOURCE_FILE <<- END_OF_SOURCE
#include <stdio.h>

int main(int argc, char* argv[]) 
{
    int i;

    puts("Hello, world! I am a pseudoportable C program.");
    for (i = 1; i < argc; ++i) 
    {
        printf("Argument %d: %s\n", i, argv[i]);
    }

    return argc - 1;
}
END_OF_SOURCE

# Compile and run:
gcc $TMP_SOURCE_FILE -o $TMP_PROGRAM_FILE
$TMP_PROGRAM_FILE $@
EXIT_STATUS=$?

# Clean the source and the binary:
rm $TMP_SOURCE_FILE
rm $TMP_PROGRAM_FILE

# Delegate the exit status of the C program to calling bash:
exit $EXIT_STATUS

So, what do you think? How can I improve anything? Also, can you come with an example of the situation where this "C script" pattern is more preferable than bash + command line utilities, Python, etc.?

\$\endgroup\$
  • \$\begingroup\$ Have you considered using tcc instead? It can run C as a script. \$\endgroup\$ – 200_success Nov 27 '15 at 10:29
  • \$\begingroup\$ Never heard about tcc before. However, the aim is to be as "portable" as possible and it seems like I can't assume that the user has tcc installed. \$\endgroup\$ – coderodde Nov 27 '15 at 10:32
6
\$\begingroup\$

It's not guaranteed that $TMP_PROGRAM_FILE is on the $PATH, so you need to either set the $PATH or use an absolute path for $TMP_PROGRAM_FILE.

You have problems with mktemp.

  • -t option: It means different things on GNU/Linux mktemp(1) and Mac OS X mktemp(1).

    On GNU/Linux:

    -t    interpret TEMPLATE as a single file name component,
          relative to a directory: $TMPDIR, if set; else the
          directory specified via -p; else /tmp [deprecated]
    

    On OS X:

    mktemp [-t prefix] template …
    mktemp -t prefix
    
        -t prefix
                Generate a template (using the supplied prefix and TMPDIR if set) to create a filename tem-plate. template.
                plate.
    

    Perhaps the confusion is the reason why the GNU/Linux man page indicates that it is deprecated. I think you just want to run mktemp TEMPLATE without the -t option.

  • Cleanup: Use trap "rm $TMP_SOURCE_FILE $TMP_PROGRAM_FILE" EXIT as a more robust way to delete the temporary files, in case your script aborts before reaching the rm commands.
  • .c suffix: By writing TMP_SOURCE_FILE="$(mktemp -t sourceXXXX).c", you end up getting mktemp to create the file sourceXXXX. That file does not get cleaned up. Also, when you do cat > $TMP_SOURCE_FILE, you may be creating sourceXXXX.c, or worse, truncating an existing file.

    On GNU/Linux, a remedy would be to use the --suffix .c option. Unfortunately, the BSD/OS X version doesn't support it. One workaround is to rename the file after creation using mv -i — which might fail, but at least it's secure.

    A better workaround would be to use gcc -x c to tell GCC that it is C source code, without considering the filename extension.

    An even better solution would be…

  • Avoid $TMP_SOURCE_FILE altogether: gcc can take its source code from its standard input! Use - as the filename.

    gcc -o "$TMP_PROGRAM_FILE" -x c - <<- END_OF_SOURCE
    …
    END_OF_SOURCE
    
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ For some reason I can't read the exit status of the embedded C program after the trap command. Tried on Ubuntu and OSX; both return systematically 0 when the number of arguments should be returned. \$\endgroup\$ – coderodde Nov 28 '15 at 9:13
  • 1
    \$\begingroup\$ Interesting. Apparently, you need to have the trap handler propagate the exit status explicitly. \$\endgroup\$ – 200_success Nov 28 '15 at 9:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.