10

I want to make a Bash script executable by the kernel. I have already used the chmod +x command and #!/bin/bash to make it executable and it works, but that I want to do is make it an executable without an interpreter, like an executable from a compiled app.

Is there any way I can do this? All I need is a way to make by Bash script executable from the macOS kernel itself and I do not need to specify a shebang. I also do not want anyone to be able to open it with text edit or similar and be able to see the code.

  • 1
    This is the same question you asked that got closed! apple.stackexchange.com/questions/402870/… – user3439894 Oct 8 at 0:01
  • 7
    As stated, it's impossible. bash scripts are, by definition, text files that tell bash what to do, not binary executables that'd be loaded by the kernel. Depending on your actual goal, there might be a way to accomplish it, but you'd have to explain what you're actually trying to achieve (see XY problem). – Gordon Davisson Oct 8 at 0:45
  • 2
    Does it have to be a bash script? There are other languages which are more suited to producing compiled binary or pseudo-code. – benwiggy Oct 8 at 15:59
  • As @GordonDavisson says, this looks like an X-Y problem. Can you clarify why you want an executable? Code obfuscation? Peformance? Inability to make changes? Ability to run with no shell installed? Ability to have an icon and sit in the Dock? Ability to run with a double-click? (There are probably different solutions depending on your actual requirements.) – gidds Oct 10 at 14:56
7

As jksoegaard already posted, there is the command shc. Below is an example of how to install and use this command. I tested this answer using macOS Big Sur.

Here are the steps I used to install shc.

  1. Open Terminal by pressing command+space, then type terminal and hit Enter key.

  2. Install homebrew first.

    ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null
    
  3. Install shc.

    brew install shc
    

Below is a listing of the test script file named hello.sh.

#!/bin/bash
osascript -e 'display dialog "Hi There"' >/dev/null'

Here are the steps I used to create and test the executable.

  1. Enter the following command to compile the script using shc.

    shc -f hello.sh
    
  2. Enter the following command to rename the executable.

    mv hello.sh.x hello
    
  3. Enter the command to test.

    ./hello
    

    Below is the resulting dialog box.


Appendix

Below is the output from running the commands.

Last login: Thu Oct  8 06:36:48 on console
dma@dmas-Mac-mini ~ % ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null 
Password:
==> You are using macOS 11.0.
==> We do not provide support for this pre-release version.
This installation may not succeed.
After installation, you will encounter build failures with some formulae.
Please create pull requests instead of asking for help on Homebrew\'s GitHub,
Discourse, Twitter or IRC. You are responsible for resolving any issues you
experience while you are running this pre-release version.

