An executable script file that runs on POSIX and Windows

16

2

Challenge: write a single script file foo.cmd which can be invoked from the vanilla Windows cmd.exe prompt (not PowerShell, not in administrator mode), to execute arbitrary Windows-specific code...

> .\foo.cmd
Hello Windows! 

...but also be invoked unaltered from a typical POSIX-compliant (Linux/OSX) shell prompt (bash, tcsh, or zsh), to execute arbitrary POSIX-specific code:

$ chmod a+x foo.cmd
$ ./foo.cmd
Hello POSIX!

...without requiring installation or creation of third-party interpreters/tools.

I know this is possible, but with cruft (i.e. on Windows, one or two lines of garbage/error-message are printed to stderr or stdout before "Hello Windows!").

The winning criterion is minimization of (first) the number of cruft lines, and (second) the number of cruft characters.

Cruft can be defined as any console output (stdout or stderr) that is not produced by the (arbitrary) payload code. Blank lines are counted in the line count. Newlines are not counted in the character count. Cruft scores should be summed across both platforms. Let's disregard mechanisms like cls that sweep away the cruft but at the cost of also blanking out previous terminal output. If Windows echos your commands because you haven't turned @echo off yet, let's exclude the characters it spends in printing the current directory and prompt.

A secondary criterion is the simplicity/elegance of the solution inside foo.cmd: if "infrastructure" is defined as any character not directly involved in the arbitrary payload code, then minimize first the number of lines that contain infrastructure characters, and second the total number of infrastructure characters.

Extra kudos if the POSIX part will work despite the file having CRLF line-endings! (Am not sure that last part is even possible.)

My existing solution, which I will post here once others have had a chance, uses 6 lines of infrastructure code (52 chars excluding newlines). It produces 5 lines of cruft, two of which are blank, all of which occur on Windows (30 chars excluding newlines and excluding the current directory/prompt string that appears on two of those lines).

jez

Posted 2015-12-15T16:54:15.233

Reputation: 351

has to be a shell/batch script, ya? – cat – 2015-12-15T18:10:07.530

1Does anyone know of a try-it-online environment for DOS? – Digital Trauma – 2015-12-15T18:22:01.097

1

I found this one but it doesn't let me enter ":", "", "{" or "}" characters :-/

– Digital Trauma – 2015-12-15T18:28:20.343

@DigitalTrauma There is Wine (which you should have installed, if you don't, because it's handy)

– cat – 2015-12-15T18:38:17.727

1@cat thanks - My answer seems to work under wine now. – Digital Trauma – 2015-12-15T20:27:49.763

Answers

15

0 cruft lines, 0 cruft chars, 2 infra. lines, 21 infra. chars, CRLF ok

:<<@goto:eof
@echo Hello Windows!
@goto:eof
echo "Hello POSIX!" #

Removed the other solution.

17 characters using exit /b from Digital Trauma's answer:

:<<@exit/b
@echo Hello Windows!
@exit/b
echo "Hello POSIX!" #

jimmy23013

Posted 2015-12-15T16:54:15.233

Reputation: 34 042

Strong contender! Particularly the second version (on the simplicity/elegance criterion, it's much nicer not to have to munge the payload lines the way that the first version does). This runs for me on Windows and OSX. Initially I got one line of cruft saying : command not found whenever a blank line crept into the posix payload, but I finally figured out that was not about : on the first line but rather because of CRLFs unprotected by #. It was news to me that a #! line is not needed—that was responsible for two of the lines of Windows cruft in my previous version. – jez – 2015-12-15T20:41:11.677

The first version is hard to score—since it presumably adds the characters : ` >&3` \ to every line of the payload, I guess you could say its infrastructure cost is arbitrarily high. – jez – 2015-12-15T20:54:26.800

@jez You're calculating a numerical score for answers? If so you need to make it much clearer in the question how to do that. – Digital Trauma – 2015-12-15T21:00:35.717

I'm new to codegolf, so TBH I thought I was expected to score answers objectively somehow. Nonetheless I think the question makes it clear: first, number of cruft lines; break ties on that by number of cruft characters; then on number of infrastructure lines, then number of infrastructure characters. I hadn't expected solutions that munge the payload lines, like jimmy's first solution. I'll change the question slightly to make it clear that's undesirable (sorry for that slight goalpost shift but I don't think it affects his second solution or any version of yours so far) – jez – 2015-12-15T21:06:35.820

Looks like our answers are converging ;-) You have the edge in scoring though with the heredoc usage. Is the final # necessary? – Digital Trauma – 2015-12-15T21:25:18.550

@DigitalTrauma It is for the CR. – jimmy23013 – 2015-12-15T21:26:56.177

@jimmy23013 <shrug> it works fine for me without the # even with CRLF endings – Digital Trauma – 2015-12-15T21:39:45.860

Under Wine, I do get this cruft output: :<<@goto:eof – Digital Trauma – 2015-12-15T21:40:21.950

@DigitalTrauma Wine doesn't work exactly the same as Windows. At least on my old Windows XP virtual machine it doesn't print the labels. Wine also allow something like () that is not allowed in Windows. – jimmy23013 – 2015-12-15T21:44:53.667

