# Pseudoportable C script pattern - follow-up

See the initial/previous iteration.

I have rewritten the script following the answer of @200_success.

Now it looks like this:

#! /bin/bash

# Create a temporary file name for the executable file:
TMP_PROGRAM_FILE="$(mktemp programXXXXXX)" # Compile the embedded C program: gcc -o "$TMP_PROGRAM_FILE" -x c - <<- 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

# Run the program delegating all the arguments:
./$TMP_PROGRAM_FILE$@

# Save the exit status of the C program:
EXIT_STATUS=$(echo$?)

# Remove the executable file:
trap "rm $TMP_PROGRAM_FILE" EXIT # Exit returning the exit status of the C program: exit$EXIT_STATUS


Any critique much appreciated.

It can be made even simpler, by arranging for the C program to remove itself when it runs:

#! /bin/bash

# Create a temporary file name for the executable file:
TMP_PROGRAM_FILE="$(mktemp programXXXXXX)" # Compile the embedded C program: gcc -o "$TMP_PROGRAM_FILE" -x c - <<- END_OF_SOURCE
#include <stdio.h>
#include <unistd.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]);
}
unlink("$TMP_PROGRAM_FILE"); return argc - 1; } END_OF_SOURCE # Run the program delegating all the arguments: ./$TMP_PROGRAM_FILE $@  This will not work under Windows but does work under Linux. I haven't tested Mac, but believe it will work there, too. The change, of course, is that the C program now deletes itself. • Did you intend to exec the executable so that the return values got passed back? Otherwise, can you explain why you left those lines out? – chicks Nov 28 '15 at 16:23 • @chicks: The last line in the script runs the program and since it's the last thing that runs, its return value is automatically used as the return value for the script. – Edward Nov 28 '15 at 16:53 • How does the program file get removed if it crashes or the compilation fails (perhaps because one of your target systems is missing a necessary header file)? – Toby Speight Aug 11 '17 at 13:47 • @TobySpeight: If compilation fails because a header is missing, then no target is created and there is nothing to delete. If it crashes at runtime, it will not be deleted unless the unlink has already been executed. If that's a concern, unlink could be executed earlier, minimizing (but not eliminating) that possibility. – Edward Aug 11 '17 at 13:54 • If compilation fails, nothing is written to the target - but it does exist, because mktemp created it. Good point that you can unlink() the program text immediately (at least on POSIX systems; I'm not sure how Cygwin behaves). An alternative to allowing shell to expand variables in the program source is to use preprocessor expansion (gcc -D"PROGRAM=\"$TMP_PROGRAM_FILE\"") to pass the name through. Or even unlink(argv[0]), since you know exactly how you'll invoke it. – Toby Speight Aug 11 '17 at 14:00

The trap is too late to be much use here. If the program fails (either during compilation, or when it's run), you'll exit before the trap has been set, and so fail to clean up. It's best to set the trap as soon as you know the file's name:

#!/bin/sh

prog="$(mktemp programXXXXXX)" trap "rm -f '$prog'" EXIT

gcc -o "$prog" -x 'c' - <<'END_OF_SOURCE' # (C code here) # END_OF_SOURCE ./"$prog" "$@" # script now exits with the return status of the program above, # after running the 'rm' command in the trap  A couple of other changes made above: • I've used << rather than <<- to allow the leading whitespace through to the compiler. This makes very little difference in C, but may help you understand any quoted code in error messages more clearly. Also, I've quoted the delimiter, as we don't need the shell to perform expansion of the program source. • I've used "$@" rather than $@ so that the arguments are passed through intact without further word-splitting. • There's no need to save the exit status in a variable - the shell's exit status is that of the last command executed in the script. • Avoid uppercase names for variables - we use uppercase as we do in C, to draw attention (in this case to environment variables, which have effects "at a distance"). • We don't depend on any non-POSIX shell features, so we can safely use /bin/sh for increased portability. (Note that mktemp isn't in POSIX, but is fairly widely available). Alternatively, if you choose to require Bash, and are willing to have it expand shell variables in your C source, you could make your compiler warnings/errors more useful by telling it where your source is: gcc -Wall -o "$prog" -x 'c' - <<END_OF_SOURCE
#line $(($LINENO + 2)) "${BASH_SOURCE[0]}" #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  The +2 is necessary because $LINENO expands to the line number of the start of gcc command, but the preprocessor is being told the line number immediately after the #line directive.

• Also, a small nit, but technically mktemp is not POSIX and, for example, did not come with Solaris SunOS 9. Still, this is a nice refinement! I wonder if it's possible to do with strictly POSIX only? E.g. use c99 and figure out a different method for a temporary name. – Edward Aug 11 '17 at 14:51
• I meant only non-POSIX shell features, and I've clarified that. I didn't realise that mktemp is non-POSIX; it could be hard to create a workaround in portable shell (and we can't make the workaround as a C program, because we need it before we can safely compile C!). – Toby Speight Aug 11 '17 at 15:03