unary operator expected with more than 1 argument - shell

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

Related

i don't know if my shell script is correct

I have a homework using for loop but I'm not quite understand the task that I have to do in there. I wrote a script but I feel like it's not a correct script. Please help!
Here is the question:
Write a shell script to list out the contents of any directory, and indicate for each file (including invisible ones) whether the file is a directory, a plain file, and whether it is public and/or executable to this process
#!/bin/bash
if [ $# -lt 1 ] ; then
echo " file doesn't exist"
echo
echo " variable needed to run a command"
fi
echo ---------------------------------------------
echo ---------------------------------------------
for i in $*
do
if [ -f $i ]; then
echo " it's a file";
echo "THIS IS A LIST OF FILE and DIRECTORY in $i"
ls -a $i
fi
done
echo -----------------------------------------
if [ -d $i ]; then
echo "directory" ;
echo "THIS IS A LIST OF FILES AND DIRETORY in $i"
ls -a $i
fi
echo ------------------------------------------
if [ -x $i ]; then
echo "executable"
echo "THIS IS A LIST OF EXECUTABLE FILE IN $i"
ls -x $i
fi
echo -----------------------------------------
if [ -r $i ]; then
echo "this file is a public file"
else "this is a private file"
fi
#!/bin/bash
if [ $# -lt 1 ] ; then
echo " file doesn't exist"
echo
echo " variable needed to run a command"
fi
echo ---------------------------------------------
echo ---------------------------------------------
for i in $*
do
if [ -f $i ]; then
echo " it's a file";
echo "THIS IS A LIST OF FILE and DIRECTORY in $i"
ls -a $i
fi
done
echo -----------------------------------------
if [ -d $i ]; then
echo "directory" ;
echo "THIS IS A LIST OF FILES AND DIRETORY in $i"
ls -a $i
fi
echo ------------------------------------------
if [ -x $i ]; then
echo "executable"
echo "THIS IS A LIST OF EXECUTABLE FILE IN $i"
ls -x $i
fi
echo -----------------------------------------
if [ -r $i ]; then
echo "this file is a public file"
else "this is a private file"
fi
Poorly written specifications are the bane of education. "Public" sounds like the wrong word here. I'll assume it means "readable".
You check if there's an argument, but you don't exit the program if there is not. I'd also confirm it's a directory, and readable.
The manual will do you a lot of good. Expect to do a lot of reading till you learn this stuff, and then reference it a lot to be sure.
Read this section carefully, create some tests for yourself to prove they work and that you understand them, and your job will be more than half done.
Don't use [. Generally it's just better to always use [[ instead, unless you are using (( or case or some other construct.
I don't see that a for loop was specified, but it ought to be fine. Just be aware that you might have to specify $1/* and $1/.* separately.
Put all your tests in one loop, though. For each file, test for whether it's a directory - if it is, report it. Test if it's a plain file - if it is, report it.
I do NOT like doing homework for someone, but it looks like you could use an example that simplifies this. I recommend you not use this as written - break it out and make it clearer, but this is a template for the general logic.
#! /bin/env bash
(( $# )) && [[ -d "$1" ]] && [[ -r "$1" ]] || {
echo "use: $0 <dir>" >&2
exit 1
}
for e in "$1"/.* "$1"/*
do echo "$e:"
[[ -d "$e" ]] && echo " is a directory"
[[ -f "$e" ]] && echo " is a plain file"
[[ -r "$e" ]] && echo " is readable"
[[ -x "$e" ]] && echo " is executable"
done
If you read the links I provided you should be able to break this apart and understand it.
Generally, your script is long and a bit convoluted. Simpler is easier to understand and maintain. For example, be very careful about block indentation to understand scope.
$: for i in 1 2 3
> do echo $i
> done
1
2
3
$: echo $i
3
Compare this to -
for i in $*
do if [ -f $i ]; then
echo " it's a file";
echo "THIS IS A LIST OF FILE and DIRECTORY in $i"
ls -a $i
fi
done
echo -----------------------------------------
if [ -d $i ]; then
echo "directory" ;
echo "THIS IS A LIST OF FILES AND DIRETORY in $i"
ls -a $i
fi
You are testing each entry to see if it is a file, and if it is, reporting "THIS IS A LIST OF FILE and DIRECTORY in $i" every time...
but then only testing the last one to see if it's a directory, because the [ -d $i ] is after the done.
...did you run this somewhere to try it, and look at the results?

Strange syntax error in if condition - Shell Script

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

bash- reading file from stdin and arguments

So I have googled this and thought I found the answers, but it still doesnt work for me.
The program computes the average and median of rows and columns in a file of numbers...
Using the file name works:
./stats -columns test_file
Using cat does not work
cat test_file | ./stats -columns
I am not sure why it doesnt work
#file name was given
if [[ $# -eq 2 ]]
then
fileName=$2
#file name was not given
elif [[ $# -eq 1 ]]
then
#file name comes from the user
fileName=/dev/stdin
#incorrect number of arguments
else
echo "Usage: stats {-rows|-cols} [file]" 1>&2
exit 1
fi
A very simple program that accepts piped input:
#!/bin/sh
stdin(){
while IFS= read -r i
do printf "%s" "$i"
done
}
stdin
Test is as follows:
echo "This is piped output" | stdin
To put that into a script / utility similar to the one in the question you might do this:
#!/bin/sh
stdin(){
while IFS= read -r i
do printf "%s" "$i"
done
}
rowbool=0
colbool=0
for i in $#
do case "$i" in
-rows) echo "rows set"
rowbool=1
shift
;;
-cols) echo "cols set"
colbool=1
shift
;;
esac
done
if [[ $# -gt 0 ]]
then
fileName=$1
fi
if [[ $# -eq 0 ]]
then fileName=$(stdin)
fi
echo "$fileName"

Getting stuck in a logic statement

I'm writing a BASH script to purge the cache from a web server. The script is designed to take arguments from positional parameters. "ShellCheck.net" is telling me that my script is functionally correct, but when I test it I'm getting error where I shouldn't ... so I thought I'd ask for some folks to put fresh eyes on it. Take a look, I'll continue below and describe my problem:
#!/bin/bash
#
# Verify the user running is root, if not, fail.
if [[ "$UID" -ne "0" ]]; #added-1438279711
then
echo 'Only ROOT may run this script.';
exit 1;
fi
#
# Set the variables
BASE="/path/to/folder/foo/bar/" #added-1438279711
DOMAINOPT="$1" #added 1438451428
PATHOPT="$2" #added 1438451428
#
# Define Functions
function usage() { #added-1438382631
echo -en "Proper Usage:\n\n"
echo -en "\tSpecify the domain to be used\n"
echo -en "\tUsage: \"cleancache.sh abc.com\"\n"
echo -en "\t\tNote: This option will search for files and folders, recursively, within the domain folder, and remove them.\n\n"
echo -en "\tSpecify the URI you'd like to act upon within the domain\n"
echo -en "\tUsage: \"cleancache.sh abc.com /path/to/folder/\"\n"
echo -en "\t\tNote: This option will search for files and folders, recursively,\n\t\twithin the specified path, and remove them. Removing a single file is not currently supported with this script.\n\n"
}
#
# Validate the input
if [[ ! -z "$DOMAINOPT" ]] && [[ "$DOMAINOPT" != "^[A-Za-z0-9-]*[\.][a-z]*$" ]] #added-1438462778
then
clear
echo -en "Please follow the proper format for the DOMAIN option\n\n"
usage
exit 1
elif [[ ! -z "$DOMAINOPT" ]] && [[ "$DOMAINOPT" = "^[A-Za-z0-9-]*[\.][a-z]*$" ]]
then
DOMAINOPT="$DOMAINOPT"
else
clear
echo -en "Please enter a domain!\n\n"
usage
exit 1
fi
if [[ ! -z "$PATHOPT" ]] && [[ "$PATHOPT" != "^[\/][\S]*[\/]$" ]] #added-1438456371
then
clear
echo "Please follow the proper format for the PATH option"
usage
exit 1
elif [[ ! -z "$PATHOPT" ]] && [[ "$PATHOPT" = "^[\/][\S]*[\/]$" ]]
then
PATHOPT="$PATHOPT"
else
echo ""
fi
#
# Doing Stuff
if [[ "$#" -gt "2" ]]
then
echo -en "Too many arguments!\n\n"
usage
exit 1
elif [[ "$#" -eq "2" ]]
then
echo "Purging Cache in \"$BASE$DOMAINOPT$PATHOPT\""
find "$BASE""$DOMAINOPT""$PATHOPT" -type d -exec rm -rf {} \;
find "$BASE""$DOMAINOPT""$PATHOPT" -type f -exec rm -f {} \;
echo "Purging Complete"
exit 0
else
echo "Purging Cache in \"$BASE$DOMAINOPT\""
find "$BASE" -type d -name "$DOMAINOPT" -exec rm -rf {} \;
mkdir -p "$BASE$DOMAINOPT" && chown apache:apache "$BASE$DOMAINOPT" && chmod 755 "$BASE$DOMAINOPT"
echo "Purging Complete!"
echo "Creating \".stat\" file"
echo "" > "$BASE""$DOMAINOPT""/.stat"
if [[ -f "$BASE""$DOMAINOPT""/.stat" ]] #added-1438387045
then
echo "$BASE$DOMAINOPT/.stat file created!"
fi
fi
echo "All Operations Complete, exiting now!"
Everything responds normally if you run the script without any arguments (Please enter a domain), It responds normally if you try to enter a path before a domain ... but when I do it correctly, when I type: "cleancache.sh abc.com", I get an error like i haven't met the required pattern ("Please follow the proper format for the DOMAIN option") ... when that is exactly write! ... I don't understand what I'm missing, been banging my head all day, no joy.
PLEASE HELP!
Use this to match a regex:
[[ "$DOMAINOPT" =~ ^[A-Za-z0-9-]*[\.][a-z]*$ ]]
or this:
[[ ! "$DOMAINOPT" =~ ^[A-Za-z0-9-]*[\.][a-z]*$ ]]
Don't quote the regex.

Check if passed argument is file or directory in Bash

I'm trying to write an extremely simple script in Ubuntu which would allow me to pass it either a filename or a directory, and be able to do something specific when it's a file, and something else when it's a directory. The problem I'm having is when the directory name, or probably files too, has spaces or other escapable characters are in the name.
Here's my basic code down below, and a couple tests.
#!/bin/bash
PASSED=$1
if [ -d "${PASSED}" ] ; then
echo "$PASSED is a directory";
else
if [ -f "${PASSED}" ]; then
echo "${PASSED} is a file";
else
echo "${PASSED} is not valid";
exit 1
fi
fi
And here's the output:
andy#server~ $ ./scripts/testmove.sh /home/andy/
/home/andy/ is a directory
andy#server~ $ ./scripts/testmove.sh /home/andy/blah.txt
/home/andy/blah.txt is a file
andy#server~ $ ./scripts/testmove.sh /home/andy/blah\ with\ a\ space.txt
/home/andy/blah with a space.txt is not valid
andy#server~ $ ./scripts/testmove.sh /home/andy\ with\ a\ space/
/home/andy with a space/ is not valid
All of those paths are valid, and exist.
That should work. I am not sure why it's failing. You're quoting your variables properly. What happens if you use this script with double [[ ]]?
if [[ -d $PASSED ]]; then
echo "$PASSED is a directory"
elif [[ -f $PASSED ]]; then
echo "$PASSED is a file"
else
echo "$PASSED is not valid"
exit 1
fi
Double square brackets is a bash extension to [ ]. It doesn't require variables to be quoted, not even if they contain spaces.
Also worth trying: -e to test if a path exists without testing what type of file it is.
At least write the code without the bushy tree:
#!/bin/bash
PASSED=$1
if [ -d "${PASSED}" ]
then echo "${PASSED} is a directory";
elif [ -f "${PASSED}" ]
then echo "${PASSED} is a file";
else echo "${PASSED} is not valid";
exit 1
fi
When I put that into a file "xx.sh" and create a file "xx sh", and run it, I get:
$ cp /dev/null "xx sh"
$ for file in . xx*; do sh "$file"; done
. is a directory
xx sh is a file
xx.sh is a file
$
Given that you are having problems, you should debug the script by adding:
ls -ld "${PASSED}"
This will show you what ls thinks about the names you pass the script.
Using -f and -d switches on /bin/test:
F_NAME="${1}"
if test -f "${F_NAME}"
then
echo "${F_NAME} is a file"
elif test -d "${F_NAME}"
then
echo "${F_NAME} is a directory"
else
echo "${F_NAME} is not valid"
fi
Using the "file" command may be useful for this:
#!/bin/bash
check_file(){
if [ -z "${1}" ] ;then
echo "Please input something"
return;
fi
f="${1}"
result="$(file $f)"
if [[ $result == *"cannot open"* ]] ;then
echo "NO FILE FOUND ($result) ";
elif [[ $result == *"directory"* ]] ;then
echo "DIRECTORY FOUND ($result) ";
else
echo "FILE FOUND ($result) ";
fi
}
check_file "${1}"
Output examples :
$ ./f.bash login
DIRECTORY FOUND (login: directory)
$ ./f.bash ldasdas
NO FILE FOUND (ldasdas: cannot open `ldasdas' (No such file or directory))
$ ./f.bash evil.php
FILE FOUND (evil.php: PHP script, ASCII text)
FYI: the answers above work but you can use -s to help in weird situations by checking for a valid file first:
#!/bin/bash
check_file(){
local file="${1}"
[[ -s "${file}" ]] || { echo "is not valid"; return; }
[[ -d "${file}" ]] && { echo "is a directory"; return; }
[[ -f "${file}" ]] && { echo "is a file"; return; }
}
check_file ${1}
Using stat
function delete_dir () {
type="$(stat --printf=%F "$1")"
if [ $? -ne 0 ]; then
echo "$1 directory does not exist. Nothing to delete."
elif [ "$type" == "regular file" ]; then
echo "$1 is a file, not a directory."
exit 1
elif [ "$type" == "directory" ]; then
echo "Deleting $1 directory."
rm -r "$1"
fi
}
function delete_file () {
type="$(stat --printf=%F "$1")"
if [ $? -ne 0 ]; then
echo "$1 file does not exist. Nothing to delete."
elif [ "$type" == "directory" ]; then
echo "$1 is a regular file, not a directory."
exit 1
elif [ "$type" == "regular file" ]; then
echo "Deleting $1 regular file."
rm "$1"
fi
}
https://linux.die.net/man/2/stat
https://en.m.wikipedia.org/wiki/Unix_file_types
A more elegant solution
echo "Enter the file name"
read x
if [ -f $x ]
then
echo "This is a regular file"
else
echo "This is a directory"
fi
Answer based on the title:
Check if passed argument is file or directory in Bash
This works also if the provided argument has a trailing slash .e.g. dirname/
die() { echo $* 1>&2; exit 1; }
# This is to remove the the slash at the end: dirName/ -> dirName
fileOrDir=$(basename "$1")
( [ -d "$fileOrDir" ] || [ -f "$fileOrDir" ] ) && die "file or directory $fileOrDir already exists"
Testing:
mkdir mydir
touch myfile
command dirName
# file or directory mydir already exists
command dirName/
# file or directory mydir already exists
command filename
# file or directory myfile already exists
#!/bin/bash
echo "Please Enter a file name :"
read filename
if test -f $filename
then
echo "this is a file"
else
echo "this is not a file"
fi
One liner
touch bob; test -d bob && echo 'dir' || (test -f bob && echo 'file')
result is true (0)(dir) or true (0)(file) or false (1)(neither)
This should work:
#!/bin/bash
echo "Enter your Path:"
read a
if [[ -d $a ]]; then
echo "$a is a Dir"
elif [[ -f $a ]]; then
echo "$a is the File"
else
echo "Invalid path"
fi

Resources