Skip to content

Instantly share code, notes, and snippets.

@jdarpinian
Last active June 2, 2025 20:17
Show Gist options
  • Save jdarpinian/6860ddfd92b5b458a20ab6055583bc3e to your computer and use it in GitHub Desktop.
Save jdarpinian/6860ddfd92b5b458a20ab6055583bc3e to your computer and use it in GitHub Desktop.
Polyglot files that can run on Windows, Linux, and Mac using bash, cmd, and C.
#if 0 // 2>NUL & GOTO :startbatch
COMPILER_OPTIONS="-g -Wall -Wextra --std=c99 -O1";THIS_FILE="$(cd "$(dirname "$0")"; pwd -P)/$(basename "$0")";OUT_FILE="/tmp/build-cache/$THIS_FILE";mkdir -p "$(dirname "$OUT_FILE")";test "$THIS_FILE" -ot "$OUT_FILE" || $(which clang || which gcc) $COMPILER_OPTIONS -xc "$THIS_FILE" -o "$OUT_FILE" || exit;exec "$OUT_FILE" "$@"
:startbatch
@echo off
setlocal enableextensions enabledelayedexpansion
md %TEMP%%~p0 2>NUL
rem Use xcopy to test if this file is newer than the cached executable.
for /f %%i in ('xcopy %0 %TEMP%%~pnx0.exe /D /Y /Q') do set copied=%%i
if "%copied:~0,1%" neq "0" (
rem Search for Visual Studio env vars. These are set globally if VS <2017 is installed.
for /L %%i in (60,10,140) DO (
set "vs=VS%%i%COMNTOOLS"
if defined !vs! (
set "vsfound=!vs!"
)
)
)
if "%copied:~0,1%" neq "0" (
rem If cl.exe is already in the PATH, use it. Otherwise, look for VS 2017, then older versions.
for %%X in (cl.exe) do (set FOUND_COMPILER=%%~$PATH:X)
if not defined FOUND_COMPILER (
rem Look for VS 2017 using its start menu link, because Microsoft has broken every other reasonable way of finding it.
set "VS_START_MENU=%ProgramData%\Microsoft\Windows\Start Menu\Programs\Visual Studio 2017.lnk"
if exist !VS_START_MENU! (
rem Use "wmic" tool to extract the devenv.exe path from the .lnk file, then run vcvars64.bat
for /f "delims=" %%I in ('wmic path win32_shortcutfile where "name='!VS_START_MENU:\=\\!'" get target /value') do for /f "delims=" %%# in ("%%~I") do set "%%~#"
pushd
call "!target!\..\..\..\VC\Auxiliary\Build\vcvars64.bat" >NUL 2>NUL
popd
) else (
if not defined vsfound (
echo Could not find a Visual Studio installation.
exit /b 1
)
if not exist "!%vsfound%!vsvars32.bat" (
echo Your Visual Studio does not have a vsvars32.bat
)
call "!%vsfound%!vsvars32.bat" >NUL 2>NUL
for %%X in (cl.exe) do (set FOUND_COMPILER=%%~$PATH:X)
if not defined FOUND_COMPILER (
echo Could not find a Visual Studio installation with a C compiler.
exit /b 1
)
)
)
cl /nologo /TC /Fe:%TEMP%%~pnx0.exe %0 >NUL 2>NUL || exit /B %ERRORLEVEL%
)
%TEMP%%~pnx0.exe %*
exit /B
#endif
#include <stdio.h>
int main(int argc, char **argv) {
printf("Hello world!\n");
return 0;
}
#!/usr/bin/env bash
echo off & echo ; wait ; echo -ne "\e[1A\e[K" # > NUL
echo ; function goto { true; } ; function rem { true; } ; echo -ne "\e[1A\e[K\e[1A\e[K" # > NUL
goto startbatch
echo running in bash
exit
:startbatch
echo 
echo running in cmd
exit /B
@TomasMalecek
Copy link

This polyshell is interesting too... Though even more convoluted.
https://github.com/llamasoft/polyshell

@Jan69
Copy link

Jan69 commented Jan 1, 2023

@TomasMalecek there's actually no reason to bother making a dummy function for @ or anything, just redirect errors, and no more errors!

#!/usr/bin/env -S 2>/dev/null=2>NUL sh
@goto batch 2>NUL;rm -f NUL

echo running in sh script
echo "you live in ${HOME}"
errors-still-work-fine
exit

:batch
@echo off
echo running in cmd script
echo you live in %USERPROFILE%
errors-still-work-fine
exit /B

the script itself should be posix, but it requires /bin/env that supports -S for the shebang to work (gnu one works, which is default in most places, but it's not posix, and busybox and toybox's env do not work)
or you can just run it directly using dash/ash/bash or whatever other bourne-style shell you like, ignoring the shebang entirely ;P

@Jan69
Copy link

Jan69 commented Jan 1, 2023

I've tested my version with both windows XP, and Wine's cmd
the 2>/dev/null=2>NUL in the shebang is making sure that both wine and real cmd will not error on the shebang, it doesn't affect anything else, and it's also why you need -S
if you don't care about shebang erroring on windows you can remove that and use a normal #!/bin/sh or #!/bin/bash

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment