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.
2 Answers 2
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 thatmktemp
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.
-
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. usec99
and figure out a different method for a temporary name. \$\endgroup\$Edward– Edward2017年08月11日 14:51:16 +00:00Commented 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\$Toby Speight– Toby Speight2017年08月11日 15:03:38 +00:00Commented Aug 11, 2017 at 15:03
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.
-
\$\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– chicks2015年11月28日 16:23:22 +00:00Commented 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– Edward2015年11月28日 16:53:00 +00:00Commented 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\$Toby Speight– Toby Speight2017年08月11日 13:47:01 +00:00Commented 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– Edward2017年08月11日 13:54:21 +00:00Commented 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 canunlink()
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 evenunlink(argv[0])
, since you know exactly how you'll invoke it. \$\endgroup\$Toby Speight– Toby Speight2017年08月11日 14:00:21 +00:00Commented Aug 11, 2017 at 14:00