diff options
Diffstat (limited to 'server/bin/oglivecli')
-rwxr-xr-x | server/bin/oglivecli | 482 |
1 files changed, 482 insertions, 0 deletions
diff --git a/server/bin/oglivecli b/server/bin/oglivecli new file mode 100755 index 00000000..451c3c12 --- /dev/null +++ b/server/bin/oglivecli @@ -0,0 +1,482 @@ +#!/bin/bash + +#/** +# oglivecli command [options ...] +#@file oglivecli +#@brief Command line tool to manage ogLive clients. +#@param $1 command Command to execute. +#@param $2 options Parameters and options. +#@warning This script uses "jq" command. +#@version 1.1.0 - Initial version. +#@author Ramón M. Gómez - ETSII Univ. Sevilla +#@date 2016-12-05 +#*/ ## + + +# Global constants definition. +PROG=$(basename "$(realpath "$0")") +OPENGNSYS=/opt/opengnsys +DOWNLOADDIR=$OPENGNSYS/lib +DOWNLOADURL="https://opengnsys.es/trac/downloads" +TFTPDIR=$OPENGNSYS/tftpboot +DEFOGLIVE="ogLive" +INFOFILE=$OPENGNSYS/etc/ogliveinfo.json + + +# Auxiliar functions. + +# Metafunction to check if JSON result exists. +function jq() { + local OUTPUT + OUTPUT=$($JQ "$@") || return $? + [[ "$OUTPUT" = "null" ]] && return 1 + echo "$OUTPUT" +} + +# Create/edit JSON file about installed ogLive clients. +function addToJson() { + local DATA OGLIVEDIST="$1" OGLIVEKRNL="$2" OGLIVEARCH="$3" OGLIVEREV="$4" + local OGLIVEDIR=$(basename $5 2>/dev/null) OGLIVEISO=$(basename $6 2>/dev/null) + # JSON data for installed ogLive. + DATA=$(cat << EOT | jq . +{"distribution":"$OGLIVEDIST","kernel":"$OGLIVEKRNL","architecture":"$OGLIVEARCH","revision":"$OGLIVEREV","directory":"$OGLIVEDIR","iso":"$OGLIVEISO"} +EOT + ) + # Check JSON file consistency. + if [ "$(jq -c keys $INFOFILE 2>/dev/null)" == '["default","oglive"]' ]; then + # Check if ogLive is defined into JSON file. + n=$(jq ".oglive | length" $INFOFILE) + for ((i=0; i<n; i++)); do + [ "$(jq ".check=$DATA | .check==.oglive[$i]" $INFOFILE)" == "true" ] && INDEX=$i + done + # Check if it needs to insert data. + if [ -z "$INDEX" ]; then + INDEX=$n + jq ".oglive |= (. + [$DATA])" $INFOFILE | sponge $INFOFILE + fi + # Show JSON entry. + jq ".oglive[$INDEX]" $INFOFILE + else + # Create new JSON file. + cat << EOT | jq . | tee $INFOFILE +{"oglive":[$DATA],"default":0} +EOT + fi +} + +# Show an error message. +function raiseError() { + case "$1" in + usage) + echo "$PROG: Usage error: Type \"$PROG help\"" >&2 + exit 1 ;; + notfound) + echo "$PROG: Resource not found: $2" >&2 + exit 2 ;; + access) + echo "$PROG: Access error: $2" >&2 + exit 3 ;; + download) + echo "$PROG: Download error: $2" >&2 + exit 4 ;; + *) + echo "$PROG: Unknown error" >&2 + exit 1 ;; + esac +} + +# Command functions. + +# Show help message. +function help() { + cat << EOT +$PROG: manage ogLive cleints. +Usage: $PROG command [options] +Commands: + help show this help + config show configuration parameters + check check system consistency + convert convert old ogclient to new default ogLive client + list list installed ogLive clients + show all show JSON information about all installed ogLive clients + show default show JSON information about ogLive client marked as default + show Index|Dir show JSON information about an installed ogLive client + search Index|Dir show corresponding index or directory + download show a menu to download an ogLive ISO image from the OpenGnsys website + download Iso download an specific ogLive ISO image from the OpenGnsys website + install Iso install a new ogLive client from a downloaded ISO image + uninstall Iso remove ISO image and uninstall its ogLive client + uninstall Index|Dir uninstall an ogLive client + get-default get index value for default ogLive client + set-default Index set default ogLive client + rebuild rebuild a lost configuration file + assign Iso Index assign an ISO file to a JSON entry +Parameters: + Index a number, starting by 0 + Dir directory (relative to installation directory) + Iso ISO file name (relative to download URL or download directory) +EOT +} + +# Convert default ogclient to a new ogLive format. +function convert() { + local OGCLIENT=ogclient OLDINFOFILE=$OPENGNSYS/doc/veroglive.txt + local OGLIVEKRNL OGLIVEDIR OGLIVEISO + [ $# -ne 0 ] && raiseError usage + [ ! -w $(dirname $INFOFILE) ] && raiseError access "Configuration file." + [ -n "$(stat -c "%N" $TFTPDIR/ogclient | awk '$3~/'$DEFOGLIVE'/ {print}')" ] && raiseError access "ogLive is already converted." + pushd $TFTPDIR >/dev/null || raiseError access "Installation directory." + [ ! -f $OGCLIENT/ogvmlinuz ] && raiseError notfound "ogclient" + # Add entry to JSON file using ogclient kernel version. + OGLIVEKRNL=$(file -bkr $OGCLIENT/ogvmlinuz | awk '/Linux/ {for(i=1;i<=NF;i++) if($i~/version/) {v=$(i+1);sub(/-.*/,"",v);print v}}') + OGLIVEDIR=$DEFOGLIVE-$OGLIVEKRNL + [ -r $OLDINFOFILE ] && OGLIVEISO="$(head -1 $OLDINFOFILE)" + addToJson "$(echo $OGLIVEISO|cut -f2 -d-)" "$OGLIVEKRNL" "i386" "${OGLIVEISO##*-}" "$OGLIVEDIR" "$OGLIVEISO.iso" + # Rename directory, link to default and clean old files. + mv -v $OGCLIENT $OGLIVEDIR + ln -vfs $OGLIVEDIR $DEFOGLIVE + rm -f $OGCLIENT + ln -vfs $DEFOGLIVE $OGCLIENT + mv -v $OGCLIENT.old $OGLIVEDIR.old 2>/dev/null + rm -fv {ogvmlinuz,oginitrd.img}{,.sum} $OLDINFOFILE + popd >/dev/null + # Delete old config file. + rm -f $OLDINFOFILE +} + +# Show script configuration parameters. +function config() { + case $# in + 0) # Show all parameters. + cat << EOT +Configuration file: $INFOFILE +ogLive download URL: $DOWNLOADURL +ogLive download directory: $DOWNLOADDIR +ogLive installation directory: $TFTPDIR +Default ogLive name: $DEFOGLIVE +EOT + ;; + 1) # Show specified parameter. + case "$1" in + config-file) echo "$INFOFILE" ;; + download-url) echo "$DOWNLOADURL" ;; + download-dir) echo "$DOWNLOADDIR" ;; + install-dir) echo "$TFTPDIR" ;; + default-name) echo "$DEFOGLIVE" ;; + *) raiseError notfound "$1" ;; + esac + ;; + *) # Usage error. + raiseError usage + ;; +esac +} + +# Check consistency, showing configuration problems. +function check() { + local ERR=0 AUX INST DEF + [ $# -ne 0 ] && raiseError usage + # Check for old system that needs conversion. + if [ -z "$(stat -c "%N" $TFTPDIR/ogclient | awk '$3~/'$DEFOGLIVE'/ {print}')" ]; then + echo "This server uses old ogclient, please run \"$PROG convert\" to update." + let ERR++ + [ ! -f $INFOFILE ] && return $ERR + fi + # Check for other problems. + [ ! -f $INFOFILE ] && echo "Configuration file does not exists: $INFOFILE" && let ERR++ + [ -f $INFOFILE -a "$(jq -c keys $INFOFILE 2>/dev/null)" != "[\"default\",\"oglive\"]" ] && echo "Format error in configuration file: $INFOFILE" && let ERR++ + [ ! -e $TFTPDIR ] && echo "TFTP directory does not exist: $TFTPDIR." && let ERR++ + # Check for installed ogLive clients. + INST=( $(find $TFTPDIR/ -type d -name "$DEFOGLIVE-*" -a ! -name "*.old" -printf "%f\n" | sort) ) + [[ ${#INST[@]} -eq 0 ]] && echo "No ogLive clients are installed." && let ERR++ + DEF=( $(jq -r .oglive[].directory $INFOFILE 2>/dev/null | sort) ) + # Compare installed and defined ogLive clients. + AUX=$(comm -23 <(printf "%s\n" ${INST[*]}) <(printf "%s\n" ${DEF[*]})) + [ -n "$AUX" ] && echo "Some ogLive are installed but not defined: ${AUX//$'\n'/, }" && let ERR++ + AUX=$(comm -13 <(printf "%s\n" ${INST[*]}) <(printf "%s\n" ${DEF[*]})) + [ -n "$AUX" ] && echo "Some ogLive are defined but not installed: ${AUX//$'\n'/, }" && let ERR++ + # Compare downloaded and defined ISO images. + INST=( $(find $DOWNLOADDIR/ -type f -name "$DEFOGLIVE-*.iso" -printf "%f\n" | sort) ) + DEF=( $(jq -r .oglive[].iso $INFOFILE 2>/dev/null | sort) ) + AUX=$(comm -23 <(printf "%s\n" ${INST[*]}) <(printf "%s\n" ${DEF[*]})) + [ -n "$AUX" ] && echo "Some ISOs are downloaded but not defined: ${AUX//$'\n'/, }" && let ERR++ + AUX=$(comm -13 <(printf "%s\n" ${INST[*]}) <(printf "%s\n" ${DEF[*]})) + [ -n "$AUX" ] && echo "Some ISOs are defined but not downloaded: ${AUX//$'\n'/, }" && let ERR++ + # Check for new ISO files downloaded after installation. + AUX=$(jq -r '.oglive[] as $og | $og.iso + ":" + $og.directory' $INFOFILE 2>/dev/null | \ + while IFS=":" read -r DEF INST; do + [ $DOWNLOADDIR/$DEF -nt $TFTPDIR/$INST ] && echo "$DEF" + done) + [ -n "$AUX" ] && echo "Some ISOs are downloaded after installation: ${AUX//$'\n'/, }" && let ERR++ + # Print result. + [ $ERR -eq 0 ] && echo "OK!" || echo "Problems detected: $ERR" + return $ERR +} + +# List installed ogLive clients. +function list() { + [ $# -ne 0 ] && raiseError usage + [ ! -r $INFOFILE ] && raiseError access "Configuration file." + # List all defined indexes, directories and check if missing. + jq -r .oglive[].directory $INFOFILE | nl -v 0 | \ + awk '{system("echo -n "$0"; test -d '$TFTPDIR'/"$2" || echo -n \" (missing)\"; echo")}' | column -t +} + +# Show information about an installed ogLive client. +function show() { + local INDEX + [ $# -ne 1 ] && raiseError usage + [ ! -r $INFOFILE ] && raiseError access "Configuration file." + # Show JSON entries. + case "$1" in + default) # Default index. + INDEX="[$(jq -r .default $INFOFILE)]" ;; + all) # All intries. + ;; + [0-9]*) # Index. + INDEX="[$1]" ;; + *) # Directory. + INDEX="[$(search "$1" 2>/dev/null)]" || raiseError notfound "Directory \"$1\"." + ;; + esac + jq ".oglive$INDEX" $INFOFILE || raiseError notfound "Index \"$1\"." +} + +# Show index or directory corresponding to searching parameter. +function search() { + [ $# -ne 1 ] && raiseError usage + [ ! -r $INFOFILE ] && raiseError access "Configuration file." + # Show corresponding index or directory. + list | awk -v d="$1" '{if ($2==d) print $1; if ($1==d) print $2}' | grep . || raiseError notfound "Index/Directory \"$1\"" +} + +# Show a menu to select and download an ogLive ISO image from the OpenGnsys website. +function download() { + local OGLIVE NISOS i SOURCELENGTH TARGETFILE + local ISOREL MINREL=5000 # Mininum compatibility release. + [ $# -gt 1 ] && raiseError usage + [ ! -d $DOWNLOADDIR ] && raiseError notfound "Download directory" + [ ! -w $DOWNLOADDIR ] && raiseError access "Download directory" + # Check parameter. + if [ -n "$1" ]; then + # ogLive to download. + OGLIVEFILE="$1" + else + # Show download menu. + OGLIVE=( $(curl --silent $DOWNLOADURL | grep "$DEFOGLIVE.*iso") ) + NISOS=${#OGLIVE[@]} + echo "Available downloads (+ = installed, * = full compatibility):" + for i in $(seq 1 $NISOS); do + [ -e $DOWNLOADDIR/${OGLIVE[i-1]} ] && OGLIVE[i-1]="(+) ${OGLIVE[i-1]}" + ISOREL=${OGLIVE[i-1]##*-r}; ISOREL=${ISOREL%.*} + [ $ISOREL -ge $MINREL ] && OGLIVE[i-1]="(*) ${OGLIVE[i-1]}" + done + select opt in "${OGLIVE[@]}"; do + [ -n "$opt" ] && OGLIVEFILE=${opt##* } && break + done + fi + # Get download size. + SOURCELENGTH=$(curl --head --silent $DOWNLOADURL/$OGLIVEFILE | awk -F: '/Content-Length:/ {print $2}') + [ -n "$SOURCELENGTH" ] || raiseError download "$OGLIVEFILE" + # Download ogLive. + TARGETFILE=$DOWNLOADDIR/$OGLIVEFILE + trap "rm -f $TARGETFILE" 1 2 3 6 9 15 + curl $DOWNLOADURL/$OGLIVEFILE -o $TARGETFILE || raiseError download "$OGLIVEFILE" +} + +# Install an ogLive client from a previously downloaded ISO image. +function install() { + local OGLIVEFILE OGLIVEDIST OGLIVEREV OGLIVEKRNL OGLIVEDIR OGINITRD OGSQFS OGCLIENT=ogclient + local SAMBAPASS TMPDIR RSYNCSERV RSYNCCLNT + [ $# -ne 1 ] && raiseError usage + OGLIVEFILE=$(realpath $DOWNLOADDIR/$1) + # Only 1 file in pathname expansion. + [ $(echo $OGLIVEFILE | wc -w) -gt 1 ] && raiseError usage + [ ! -f $OGLIVEFILE ] && raiseError notfound "Downloaded file: \"$1\"." + [ ! -r $OGLIVEFILE ] && raiseError access "Downloaded file: \"$1\"." + [ ! -w $(dirname $INFOFILE) ] && raiseError access "Configuration directory." + [ ! -w $TFTPDIR ] && raiseError access "Installation directory." + [ -z "$(file -b $OGLIVEFILE | grep "ISO.*ogClient")" ] && raiseError access "File is not an ogLive ISO image." + # Working directory (ogLive-Distribution-KernelVersion-Architecture-CodeRevision). + OGLIVEDIST="$(echo $OGLIVEFILE|cut -f2 -d-)" + OGLIVEREV="${OGLIVEFILE##*-}"; OGLIVEREV="${OGLIVEREV%.*}" + OGLIVEKRNL="$(echo $OGLIVEFILE|cut -f3- -d-)"; OGLIVEKRNL="${OGLIVEKRNL%-$OGLIVEREV.*}" + OGLIVEARCH="$(echo $OGLIVEFILE|awk -F- '{print $(NF-1)}')" + case "$OGLIVEARCH" in + i386|amd64) # Get architecture. + OGLIVEKRNL="${OGLIVEKRNL%-$OGLIVEARCH}" ;; + *) # 32-bit by default. + OGLIVEARCH="i386" ;; + esac + OGLIVEDIR="$TFTPDIR/$DEFOGLIVE-$OGLIVEDIST-${OGLIVEKRNL%%-*}-$OGLIVEARCH-$OGLIVEREV" + # Get current or default Samba key. + OGINITRD=$OGLIVEDIR/oginitrd.img + [ ! -r $OGINITRD ] && OGINITRD=$TFTPDIR/$DEFOGLIVE/oginitrd.img + if [ -r $OGINITRD ]; then + SAMBAPASS=$(gzip -dc $OGINITRD | \ + cpio -i --to-stdout scripts/ogfunctions 2>&1 | \ + grep "^[ ].*OPTIONS=" | \ + sed 's/\(.*\)pass=\(\w*\)\(.*\)/\2/') + fi + # Make ogLive backup. + rm -fr ${OGLIVEDIR}.old + mv -fv $OGLIVEDIR ${OGLIVEDIR}.old 2>/dev/null + # Mount ogLive ISO image, update its files and unmount it. + TMPDIR=/tmp/${OGLIVEFILE%.iso} + mkdir -p $OGLIVEDIR $TMPDIR + trap "umount $TMPDIR; rm -fr $TMPDIR" 1 2 3 6 9 15 + mount -o loop,ro $OGLIVEFILE $TMPDIR + cp -va $TMPDIR/ogclient/* $OGLIVEDIR || raiseError access "Cannot copy files to ogLive directory." + umount $TMPDIR + # Link to default directory if it's the first ogLive. + if [ ! -f $INFOFILE ]; then + rm -f $TFTPDIR/$DEFOGLIVE $TFTPDIR/$OGCLIENT + ln -vfs $(basename $OGLIVEDIR) $TFTPDIR/$DEFOGLIVE + ln -vfs $DEFOGLIVE $TFTPDIR/$OGCLIENT + fi + # Recover or ask for a new Samba access key. + if [ -n "$SAMBAPASS" ]; then + echo -ne "$SAMBAPASS\n$SAMBAPASS\n" | $OPENGNSYS/bin/setsmbpass "$(basename $OGLIVEDIR)" + else + $OPENGNSYS/bin/setsmbpass "$(basename $OGLIVEDIR)" + fi + # Set permissions. + find -L $OGLIVEDIR -type d -exec chmod 755 {} \; + find -L $OGLIVEDIR -type f -exec chmod 644 {} \; + chown -R :opengnsys $OGLIVEDIR + # Mount SquashFS and check Rsync version. + OGSQFS=$OGLIVEDIR/ogclient.sqfs + mount -o loop,ro $OGSQFS $TMPDIR + # If Rsync server version > client version, link to compiled file. + RSYNCSERV=$(rsync --version 2>/dev/null | awk '/protocol/ {print $6}') + RSYNCCLNT=$(chroot $TMPDIR /usr/bin/rsync --version 2>/dev/null | awk '/protocol/ {print $6}') + if [ -z "$RSYNCSERV" -o ${RSYNCSERV:-0} -gt ${RSYNCCLNT:-1} ]; then + [ -e $OPENGNSYS/client/bin/rsync-$RSYNCSERV ] && mv -f $OPENGNSYS/client/bin/rsync-$RSYNCSERV $OPENGNSYS/client/bin/rsync + else + # Else, rename compiled file using Rsync protocol number. + [ -e $OPENGNSYS/client/bin/rsync ] && mv -f $OPENGNSYS/client/bin/rsync $OPENGNSYS/client/bin/rsync-$($OPENGNSYS/client/bin/rsync --version 2>/dev/null | awk '/protocol/ {print $6}') + fi + # Unmount SquashFS. + umount $TMPDIR + rmdir $TMPDIR + # Update JSON file. + addToJson "$OGLIVEDIST" "$OGLIVEKRNL" "$OGLIVEARCH" "$OGLIVEREV" "$OGLIVEDIR" "$OGLIVEFILE" +} + +# Uninstall an ogLive client. +function uninstall() { + local ISO DIR INDEX DEFINDEX + [ $# -ne 1 ] && raiseError usage + [ ! -r $INFOFILE ] && raiseError access "Configuration file." + [ ! -w $TFTPDIR ] && raiseError access "Installation directory." + # Get index and directory for the entry. + case "$1" in + */*) # Error (access to other directory). + raiseError access "Cannot access outside installation directory." + ;; + *.iso) # ISO file. + ISO="$1" + # Working directory (ogLive-Distribution-KernelVersion-CodeRevision). + DIR="$(echo $ISO|cut -f1,3 -d-)-${ISO##*-}"; DIR=${DIR%.*} + INDEX=$(search $DIR 2>/dev/null) + ;; + [0-9]*) # Index. + INDEX=$1; DIR=$(search $INDEX 2>/dev/null) + ;; + *) # Directory. + DIR="$1"; INDEX=$(search $DIR 2>/dev/null) + ;; + esac + DEFINDEX=$(getdefault) + [[ $INDEX = $DEFINDEX ]] && raiseError access "Cannot uninstall default ogLive." + # Remove files and delete index entry. + rm -vfr ${ISO:+$DOWNLOADDIR/$ISO} ${DIR:+$TFTPDIR/$DIR} ### Remove $TFTPDIR/$DIR.old ? + if [ -n "$INDEX" ]; then + jq "del(.oglive[$INDEX])" $INFOFILE | sponge $INFOFILE + # Decrement default index if needed (removed < default). + [[ $INDEX < $DEFINDEX ]] && jq ".default=$((DEFINDEX-1))" $INFOFILE | sponge $INFOFILE + fi +} + +# Get default ogLive index. +function getdefault() { + [ $# -ne 0 ] && raiseError usage + [ ! -r $INFOFILE ] && raiseError access "Configuration file." + # Read default parameter. + jq -r .default $INFOFILE || raiseError notfound "Undefined default index." +} + +# Set default ogLive index. +function setdefault() { + local INDEX OGLIVEDIR + [ $# -ne 1 ] && raiseError usage + [ ! -w $INFOFILE ] && raiseError access "Configuration file." + INDEX=$1 + # Check if index entry exists. + jq ".oglive[$INDEX]" $INFOFILE || raiseError notfound "Index \"$INDEX\"." + # Get ogLive directory. + OGLIVEDIR=$(jq -r ".oglive[$INDEX].directory" $INFOFILE) || raiseError notfound "Directory for index \"$INDEX\"." + # Update default parameter. + jq ".default=$INDEX" $INFOFILE | sponge $INFOFILE + # Link to default directory. + rm -f $TFTPDIR/$DEFOGLIVE + ln -vfs $(basename $OGLIVEDIR) $TFTPDIR/$DEFOGLIVE +} + +# Rebuild a lost configuration file. +function rebuild() { + local INST i NF DEF + [ $# -ne 0 ] && raiseError usage + [ -f $INFOFILE ] && raiseError access "Configuration file exists." + INST=$(find $TFTPDIR/ -type d -name "$DEFOGLIVE-*" -a ! -name "*.old" -printf "%f\n" | sort) + for i in $INST; do + NF=$(echo $i | awk -F- '{print NF-1}') + case $NF in + 1) addToJson "" "$(echo $i|cut -f2 -d-)" "i386" "" "$i" "" ;; + 4) addToJson $(echo $i | awk -F- '{printf "%s %s i386 %s %s '""'",$2,$3,$4,$0}') ;; + 5) addToJson $(echo $i | awk -F- '{printf "%s %s %s %s %s '""'",$2,$3,$4,$5,$0}') ;; + esac + # Check for is default oglive. + [ -n "$(stat -c "%N" $TFTPDIR/$DEFOGLIVE | awk '$3~/'$i'/ {print}')" ] && DEF="$i" + done + # Set default ogLive. + [ -n "$DEF" ] && setdefault $(search $DEF) +} + +# Assign an ISO file to a JSON entry. +function assign() { +local ISOFILE DIR + [ $# -ne 2 ] && raiseError usage + [ ! -w $INFOFILE ] && raiseError access "Configuration file." + # Check if exist ISO file and index directory. + ISOFILE=$DOWNLOADFILE/$1 + [ ! -f $DOWNLOADDIR/$ISOFILE ] && raiseError notfound "ISO file \"$1\"." + DIR=$(search $2 2>/dev/null) + [ ! -d $TFTPDIR/$DIR ] && raiseError notfound "Directory for index \"$2\"." + # Assign ISO file to JSON entry. + jq ".oglive[$2].iso=\"$1\"" $INFOFILE | sponge $INFOFILE && jq ".oglive[$2]" $INFOFILE +} + + +# Main progrram. + +# Access control. +[ -r $OPENGNSYS/www/controlacceso.php ] && ACCESS="web" +[ "$USER" = "root" ] && ACCESS="root" +[ -z "$ACCESS" ] && raiseError access "Need to be root." +# Check dependencies. +JQ=$(which jq 2>/dev/null) || raiseError notfound "Need to install \"jq\"." +which sponge &>/dev/null || raiseError notfound "Need to install \"moreutils\"." +# Commands control. +shopt -s extglob +case "$ACCESS" in + root) CMDS='+(help|convert|config|check|list|show|search|download|install|uninstall|get-default|set-default|rebuild|assign)' ;; + web) CMDS='+(list|show|search|get-default)' ;; +esac +case "$1" in + $CMDS) COMMAND="${1/-/}"; shift; $COMMAND "$@" ;; + *) raiseError usage ;; +esac + +exit $? + |