You can use command-line MiniScript to write shell scripts that can be invoked directly, just like scripts written in BASH, Python, Perl, etc. If you're already comfortable with MiniScript, this lets you use those skills to write scripts that manipulate files, invoke other shell commands, and otherwise improve your command-line life.
Using a shebang
Several features of command-line MiniScript support this sort of usage. First, when executing a script file, it ignores the first line if it starts with "#!" (a shebang). A shebang is used to direct the shell to the proper interpreter for the script. So, you can make a text file like this:
#!/usr/local/bin/miniscript
print "Hello world!"
print "The current directory is: " + file.curdir
print "And my arguments are: " + shellArgs
Change the path after the shebang in the first line to point to wherever your command-line MiniScript lives (which miniscript
will tell you that if you've forgotten). Save the file as, for example, "hello" (traditionally, you don't use any file extension for an executable shell script). If your editor doesn't automatically detect the shebang and set the executable bit, you can set it with a command like:
chmod u+x hello
And now, you can execute that script by doing just
./hello
Or if you drop the script somewhere in your PATH, then you don't even need ./
; you can just type hello
. Neat, huh?
The file
module
Shell scripts are very often written to do some task in your file system. For this, the intrinsic file
module will be helpful. This contains a bunch of commands (documented here) for getting and changing the current directory, getting info on files, copying/renaming/deleting files, and reading/writing text files.
We'll see a practical example of this in the next section, so let's get to it!
shellArgs
The next useful feature to know about is shellArgs
. This gives you all the arguments to your shell script, as a list of strings. shellArgs[0]
is always the path to the script file itself, which can be useful if you need some data file or import module that's stored next to it. The remaining entries in the list are the arguments, which could be command-line options or file paths — use these for whatever you like. Note that if you give a wildcard argument, like *.txt
, the shell will automatically expand this into all matching file names, before invoking your script.
As an example, let's make a "purge" command that deletes any files it's given if they are more than 30 days old.
#!/usr/local/bin/miniscript
import "dateTime"
dayLimit = 30
limit = dayLimit * 24 * 60 * 60 // time limit, in seconds
oldFiles = []
for f in shellArgs[1:]
info = file.info(f)
if info.isDirectory then
print f + ": directory"
continue
end if
secsOld = dateTime.nowVal - dateTime.val(info.date)
if secsOld > limit then oldFiles.push f
end for
if not oldFiles then
print "No given files are over " + dayLimit + " days old."
exit
end if
print "The following files are over " + dayLimit + " days old:"
for f in oldFiles
print " " + f + " (" + file.info(f).date + ")"
end for
yesNo = input("Delete these files? ").lower
if yesNo and yesNo[0] == "y" then
count = 0
for f in oldFiles
if file.delete(f) then count += 1
end for
print count + " file(s) deleted."
end if
Here we're using shellArgs[1:]
to get the list of files passed to the script, and then file.info
to get some details about each one. This script skips directories (of course it could be expanded to handle those too), and then collects all the other files more than 30 days old, with the help of the dateTime module.
This script also demonstrates the use of input
to get more information from the user — in this case, to confirm deletion of the old files that were found.
env
Shell scripts often find it useful to interact with the shell environment variables. In command-line MiniScript, these are accessed via the env
map. If you do
print env.indexes
then you will see the names of all environment variables. You can get a value using square-brackets syntax, like env["PATH"]
, or with simply dot syntax, like env.PATH
.
Modifications to env
change the environment variables, but only for the script session. So this is a good way to set up an environment for some other shell command you might execute with:
exec
The exec
intrinsic spawns a shell command. It can run any non-interactive command that you could run yourself on the command line: manipulate docker images, convert images from one format to another, repack video files, zip or unzip files, push the latest version of your game up to itch.io, etc.
exec
returns a little map with three entries:
-
status
: the exit code of the command, which is generally 0 for success, and some nonzero number for failure -
output
: whatever the command printed to stdout -
errors
: whatever the command printed to stderr
(See Standard Streams for more insight on stdout and stderr.)
As an example, here's a script for MacOS that checks whether any of the given files have been quarantined by the OS, and if so, clears the quarantine flag so they can be used.
#!/usr/local/bin/miniscript
import "stringUtil"
for f in shellArgs[1:]
attrs = exec("xattr -l " + f).output
if attrs.contains("com.apple.quarantine") then
exec "xattr -d com.apple.quarantine " + f
print "Cleared quarantine on " + f
else
print f + " is not in quarantine."
end if
end for
Conclusion
With these tools — the shebang, plus file
, shellArgs
, env
, and exec
— you have an extremely powerful tool in your toolbox. Give yourself elegant commands that suit your needs, or even schedule them to run automatically, all while working in the clean, modern language of MiniScript.
How might you use this power? Post your ideas or experiences in the comments below!
Top comments (0)