Shell++ is a programming language that aims bring features from modern languages, as facility to manipulate data structures, object oriented programming, functional programming and others, to shell script. https://alexst07.github.io/shell-plus-plus/
- easy to manipulate data structure, as in Python
- lambda functions
- classes
- executes commands like in Bash
- pipe, redirection to or from files, sub shell
- dynamic typing
- glob and recursive glob
- closures
- reference counter
- operator overriding
I wanted a language that runs shell commands like Bash, and manipulate data structure with the ease of Python, I searched, but I did not find any that met my requirements, so I decided to use my free time to create one.
This is still in development, but it has already made life easier for me in many day-to-day tasks, maybe this might be useful for someone else, so I shared it on github.
However everything here can change without warning, because to date, the only purpose of it is MAKE MY LIFE EASIER AND ONLY THAT.
Follows some examples just to get an idea of how the language works.
Shell++ has a powerful glob, that allows you to perform laborious tasks in a few lines. For example, suppose you want to modify the extension of all txt
files to csv
recursively.
for p, [n] in G"**/*.txt" {
mv ${p} ${p.parent_path()/"{n}.csv"}
}
files = [f for f in $(ls) if path(f).size("M") > 50]
In this example files will be an array with all files listed by ls command with size bigger than 50Mb.
try catch helps you handle situations in an elegant way, for example, instead of giving a raw message to the user's face, you can treat the message that a command does not exist.
try {
git clone git@github.com:alexst07/shell-plus-plus.git
} catch InvalidCmdException as ex {
print("git not installed [msg: {ex}]")
}
line = read()
print(line)
Function read
is used to read user input, until the enter key is pressed, and the next line print the user input.
for line in file("example.txt") {
print("line: {line}")
}
shell {
while let line = read() {
print("line: {line}")
}
} < example.txt
echo Hello World
print("Hello World")
In Shell++ you can use an other program as echo, or a native function print.
str = "example"
print("length: {len(str)}") # length: 7
x = "HELLO"
echo ${x} # HELLO
echo ${x.lower()} # hello
echo ${x.upper()} # HELLO
str = "string test"
str1 = str[2:] # ring test"
str2 = str[:-2] # string te
str3 = str[2:4] # ri
filename = "Path of the bash is /bin/bash"
print("After Replacement: {filename.replace('bash', 'sh')}")
# After Replacement: Path of the sh is /bin/sh
print("length: {len(str)}")
echo ${t} # Hello
echo ${t + " World"} # Concatenate string
# ${} solve an expression and return the value to echo
echo "Test" | cat # simple pipeline
ls src* | grep -e "test" # using glob
# using variables content as command
cip = "ipconfig"
cgrep = ["grep", "-e", "10\..*"]
${cip} | ${cgrep} # pass an array to command
cat < filename.txt
cat < filename.txt | grep test
cat < filename.txt | grep test > result.txt
# using variables
filename = "filename.txt"
result = "result.txt"
cat < ${filename} | grep test > ${result}
echo line1 > test.txt
echo lene2 >> test.txt
test.txt has this content: line1 lene2
prog_test 2> /dev/null
Any output on stderr go to /dev/null.
p = $(prog_test -opt 1)
# check result
if p {
print(p)
} else {
print("error: {p.err()}")
}
Check the status result of the command prog_test, if prog_test exited normally with status 0, the result is true and the line print(p) is executed, if not, the else is executed.
for f in $(ls -a mypath) {
print("file: {f}")
}
Print all lines generated by ls command.
ex = "ls"
${ex} # execute ls commad
scp = ["sch", "-i", "path/file.pem", "user@addr:path"]
${scp} # execute command from iterator
It is possible execute a command from an array, using this feature you have much more flexibility to mount the command with its arguments using arrays.
file *.txt
Glob in this case works like in bash.
for f in g"**/*.txt" {
file ${f}
}
iterate over all txt files recursively
file **/*.txt
This peace of code has the same output from above
if path("/home/me/file.txt").exists() {
print ("file exists")
}
p1 = path("/home/../home/me/file.txt")
p2 = path("/home/me/file.txt")
print(p1 == p2) # true
p1 = p"/home/me/file.txt"
p2 = p"/home/me/file2.txt"
print(p1 == p2) # false
In Shell++ you can compare relatives path, it means, using path object you compare path not string.
In Shell++ is easy create and access array, it works like in Python
array1 = ['physics', 'chemistry', 1997, 2000];
array2 = [1, 2, 3, 4, 5, 6, 7 ];
print("array1[0]: {array1[0]}")
print("array2[1:5]: {array2[1:5]}")
Output:
array1[0]: physics
array2[1:5]: [2, 3, 4, 5]
Map is a hash map object, it works like dictionary in Python.
m = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}
print("m['Name']: {m['Name']}")
print("m['Age']: {m['Age']}")
Output:
m['Name']: Zara
m['Age']: 7
In Shell++ function is not a command as in Bash, functions in Shell++ are similar with Python.
func test(a, b = 5) {
m = {"sum": a+b, "sub":a-b}
return m
}
print(test(6)["sum"])
Output:
11
Return a closure function
func ftest(a) {
v = ["echo", "ls", a]
# closures
return func(x) {
return v.append(x)
}
}
The keyword lambda is valid too. This example convert all array elements to upper case string.
vec = ["apple", "orange", "grape"]
vec.map(lambda x: x.to_upper())
# Complex number
class Complex {
func __init__(r, i) {
this.r = r
this.i = i
}
func __add__(n) {
return Complex(n.r + this.r, n.i + this.i)
}
func __sub__(n) {
return Complex(n.r - this.r, n.i - this.i)
}
func __print__() {
return "{string(this.r)} {string(this.i)}i"
}
}
c1 = Complex(2, 3)
c2 = Complex(1, 2)
c = c1 + c2
print(c)
Output:
3 + 5i
func quicksort(A, lo, hi) {
if lo < hi {
p = partition(A, lo, hi)
A = quicksort(A, lo, p - 1)
A = quicksort(A, p + 1, hi)
}
return A
}
func partition(A, lo, hi) {
pivot = A[hi]
i = lo
j = lo
for j in range(lo, hi) {
if A[j] <= pivot {
A[i], A[j] = A[j], A[i]
i = i + 1
}
}
A[i], A[hi] = A[hi], A[i]
return i
}
a = [1,4,6,4,10,11,22,100,3,3,9]
print(quicksort(a, 0, len(a) - 1))
Output:
[1, 3, 3, 4, 4, 6, 9, 10, 11, 22, 100]
If you need execute a no standard application, sometimes you need to check if the application is installed, with Shell it is a way.
func exist_cmd(name) {
c = $(which ${name})
if string(c) == "" {
return false
}
return true
}
print("Does cmd exist?")
print("ls: {exist_cmd('ls')}")
print("non-exist-cmd { exist_cmd('non-exist-cmd')}")
the command which inside $() have as result an cmd object, you can convert its result to string and handle it.
- A compiler that supports C++ 14 (gcc or clang)
- Boost
- Readline
- CMake
- Linux
First you need intall the requirements to build Shell++
# apt-get install -y build-essential
# apt-get install -y libboost-all-dev libreadline6 libreadline6-dev git cmake
With the requirements installed, make a clone of the repository
$ git clone git@github.com:alexst07/shell-plus-plus.git
After the clone, change to directory of the project
$ cd shell-plus-plus
To compile the project, you need create a directory when the cmake files will be generated, and so, build the project
$ mkdir build
$ cd build
$ cmake ..
$ make
Now you can already use the Shell++, the binary is generated at shell/ directory. To use Shell++ to execute a script, first, create an example script called my.shpp.
echo my first script in shell++
In the build path, call the binary to execute your script
$ ./shell/shpp my.shpp
You should see the output:
my first script in shell++
If you saw this output your build is working, if you want install Shell++.
$ sudo make install