Objective Bash
⏳ Doba čtení: ~17 min (2204 slov)Objective Bash
Odkaz na github
Bash verze 4.0 podporuje asociativní pole, která umožňují vytvářet náhradu za vícerozměrná pole:
declare -A a # deklaruje asociativní pole 'a'
key="mykey"
a[$key]=5 # asociuje hodnotu "5" s klíči "mykey"
echo ${a[$key]} # vypíše uložené hodnoty na klíči "mykey"
Asociativní pole se mi staly základem pro implementaci objektů v Bashi. Vytvořil jsem jednoduchou knihovnu, která umožňuje vytvářet objekty s metodami a vlastnostmi.
Celý soubor class.sh:
#!/bin/bash
# By Pytel
indir_keys() {
eval "echo \${!$1[@]}"
}
indir_val() {
eval "echo \${$1[$2]}"
}
# get atribut
function get () { # ( instance.atribut )
declare -n object=$(echo $1 | cut -d "." -f 1)
local atribut=$(echo $1 | cut -d "." -f 2)
echo -e "${object["$atribut"]}"
}
# set atribut
function sat () { # ( instance.atribut = "value" )
declare -n object=$(echo $1 | cut -d "." -f 1)
local atribut=$(echo $1 | cut -d "." -f 2)
shift; if [ "$1" == "=" ]; then shift; fi
object["$atribut"]="$1"
}
# run function
function rfn () { # ( instance func atributs )
local this=$1; shift
local fun=$1; shift
local code=$(get $this.$fun)
eval $code
}
# run object function
function rof () { # ( instance.func atributs ... )
local this=$(echo $1 | cut -d "." -f1)
local fun=$(echo $1 | cut -d "." -f2 ); shift
local code=$(get $this.$fun)
#$DEBUG && echo -e "Code: \n$code"
eval $code
}
# copy class
function copy () { # ( object class )
declare -n object=$1
declare -n class=$2
for atribut in "${!class[@]}"; do
object["$atribut"]="${class[$atribut]}";
done
}
# exnteds class
function extend () { # ( object class )
copy $@
}
# generic contructor
function new () { # ( object = class )
local args=$(echo $@ | tr -d " " )
local object=$(echo $args | cut -d "=" -f 1)
local class=$(echo $args | cut -d "=" -f 2)
declare -gA $object
copy $object $class
}
function delete () { # ( object )
unset $1
}
# END
Ukázka použití
!/bin/bash
# By Pytel
source class.sh
# Definice "tridy"
declare -A ServerClass
ServerClass["name"]="GenericServer"
ServerClass["start"]='echo "Starting server: $(get $this.name)..."'
# Vytvoreni instance
new MyServer = ServerClass
# Zmena atributu
sat MyServer.name = "WebProduction"
# Zavolani metody
rof MyServer.start
# Vystup: Starting server: WebProduction...
Tests
Odkaz na github
TDD - test driven development - je přístup k vývoji software, který jde ruku v ruce s objektově orientovaným programováním.
Vytvořil jsem jednoduchou testovací knihovnu pro Bash, která umožňuje psát testy pro mé objekty. Testy jsou psány jako funkce, které volají metody objektů a porovnávají výsledky s očekávanými hodnotami.
Testovací knihovna test.sh má asi 150 řádků a obsahuje funkce pro definici testů, spouštění testů a reportování výsledků.
#!/bin/bash
# By Pytel
# framework for testing bash scripts
# testing files: test_...sh
# testing metodes: test_... ()
#DEBUG=true
DEBUG=false
VERBOSE=false
function printHelp () {
echo -e "COMMANDS:"
echo -e " -h, --help \t\t print this text"
echo -e " -d, --debug\t\t enable debug output"
echo -e " -v, --verbose\t\t increase verbosity"
}
function run_function () { # ( function )
local function=$1
eval $function
return $?
}
function print_function_and_error_line () { # ( file function errno )
# print function & error line
local file=$1
local function=$2
local errno=$3
indexes=$(cat -n $file | grep "function" | grep "()" | tr "\t" " " | tr -s " ")
len=$(cat $file | wc -l)
indexes="${indexes}\n ${len}"
start_end=$(echo -e "$indexes" | grep -A 1 $function | cut -d " " -f 2 | tr "\n" " ")
start=$(echo $start_end | cut -d " " -f 1)
end=$(echo $start_end | cut -d " " -f 2)
end=$(( $end - 1 ))
code=$(sed -n ${start},${end}p $file)
faigled_line=$(echo -e "$code" | grep -n "return $errno" | cut -d ":" -f1)
echo -e "${Red}=== FAILURE ===${NC}\n"
echo "$code" | sed 's/^/\t/' | sed "${faigled_line}s/^/>/"
echo -e "\n${Red}ERROR${NC} in: ${Blue}$file${NC} on line: $(( $start + $faigled_line - 1 ))."
}
function test_function () { # ( file function )
local file=$1
local function=$2
local output
output=$(run_function $function); errno=$?
if [ $errno -ne 0 ]; then
print_function_and_error_line $file $function $errno
fi
if $VERBOSE; then
echo -e "\nFunction output:"
echo -e "$output"
fi
if [ $errno -ne 0 ]; then
return 1
fi
return 0
}
function find_test_functions () { # ( file )
local file=$1
local functions=$(cat $file | grep "function test_" | cut -d " " -f 2)
echo $functions
}
function test_file () { # ( file )
local file=$@
local functions=$(find_test_functions $file)
# load functions
source $file
local progres=""
local pass="${Green}.${NC}"
local fail="${Red}F${NC}"
$DEBUG && echo -e "All func: $functions"
for function in $functions; do
$DEBUG && echo -e "\nFunction: ${Blue}$function${NC}"
test_function $file $function
ret=$?
if [ $ret -eq 0 ]; then
passed=$((passed+1))
progres=$progres$pass
else
faigled=$((faigled+1))
progres=$progres$fail
fi
done
# return stats (passed faigled)
echo -e "Progress: $progres"
return 0
}
# kazdou funkci spusti a odchyti jeji nermolni a chybovy vystup
# chyby funkci se posilaji na chybovy vystup
# vse ostatni je na normalnim
# standardni format chybovych vystupu
# kdyz funkce chybuje, tak vypisuji do konzole jeji kod
# zvyrazneni radku, ktery navraci danou chybovou hodnotu
# colors
source ../colors.sh
passed=0
faigled=0
files=""
# parse input
$DEBUG && echo "Args: [$@]"
arg=$1
while [ $# -gt 0 ] ; do
$DEBUG && echo "Arg: $arg remain: $#"
# vyhodnoceni
case $arg in
-h | --help) printHelp; exit 2;;
-d | --debug) DEBUG=true;;
-v | --verbose) VERBOSE=true;;
*) files=$arg;;
esac
# next arg
shift
arg=$1
done
# are files set?
if [ -z $files ]; then
files=$(ls $pwd | tr " " "\n" | grep ".sh" | grep "test_")
fi
number=$(echo $files | tr " " "\n" | wc -l)
echo -e "${Green}=== test session starts ===${NC}"
echo -e "rootdir: ${Blue}$(pwd)${NC}"
echo -e "collected: ${Blue}$number${NC} files"
for file in $files; do
echo -e "File: ${Blue}$file${NC}"
# do file exist?
if [ ! -f $file ]; then
$VERBOSE && echo "ERROR: $file do not exist!"
continue
fi
test_file $file
done
echo -e "=== ${Green}$passed passed${NC}, ${Red}$faigled failed${NC} ==="
$VERBOSE && echo -e "Done"
exit 0
#END
Příklad testů
Ukazá jak může vypadat test pro funkci add:
#!/bin/bash
inA=( 1 1 4 )
inB=( 1 2 2 )
out=( 2 3 42 )
function add () {
echo $(( $1 + $2 ))
}
function test_add () {
local len=${#out[@]}
echo "len: $len"
for i in $(seq 1 $len); do
A=${inA[$i]}
B=${inB[$i]}
echo "A: $A"
echo "B: $B"
sum=$(add $A $B)
if [ $sum -gt ${out[$i]} ]; then
return 1
elif [ $sum -lt ${out[$i]} ]; then
return 2
fi
done
return 0
}
#END