Generate BASH from BASH
Download the latest version by clicking one of the download links above or:
curl -o- https://shellpen.sh/installer.sh | bash
Once you have downloaded the latest version of ShellPen, source the library:
source shellpen.sh
You're now ready to start generating shell script source code!
ShellPen offers three sources of documentation:
- ShellPen User Guide (this page)
- ShellPen Command Reference (details for individual commands)
- ShellPen Extension Guide (for extending the built-in syntax)
"Pens" are BASH functions used to author snippets of BASH source code.
Every pen is associated with a separate snippet of source code:
- If you want to author 3 source code files, consider creating 3 separate pens
To create a pen:
shellpen pens new [pen name]
Or use the preferred shorthand:
shellpen [pen name]
A pen can have any name (which is a valid BASH function name):
- No spaces are allowed
- It is recommended to not name your pen
:
- None of the following characters:
<
>
|
(
)
!
When writing to only one source, the conventional pen name is: -
shellpen -
You will occassionally want to see the code written by your pen!
To do that, run [pen] code
# Create a pen
shellpen -
# Write some source code
- echo "Hello, world!"
# Preview the generated source code
- code
# => 'echo "Hello, world!"'
If at any time you wish to clear a pen's source code, run [pen] cleanSlate
# Create a pen
shellpen -
# Write some source code
- echo "Hello, world!"
# Preview the generated source code
- code
# => 'echo "Hello, world!"'
# Clear the pen's source code
- cleanSlate
# Preview the generated source code (now empty)
- code
# => ""
Once you are finished with your pen, put it away via [pen] putAway
This will delete the pen and any source code associated with it.
# Create pen
shellpen -
# ...
# Put pen away
- putAway
# Trying to use the - pen again will now result in an error
- code
# => -: command not found
This page describes the basic features of ShellPen.
To extend the syntax, view the ⚙️ Extending Syntax Reference.
The most basic pen function is writeln
which appends one line of source code.
- writeln "Hello this is some code text"
- writeln "More code goes here"
- code
# => "Hello this is some code text\nMore code goes here"
Similar commands:
$ [command] [arguments]
is an alias for writeln
for "syntax sugar"
- $ someCommand "Argument1" "Hello, world!"
- code
# => 'someCommand Argument1 Hello, world!'
When you want to generate code that calls a command, use $
- Exceptions are
echo
andprintf
which have useful generation helpers
If you want the provided arguments wrapped in "
quotes, use $$
instead:
- $$ someCommand "Argument1" "Hello, world!"
- code
# => 'someCommand "Argument1" "Hello, world!"'
If you would like to add a "shebang" or "hashbang" to your source:
- shebang
- code
# => '#! /bin/bash'
- You can also provide your own path, e.g.
- shebang /usr/bin/env bash
Use function
to start the definition of a function and }
to finish:
- function hello
- echo Hello
- }
hello() {
echo "Hello"
}
ℹ️ Levels of indentation are automatically added in functions et al.
Use main
to add conventional code to run a function when the script is run:
- function hello
- :
- }
- main hello
hello() {
:
}
[ "${BASH_SOURCE[0]}" = "$0" ] && "hello" "$@"
Helper functions are provided for declaring different types of BASH variables:
var
Defines a generic BASH variable
- var x = 10
x=10
local
Defines alocal
variable for use in a BASH function
- fn hello
- local x = 10
hello() {
local x=10
}
int
declares a BASH integer value variable
- int x = 10
declare -i x=10
array
declares a single-dimensional BASH array
- array x
declare -a x
- array x Hello World '$@'
declare -a x=("Hello" "World" "$@")
- "Map" is shorthand for defining an associative-array:
- map x
declare -A x
- map x [Hello]=World [Foo]="Foo Bar"
declare -A x=([Hello]="World" [Foo]="Foo Bar")
ShellPen provides helpers for generating echo
and printf
commands.
- Any commands provided to
echo
will be wrapped in"
quotes
- echo "Hello, world" foo bar
echo "Hello, world" "foo" "bar"
printf
specifically supports providing a'
single quoted formatter string
- printf "Hello"
printf "Hello"
- printf '%s' "Hello"
printf '%s' "Hello"
- printf -v varname '%s' -- "Hello"
printf -v "varname" '%s' -- "Hello"
- Any command can have output sent to STDERR by prepending the command with
toStderr
- toStderr echo "Hello"
echo "Hello" >&2
- toStderr $ myCommand arg1 arg2
myCommand arg1 arg2 >&2
- Any command can have output sent to a file by prepending the command with
toFile [path]
- toFile log.log echo "Hello"
echo "Hello" > "log.log"
- toFile log.log $ myCommand arg1 arg2
myCommand arg1 arg2 > "log.log"
if
conditionals are supported (then
is an optional keyword)
- if [ '$#' -eq 0 ]
- comment Hello
- elif [ '$#' -eq 1 ]
- else
- echo "Hello, world"
- fi
if [ $# -eq 0 ]
then
# Hello
:
elif [ $# -eq 1 ]
then
:
else
echo "Hello, world"
fi
ℹ️ Empty blocks (or those with only comments) automatically have a :
added
case
/esac
conditionals are supportedoption
is used for options::
is used in place of;;
for closing each option
- case '$1' in
- option "foo"
- echo "Hello from foo"
- ::
- option "bar"
- ::
- option '*'
- echo "Other option!"
- ::
- esac
case "$1" in
foo)
echo "Hello from foo"
;;
bar)
:
;;
*)
echo "Other option!"
;;
esac
- One-liner conditionals are supported with the use of
AND
and/orOR
- [ '$#' -eq 0 ] AND toStderr echo "At least one argument is required"
[ $# -eq 0 ] && echo "At least one argument is required" >&2
- One liner statements with
{ ... }
are supported using,
in place of;
- [ '$#' -eq 0 ] AND { toStderr echo "Argument is required" , return 1 , }
[ $# -eq 0 ] && { echo "Argument is required" >&2; return 1; }
for
loops are supported (do
is an optional keyword)
- for arg in '"$@"'
- echo Argument: '$@'
- done
for arg in "$@"
do
echo "Argument:" "$@"
done
while
loops are supported (do
is an optional keyword)
- while [ '$#' -gt 0 ]
- echo Argument: '$1'
- shift
- done
while [ $# -gt 0 ]
do
echo "Argument:" "$1"
shift
done
(( ... ))
arithmetic is supported using{{ ... }}
in place of parenthesis
- int i=42
- {{ i++ }}
declare -i i=42
(( i++ ))
It's common to need to use |
pipes in generated BASH source code.
- Pipes are supported by providing an escaped
\|
in place of a|
pipe character
- echo '$1' \| $ sed "'s/foo/bar/'" \| $ head -1 \| $ xargs -n1 echo
echo "$1" | sed 's/foo/bar/' | head -1 | xargs -n1 echo
- Any command can accept input from STDIN by prepending the command with
fromStdin [source of stdin]
- while 'IFS=""' read -r line \|\| [ -n '"$line"' ]
- echo '$line'
- fromStdin some/file.txt done
while IFS="" read -r line || [ -n "$line" ]
do
echo "$line"
done < some/file.txt
ℹ️ The previous example uses an escaped OR (\|\|
) instead of OR
- Because
OR
adds an||
after the previous command's output, this would result in the||
being added after the while'sdo
is automatically added.
- Any command can accept STDIN from a file path by prepending the command with
fromFile [file path]
- This is the same as
fromStdin
but wraps provided arguments in"
- This is the same as
- while 'IFS=""' read -r line \|\| [ -n '"$line"' ]
- echo '$line'
- fromFile some/file.txt done
while IFS="" read -r line || [ -n "$line" ]
do
echo "$line"
done < "some/file.txt"
- Any command can accept input from a command by prepending the command with
fromCommand "[command with args]"
- var text = '"$(<"$filePath")"'
- while 'IFS=""' read -r line \|\| [ -n '"$line"' ]
- echo '$line'
- fromCommand "printf '%s' \"\$text\"" done
text="$(<"$filePath")"
while IFS="" read -r line || [ -n "$line" ]
do
echo "$line"
done < <(printf '%s' "$text")
- Any command can accept input from a string with
fromText "string"
- while 'IFS=""' read -r line \|\| [ -n '"$line"' ]
- echo '$line'
- fromText '$text' done
while IFS="" read -r line || [ -n "$line" ]
do
echo "$line"
done <<< "$text"