==> This script will install:
/usr/local/bin/brew
/usr/local/share/doc/homebrew
/usr/local/share/man/man1/brew.1
/usr/local/share/zsh/site-functions/_brew
/usr/local/etc/bash_completion.d/brew
/usr/local/Homebrew
==> The following existing directories will be made group writable:
/usr/local/bin
==> The following existing directories will have their owner set to dma:
/usr/local/bin
==> The following existing directories will have their group set to admin:
/usr/local/bin
==> The following new directories will be created:
/usr/local/etc
/usr/local/include
/usr/local/lib
/usr/local/sbin
/usr/local/share
/usr/local/var
/usr/local/opt
/usr/local/share/zsh
/usr/local/share/zsh/site-functions
/usr/local/var/homebrew
/usr/local/var/homebrew/linked
/usr/local/Cellar
/usr/local/Caskroom
/usr/local/Homebrew
/usr/local/Frameworks
==> The Xcode Command Line Tools will be installed.
==> /usr/bin/sudo /bin/chmod u+rwx /usr/local/bin
==> /usr/bin/sudo /bin/chmod g+rwx /usr/local/bin
==> /usr/bin/sudo /usr/sbin/chown dma /usr/local/bin
==> /usr/bin/sudo /usr/bin/chgrp admin /usr/local/bin
==> /usr/bin/sudo /bin/mkdir -p /usr/local/etc /usr/local/include /usr/local/lib /usr/local/sbin /usr/local/share /usr/local/var /usr/local/opt /usr/local/share/zsh /usr/local/share/zsh/site-functions /usr/local/var/homebrew /usr/local/var/homebrew/linked /usr/local/Cellar /usr/local/Caskroom /usr/local/Homebrew /usr/local/Frameworks
==> /usr/bin/sudo /bin/chmod g+rwx /usr/local/etc /usr/local/include /usr/local/lib /usr/local/sbin /usr/local/share /usr/local/var /usr/local/opt /usr/local/share/zsh /usr/local/share/zsh/site-functions /usr/local/var/homebrew /usr/local/var/homebrew/linked /usr/local/Cellar /usr/local/Caskroom /usr/local/Homebrew /usr/local/Frameworks
==> /usr/bin/sudo /usr/sbin/chown dma /usr/local/etc /usr/local/include /usr/local/lib /usr/local/sbin /usr/local/share /usr/local/var /usr/local/opt /usr/local/share/zsh /usr/local/share/zsh/site-functions /usr/local/var/homebrew /usr/local/var/homebrew/linked /usr/local/Cellar /usr/local/Caskroom /usr/local/Homebrew /usr/local/Frameworks
==> /usr/bin/sudo /usr/bin/chgrp admin /usr/local/etc /usr/local/include /usr/local/lib /usr/local/sbin /usr/local/share /usr/local/var /usr/local/opt /usr/local/share/zsh /usr/local/share/zsh/site-functions /usr/local/var/homebrew /usr/local/var/homebrew/linked /usr/local/Cellar /usr/local/Caskroom /usr/local/Homebrew /usr/local/Frameworks
==> /usr/bin/sudo /bin/mkdir -p /Users/dma/Library/Caches/Homebrew
==> /usr/bin/sudo /bin/chmod g+rwx /Users/dma/Library/Caches/Homebrew
==> /usr/bin/sudo /usr/sbin/chown dma /Users/dma/Library/Caches/Homebrew
==> Searching online for the Command Line Tools
==> /usr/bin/sudo /usr/bin/touch /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress
==> Installing Command Line Tools for Xcode-12.0
==> /usr/bin/sudo /usr/sbin/softwareupdate -i Command\ Line\ Tools\ for\ Xcode-12.0
Software Update Tool

Finding available software

Downloading Command Line Tools for Xcode
Downloaded Command Line Tools for Xcode
Installing Command Line Tools for Xcode
Done with Command Line Tools for Xcode
Done.
==> /usr/bin/sudo /bin/rm -f /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress
Password:
==> /usr/bin/sudo /usr/bin/xcode-select --switch /Library/Developer/CommandLineTools
==> Downloading and installing Homebrew...
HEAD is now at f84b9a027 Merge pull request #8878 from reitermarkus/upgrade-casks
==> Homebrew is run entirely by unpaid volunteers. Please consider donating:
  https://github.com/Homebrew/brew#donations
Already up-to-date.
==> Installation successful!

==> Homebrew has enabled anonymous aggregate formulae and cask analytics.
Read the analytics documentation (and how to opt-out) here:
  https://docs.brew.sh/Analytics
No analytics data has been sent yet (or will be during this `install` run).

==> Homebrew is run entirely by unpaid volunteers. Please consider donating:
  https://github.com/Homebrew/brew#donations

==> Next steps:
- Run `brew help` to get started
- Further documentation: 
    https://docs.brew.sh
dma@dmas-Mac-mini ~ % brew install shc
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/core).
==> Updated Formulae
Updated 4 formulae.

Warning: You are using macOS 11.0.
We do not provide support for this pre-release version.
You will encounter build failures with some formulae.
Please create pull requests instead of asking for help on Homebrew's GitHub,
Discourse, Twitter or IRC. You are responsible for resolving any issues you
experience while you are running this pre-release version.

