16
\$\begingroup\$

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).

\$\endgroup\$
  • \$\begingroup\$ has to be a shell/batch script, ya? \$\endgroup\$ – cat Dec 15 '15 at 18:10
  • 1
    \$\begingroup\$ Does anyone know of a try-it-online environment for DOS? \$\endgroup\$ – Digital Trauma Dec 15 '15 at 18:22
  • 1
    \$\begingroup\$ I found this one but it doesn't let me enter ":", "\", "{" or "}" characters :-/ \$\endgroup\$ – Digital Trauma Dec 15 '15 at 18:28
  • \$\begingroup\$ @DigitalTrauma There is Wine (which you should have installed, if you don't, because it's handy) \$\endgroup\$ – cat Dec 15 '15 at 18:38
  • 1
    \$\begingroup\$ @cat thanks - My answer seems to work under wine now. \$\endgroup\$ – Digital Trauma Dec 15 '15 at 20:27
15
\$\begingroup\$

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!" #
\$\endgroup\$
  • \$\begingroup\$ 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. \$\endgroup\$ – jez Dec 15 '15 at 20:41
  • \$\begingroup\$ 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. \$\endgroup\$ – jez Dec 15 '15 at 20:54
  • \$\begingroup\$ @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. \$\endgroup\$ – Digital Trauma Dec 15 '15 at 21:00
  • \$\begingroup\$ 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) \$\endgroup\$ – jez Dec 15 '15 at 21:06
  • \$\begingroup\$ Looks like our answers are converging ;-) You have the edge in scoring though with the heredoc usage. Is the final # necessary? \$\endgroup\$ – Digital Trauma Dec 15 '15 at 21:25
4
\$\begingroup\$

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
\$\endgroup\$
  • \$\begingroup\$ 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? \$\endgroup\$ – jez Dec 15 '15 at 20:32
  • \$\begingroup\$ @jez make sure you save the file encoded with UTF-8 and preserve the Windows line endings! \$\endgroup\$ – cat Dec 15 '15 at 20:36
  • \$\begingroup\$ Ah, it was happening because I unknowingly had Windows line-endings and was using the first version. \$\endgroup\$ – jez Dec 15 '15 at 20:42
  • \$\begingroup\$ Note that you can input the CR character in Bash by insert (or C-v), enter (or C-m). \$\endgroup\$ – jimmy23013 Dec 15 '15 at 21:10
  • \$\begingroup\$ @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? \$\endgroup\$ – Digital Trauma Dec 15 '15 at 21:23
2
\$\begingroup\$

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)
\$\endgroup\$
  • \$\begingroup\$ 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. \$\endgroup\$ – kasperd Dec 15 '15 at 22:13
  • \$\begingroup\$ 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 :-) \$\endgroup\$ – jez Dec 15 '15 at 22:21
  • \$\begingroup\$ 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. \$\endgroup\$ – kasperd Dec 15 '15 at 22:36
  • \$\begingroup\$ 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. \$\endgroup\$ – jez Dec 15 '15 at 22:46
2
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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