Extending bash script to receive more parameters? - bash

I have the following bash script, which is called trash.sh. In my script I request one parameter for from the user, simply a file name. And move the file to the Trash folder which is located in the user's home directory. If the directory doesn't exist, it simply creates one and then moves the file there. On the other hand, if the file doesn't exist, it informs the user.
#!/bin/bash
FILE=$1
FOLDER="$HOME/Trash"
ARGS=1
if [ $# -ne $ARGS ]
then
echo "Error: You are missing an argument!"
echo "Usage: ./trash.sh <file_name>"
else
if [ -s $FILE ]
then
if [ -d $FOLDER ]
then
mv -v $FILE $FOLDER
else
mkdir $FOLDER
mv -v $FILE $FOLDER
fi
else
echo "The file you have entered does not exist!"
fi
fi
Now, I want to extend my script in the followign ways, but I don't know how, because I am not that much experienced with bash scripting. First of all, I want to let the user enter more than one parameters, simply more then one file name, and move all the files in the Trash folder. If one or more of the files don't exist, it will inform the user which file or files don't exist. Simply, I want my script to recieve as many parameters as the user wants.
For example, if the user calls the script like this:
./trash.sh file1 file2 file3
and let's say that file2 doesn't exist I want the output to be.
file1 -> /home/user/Trash/file1
file3 -> /home/user/Trash/file3
file2 doesn't exist!
And lastly, I also want it to accept a parameter like this:
./trash *.txt
Simply, which will move all the files that end with .txt extension. If someone could help me achieve those things/extend my script, I would be glad.

[ $# = 0 ] && { echo "Usage: $0 file [...]" >&2; exit 1; }
FOLDER="$HOME/Trash"
mkdir -p "$FOLDER" || exit 1
for file in "$#"
do
if [ -s "$file ]
then mv -v "$file" "$FOLDER"
else echo "$0: $file does not exist" >&2
fi
done
You can detect zero arguments and issue a usage message before the loop. Using "$#" is crucial to working with filenames containing blanks, etc; ditto with using "$file" to reference the files. Using mkdir -p does not fail if the target directory already exists; it does fail if the directory can't be created or if a file with the given name exists (instead of a directory). Note that the error messages both contain $0; note that the 'file not found' message specifies the file name.
As discussed in the comments, you can simplify this in two steps:
[ $# = 0 ] && { echo "Usage: $0 file [...]" >&2; exit 1; }
FOLDER="$HOME/Trash"
mkdir -p "$FOLDER" || exit 1
for file in "$#"
do mv -v "$file" "$FOLDER"
done
and then:
[ $# = 0 ] && { echo "Usage: $0 file [...]" >&2; exit 1; }
FOLDER="$HOME/Trash"
mkdir -p "$FOLDER" || exit 1
mv -v "$#" "$FOLDER"
Note that if you delete the makefiles in three directories, only one of them will be in the Trash.

Related

I am having a hard time why the if statements don't work in ShellScript

I am having a hard wondering why is it that when i type the most random names they are accepted as a directory then. and when an if statement checks if its a readable file it says yes for all i type in. the goal here is search for a directory check if it is a directory. then search the directory for a file then in that file search for word in it using forloops. the while loop is to ask 3 times for the file name. It a little bit rough I just need an explanation for the if statements not working
#!/bin/sh
DIR='/home/collin2/'
x=1
echo "Please enter directory"
read directory
for directory in "$DIR";
do
if [ -d "$directory" ];
then echo "This is a directory Please enter the file name"
read filename
while [ $x -le 3 ]; do
for filename in "$directory";
do
if [ -r "$filename" ]
then echo "The filename is readable"
echo "Please Enter a word "
read word
grep "$word" "$filename"
exit 1
fi
done
echo "Doesn't exist please try again"
read filename
x=`expr $x + 1`
done
#exit 1
fi
done
echo "not a directory"
exit 0
Your for commands are wrong:
Wheb you write for directory in "$DIR";, it will set the value of the variable directory to "$DIR". You wanted to check, that you could find directory in "$DIR" , that can be done without a for command:
cd "$DIR" || { echo "Can not go to $DIR"; exit 1; }
test -d "${directory}" || { echo "Wrong directory ${directory}"; exit 1; }
# or
test -d "$DIR/${directory}" || { echo "Can not go to $DIR"; exit 1; }
The same problem with the other for-loop.
The test if [ -r "$filename" ] should be done after cd "$DIR/${directory}" or include the complete path.

Having trouble creating a variable using the locate command in bash [duplicate]

This checks if a file exists:
#!/bin/bash
FILE=$1
if [ -f $FILE ]; then
echo "File $FILE exists."
else
echo "File $FILE does not exist."
fi
How do I only check if the file does not exist?
The test command (written as [ here) has a "not" logical operator, ! (exclamation mark):
if [ ! -f /tmp/foo.txt ]; then
echo "File not found!"
fi
Bash File Testing
-b filename - Block special file
-c filename - Special character file
-d directoryname - Check for directory Existence
-e filename - Check for file existence, regardless of type (node, directory, socket, etc.)
-f filename - Check for regular file existence not a directory
-G filename - Check if file exists and is owned by effective group ID
-G filename set-group-id - True if file exists and is set-group-id
-k filename - Sticky bit
-L filename - Symbolic link
-O filename - True if file exists and is owned by the effective user id
-r filename - Check if file is a readable
-S filename - Check if file is socket
-s filename - Check if file is nonzero size
-u filename - Check if file set-user-id bit is set
-w filename - Check if file is writable
-x filename - Check if file is executable
How to use:
#!/bin/bash
file=./file
if [ -e "$file" ]; then
echo "File exists"
else
echo "File does not exist"
fi
A test expression can be negated by using the ! operator
#!/bin/bash
file=./file
if [ ! -e "$file" ]; then
echo "File does not exist"
else
echo "File exists"
fi
Negate the expression inside test (for which [ is an alias) using !:
#!/bin/bash
FILE=$1
if [ ! -f "$FILE" ]
then
echo "File $FILE does not exist"
fi
The relevant man page is man test or, equivalently, man [ -- or help test or help [ for the built-in bash command.
Alternatively (less commonly used) you can negate the result of test using:
if ! [ -f "$FILE" ]
then
echo "File $FILE does not exist"
fi
That syntax is described in "man 1 bash" in sections "Pipelines" and "Compound Commands".
[[ -f $FILE ]] || printf '%s does not exist!\n' "$FILE"
Also, it's possible that the file is a broken symbolic link, or a non-regular file, like e.g. a socket, device or fifo. For example, to add a check for broken symlinks:
if [[ ! -f $FILE ]]; then
if [[ -L $FILE ]]; then
printf '%s is a broken symlink!\n' "$FILE"
else
printf '%s does not exist!\n' "$FILE"
fi
fi
It's worth mentioning that if you need to execute a single command you can abbreviate
if [ ! -f "$file" ]; then
echo "$file"
fi
to
test -f "$file" || echo "$file"
or
[ -f "$file" ] || echo "$file"
I prefer to do the following one-liner, in POSIX shell compatible format:
$ [ -f "/$DIR/$FILE" ] || echo "$FILE NOT FOUND"
$ [ -f "/$DIR/$FILE" ] && echo "$FILE FOUND"
For a couple of commands, like I would do in a script:
$ [ -f "/$DIR/$FILE" ] || { echo "$FILE NOT FOUND" ; exit 1 ;}
Once I started doing this, I rarely use the fully typed syntax anymore!!
To test file existence, the parameter can be any one of the following:
-e: Returns true if file exists (regular file, directory, or symlink)
-f: Returns true if file exists and is a regular file
-d: Returns true if file exists and is a directory
-h: Returns true if file exists and is a symlink
All the tests below apply to regular files, directories, and symlinks:
-r: Returns true if file exists and is readable
-w: Returns true if file exists and is writable
-x: Returns true if file exists and is executable
-s: Returns true if file exists and has a size > 0
Example script:
#!/bin/bash
FILE=$1
if [ -f "$FILE" ]; then
echo "File $FILE exists"
else
echo "File $FILE does not exist"
fi
You can do this:
[[ ! -f "$FILE" ]] && echo "File doesn't exist"
or
if [[ ! -f "$FILE" ]]; then
echo "File doesn't exist"
fi
If you want to check for file and folder both, then use -e option instead of -f. -e returns true for regular files, directories, socket, character special files, block special files etc.
You should be careful about running test for an unquoted variable, because it might produce unexpected results:
$ [ -f ]
$ echo $?
0
$ [ -f "" ]
$ echo $?
1
The recommendation is usually to have the tested variable surrounded by double quotation marks:
#!/bin/sh
FILE=$1
if [ ! -f "$FILE" ]
then
echo "File $FILE does not exist."
fi
In
[ -f "$file" ]
the [ command does a stat() (not lstat()) system call on the path stored in $file and returns true if that system call succeeds and the type of the file as returned by stat() is "regular".
So if [ -f "$file" ] returns true, you can tell the file does exist and is a regular file or a symlink eventually resolving to a regular file (or at least it was at the time of the stat()).
However if it returns false (or if [ ! -f "$file" ] or ! [ -f "$file" ] return true), there are many different possibilities:
the file doesn't exist
the file exists but is not a regular file (could be a device, fifo, directory, socket...)
the file exists but you don't have search permission to the parent directory
the file exists but that path to access it is too long
the file is a symlink to a regular file, but you don't have search permission to some of the directories involved in the resolution of the symlink.
... any other reason why the stat() system call may fail.
In short, it should be:
if [ -f "$file" ]; then
printf '"%s" is a path to a regular file or symlink to regular file\n' "$file"
elif [ -e "$file" ]; then
printf '"%s" exists but is not a regular file\n' "$file"
elif [ -L "$file" ]; then
printf '"%s" exists, is a symlink but I cannot tell if it eventually resolves to an actual file, regular or not\n' "$file"
else
printf 'I cannot tell if "%s" exists, let alone whether it is a regular file or not\n' "$file"
fi
To know for sure that the file doesn't exist, we'd need the stat() system call to return with an error code of ENOENT (ENOTDIR tells us one of the path components is not a directory is another case where we can tell the file doesn't exist by that path). Unfortunately the [ command doesn't let us know that. It will return false whether the error code is ENOENT, EACCESS (permission denied), ENAMETOOLONG or anything else.
The [ -e "$file" ] test can also be done with ls -Ld -- "$file" > /dev/null. In that case, ls will tell you why the stat() failed, though the information can't easily be used programmatically:
$ file=/var/spool/cron/crontabs/root
$ if [ ! -e "$file" ]; then echo does not exist; fi
does not exist
$ if ! ls -Ld -- "$file" > /dev/null; then echo stat failed; fi
ls: cannot access '/var/spool/cron/crontabs/root': Permission denied
stat failed
At least ls tells me it's not because the file doesn't exist that it fails. It's because it can't tell whether the file exists or not. The [ command just ignored the problem.
With the zsh shell, you can query the error code with the $ERRNO special variable after the failing [ command, and decode that number using the $errnos special array in the zsh/system module:
zmodload zsh/system
ERRNO=0
if [ ! -f "$file" ]; then
err=$ERRNO
case $errnos[err] in
("") echo exists, not a regular file;;
(ENOENT|ENOTDIR)
if [ -L "$file" ]; then
echo broken link
else
echo does not exist
fi;;
(*) syserror -p "can't tell: " "$err"
esac
fi
(beware the $errnos support was broken with some versions of zsh when built with recent versions of gcc).
There are three distinct ways to do this:
Negate the exit status with bash (no other answer has said this):
if ! [ -e "$file" ]; then
echo "file does not exist"
fi
Or:
! [ -e "$file" ] && echo "file does not exist"
Negate the test inside the test command [ (that is the way most answers before have presented):
if [ ! -e "$file" ]; then
echo "file does not exist"
fi
Or:
[ ! -e "$file" ] && echo "file does not exist"
Act on the result of the test being negative (|| instead of &&):
Only:
[ -e "$file" ] || echo "file does not exist"
This looks silly (IMO), don't use it unless your code has to be portable to the Bourne shell (like the /bin/sh of Solaris 10 or earlier) that lacked the pipeline negation operator (!):
if [ -e "$file" ]; then
:
else
echo "file does not exist"
fi
envfile=.env
if [ ! -f "$envfile" ]
then
echo "$envfile does not exist"
exit 1
fi
To reverse a test, use "!".
That is equivalent to the "not" logical operator in other languages. Try this:
if [ ! -f /tmp/foo.txt ];
then
echo "File not found!"
fi
Or written in a slightly different way:
if [ ! -f /tmp/foo.txt ]
then echo "File not found!"
fi
Or you could use:
if ! [ -f /tmp/foo.txt ]
then echo "File not found!"
fi
Or, presing all together:
if ! [ -f /tmp/foo.txt ]; then echo "File not found!"; fi
Which may be written (using then "and" operator: &&) as:
[ ! -f /tmp/foo.txt ] && echo "File not found!"
Which looks shorter like this:
[ -f /tmp/foo.txt ] || echo "File not found!"
The test thing may count too. It worked for me (based on Bash Shell: Check File Exists or Not):
test -e FILENAME && echo "File exists" || echo "File doesn't exist"
This code also working .
#!/bin/bash
FILE=$1
if [ -f $FILE ]; then
echo "File '$FILE' Exists"
else
echo "The File '$FILE' Does Not Exist"
fi
The simplest way
FILE=$1
[ ! -e "${FILE}" ] && echo "does not exist" || echo "exists"
This shell script also works for finding a file in a directory:
echo "enter file"
read -r a
if [ -s /home/trainee02/"$a" ]
then
echo "yes. file is there."
else
echo "sorry. file is not there."
fi
sometimes it may be handy to use && and || operators.
Like in (if you have command "test"):
test -b $FILE && echo File not there!
or
test -b $FILE || echo File there!
If you want to use test instead of [], then you can use ! to get the negation:
if ! test "$FILE"; then
echo "does not exist"
fi
You can also group multiple commands in the one liner
[ -f "filename" ] || ( echo test1 && echo test2 && echo test3 )
or
[ -f "filename" ] || { echo test1 && echo test2 && echo test3 ;}
If filename doesn't exit, the output will be
test1
test2
test3
Note: ( ... ) runs in a subshell, { ... ;} runs in the same shell.

Explain me 2 lines of this shell script

What does this mean?
if [ -f $2/$1 ]
and this line:
cp $1 $2/$1
Does $2/$1 represent a file, because it's associated with -f?
#!/bin/bash
if test $# -ne 2
then
echo "Numbers of argument invalid"
else
if [ -f $2/$1 ]
then
echo "file already exist , replace ?"
read return
if test $return = 'y'
then
cp $1 $2/$1
else
exit
fi
else
cp $1 $2/$1
fi
fi
./script.sh "tmp.txt" "/project"
echo $1 = tmp.txt
echo $2 = /project
$1 - file name
$2 - directory
if [ -f $2/$1 ] - checks if file exists, -f for files, -d for directory
cp $1 $2/$1 - copy file to directory/file_name
if [ -f /project/tmp.txt ]
cp tmp.txt /project/tmp.txt
Given 2 arguments / names ($2 and $1), this is testing to ensure that a file under directory $2 with name $1 exists. If so, then it copies a local file indicated by $1 into directory $2 with the same name.
However, per your following example, it is put to use a little differently. If the file doesn't already exist in the destination, it immediately copies a file into a subdirectory with the name specified by $2. If the file does exist in the destination, it first prompts the user if it is o.k. to overwrite the existing file.
http://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html

Recursively putting files in a Recycle Bin in Unix

I've written script to temporarily delete files in Unix and then save the file path so they can be restored. I have 2 functions, one to delete files, and one to delete a directory that also recursively deletes files. I have the appropriate commands to recognize the directory name and path as well as checking to see if files remain in the directory and then deleting them. At first I was getting an infinite loop but I made some changes and now, it's saying there is no such file or directory when I try to delete a directory. It still runs through the code but it doesn't delete any files. Can anyone figure this out?
#!/bin/bash
#checks to see if deleted folder exists. If it doesn't, it is created.
if [ ! -d ~/deleted ];
then
mkdir ~/deleted
fi
if [ ! -f ~/.restore.info ] ;
then
touch ~/.restore.info
fi
function recur_delete {
dir=$1 #this will indicate the directory name only
dirpath=$(dirname $dir) #gets the directory path
if [ "$( ls -A /$dirpath/$dir)" ]; #determines if the directory contains files.
then
filename=$(find dirpath/dir -type f -printf "%f\n" | head -1)
delete_file $filename #filename is found and sent to delete_file function to be deleted.
recur_delete $dir #function is called again to see if more files are present.
else
echo Directory is empty, the directory will be deleted
echo $dirpath/$dir >> ~/.restore.info
rmdir ${dirpath/$dir}
fi
}
function delete_file {
inode=$(stat -c%i $filename) #grabs inode # for the chosen filename.
filename=$1 #reinitializes the variable filename as the first argument
pwd=$(readlink -e $filename) #This gets the entire path for the chosen file
if $interactive
then
if [ $verbose = true ];
then
read -p "Are you SURE you want to delete $filename ????" i_input
if [ $i_input == "y" ] || [ $i_input == "Y" ];
then
mv $filename ~/deleted/${filename}_$inode
echo ${filename}_$inode:$pwd >> ~/.restore.info
echo $filename has been deleted. Congrats.
else
echo Nothing has been done, the file or files remain.
fi
else
read -p "Are you SURE you want to delete $filename ????" i_input
if [ $i_input == "y" ] || [ $i_input == "Y" ];
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:$pwd >> ~/.restore.info
echo $filename has been deleted. Congrats.
else
mv $filename ~/deleted/${filename}_$inode
echo ${filename}_$inode:$pwd >> ~/.restore.info
echo Executed
fi
}
interactive=false
verbose=false
recursive=false
while getopts ivr OPTION
do
case $OPTION in
i) interactive=true;;
v) verbose=true;;
r) recursive=true;;
esac
done
shift $[OPTIND-1]
for i in $*
do
filename=$i
basefile=$(basename $i)
if [ "$i" == "" ];
then
echo No file provided
elif [ -d $filename ];
then
if [ $recursive = true ];
then
recur_delete $filename
else
echo This is a directory, please provide a file name.
fi
elif [ ! -f $filename ];
then
echo File does not exist
elif [ "$basefile" == "safe_rm" ];
then
echo Attempting to delete safe_rm - operation aborted!!!!
#This is the line that takes the filename to be deleted and modifies the
#experience based on what the user wants.
else
delete_file $filename
fi
done
This seems super-complicated. How about building something around
$ mkdir ~/.trashbin
$ mv /absolute/path/to/dir/or/file ~/.trashbin
for temp delete and then
$ mv ~/.trashbin/absolute/path/to/dir /absolute/path/to/dir
for restore?
You need one little fragment of code to get the old absolute path with the ~/.trashbin deleted, but that's simple.
Update
Aaah, it's the silly professor problem. I resemble that.
Okay, here's the deal: in any recursion, there has to be something that "gets smaller" in some sense with each call, and finally gets to the point where you stop recurring. In your case, that should be the results of ls -A. Step away from the code and examine what you really get from ls -A on an empty directory, and whether that evaluates to 0 or non-zero in that if. Hint: I bet it never does.

How can I create an updating directory log in unix?

The goal of this script is to create a log file if it does not exist, or update the existing log file if it does exist of the directory that the user inputs when the program is run. My problem is that I cannot find a way to compare the log file with the current state of the directory while still being able to update the log file properly, and display each file that is being added to the screen. This is the code I am using so far:
userinput=$1
if [ ! -d "$userinput" ];
then
echo "Usage: dirlog.sh directory_name" 1>&2
exit 0
else
if [ ! -f "$userinput.log" ];
then
ls -l > $userinput.log
echo ".logfile created for $userinput"
elif [ -f "$userinput.log" ];
then
if [ ];
then
echo "$file missing from directory $userinput"
else #file not missing
echo "no files missing from directory $userinput"
fi
#Update this no matter what
ls -l > $userinput.log
echo "logfile updated for directory $userinput"
fi
fi

Resources