==> Downloading https://homebrew.bintray.com/bottles/shc-4.0.3.catalina.bottle.tar.gz
######################################################################## 100.0%
==> Pouring shc-4.0.3.catalina.bottle.tar.gz
🍺  /usr/local/Cellar/shc/4.0.3: 17 files, 101.2KB
dma@dmas-Mac-mini ~ % pwd
/Users/dma
dma@dmas-Mac-mini ~ % cd Documents 
dma@dmas-Mac-mini Documents % shc -f hello.sh
dma@dmas-Mac-mini Documents % mv hello.sh.x hello
dma@dmas-Mac-mini Documents % ./hello
| improve this answer | |
  • 3
    No need to install homebrew first: shc compiles from source easily – lhf Oct 8 at 16:17
  • 2
    Maybe, but I did not have a complier installed. Installing Brew did that for me. See the Appendix part of my answer. I could be wrong, but I assume this took less space that installing all of Xcode. – David Anderson Oct 8 at 16:46
  • 2
    All one needs to have installed is Command Line Tools for Xcode to directly compile from source code and install as shown under Install at github.com/neurobin/shc, which when installing Homebrew does if they are not already installed. – user3439894 Oct 8 at 16:51
  • 2
    Note that it will still be easy for an expert to get the source code of the original script, despite whatever "encryption" shc purports to be doing to it. – Glenn Willen Oct 8 at 17:31
  • 1
    @GlennWillen: Yes, I believe jksoegaard's answer states that. My answer references that answer. – David Anderson Oct 8 at 17:47
14

I would take a look at a project named shc (Shell script compiler).

https://github.com/neurobin/shc

It takes a shell script and compiles it into C source code. The C source code can then be compiled with a standard C compiler into a binary executable.

This allows you to run the script you've made without the contents of the shell script being immediately revealed in clear text. It would be a binary executable like so many others.

It might take a bit of trial and error to get your specific script parsed by shc depending on how advanced features you're using in your bash script, but this should take you most of the way there.

Note that the program really just encrypts your shell script and integrates it within the binary. When the binary is executed, the script is decrypted and executed using ordinary shell again. For programmers it would be trivial to break this to reveal the contents of your script, but for hiding the script contents from the average home user it would perhaps be sufficient.

If you have HomeBrew installed, you can install shc by running the following command in the Terminal:

brew install shc

Then it's just a matter of running shc to convert your script into a binary:

shc -U -f myscript.sh -o mybinary

The mybinary command is then the finished product.

| improve this answer | |
6

Bash is technically an interpreted language, it's not compiled; compiling it is not possible.

Bash is the shell, or command language interpreter, for the GNU operating system.

However, it is possible to obfuscate it so prying eyes cannot look at your script.

Use openssl to encode the file.