@jez Actually the #! line is needed. If you try to run the code from this answer under strace you will see that the kernel is in fact rejecting it as invalid -1 ENOEXEC (Exec format error). If you try to run the code from another shell, then that shell may have a workaround for malformed scripts. But that workaround will not be the same for all shells, and some might not even have a workaround. So any answer not starting with #! would strictly speaking not be a valid POSIX script. – kasperd – 2015-12-15T22:18:54.440

@kasperd It might not be a valid "POSIX script", but it is surely a valid bash or zsh script, which can be run with bash foo.cmd or zsh foo.cmd. – jimmy23013 – 2015-12-15T22:34:16.720

It's valuable to be able to do bash foo.cmd for sure, but kasperd is right to make this point since strictly the question did ask for an executable script and even specify how it should be called. Having said that, given the fairly wide range of systems/usages in which ./foo.cmd will work anyway, I think answers without #! can still be considered winners. A solution like #!/bin/sh # 2>NUL can be added if needed, or not. – jez – 2015-12-16T01:27:18.707

@jez Note that ./foo.exe works for normal Windows executables in my system. It even knows whether to call wine, mono or dosbox depending on the type of the executable. So if it doesn't have to be a script file, you can just compile a standalone program and ./foo.exe will work anyway with no cruft or infrastructure. – jimmy23013 – 2015-12-16T12:31:29.840

@jimmy23013 that's pretty cool. But I can't rely on users having their system configured so seamlessly. The typical cases are Windows vs OSX which come out of the box with no developer tools or emulators (or compatibility layers that Are Not Emulators) – jez – 2015-12-16T16:12:20.593

@kasperd I've added a new answer which may at least minimize the damage from lack of #! – jez – 2015-12-16T18:00:13.227

4

Score 0 cruft + 4 infra lines + 32 infra chars. LF & CRLF OK.

This is based off what I found at this blog, with the Amiga bits and other unnecessary lines taken out. I hid the DOS lines in commented quotes instead of using \ line continues, so that this can work with both CRLF and LF.

@REM ()(:) #
@REM "
@ECHO Hello Windows!
@EXIT /B
@REM "
echo Hello POSIX!

With either DOS CRLF or *nix LF line endings, it works on Ubuntu, OSX and wine:

ubuntu@ubuntu:~$ ./dosix.bat
Hello POSIX!
ubuntu@ubuntu:~$ wine cmd.exe
Wine CMD Version 5.1.2600 (1.6.2)

Z:\home\ubuntu>dosix.bat
Hello Windows!

Z:\home\ubuntu>exit
ubuntu@ubuntu:~$ 

To create this exactly (with CRLFs) on a *nix machine (including OSX), paste the following to a terminal:

[ $(uname) = Darwin ] && decode=-D || decode=-d
ubuntu@ubuntu:~$ base64 $decode > dosix.bat << EOF
QFJFTSAoKSg6KSAjDQpAUkVNICINCkBFQ0hPIEhlbGxvIFdpbmRvd3MhDQpARVhJVCAvQg0KQFJF
TSAiDQplY2hvIEhlbGxvIFBPU0lYIQ0K
EOF

Digital Trauma

Posted 2015-12-15T16:54:15.233

Reputation: 64 644

Looks nice! dosix is a nice name too. But on my Mac (OS 10.9.4, Darwin Kernel Version 13.3.0, GNU bash version 3.2.51) it fails to run with: ./dosix.cmd: line 13: syntax error: unexpected end of file Any idea why? – jez – 2015-12-15T20:32:04.387

@jez make sure you save the file encoded with UTF-8 and preserve the Windows line endings! – cat – 2015-12-15T20:36:07.120

Ah, it was happening because I unknowingly had Windows line-endings and was using the first version. – jez – 2015-12-15T20:42:58.017

Note that you can input the CR character in Bash by insert (or C-v), enter (or C-m). – jimmy23013 – 2015-12-15T21:10:13.127

@jez So this scores as 0 cruft lines + 4 infrastructure lines + 32 infrastructure chars. Should these numbers be combined in any meaningful manner to create a single score for comparison? – Digital Trauma – 2015-12-15T21:23:02.240

