7
\$\begingroup\$

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.

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

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. (Note that trap handlers don't affect the exit status).
  • 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.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ 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. \$\endgroup\$
    – Edward
    Commented Aug 11, 2017 at 14:51
  • \$\begingroup\$ 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!). \$\endgroup\$ Commented Aug 11, 2017 at 15:03
2
\$\begingroup\$

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.

\$\endgroup\$
6
  • \$\begingroup\$ 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? \$\endgroup\$
    – chicks
    Commented Nov 28, 2015 at 16:23
  • \$\begingroup\$ @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. \$\endgroup\$
    – Edward
    Commented Nov 28, 2015 at 16:53
  • \$\begingroup\$ 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)? \$\endgroup\$ Commented Aug 11, 2017 at 13:47
  • \$\begingroup\$ @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. \$\endgroup\$
    – Edward
    Commented Aug 11, 2017 at 13:54
  • \$\begingroup\$ 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. \$\endgroup\$ Commented Aug 11, 2017 at 14:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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