I created a simple Zsh script file called arraytest.sh that will execute in Zsh (it's what I had available at the moment). I'm not going to post the actual shell script, instead, I am going to post the base64 encoding.

On my computer, I run the following command on my script and get the following results:

% openssl base64 < arraytest.sh

IyEvYmluL3pzaAoKZGVjbGFyZSAtQSBwcm9ncz0oW2dzXT0iR2hvc3RzY3JpcHQi
IFtic109IkJ1bGxzaGl0IiBbYWJdPSJWb2RrYSIpCgoKbGV0IGFzaXplPSR7I3By
b2dzW0BdfQpsZXQgYnNpemU9JGFzaXplLTEKbGV0IGk9MQpmb3IgeCBpbiAiJHtw
cm9nc1tAXX0iOwpkbwogIGlmIFsgJGkgLWVxICRic2l6ZSBdICAgCiAgdGhlbgog
ICBwcmludGYgIiR4IGFuZCAiCiAgZWxpZiBbICRpIC1sZSAkYnNpemUgXSAmJiBb
ICRpIC1nZSAxIF0KICB0aGVuCiAgICBwcmludGYgIiR4LCAiCiAgZWxzZQogICAg
cHJpbnRmICIkeCIKICBmaQogICgoaSsrKSkKZG9uZQoKcHJpbnRmICIuXG4iCmVj
aG8gRG9uZS4KCg==

That output needs to be saved to a file. Let's called it myencodedscript. You can copy and paste it into a new file. You don't have to make it executable. Once you've copied that script into your clipboard, issue the command

% pbpaste > myencodedscript

Now, you should have a file called myencodedscript. Next, to run it, you need to feed that encoding back through openssl to decode it and then pipe it to your shell.

% cat myencodedscript | openssl base64 -d | zsh

It should run the the script and generate output from an associative array.

So, how can you make this self contained?

#!/bin/zsh
#
#Script Name:  encoded_script.sh

code="IyEvYmluL3pzaAoKZGVjbGFyZSAtQSBwcm9ncz0oW2dzXT0iR2hvc3RzY3JpcHQi
IFtic109IkJ1bGxzaGl0IiBbYWJdPSJWb2RrYSIpCgoKbGV0IGFzaXplPSR7I3By
b2dzW0BdfQpsZXQgYnNpemU9JGFzaXplLTEKbGV0IGk9MQpmb3IgeCBpbiAiJHtw
cm9nc1tAXX0iOwpkbwogIGlmIFsgJGkgLWVxICRic2l6ZSBdICAgCiAgdGhlbgog
ICBwcmludGYgIiR4IGFuZCAiCiAgZWxpZiBbICRpIC1sZSAkYnNpemUgXSAmJiBb
ICRpIC1nZSAxIF0KICB0aGVuCiAgICBwcmludGYgIiR4LCAiCiAgZWxzZQogICAg
cHJpbnRmICIkeCIKICBmaQogICgoaSsrKSkKZG9uZQoKcHJpbnRmICIuXG4iCmVj
aG8gRG9uZS4KCg=="

echo "$code" | openssl base64 -d | zsh

Save that script as encoded_script.sh, make it executable and then run it with the command ./encoded_script.sh

Bonus...

(This assumes a scenario like an admin needing to run a script discretely on 1 or more machines in his care.)

If you want to truly hide this from "prying eyes", use this single line command:

% openssl base64 < script.sh | ssh user@hostname.domain ' openssl base64 -d | bash'

This will encode the script, pipe it through an SSH connection where it decodes the script and passes that to the shell interpreter.

green check Obfuscates code
green check No third party tools
green check Cross platform (tested from macOS host to FreeBSD remote)
green check No need for executable bit set (chmod +x)
green check Gatekeeper on macOS is rendered moot as it not an app that must be notarized or explicitly allowed by the user.

Obviously you need an account on the remote and while nice, but not required, SSH Keys enabled so you don't have to enter a password, especially if you're running this on a batch of machines.


Note: This was done in Zsh with an associative array. It may work in Bash but you'll need at least Bash version 4 to operate properly. Also there's nothing nefarious going on with the script. It will print 3 words and then, on a new line, the word "Done" to indicate the script is finished.

If you want to verify that the code is actually from me, create a file called myencodedscript in vi, paste the code (no quotes), be sure to press "I" for "Insert" before pasting, save it and issue the command and check the hash below

% shasum -a 256 myencodedscript
d4fc6ce35480b0ace6ed0e1c910f1f40079a106b1914d767fa17ada02a422f88  myencodedscript

It will only work if you use the same filename. I tested this cross platform on macOS and on FreeBSD. If you don't get the same hash, let me know.

| improve this answer | |
  • 3
    The OP, among other things, states "I also do not want anyone to be able to open it with text edit or similar and be able to see the code." and what you propose doesn't really cover it because, not only can one open the e.g. encoded_script.sh, one can also see what's in it and also easily decode the encoded text to see what it says as well. – user3439894 Oct 8 at 1:56
  • 2
    @user3439894 I guess the fact I say what he want's is not possible but I say "However, it is possible to obfuscate it so prying eyes cannot look at your script" is of no consequence? – Allan Oct 8 at 1:57
  • 2
    Technically it is not so that just because an language is "an interpreted language" right now, it cannot become a compiled language later. So saying it is not possible based on that is a bit thin. – jksoegaard Oct 8 at 12:25
  • 2
    @Allan can base64 encryption even be said to count as obfuscation? It is way too easy to deobfuscate... – theonlygusti Oct 8 at 12:59
  • Allan, To your comment to me, but prying eyes can look at the script and easily decode it too! So yes, saying what you did is of no consequence. – user3439894 Oct 8 at 13:01
3

As it is not completely clear what it is you are actually after, what I'm going to suggest does the following:

  • Creates from a bash script an application bundle structure with a single binary executable which does not contain in human readable form any of the script code used to create it.
  • Executes like an ordinary macOS application bundle running as the name of application bundle.
  • Does not open Terminal during its execution.

That said, it still may be interpreted because if you look at the Open Files and Ports in Activity Monitor for the e.g. name.app application bundle, /bin/bash is listed.

There is a GitHub project named bashapp which states:

bashapp takes as input a bash script and generates a binary executable and OS X application directory structure. This allows developers to provide Finder clickable bash scripts without terminals popping up, etc. Useful for launch, service scripts, etc.

It also provides simple source encryption as a means to obfuscate the bash script. You can specify your own key, or let bashapp generate a randomly sized random key for you, no fewer than 32 bytes long.

You will need to have Command Line Tools for Xcode installed in order to compile the bashapp executable that creates the application bundle. See: How to Install Command Line Tools in Mac OS X (Without Xcode) Or in Terminal run: xcode-select --install

That said, there was one annoying thing in that the Dock Tile for the application bounces in the Dock while it's being executed. A workaround for this is to not have it show in the Dock by running the following in Terminal:

defaults write '/path/to/$name.app/Contents/Info.plist' LSUIElement -bool yes

Changing /path/to/$name.app as appropriate.

| improve this answer | |
  • This is a very interesting tool, however there is an issue when you go about distributing the app - Gatekeeper. This App will not run on macOS 10.14+ without some sort of intervention. – Allan Oct 8 at 4:01
  • @Allan, RE: "This App will not run on macOS 10.14+ without some sort of intervention." -- I find that statement not to be true because I tested an application created with bashapp on macOS High Sierra and it ran on macOS Catalina without issue. That said, there was no explicit mention of distributing the file in the OP, so it's a non-issue at this point AFAIC. – user3439894 Oct 8 at 4:28
  • How does your answer improve on the "The OP, among other things, states "I also do not want anyone to be able to open it with text edit or similar and be able to see the code." " comment you've left beneath the other answers here? :-) – nohillside Oct 8 at 7:28
  • @nohillside, It improves upon them by the fact that that the binary executable of the created application does not contain any human readable form of any of the script code used to create it, nor is it included in such a way that it can be easily opened and read or decrypted as the two I left comments under. – user3439894 Oct 8 at 12:58
  • 1
    I agree that it is a little bit more complicated to decode the script after using the method here. But basically creating a script/binary called bash which copies its STDIN to /tmp/foo, and placing this at the front of PATH before calling the "binary" is enough. – nohillside Oct 8 at 13:04
2

An interesting approach to solving this problem could be using process checking pointing or process hibernation.

This would basically involve adding an initial sleep delay to the start of your bash script. Start the bash interpreter as usual, the script sleeps - and then you "hibernate" the process. This would basically save its state into a binary file on disk.

Later you can resume the file (on any computer) which would continue the execution of your program.

This is a method that doesn't require reimplementing the bash shell language. On the other hand, there's a risk that this breaks with large scripts or similar, where the file descriptors would need to be automatically reopened for bash to read in the script again (defeating the purpose of this question).

The only problem now is that I don't know of a process hibernation tool for macOS right now. So you would probably have to start with building one (or porting one). You could look at solutions for other operating systems, like for example the Linux tools CRIU, CryoPID and ckpt.

A very round-about way of achieving something similar to this would be to use a virtualization system such as VMware or even something like Docker on the Mac. Run the script inside Docker for example, checkpoint your container when the script is sleeping (the container is then paused). Then remove the shell script from the storage - and later you could restore the container to resume execution,.

| improve this answer | |
  • If you know how to program in any ahead-of-time compiled language, and know your way around POSIX system calls, for some bash scripts it's probably not very hard to reimplement them in C for example. Especially if you don't mind imposing some fixed size limits on size of buffers you capture from a pipe, if you even need to emulate foo=$(prog) at all. But yes, neat idea to do it this way. – Peter Cordes Oct 10 at 8:37
  • Porting to perl might be a lot easier; it can do shell-like things, sometimes with similar syntax. And there is a perl->executable tool. metacpan.org/pod/distribution/B-C/script/perlcc.PL. IIRC there used to be one that worked by dumping the perl interpreter after parsing; that's why your answer made me think of it. But it seems perlcc might be more sophisticated these days. – Peter Cordes Oct 10 at 8:41
  • Yes, I used such a tool for one of my Perl programs back in the nineties I think... however in either case you would really be programming in C or Perl - and then it’s not longer a bash script. On a modern Mac it’s probably easier to use the included PHP if you want to go that route - but it’s really a different ball game. – jksoegaard Oct 10 at 12:32
  • Well yeah, of course it's not bash anymore. But other than this answer (which may not be portable to Mac), all of the other answers basically end up feeding a bash script to bash in a way that would be easy to capture by tracing system calls with truss or dtrace or whatever the MacOS equivalent of Linux strace is. My suggestion is to do a bit of porting work: Perl is a capable scripting language that's easy to port many bash scripts to, which can truly be compiled. I don't know PHP well enough to see how that would help; is there a similar ahead-of-time compiler for it? – Peter Cordes Oct 10 at 12:37
  • The shc answer I wrote actually has an option that protects somewhat against dtrace and similar, but yes it is a major weakness of those approaches. The Perl idea also suffers something similar - at least with the “compilers” available when I tried that many years ago, it wasn’t that difficult to reverse back into Perl source code. For PHP similar ahead-of-time compilation is possible... it’s just that macOS comes with more recent versions preinstalled and that it might be easier accessible to newcomers than Perl - while still being easy to use for string manipulation. – jksoegaard Oct 10 at 13:57
2

I don't know if this will do what you want but the free app Platypus will take a command line script and convert it into a Mac App. Also works with Python, Perl and other scripts. You can read about it at the link I provided to see if it will do what you want. Note: Using my method it is possible to see the original script.

| improve this answer | |
  • The OP, among other things, states "I also do not want anyone to be able to open it with text edit or similar and be able to see the code." and while Platypus does create an application bundle, nonetheless, the shell script passed to it is still in human readable form within the application bundle at e.g. name.app/Contents/Resources/$script. – user3439894 Oct 8 at 1:55
  • i tried looking at the code but couldn't find the human readable file. i'm not saying it not there just that i couldn't find it and i did take that into consideration. i'll check again. Thanks. – jmh Oct 8 at 15:02
  • I went back into the app but still couldn't find a the original script file. If you know where it might be in the app file directory please let me know. i checked ccontents/MacOS and only file is the executable file which looks as if its in binary or other unreadable format. Also checked Contents/Resources and it wasn't there either. – jmh Oct 8 at 15:14
  • In Finder right-click on the application bundle created by Platypus and select Show Package Contents. Then in list view navigate to Contents > Resources and select script, then if you have Preview set to show you'll actually see the script code as in this image: imgur.com/a/B0lmJoO. Or just open the file script in TextEdit. – user3439894 Oct 8 at 15:25
  • Ok thanks. I obviously did something wrong. I saw that file using the command line but couldn't get it to show script. i know better now. thanks again. – jmh Oct 8 at 15:53

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .