# ./rap.sh > Shell Script Golf (August 19, 2024)Shell Script Golf
Introduction
This blog post details my exploration of shell script golf. I looked at this as part of the BGGP4 (https://binary.golf) competition where a whole bunch of file formats are in scope. But at least initially I wanted to look at shell scripts in greater detail.
The goal for BGGP4 was to create the smallest self-replicating file that meets the following criteria:
A valid submission will: - Produce exactly 1 copy of itself - Name the copy "4" - Not execute the copied file - Print, return, or display the number 4
The example provided on the site was the following:
#!/bin/sh cp $0 4 echo 4
Shell Script Golfing Resources
As a starting point the Advanced Bash-Scripting Guide by Mendel Cooper gives some good insights. It comprehensively covers many relevant areas, such as Special Shell Variables which includes $0 used in example to get the filename of the current script for the copy command.
Shortening the example
The example is already pretty small in terms of the commands being used cp and echo. We should look to see if there any alternative commands or argument combinations or features that could help us get the length of the payload down.
Having a glance at man cp shows that a verbose option can be used to show details of the files being copied:
cp -v ./test.sh 4
outputs:
./test.sh -> 4
so
#!/bin/sh cp -v $0 4|tail -c 2
Would be a valid alternative however this is longer than the original echo command. Taking a slightly different strategy would be to keep the original copy command and then do:
ls 4
This would list files with the name 4, and there would be one after the copy has succeeded. This is two characters shorter than:
echo 4
Sha-Bang
Typically shell scripts start with a sha-bang (#!). This tells your system that this file is a set of commands to be fed to the command interpreter indicated. You can directly target this line to execute commands (see https://github.com/geon/shebang-abuse/tree/master).
Is the Sha-Bang actually needed?
From the Advanced Bash-Scripting Guide it states that “#! can be omitted if the script consists only of a set of generic system commands, using no internal shell directives”. So in some cases we can remove the whole line.
My shortest attempt
rap.sh % cat test.sh cp $0 4;ls 4 rap.sh % ls -alh test.sh -rwxr-xr-x 1 rapsh staff 13B 27 Jul 08:53 test.sh rap.sh % ./test.sh 4 rap.sh % shasum test.sh f3dd67aa93c2b8d3248f90ec12e45ddaba1a49fb test.sh rap.sh % shasum 4 f3dd67aa93c2b8d3248f90ec12e45ddaba1a49fb 4
So we have a 13B example. We should double check there’s no optimizations we can make to the original script. You can see there is a 0a at the end we can remove.
% python3 yxd.py -f ./test.sh 00000000│6370 2024 3020 343b│6c73 2034 0a │cp $0 4;ls 4.
yxd is excellent, and if like me you prefer working with python hex strings than a hex editor you can use this trick to get most of the way there:
python3 yxd.py -f ./test.sh --py > output.py
This gives output.py - that we can manipulate the hex trivially and write it to a file. After removing that 0a we get 12B:
rap.sh % ls -alh test.sh -rwxr-xr-x 1 rapsh staff 12B 27 Jul 09:06 test.sh rap.sh % ./test.sh 4 rap.sh % shasum test.sh 93bbb53ad90c55137a2faf9f72200bc6addff0d9 test.sh rap.sh % shasum 4 93bbb53ad90c55137a2faf9f72200bc6addff0d9 4
So this is the entry I submitted to BGGP4 on 30 June:
rap.sh % shasum -a 256 test.sh ba516f1a18c5af46e469fb0a64c032563fbaedf421b6fc7bc04a67eae1616a2d test.sh rap.sh % base64 -i test.sh Y3AgJDAgNDtscyA0
Internet hosted
Its worth noting that an Internet hosted script could be used to get the same 12 characters IF you own a 1 character domain which I don’t:
curl a.sh|sh cp $0 4;ls 4
Final thoughts
There are many other ideas/tricks I still want to explore. The trouble is the setup alone for other novel vectors at least for shell scripts is probably already >12B.
The competition is still in motion into August and there’s also a lot more file-formats to target.
Very briefly since makefiles is another target, similar tricks can also be applied here. Here’s a 26B makefile solution - however, I’m sure there’s much more that can be done.
Happy file format hacking all!
rap.sh % cat Makefile 4: Makefile @cp $< 4;ls 4% rap.sh % ls -alh Makefile -rw-r--r-- 1 rapsh staff 26B 27 Jul 09:32 Makefile rap.sh % make 4 rap.sh % shasum Makefile e206a134ffb0af584655590180a77708955f534f Makefile rap.sh % shasum 4 e206a134ffb0af584655590180a77708955f534f 4 rap.sh % shasum -a 256 Makefile 08368daf26adca5af2708dbaf1ca69ff535a43fe92c1dbfb1aedbfa971a1b433 Makefile rap.sh % base64 -i Makefile NDogTWFrZWZpbGUKCUBjcCAkPCA0O2xzIDQ