Not combining scores, but applying one criterion after the other to break ties. Zero-cruft solutions beat my solution outright. Next criterion in line is the 4 infrastructure lines. So far that doesn't beat jimmy's version which had 3 lines and could get away with 2 (ah, yes, I see he's changed it :-) ). I introduced the CRLF thing as a kind of nice-to-have extra so it (and the end-of-line comment characters motivated by it) are not included in scoring. That said, I might actually end up using your version because it allows CRLF-compatibility without those trailing #. – jez – 2015-12-15T21:43:06.403

@jez Both programs work in the same way for the trailing #. If there isn't the trailing # it also outputs the CR, which is invisible (moving to beginning of the line in fact). And it may break most non-printing commands. – jimmy23013 – 2015-12-15T22:25:10.070

Golfed (exits cmd.exe in wine but works in Windows). – jimmy23013 – 2015-12-15T22:47:57.830

Or you may use : as the function name. – jimmy23013 – 2015-12-15T22:51:18.007

Golfed more. – jimmy23013 – 2015-12-15T22:59:39.880

@jimmy23013 the golfed versions are very neat but won't they be screwed up as soon as there's a double-quote character anywhere in the Windows payload? At least if there's an odd number of double-quote characters (which, stupidly, can very easily happen in Windows batch code) – jez – 2015-12-17T16:28:00.417

2

I'll already post the solution I had been using, since it has already been beaten. It's courtesy of a colleague of mine who I think must have read the same blog entry as Digital Trauma.

#!/bin/sh # >NUL 2>&1
echo \
@goto c \
>/dev/null
echo "Hello Posix!"
exit
:c
@echo Hello Windows!
  • Windows cruft: 5 lines (of which two are blank) / 30 chars
  • OSX cruft: 0
  • Infrastucture: 6 lines / 52 chars
  • CRLF compatibility: only if the interpreter named on the #! line doesn't care (so for standard interpreters like sh and friends, it fails)

jez

Posted 2015-12-15T16:54:15.233

Reputation: 351

On POSIX a script always starts with #!, which means yours is the only answer so far to be a valid script on a POSIX system. Sure some of the others may run if - but only if they are started from a shell with a workaround for faulty scripts. – kasperd – 2015-12-15T22:13:58.997

Good to know! I guess I'm fuzzy on the strict criteria for POSIX compliance. My real goal is to have script files that work "out of the box" on "most" modern desktop systems, whatever that means - Windows 7/8/10, Ubuntu, OSX... How universal is the workaround for shebangless scripts, I wonder? Anyway, I'm not going to award the points to myself :-) – jez – 2015-12-15T22:21:28.763

I hadn't noticed that both answer and question was written by the same person. I think all the shells I have worked with have a workaround for for a missing #! line, but they will use different shells for interpreting the script. That means a "script" which doesn't start with #! has to be valid in not just one shell, but every shell that it could plausibly be interpreted by. But even worse, it only works when being started from another shell. Such a script may work from the command line, but not in the context where you finally intend to use it. – kasperd – 2015-12-15T22:36:33.200

Thank you, this is very educational stuff and exactly the sort of thing I had hoped to learn by starting this challenge. For cases where this will (or might) matter, the #! line in my edited answer (1 infrastructure line with 21 chars) can be combined with anybody else's answer at a Windows-only cruft cost of 2 lines (of which one is blank) or 23 chars. – jez – 2015-12-15T22:46:17.137

2

Summary/synthesis of answers and discussion

This was fun, and I learned a lot.

Some Windows-specific cruft is inevitable if, on your POSIX system, you need to start your script with a #! line. If you have no alternative but to do this, then this line:

#!/bin/sh # 2>NUL

is probably the best it can get. It causes one blank line and one line of cruft to be output on the Windows console. However, you may be able to get away without a #! line: on most systems, one of the usual shell interpreters will end up executing the script (the problem is that it's not universally predictable which interpreter that will be - it depends on, but will not necessarily be identical to, the shell you use to invoke the command).

Beyond that tricky first line, there were some really ingenious cruftless solutions. The winning submission by jimmy23013 consisted of only two short infrastructure lines, and made use of the dual role of the : character to implement a "silent" line on both platforms (as a de-facto no-op in sh and friends, and as a label marker in cmd.exe):

:<<@exit/b

:: Arbitrary Windows code goes here

@exit/b
#
# Arbitrary POSIX code goes here 

It is possible to make such a script run on POSIX systems even despite CRLF line-endings, but to do this for most interpreters you have to end every line of your POSIX section (even blank lines) with a comment or comment character.

Finally, here are two variants on a solution I have developed based on everybody's input. They might be almost the best of all worlds, in that they minimize the damage from the lack of #! and make CRLF-compatibility even smoother. Two extra infrastructure lines are needed. Only one (standardized) line has to be interpreted by the unpredictable POSIX shell, and that line allows you to select the shell for the rest of the script (bash in the following example):

:<<@GOTO:Z

@echo Hello Windows!

@GOTO:Z
bash "$@"<<:Z
#
echo "Hello POSIX!" #
ps # let's throw this into the payload to keep track of which shell is being used
#
:Z

Part of the beauty of these heredoc solutions is that they are still CRLF-robust: as long as <<:Z comes at the end of the line, the heredoc processor will actually be looking for, and will find, the token :Z\r

As a final twist, you can get rid of those pesky end-of-line comments and still retain CRLF-robustness, by stripping the \r characters out before passing the lines to the shell. This places slightly more faith in the unpredictable shell (it would be nice to use { tr -d \\r|bash;} instead of (tr -d \\r|bash) but curly brackets are bash-only syntax):

:<<@GOTO:Z

@echo Hello Windows!

@GOTO:Z
(tr -d \\r|bash "$@")<<:Z

echo "Hello POSIX!"
ps

:Z

Of course, this approach sacrifices the ability to pipe stdin input into the script.

jez

Posted 2015-12-15T16:54:15.233

Reputation: 351