A Couple of Shell Quickies
Since I got asked several sh-related questions, I might as well get a post out of them.
One person asks:
I’m writing a portable shell script to download a file from the web, and then compare it to a locally-stored version of the same file, using diff.
My first version of the script used mktemp to download the web-file to temporary file, run the diff command, and then delete the temporary file afterwards. e.g.
TEMPFILE=$(mktemp) wget -q $ONLINEFILEURL -O $TEMPFILE diff $TEMPFILE $LOCALFILE rm $TEMPFILEHowever I later discovered that the BSD version of mktemp has an incompatible syntax to the GNU version of mktemp. So then I got rid of the usage of temporary files completely, by using input-redirection, e.g.
diff <(wget -q $ONLINEFILEURL -O -) $LOCALFILEHowever while this works fine under bash and ksh, it fails under ash and sh with
Syntax error: "(" unexpected
to which I replied:
The first obvious problem here is that “(wget -q $ONLINEFILEURL -O -)” isn’t a filename, it’s a subprocess. So the shell sees “<” and expects a filename, but finds “(” instead.
It looks as though the way to get diff to read from stdin is the standard way: specify “-” as the filename, and give it input on stdin. Since you’re feeding it the output from a process, you want to use a pipe:
wget -q $ONLINEFILEURL -O - | diff - $LOCALFILE
I also suggested that he could try to figure out which version of mkfile he was using:
# Wrapper function for GNU mktemp gnu_mktemp() { mktemp /tmp/tmpfile.XXXXXX "$@" } # Wrapper function for BSD mktemp bsd_mktemp() { mktemp -t /tmp/tmpfile.XXXXXX "$@" } # Try to figure out which wrapper to use if mktemp -V | grep version >/dev/null 2>&1; then MKTEMP=gnu_mktemp else MKTEMP=bsd_mktemp fi mytmpfile=`$MKTEMP`
And, of course, if race conditions and security aren’t a big concern, there’s always
mytmpfile=/tmp/myprogramname.$$
Another person wanted to write a bash script that would do one thing when run by him or root, and another thing if run by anyone else (basically, die with an error message about insufficient privileges and/or grooviness).
He asked whether the following two expressions were equivalent:
- if [[ ( `whoami` != "root" ) || ( `whoami` != "coolguy" ) ]]
- if [[ ! ( `whoami` = "root" ) || ( `whoami` = "coolguy" ) ]]
They’re not, but maybe not for obvious reasons, because propositional logic is a harsh mistress.
In the first expression,
if [[ ( `whoami` != "root" ) || ( `whoami` != "coolguy" ) ]]
let’s say that the user is joeblow. In this case, “`whoami` != "root"” is true, and so the shell can short-circuit the rest of the “||“, because the entire expression is true.
If the user is root, then the first part, “( `whoami` != "root" )” is false. However, the second part, “( `whoami` != "coolguy" )” is true (because root ≠ coolguy), and so the entire expression is “false || true”, which is true.
The second expression,
if [[ ! ( `whoami` = "root" ) || ( `whoami` = "coolguy" ) ]]
is closer to what he wanted, but doesn’t work because of operator precedence: “!” binds more tightly than “||“, so the expression is equivalen tto “(whoami ≠ root) || (whoami = coolguy)”.
In this case, if the user is joeblow, the first clause, “whoami ≠ root“, is true, and so the entire expression is true.
Worse yet, if the user is root, then neither the first nor second clause is true, so the entire clause is false.
What he really wanted was something like:
if [[ ( `whoami` = "root" ) || ( `whoami` = "coolguy" ) ]]; then # Do nothing : else # Do something echo "Go away" exit 1 fi
Except, of course, that since the if-clause is empty, it can be cut out entirely. Then all we need to do is to negate the condition and only keep the code in the else-clause:
if [[ ! ((`whoami` = "root" ) || ( `whoami` = "coolguy" )) ]]Note the extra pair of parentheses, to make sure that the “!” applies to the whole thing.
(Update, May 18: Fixed HTML entities.)
Ramp Closed. Use Next Exit
(See what I did there? It’s because my site is a fill-in-the-blank on the Information Superhighway. Get it?)
The more eagle-eyed among you who visit this site on a regular basis (both of you) may have noticed some changes to the layout and whatnot. Or maybe something just went kerflooie in the RSS feed and your aggregator has just tossed the whole thing in the trash rather than try to deal with it.
Well, not that you asked (you could’ve asked, you know. I take an interest in your lives, you insensitive assholes1), but I’ve been messing with things behind the scenes, mainly to avoid having to update stuff all the time. So, in keeping with the vintage 1992 metaphor in the title, I’ve stopped leaning on my shovel, drained the last of my coffee, and actually gotten to work fixing the actual roadway underneath the twenty-times-patched potholes. And then knocking off early and asking someone to punch my time clock for me, because that’s the kind of tireless lazy fucker I am.
Actually, one thing y’all might like is the “Reply” button underneath comments, that allow you to reply to individual comments.
And now, if you’ll excuse me, I need to pop over to Geocities to download some animated “Under Construction” and flashing-light GIFs.
1: Not intended as a factual statement.