I'm trying to build a really simple TODO list with a bash script. It should allow user to add and remove a task and also see the entire list.
I've done it with the following script. But I've issues allowing a given task to take a whitespace as a string. For instance, if I'm adding a task with the command: ./programme_stack.sh add 1 start projet n1, it will only add a task with "start".
I've read a couple of things online, I know, I should double quote the variables but after trying this, it doesn't work. I must be missing something on the road.
Here is my script:
#!/bin/bash
TACHES=$HOME/.todo_list
# functions
function remove() {
res_remove=$(sed -n "$1p" $TACHES)
sed -i "$1d" $TACHES
}
function list() {
nl $TACHES
}
function add() {
if [ ""$(($(wc -l $TACHES | cut -d " " -f 1) + 1))"" == "$1" ]
then
echo "- $2" >> $TACHES
else
sed -i "$1i - $2" $TACHES
fi
echo "Task \"$2\" has been add to the index $1"
}
function isNumber() {
re='^[0-9]+$'
if ! [[ $# =~ $re ]] ; then
res_isNumber=true
else
res_isNumber=false
fi
}
# application
case $1 in
list)
list
;;
done)
shift
isNumber $#
if ! [[ "$res_isNumber" = false ]] ; then
echo "done must be followed by an index number"
else
nb_taches=$(wc -l $TACHES | cut -d " " -f 1)
if [ "$1" -ge 1 ] && [ "$1" -le $nb_taches ]; then
remove $1
echo "Well done! Task $i ($res_remove) is completed"
else
echo "this task doesn't exists"
fi
fi
;;
add)
shift
isNumber $1
if ! [[ "$res_isNumber" = false ]] ; then
echo "add must be followed by an index number"
else
index_max=$(($(wc -l $TACHES | cut -d " " -f 1) + 1))
if [ "$1" -ge 1 ] && [ "$1" -le $index_max ]; then
add $1 $2
else
echo "Idex must be between 1 and $index_max"
fi
fi
;;
*)
echo "./programme_stack.sh (list|add|done) [args]"
;;
esac
Can you guys see what I'm missing?
Many thanks!!
To get the script to support embedded spaces, 2 changes are needed
1) Accept embedded space - either
1A) Pass in the task name in quote script add nnn "say hello", OR
1B) Concatenate all the input parameters into single string.
2) Quote the task name to prevent it from being broken into individual words
In the code, implement 1B and 2
add)
...
if [ "$1" -ge 1 ] && [ "$1" -le $index_max ]; then
num=$1
shift
# Combine all remaining arguments
todo="$#"
add "$num" "$todo"
...
Related
So I'm making a shell script in Ubuntu. It's purpose is simple. You give a command with arguments and you get a different operation each time. The problem is that when I run the the script it won't actually run because of a syntax error in one elif. The most suspicious thing is that I have a similar elif above wich works or at least doesn't pop a syntax error...
I'm leaving my code for you to see it and understand. Thanks in advance!
if [ "$1" = "-a" -a $# -lt 3 ]
then
echo "Add a new line in katalogos!"
read -p "Give me a name... " name
read -p "Give me a surname... " surname
read -p "Give me a city name... " cityName
read -p "Give me a phone number... " num
echo "$name $surname $cityName $num" > katalogos
elif [ "$1" = "-l" -a $# -lt 3 ]
then
echo "Content of katalogos will be sorted numerically and blank lines will be excluded!"
sort -b -n katalogos
elif [ "$1" = "-s" -a $# -lt 4 ]
if [[ $2 != *[!0-9]* ]]
then
echo "Content of katalogos will be sorted according to the second argument!"
sort +$3 katalogos
fi
elif [ "$1" = "-c" -a $# -lt 4 ] // syntax error
if [[ $2 = *[!0-9]* ]]
then
echo "Content of katalogos will be sorted according to the keyword!"
if [ $(grep -e "$2" katalogos | wc -l) -eq 0 ]
then
echo "String is not matched."
else
grep -e "$2" katalogos
fi
fi
elif [ "$1" = "-d" -a ( "$3" = "-b" -o "$3" = "-r" ) ]
if [[ $2 = *[!0-9]* ]]
then
echo "Katalogos's string matching lines will be deleted and blank lines will be in their place, assuming that the third argument equals -b, else just the lines will be deleted!"
if [ $(grep -e $2 katalogos | wc -l) -eq 0 ]
then
echo "String is not matched."
else
if [ "$3" = "-b" ]
then
sed -i "$3" katalogos | sed -i '$ a '
echo "A blank line inserted in place of the deleted one."
else
sed -i "$3" katalogos
echo "Line deleted."
fi
fi
fi
elif [ "$1" = "-n" ]
echo "katalogos's number of blank lines will be shown with the ability to delete them!"
grep -cvP '\S' katalogos
read -p "Do you want to delete them? Type 1 for yes or 0 for no... " ans
if [ $ans -eq 1 ]
then
grep -cvP '\S' file | sed -i
echo "Lines deleted."
fi
else
echo "Help centre!"
echo "-Type ./telcat -a to insert a new line to katalogos."
echo "-Type ./telcat -l to see the contents of katalogos sorted numerically (excluding blank lines)."
echo "-Type ./telcat -s plus a number to see the contents of katalogos sorted by the data that the number points to."
echo "-Type ./telcat -c plus a keyword to see only the lines that match with the word given."
echo "-Type ./telcat -d plus a keyword and -b or -r to delete the lines that contain the word given. Specifically if the third argument is -b it will automatically add a blank line to the deleted one and if it is -r it will not."
echo "-Type ./telcat -n to see the number of blank lines of katalogos."
echo "End of help centre!"
fi
I am dealing with sorting words in Bash according to a given argument. I am given either argument -r, -a , -v or -h and according to it there are options to sort the words, as you can see at my "help".
Somehow, if I pass the argument -r it creates an error. I really don't understand what I am doing wrong, as if[["$arg"=="-a"]] works, but I have to use case somehow.
Here is my code:
#!/bin/bash
# Natalie Zubkova , zubkonat
# zubkonat#cvut.fel.cz , LS
#help
help="This script will calculate occurances of words in a given file, and it will sort them according to the given argument in following order> \n
without parametre = increasing order according to a number of occurance\n
-r = decreasing order according to a number of occurance\n
-a = in alphabetical increasing order\n
-a -r = in alphabetical decreasing order\n
There are also special cases of the given parametre, when the script is not sorting but:\n
-h = for obtaining help \n
-v = for obtaining a number of this task "
# this function will divide a given chain into a words, so we can start calculating the occurances, we also convert all the capital letters to the small ones by - tr
a=0;
r=0;
EXT=0;
if [ "$1" == "-h" ]; then
echo $help
exit 0
fi
if [ "$2" == "-h" ]; then
echo $help
exit 0
fi
if [ "$1" == "-v" ]; then
echo "5"
exit 0
fi
if [ "$2" == "-v" ]; then
echo "5"
exit 0
fi
function swap {
while read x y; do
echo "$y" "$x";
done
}
function clearAll {
sed -e 's/[^a-z]/\n/gI' | tr '[A-Z]' '[a-z]' | sort | uniq -c |awk '{i++; if(i!=1) print $2" "$1}' #swap
}
for arg do
case "$arg" in
"-a")
a=1
;;
"-r")
r=1
;;
"-v")
echo "5" #number of task is 5
exit 0
;;
"-h")
echo $help
exit 0
;;
"-?")
echo "invalid parametre, please display a help using argument h"
exit 0
;;
esac
done
#Sort according to parametres -a and -r
function sortWords {
if [[ a -eq 1 ]]; then
if [[ r -eq 0 ]]; then
clearAll | sort -nk1
fi
fi
if [[ a -eq 1 ]]; then
if [[ r -eq 1 ]]; then
clearAll | sort -nk1 -r
fi
fi
if [[ r -eq 1 ]]; then
if [[ a -eq 0 ]]; then
clearAll | sort -nk2 -r
fi
fi
if [[ a -eq 0 ]]; then
if [[ r -eq 0 ]]; then
clearAll | sort -nk2
fi
fi
}
#code is from Stackoverflow.com
function cat-all {
while IFS= read -r file
do
if [[ ! -z "$file" ]]; then
cat "$file"
fi
done
}
#histogram
hist=""
for arg do
if [[ ! -e "$arg" ]]; then
EXT=1;
echo "A FILE DOESNT EXIST" >&2
continue;
elif [[ ! -f "$arg" ]]; then
EXT=1;
echo "A FILE DOESNT EXIST" >&2
continue;
elif [[ ! -r "$arg" ]]; then
EXT=1;
echo "A FILE DOESNT EXIST" >&2
continue;
fi
done
for arg do
hist="$hist""$arg""\n"
done
echo -e "$hist" | cat-all | sortWords
exit $EXT;
Here is what our upload system which does some test to see if our program works says:
Test #6
> b5.sh -r ./easy.txt
ERROR: script output is wrong:
--- expected output
+++ script stdout
## --- line 1 (167 lines) ; +++ no lines ##
-the 89
-steam 46
-a 39
-of 37
-to 35
...
script written 484 lines, while 484 lines are expected
script error output:
A FILE DOESNT EXIST
cat: invalid option -- 'r'
Try `cat --help' for more information.
script exit value: 1
ERROR: Interrupted due to failed test
If anyone could help me I would really appreciate it.
You forgot to move the parameter index position with shift:
"-r")
r=1
shift
;;
shift above moves to the next command line arg: ./easy.txt in your case.
Without it, read -r file will read -r instead of the file name.
TABLE=`echo "${1}" | tr '[:upper:]' '[:lower:]'`
if [ $1 = -d ]
then TABLE=daminundation
elif [ $1 = -b ]
then TABLE=burnscararea
elif [ $1 = -r ]
then TABLE=riverpointinundation
elif [ $1 = " " ]
then echo "User must input -d (daminundation), -b (burnscararea)
or -r (riverpointinundation)."
fi
SHAPEFILEPATH=${2}
MERGEDFILENAME=${3}
if [ -z $3 ] ;
then MERGEDFILENAME=merged.shp
else
MERGEDFILENAME=${3}
fi
COLUMNNAME=${4}
if [ -n $4 ]
then COLUMNNAME=$4
fi
$3 & $4 are optional arguments. However, if I choose not to use $3 but I want to use $4, it will read the command as $3. Confused by other methods, how should I make it so that an undesired optional command can be bypassed for the next one?
You probably want this:
#!/bin/bash
while getopts ":b :d :r" opt; do
case $opt in
b)
TABLE=burnscararea
;;
d)
TABLE=daminundation
;;
r)
TABLE=riverpointinundation
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
esac
done
shift $((OPTIND-1))
[ -z "$TABLE" ] && ( echo "At least one of -b/-d/-r options must be provided"; exit 1; )
[ $# -ne 3 ] && ( echo "3 params expected!"; exit 1; )
SHAPEFILEPATH="$2"
MERGEDFILENAME="$3"
COLUMNNAME="$4"
# other stuff
I am trying to write a simple sh script that must be invoked with 2 arguments:
sh myscript.sh --user "some user" --fizz "buzz"
At the top of myscript.sh I have:
#!/bin/sh
# VALIDATION
# 1. Make sure there are 5 positional arguments (that $4 exists).
die () {
echo >&2 "$#"
exit 1
}
[ "$#" -eq 5 ] || die "5 arguments required, $# provided"
# 2. Make sure $1 is "-u" or "--user".
# 3. Make sure $3 is "-f" or "--fizz".
If validation fails, I'd like to print a simple usage message and then exit the script.
I think I have #1 correct (checking # of positional arguments), but have no clue how to implement #2 and #3. Ideas?
# 2. Make sure $1 is "-u" or "--user".
if ! [ "$1" = -u -o "$1" = --user ]; then
# Test failed. Send a message perhaps.
exit 1
fi
# 3. Make sure $3 is "-f" or "--fizz".
if ! [ "$3" = -f -o "$3" = --fizz ]; then
# Test failed. Send a message perhaps.
exit 1
fi
Other forms for testing a variable for two possible possible values:
[ ! "$var" = value1 -a ! "$var" = value2 ]
[ ! "$var" = value1 ] && [ ! "$var" = value2 ]
! [ "$var" = value1 && ! [ "$var" = value2 ]
For Bash and similarly syntaxed shells:
! [[ $var = value1 || $var = value2 ]]
[[ ! $var = value1 || ! $var = value2 ]]
Besides using negated conditions with if blocks, you can also have positive conditions with ||
true_condition || {
# Failed. Send a message perhaps.
exit 1
}
true_condition || exit 1
Of course && on the other hand would apply with negated conditions.
Using case statements:
case "$var" in
value1|value2)
# Valid.
;;
*)
# Failed.
exit 1
;;
esac
Manually:
if [ -z "$1" ];then
fi
if [ -z "$2" ];then
fi
if [ -z "$3" ];then
fi
...
Or check getopt
while getopts "uf" OPTION
do
case $OPTION in
u)
echo "-u"
;;
f)
echo "-f"
;;
esac
done
for var in "$#"
do
if test -z $var
then
echo "missing operand"
elif [ -d $var ]
then
echo "This is a directory"
elif [ ! -f $var ]
then
echo "The file does not exist"
else
basename=$(basename $var)
dirname=$(readlink -f $var)
inodeno=$(ls -i $var| cut -d" " -f1)
read -p "remove regular file $#" input
if [ $input = "n" ]
then exit 1
fi
mv $var "$var"_"$inodeno"
echo "$basename"_"$inodeno":"$dirname" >> $HOME/.restore.info
mv "$var"_"$inodeno" $HOME/deleted
fi
done
**Hello, the above code is trying to mimic the rm command in unix. Its purpose is to remove the file .
Eg if I type in bash safe_rm file1 , it works however if type in
bash safe_rm file1 file 2 , it prompts me to remove file 1 twice and gives me a unary operater expected for line 27(if [ $input = "n" ]).
Why does it not work for two files, ideally I would like it to prompt me to remove file1 and file 2.
Thanks
read -p "remove regular file $#" input
should probably be
read -p "remove regular file $var" input
That's the basic.
And this is how I'd prefer to do it:
for T in "$#"; do
if [[ -z $T ]]; then
echo "Target is null."
elif [[ ! -e $T ]]; then
echo "Target does not exist: $T"
elif [[ -d $T ]]; then
echo "Target can't be a directory: $T"
else
BASE=${T##*/}
DIRNAME=$(exec dirname "$T") ## Could be simpler but not sure how you want to use it.
INODE_NUM=$(exec stat -c '%i' "$T")
read -p "Remove regular file $T? "
if [[ $REPLY == [yY] ]]; then
# Just copied. Not sure about its logic.
mv "$T" "${T}_${INODE_NUM}"
echo "${BASE}_${INODE_NUM}:${DIRNAME}" >> "$HOME/.restore.info"
mv "${T}_${INODE_NUM}" "$HOME/deleted"
fi
fi
done