Skip to content

shellbox-sh/ShellPen

Repository files navigation

🖋️ Shell Pen

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

Getting Started

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!

Documentation

ShellPen offers three sources of documentation:

Using Pens

"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: < > | ( ) !

shellpen -

When writing to only one source, the conventional pen name is: -

Create Pen

shellpen -

$pen code

You will occassionally want to see the code written by your pen!

To do that, run [pen] code

Preview Generated Source

# Create a pen
shellpen -

# Write some source code
- echo "Hello, world!"

# Preview the generated source code
- code
# => 'echo "Hello, world!"'

$pen cleanSlate

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
# => ""

$pen putAway

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

shellpen extend

This page describes the basic features of ShellPen.

To extend the syntax, view the ⚙️ Extending Syntax Reference.

Writing Basics

The most basic pen function is writeln which appends one line of source code.

writeln

- 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 Description
write Appends text without newlines but with indentation
writeln Appends a line of text with indentation
append Appends text without newlines or indentation
appendln Appends a line of text without indentation
prepend Prepends text without newlines or indentation
prependln Prepends a line of text without indentation

$ command

$ [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 and printf which have useful generation helpers

$$ command

If you want the provided arguments wrapped in " quotes, use $$ instead:

- $$ someCommand "Argument1" "Hello, world!"

- code
# => 'someCommand "Argument1" "Hello, world!"'

shebang

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

function

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.

main

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" "$@"

Variables

Helper functions are provided for declaring different types of BASH variables:

  • var Defines a generic BASH variable
- var x = 10
x=10
  • local Defines a local 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
With Items
- array x Hello World '$@'
declare -a x=("Hello" "World" "$@")
  • "Map" is shorthand for defining an associative-array:
- map x
declare -A x
With Items
- map x [Hello]=World [Foo]="Foo Bar"
declare -A x=([Hello]="World" [Foo]="Foo Bar")

Output

ShellPen provides helpers for generating echo and printf commands.

echo

  • Any commands provided to echo will be wrapped in " quotes
- echo "Hello, world" foo bar
echo "Hello, world" "foo" "bar"

printf

  • printf specifically supports providing a ' single quoted formatter string
- printf "Hello"
printf "Hello"
With Formatter
- printf '%s' "Hello"
printf '%s' "Hello"
With Flags
- printf -v varname '%s' -- "Hello"
printf -v "varname" '%s' -- "Hello"

toStderr

  • Any command can have output sent to STDERR by prepending the command with toStderr
- toStderr echo "Hello"
echo "Hello" >&2
Arbitrary Command
- toStderr $ myCommand arg1 arg2
myCommand arg1 arg2 >&2

toFile

  • 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"
Arbitrary Command
- toFile log.log $ myCommand arg1 arg2
myCommand arg1 arg2 > "log.log"

Conditionals

if / fi

  • 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 / option / esac

  • case / esac conditionals are supported
    • option 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

[ ... ] AND / OR

  • One-liner conditionals are supported with the use of AND and/or OR
- [ '$#' -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; }

Loops

for

  • for loops are supported (do is an optional keyword)
- for arg in '"$@"'
  - echo Argument: '$@'
- done
for arg in "$@"
do
  echo "Argument:" "$@"
done

while

  • 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

{{ ... }}

  • (( ... )) arithmetic is supported using {{ ... }} in place of parenthesis
- int i=42
- {{ i++ }}
declare -i i=42
(( i++ ))

Pipes | STDIN

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

fromStdin

  • 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's do is automatically added.

fromFile

  • 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 "
- 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"

fromCommand

  • 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")

fromText

  • 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"