Hi I am writing a program that ask the user to enter a directory, contact name and number.
What the program does is that it create a .contact file like <contactname>.contact and adds the contact number inside it. If the file already exists in the specified directory, then the program creates a new file called contact(1).contact, if that exists then contact(2).contact etc.
When a contact(1).contact already exists, I want the program to create contact(2).contact, but the program creates contact(1)(2).contact file instead. I don't know what the problem is. Any help would be appreciated ! :)
The code I have written so far in bash.
#!/bin/bash
directory=
if(($# == 0))
then
echo -n "Please enter directory path:"
read directory
cd $directory
echo -n "Please enter contact name:"
read name
echo -n "Please enter contact number:"
read number
else
directory=$1
cd ${directory}
name=$2
number=$3
fi
if [ -e $name.contact ];
then
exists=1
count=1
while [ $exists -eq 1 ];
do
name=$name\($count\)
count=$(($count+1))
if [ ! -f $name.contact ];
then
exists=0
fi
done
echo $number > $name.contact
name=5
else
echo $number > $name.contact
fi
You keep appending to the modified name. Use the original name instead.
And that condition...
origname="$name"
while [ -f $name.contact ];
do
name="$origname($count)"
count=$(($count+1))
done
Probably you need to save original name:
Try changing that part:
while [ $exists -eq 1 ];
do
name=$name\($count\)
count=$(($count+1))
if [ ! -f $name.contact ];
then
exists=0
fi
done
to this:
origname=$name
while [ $exists -eq 1 ];
do
name=$origname\($count\)
count=$(($count+1))
if [ ! -f $name.contact ];
then
exists=0
fi
done
Related
I am new to shell, and my code takes two arguments from the user. I would like to confirm their arguments before running the rest of the code. I would like a y for yes to prompt the code, and if they type n for no, then the code will ask again for new arguments
Pretty much, if i type anything when I am asked to confirm, the rest of the code runs anyways. I tried inserting the rest of the code after the first then statement, but that didn't work either. I have also checked my code with ShellCheck and it all appears to be legal syntax. Any advice?
#!/bin/bash
#user passes two arguments
echo "Enter source file name, and the number of copies: "
read -p "Your file name is $1 and the number of copies is $2. Press Y for yes N for no " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
echo "cloning files...."
fi
#----------------------------------------REST OF CODE
DIR="."
function list_files()
{
if ! test -d "$1"
then echo "$1"; return;
fi
cd ... || $1
echo; echo "$(pwd)":; #Display Directory name
for i in *
do
if test -d "$i" #if dictionary
then
list_files "$i" #recursively list files
cd ..
else
echo "$i"; #Display File name
fi
done
}
if [ $# -eq 0 ]
then list_files .
exit 0
fi
for i in "$#*"
do
DIR=$1
list_files "$DIR"
shift 1 #To read next directory/file name
done
if [ ! -f "$1" ]
then
echo "File $1 does not exist"
exit 1
fi
for ((i=0; i<$2; i++))
do
cp "$1" "$1$i.txt"; #copies the file i amount of times, and creates new files with names that increment by 1
done
status=$?
if [ "$status" -eq 0 ]
then
echo 'File copied succeaful'
else
echo 'Problem copying'
fi
Moving the prompts into a while loop might help here. The loop will re-prompt for the values until the user confirms them. Upon confirmation, the target code will be executed and the break statement will terminate the loop.
while :
do
echo "Enter source file name:"
read source_file
echo "Number of copies"
read number_of_copies
echo "Your file name is $source_file and the number of copies is $number_of_copies."
read -p "Press Y for yes N for no " -n 1 -r
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "cloning files...."
break ### <<<---- terminate the loop
fi
echo ""
done
#----------------------------------------REST OF CODE
My script is a mimic of the rm command, long story short. Can anyone point out the errors/unnecessary lines I have in my remove script that causes my output to produce excessive/irrelevant lines? The code works as intended but it produces all these unnecessary/excessive/duplicate lines. Output below is what looks like when I try to remove 2 files in the same line and do some other simple commands. Thank you in advance. I appreciate any help.
input: sh remove file2 file4
output:
Executed
Executed
cannot remove file4: no such file or directory
stat: cannot stat 'file4': No such file or directory
mv: cannot stat 'file4': No such file or directory
Executed
mv: cannot stat 'file4': No such file or directory
File moved to recycle bin
#/bin/bash
function directory(){
if [ ! -d ~/deleted ]
then
mkdir ~/deleted
fi
if [ ! -f ~/.restore.info ]
then
touch ~/.restore.info
fi
}
function movefile(){
mv $1 ~/deleted/$1
echo "file moved to recycle bin"
}
function error_conditions(){
#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
inode=$(stat -c%i $filename)
filename=$1
#pwd=$(readlink -e$filename)
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 ~/delete/${filename}_inode
echo ${filename}_$inode:$pwd>>~/.restore.info
echo "$filename has been deleted"
else
echo "Nothing has been deleted"
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:$inode:pwd>>~/.restore.info
echo "$filename has been deleted."
else
mv $filename ~/deleted/${filename}_$inode
echo ${filename}_$inode:$pwd>>~/.restore.info
echo Executed
fi
}
interactive=false
verbose=false
while getopts iv option
do
case $option in
i) interactive=true;;
v) verbose=true;;
esac
done
shift $[OPTIND-1]
for i in $*
do
filename=$i
baseline=$(basename $i)
if [ "$i" == "" ];
then
echo "No filename provided"
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 [ "$basefule" == "safe_rm" ]
then
echo "Attempting to delete safe_rm"
else
delete_file $filename
fi
done
#################################M A I N###############################
directory
error_conditions $*
delete_file $*
movefile $*
Please indent properly.
for i in $* should be for i in "$#" or simply for i.
In general, variables should be quoted
(e.g., "$1", "$i",
"$filename",
"$verbose", etc.)
$[expression] is obsolete.
Use $((expression)).
Your main loop calls
delete_file $filename
(line 100).
Your delete_file function sets
filename=$1
(line 35),
which is somewhat redundant and therefore confusing.
You set baseline but never use it.
You test (i.e., reference) $basefule without ever setting it.
Are these meant to be the same variable?
The code says
if [ ! -f ~/project ]
then
echo "cannot remove $filename: no such file or directory"
︙
This is a very misleading message.
You have a big comment that says “M A I N”,
but the “main” code begins about 33 lines earlier.
The code doesfor i in $*
do
filename=$i # This is an example of terrible indenting.
︙
delete_file $filename
︙
donebut then, five lines later,delete_file $*
so you’re processing the files twice.
So, even if delete_file succeeds the first time you call it,
the file will be gone when you call it a second time.
And, if you want to call a function (e.g., delete_file)
with all the arguments to the script,
you should use "$#" rather than $*.
And, if you’re going to call delete_file with a list of filenames,
then delete_file needs to iterate (loop) over those arguments.
Your delete_file function only looks at $1.
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.
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?
I am looking for suggestions for a solution and on best approaches to handle figuring out if multiple IFs are null.
I have:
if [ -n "$sfcompname" ]; then
echo $sfcompname
fi
if [ -n "$sfcompip" ]; then
echo $sfcompip
fi
if [ -n "$lacompname" ]; then
echo $lacompname
fi
if [ -n "$lacompip" ]; then
echo $lacompip
fi
.. I'm sure that can be done better, but my main problem at the moment is trying to then say:
if (all those IFs) = null
echo "Please check the name you entered and try again"
Somewhat silly, but should work
if ! [[ ${sfcompname}${sfcompip}${lacompname}${lacompip} ]]
then
echo "Please check the name you entered and try again"
fi
You can use another variable for this which you initialise to a value and then change if any of the if statements fire. Then at the end, if it hasn't changed, then you know that none of them fired. Something like this will suffice:
fired=0
if [ -n "$sfcompname" ]; then
echo $sfcompname
fired=1
fi
if [ -n "$sfcompip" ]; then
echo $sfcompip
fired=1
fi
if [ -n "$lacompname" ]; then
echo $lacompname
fired=1
fi
if [ -n "$lacompip" ]; then
echo $lacompip
fired=1
fi
if [[ ${fired} -eq 0 ]] ; then
echo 'None were fired'
fi
Another possibility is to use the variable check short-cut:
name="$sfcompname$sfcompip$lacompname$lacompip"
${name:?"Please check the name you entered and try again"}
This will exit the program if none of the variables are set. The message is optional, it overrides the standard "parameter null or not set".