Too many arguments error in bash - bash

I am writing a script to delete all files in a directory for practice. I am using quotes around my variables, and yet I am still getting the following error:
/usr/local/bin/deleteall: line 6: [: too many arguments
/usr/local/bin/deleteall: line 11: [: too many arguments
Here is my code:
#!/bin/bash
#Deletes all files in the current directory
read -p "You are about to delete all files in $(pwd). Are you sure you want to do this? y/n" yn
echo $yn
if [ [ "$yn" = "y" ] -o [ "$yn" = "Y" ] ] ; then
for i in `ls`; do
rm $i
done
exit;
elif [ [ "$yn" = "n" ] -o [ "$yn" = "N" ] ] ; then
exit;
else
read -p "Please enter y (yes) or n (no)"
exit;
fi
And this is the entire output:
You are about to delete all files in <my dir>. Are you sure you want to do this? y/nn
n
/usr/local/bin/deleteall: line 6: [: too many arguments
/usr/local/bin/deleteall: line 11: [: too many arguments
Please enter y (yes) or n (no)n
What am I doing wrong?

This line appears to be problem:
if [ [ "$yn" = "y" ] -o [ "$yn" = "Y" ] ] ; then
You can replace this with:
if [[ "$yn" == [yY] ]]; then
PS: Do same for the line where you check n or N.

You can't nest []. It's literally interpreting the nested brackets as arguments, and printing an error that you have too many.
Just this will work
if [ "$yn" = "y" -o "$yn" = "Y" ]; then
Another alternate syntax using double brackets and same logic
if [[ $yn == "y" || $yn == "Y" ]]; then
Also
for i in `ls`; do
rm $i
done
Should really be
for i in *; do
[ -f "$i" ] && rm $i
done
So it only tries to remove regular files (you will get errors for dirs, unless you overwrote rm, and you can decide what you want to do with symlinks). And ls is simply extraneous.

this is what you want:
read -p "Are you sure? " -n 1 -r
echo # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
for i in `ls`; do
rm -rf $i
done
fi
if [[ $REPLY =~ ^[Nn]$ ]]
then
#do what you want
done
fi

Related

How do I use round brackets in an 'if' condition

I'm creating a bash script and somewhere inside I have this code:
if [ $# -eq 2 -a (! -r "$2" -o ! -f "$2") ]; then
echo "rvf: bestand \""$2"\" bestaat niet of is onleesbaar" 1>&2
exit 2
fi
When i try to run this inside the script I get this error:
Syntax Error (bash -n):
rvf: line 14: syntax error in conditional expression
rvf: line 14: syntax error near `-a'
rvf: line 14: `if [[ $# -eq 2 -a (! -r "$2" -o ! -f "$2") ]]; then'
How does '()' work inside Bash scripts?
[[ doens't support -a, and it is considered obsolete and non portable for [. The correct solution using [ would be
if [ "$#" -eq 2 ] && { [ ! -r "$2" ] || [ ! -f "$2" ]; }; then
Grouping is done with { ... } rather than ( ... ) to avoid creating an unnecessary subshell.
Using [[ is simplifies to
if [[ "$#" -eq 2 && ( ! -r "$2" || ! -f "$2" ) ]]; then
Parentheses can be used for grouping inside [[; as a compound command, it uses separate parsing and evaluation rules, compared to an ordinary command like [ (which is just an alias for test, not syntax of any kind).
In either case, De Morgan's laws lets you refactor this to something a little simpler:
if [ "$#" -eq 2 ] && ! { [ -r "$2" ] && [ -f "$2" ] }; then
if [[ "$#" -eq 2 && ! ( -r "$2" && -f "$2" ) ]]; then
There are multiple points of confusion here.
[ can (as an optional XSI extension to the standard) support ( as a separate word (meaning there needs to be spaces around it), but the POSIX sh specification marks it (like -a and -o) as "obsolescent" and advises against its use.
[[ does support (, but again, it needs to always be a separate word.
Don't do that at all, though. You're using only well-supported and portable functionality if you keep each test its own simple command and combine them only with the shell's boolean logic support.
That is:
if [ "$#" -eq 2 ] && { [ ! -r "$2" ] || [ ! -f "$2" ]; }; then
echo "rvf: bestand \"$2\" bestaat niet of is onleesbaar" >&2
exit 2
fi
Restructure your logic.
"Not A or Not B" is just a more complicated way to say "not (A and B)".
In bash, try
if [[ "$#" == 2 ]] && ! [[ -r "$2" && -f "$2" ]]; then
Better,
if [[ "$#" == 2 && -r "$2" && -f "$2" ]]
then : all good code
else : nope code
fi
Even better,
if [[ "$#" == 2 ]] # correct args
then if [[ -r "$2" ]] # is readable
then if [[ if -f "$2" ]] # is a file
then echo "all good"
: do all good stuff
else echo "'$2' not a file"
: do not a file stuff
fi
else echo "'$2' not readable"
: do not readable stuff
fi
else echo "Invalid number of args"
: do wrong args stuff
fi
Clear error logging is worth breaking the pieces out.
Even better, imho -
if [[ "$#" != 2 ]]
then : wrong args stuff
fi
if [[ ! -r "$2" ]]
then : unreadable stuff
fi
if [[ ! -f "$2" ]]
then : do not a file stuff
fi
: do all good stuff

How to use elif with && and -o in a bash script properly

Bash version 4.4.20
Ubuntu 16.04
I need to compare time and extensions for a particular project I have. Below is something similar to what I am trying to do but the error is the same. I am not sure where exactly the error is as shellcheck is not producing one.
#!/bin/bash
#
while read -r filename; do
extension="${filename##*.}"
if [ "$extension" == "zip" ] && [ "$filename" == "one.zip" ]; then
echo "Filename is $filename"
elif [ "$extension" == "zip" ] && [ "$filename" == "file_1.zip" ] -o [ "$filename" == "file_2.zip" ] -o [ "$filename" == "file_3.zip" ]; then
echo "Filename is $filename"
elif [ "$extension" == "csv" ] && [ "$filename" == "two.csv" ]; then
echo "Filename is $filename"
else
echo "Filename is $filename"
fi
done<fileList.txt
Error:
Filename is one.zip
check.sh: line 8: [: too many arguments
Filename is file_1.zip
check.sh: line 8: [: too many arguments
Filename is file_2.zip
check.sh: line 8: [: too many arguments
Filename is file_3.zip
Filename is two.csv
Filename is three.sql
Use pattern matching to your advantage:
while IFS= read -r filename; do
if [[ "$filename" = one.zip ]; then
echo "Filename is $filename"
elif [[ "$filename" = file_[123].zip ]; then
echo "Filename is $filename"
elif [[ "$filename" = two.csv ]; then
echo "Filename is $filename"
else
echo "Filename is $filename"
fi
done < fileList.txt
A case statement will work in any POSIX shell, not just a shell that supports a bash-like [[ ... ]] command.
while IFS= read -r filename; do
case $filename in
one.zip) echo "Filename is $filename" ;;
file_[123].zip) echo "..." ;;
two.csv) echo "..." ;;
*) echo "..." ;;
esac
done
To match a range of years(?), you can use
case $value in
200[0-9]|201[0-9]|202[0-1]) echo "Year between 2000 and 2021" ;;
esac
You can't do that as simply with [[ value = ... ]], since the | is part of the case statement's syntax, not an alternation operator in the pattern. Instead, you would need multiple match operators:
if [[ $value = 200[0-9] || $value = 201[0-19] || $value = 202[0-1] ]]; then
Here an explanation for why you get this error, as you get already good answers on how to do it correctly:
Note that the command [ is equivalent to test, hence your line 8 in effect (also removing unnecessary quotes) contains:
elif test "$extension" == zip ] && test "$filename" == file_1.zip ] -o [ "$filename" == file_2.zip ] -o [ "$filename" == file_3.zip ]
From the test man-page, we can see that the closing ] is optional, but if it is present, it terminates the expression to be tested.
The first test, which is just
test "$extension" == "zip" ]
is fine in this respect, but the second one starts with
test "$filename" == file_1.zip ] -o [ ...
Hence, when parsing the arguments, test encounters a ] and knows that this is the terminator for the arguments, but then finds another argument (-o), and doesn't know what to do with it. Hence it complains that it has got too many arguments
I have updated the syntax to what I think is the modern bash idioms. The script now works. Please correct me if I missed something else :
#!/bin/bash
#
while read -r filename
do
extension="${filename##*.}"
if [[ "$extension" = "zip" && "$filename" = "one.zip" ]]
then
echo "Filename is $filename"
elif [[ "$extension" = "zip" && "$filename" = "file_1.zip" ]] \
|| [[ "$filename" = "file_2.zip" ]] \
|| [[ "$filename" = "file_3.zip" ]]
then
echo "Filename is $filename"
elif [[ "$extension" = "csv" && "$filename" = "two.csv" ]]
then
echo "Filename is $filename"
else
echo "Filename is $filename"
fi
done < fileList.txt
Regards!

UNIX how to make my script delete multiple files and wildcards?

I was given the task of making a remove script that imitates the rm command. As you know, the rm command deletes all files if you were to type something like rm file1 file2. Using this example, my script would only delete file2. Can anyone help me on how to make it so my remove script would delete all files listed? My script is below. I apologise if its a little messy, I am new to coding.
#!/bin/bash
function directory(){
#Checks if deleted directory & .restore.info file exists
#If they don't exist, it creates them
if [ ! -d ~/deleted ]
then
mkdir ~/deleted
fi
if [ ! -f ~/.restore.info ]
then
touch ~/.restore.info
fi
}
function movefile(){
#not currently using
mv "$1" ~/deleted/$1
echo "file moved to recycle bin"
}
function error_conditions(){
#not currently using
#Prints error messages and checks if file is in project directory
if [ ! -f ~/project ]
then
echo "cannot remove $filename: no such file or directory"
elif [ -d ~/project ]
then
echo "cannot remove $filename: is a directory"
else
echo "missing operand"
fi
}
function delete_file(){
#Gets inode for filename
#Takes user input and puts file wherever based on user input
inode=$(stat -c%i "$filename")
pwd=$(readlink -e $filename)
if "$interactive"
then
if [ "$verbose" = true ]; then
read -p "Are you sure you want to delete $filename? " user_input
if [ $user_input == "y" ] || [ $user_input == "Y" ] || [ $user_input == "yes" ] || [ $user_input == "Yes" ];
then
mv $filename ~/deleted/${filename}_$inode
#moves deleted file to deleted directory (with inode at end)
echo ${filename}_$inode:$pwd>>~/.restore.info
#stores info of removed file in .restore.info (with path)
echo "removed '$filename'"
else
echo "Nothing has been deleted"
fi
else
read -p "Are you sure you want to delete $filename? " user_input
if [ $user_input == "y" ] || [ $user_input == "Y" ] || [ $user_input == "yes" ] || [ $user_input == "Yes"];
then
mv "$filename" ~/deleted/${filename}_$inode
echo ${filename}_$inode:$pwd>>~/.restore.info
else
echo "Aborted"
fi
fi
elif "$verbose"
then
mv "$filename" ~/deleted/${filename}_$inode
echo ${filename}_$inode:$inode:pwd>>~/.restore.info
echo "removed '$filename'"
else
mv "$filename" ~/deleted/${filename}_$inode
echo ${filename}_$inode:$pwd>>~/.restore.info
echo "Executed"
fi
}
#Setting all flags to false
interactive=false
verbose=false
recursive=false
while getopts :ivr optionvar
do
case "$optionvar" in
i) interactive=true;;
v) verbose=true;;
r) recursive=true;;
esac
done
shift $((OPTIND-1)) #process arguments.
#doing error commands with help of recursive
for i in $*
do
filename=$i
basefile=$(basename $i)
if [ "$filename" == " " ];
then
echo "No filename provcided"
elif [ -d $filename ];
then
if [ ! $recursive = true ];
then
echo "Directory name provided, please provide a file"
fi
elif [ ! -f $filename ];
then
echo "File does not exist"
# elif [ "$basefile" == "safe_rm" ]
# then
# echo "Attempting to delete safe_rm"
fi
done
#################################M A I N###############################
directory
delete_file $*
#error_conditions $* #- this gives me duplicate output lines
#movefile "$#" - this gives me an unnecessary "mv: cannot stat" output line
I'm not going to do a detailed code review of your whole script, but here are a few notes.
You are looping over the arguments in the main part of your script, but then you're calling the delete function with multiple arguments. That function has no looping in it. Move the loop from main() to delete_files() (and note that I pluralized its name for clarity).
And speaking of main(), you might as well encapsulate that code (option processing, function dispatch, etc.) in a function of that name, then at the bottom of your script have a line that calls it: main "$#"
Don't use $* unless you need what it does and understand its use - instead use "$#" almost always and always quote it (with very rare exceptions)
Use indentation consistently
If your script doesn't need to be portable to shells other than Bash, then use Bash-specific features such as [[ ]] instead of [ ]
You're using both methods of naming a function at the same time (function f()). Use one or the other - parens are preferred over using function - so f () { ...; }
Use more quotes, some examples:
pwd=$(readlink -e "$filename")
mv "$filename" ~/deleted/"${filename}_$inode"
echo "${filename}_$inode:$pwd" >> ~/.restore.info
But I don't recommend using tilde (~) in scripts - use $HOME instead. And if you need to look up a user's home directory, use getent instead of other methods.

Bash script, syntax on OR statement

Can someone tell me why this script isnt working? I'm getting
./FileDirTest.sh: line 10: [: missing `]'
./FileDirTest.sh: line 10: n: command not found
./FileDirTest.sh: line 13: [: missing `]'
./FileDirTest.sh: line 13: n: command not found
Here is my script.
if [ -d "$PASSED1" ]
then echo "Do you want to execute whole directory?(Y/N)"
read answer
if [ "$answer" == "y" || "$answer" == "Y" ] ;
then echo "Execute"
fi
if [ "$answer" == "n" || "$answer" == "N" ] ;
then echo "No"
exit 1
fi
fi
Im sure it is something simple. I new to all of this.
|| is not a valid operator for the [ command; you can only use it to join two distinct [ commands:
if [ "$answer" = "y" ] || [ "$answer" = "Y" ];
You can, however, use || inside bash's conditional command:
if [[ "$answer" = "y" || "$answer" = "Y" ]];
The first of the two errors occurs because ||, being a special shell operator, indicates that the previous command is complete, but [ requires ] be given as the final argument. The second error occurs because the value of $answer, immediately following ||, is taken as the name of the command to run.
In addition to #Chepner's answer, you can also use, bash -o operator,
if [ "$answer" == "y" -o "$answer" == "Y" ]; then
echo "Execute"
else
echo "No"
exit 1
fi

press y to continue, any other key to exit (Shell script)

I've written below code, but it won't work for SPACE,special symbols etc.
I want my script to exit if any key pressed on keyboard apart from Y/y.
It should handle SPACEBAR,special symbols
echo "enter y, any other key to exit "
read input
if [ $input != "Y" -o $input = "y" ]; then
echo "Exiting"
fi
if [ $input == "Y" -o $input == "y" ]; then
echo "Working"
fi
See http://wiki.bash-hackers.org/commands/builtin/read?s[]=read
Normally you always want to use the -r flag. -n 1 tells read to only read 1 character.
asksure() {
echo -n "Are you sure (Y/N)? "
while read -r -n 1 -s answer; do
if [[ $answer = [YyNn] ]]; then
[[ $answer = [Yy] ]] && retval=0
[[ $answer = [Nn] ]] && retval=1
break
fi
done
echo # just a final linefeed, optics...
return $retval
}
### using it
if asksure; then
echo "Okay, performing rm -rf / then, master...."
else
echo "Pfff..."
fi
First, you need to quote the parameter expansions to make sure certain inputs are not removed altogether during word splitting and quote removal.
You are using the wrong operator. No matter what key you type, one of not Y and not y will be true. For example, "Y" != "y". You want to use and instead of or.
if [ "$input" != "Y" ] && [ "$input" != "y" ]; then
The boolean operators -o and -a are no longer recommended, due to ambiguities than can arise from their use. Use separate test commands joined by || and && (respectively) instead.
You need to put $input in quotes, so the statement parses properly. If your response is a space, what the script sees is
if [ != "Y" -o != "y" ]; then
Which it obviously can't parse properly.
Use
if [ "$input" != "Y" -o "$input" = "y" ]; then
and you should be OK.
By the way, you don't actually exit if the condition is met. Add a return after the echo "Exiting" line.

Resources