Tell HN: People forget that you can stick any data at the end of a bash script
This is a neat trick I've used to write self-extracting software or scripts that extract files from archives by just using
tail -c <number of bytes for the binary> $0
All you have to do is make sure you append an explicit 'exit' to the end of your program before your new 'data section', so that bash won't parse any of the 'data section'.One thing to bear in mind is that if you append binary data, it will be corrupted if you save it in most text editors so when I want to make changes I just delete all the binary and reappend it.
148 comments
[ 3.8 ms ] story [ 209 ms ] thread(edit: a subsequent correction has obsoleted this comment)
in fact I found an old archive of mine floating around on usenet and wrote a python script to unpack it. Looking at the original, it was using a scripting. language bootstrap to make a COM script unpack embedded the original code.
https://en.wikipedia.org/wiki/Here_document
payload_offset=$(($(grep -na -m1 "^MARKER:$" "$0"|cut -d':' -f1) + 1))
and at the end of the code it just has:
exit 0
MARKER:
Or add compression to reclaim at least some of the wasted space:
Also note that bash accepts line breaks in quoted strings and the base64 utility has an "ignore garbage" option that lets it skip over e.g. whitespace in its input. You can use those to break up the base64 over multiple lines:https://github.com/qntm/base65536
It's almost always the case that either Base32768 is denser, or encodings with 2^17 or 2^20 characters are denser.
It's also useful if you want to check the file into a git repo and want to keep using the diff and merge tools.
The reasoning being that the base64'd binary data is safe from being corrupted when the file is edited in text editors, as a response to the warning stated on the last paragraph of the original post.
It's just to mitigate the wastefulness of base64 encoding. You end up with a file that is text editor friendly and not quite as bloated as directly encoding the binary would be - but of course the file is still larger than simply appending the binary directly like in the OP.
Also, if you don't care about text editor friendlyness, you could indeed just zip the binary and then append it to the script for an even smaller file.
Any reasonable person will indent a conditional exit within the block testing its condition, and more than one unconditional exit doesn't make sense.
The reason that running a binary needs a file is because execve() takes a path as an argument. But, as you said, there are other ways to load code into memory.
pipe(2) doesn't create a file, at least not in the sense that I usually think about files (something that's accessible through the filesystem).
Long story but trust me that I had good intentions.
I don't append it in binary form, though. I uuencode it. That way, there is no danger in using text editors.
What are their pros and cons, in your opinion?
Yes, uuencode / uudecode are probably older too.
They are from the uucp dialup comms era of networking.
A suppressed obscure part of my lizard brain secretly wishes I could just code for 8bit computers from the 80s, just with all the modern niceties like text editors, assemblers and emulators etc.
IIRC, Perl copied it from BASIC, because BASIC came much before Perl.
And, again, IIRC, I've read about the shar (shell archive) method that someone else commented about in this thread (and which even has a Wikipedia entry), in either the classic Kernighan and Pike book, The Unix Programming Environment (which I've recommended here multiple times before), or in some Unix man pages, long ago.
So it's quite an old method.
It's a cool abstraction, during development you can have assets just living on the file system and then "deploy" a flat file that accesses those assets the same way.
That's a great expression
Front Short, Back Long
https://de.wikipedia.org/wiki/Vokuhila
[1] https://da.wikipedia.org/wiki/Frisure
[2] https://pl.wikipedia.org/wiki/Czeski_pi%C5%82karz
[3] https://sv.wikipedia.org/wiki/Hockeyfrilla
https://portswigger-cdn.net/burp/releases/download?product=c...
Wow, that triggered my wordplay radar, which I'm working on as a fun side line these days, thanks :)
port, suite (sweet)
swig, burp
Heh.
``` #!/usr/local/bin/bash
echo "HELLO"
TAIL_REMOTE_MARKER=`awk '/^__THE_REMOTE_PART__/{flag=1;next}/^__END_THE_REMOTE_PART__/{flag=0;exit}flag' ${0}`
eval "$TAIL_REMOTE_MARKER"
exit 0
__THE_REMOTE_PART__
echo "WORLD"
__END_THE_REMOTE_PART__ ```
https://perldoc.perl.org/functions/__DATA__
https://news.ycombinator.com/newsfaq.html
When everyone in the world INCLUDING cyber-security professionals seems to think that it's perfectly OK to download setup.exe or install.msi from sketchy driver mfgs. and run them. But SHARs and curl | sh is too scary!
It's not that any of these are a good idea, it's the blind-spot to traditional binary installers that's annoying. How do you audit them? Why are they OK (because they have to be)
Some people took the “don’t curl | sh” advice as “you have to inspect every line of a shell script installer you download”, which is of course absurd.
It'd only work if, like your example, it was the truncation that caused the flub. You can't have random corruption.
Does curl in a pipeline really behave that way - if the connection is interrupted (TCP RESET), does it just end the pipe? It probably actually does, from what I see.
It's an interesting edge case.
Personally I use, which would seem more immune:
> Some people took the “don’t curl | sh” advice as “you have to inspect every line of a shell script installer you download”, which is of course absurd.
... to ever mean anything BUT "inspect every line". Which is, as you say, completely absurd.
This is what the (now defunkt) Capsule project prepends to get this same effect
The “unzip” utility can be useful as a sanity check; run “unzip -t” to test the integrity of the file.
I have a small bash stub that I use. It’s roughly (I also add JAVA_OPTS, etc…):
Then… See: https://github.com/compgen-io/ngsutilsj/blob/master/src/scri...