can't seem to get this function with bash to work - bash

# define some variables for later
date=`date`
usr=`whoami`
# define usage function to echo syntax if no args given
usage(){
echo "error: filename not specified"
echo "Usage: $0 filename directory/ directory/ directory/"
exit 1
}
# define copyall function
copyall() {
# local variable to take the first argument as file
local file="$1" dir
# shift to the next argument(s)
shift
# loop through the next argument(s) and copy $file to them
for dir in "$#"; do
cp -R "$file" "$dir"
done
}
# function to check if filename exists
# $f -> store argument passed to the script
file_exists(){
local f="$1"
[[ -f "$f" ]] && return 0 || return 1
}
# call usage() function to print out syntax
[[ $# -eq 0 ]] && usage
here's what I can't figure out
# call file_exists() and copyall() to do the dirty work
if ( file_exists "$1" ) then
copyall
I would also love to figure out how to take this next echo section and condense it to one line. Instead of $1 then shift then move on. Maybe split it into an array?
echo "copyall: File $1 was copied to"
shift
echo "$# on $date by $usr"
else
echo "Filename not found"
fi
exit 0

It seems to me that the file_exists macro is superfluous:
if [ -f "$1" ]
then copy_all "$#"
else echo "$0: no such file as $1" >&2; exit 1
fi
or even just:
[ -f "$1" ] && copy_all "$#"

You probably just need to remove the parentheses around file_exists "$1", and add a semicolon:
if file_exists "$1"; then
copyall

Related

UNIX How do delete my unnecessary/excessive lines in my output?

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.

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 associative arrays error

I seem to have this problem. This code breaks at line 119 in my script with bash associative arrays. I am sorry for the comments but I am kind to new to bash scripting. This is the code:
#!/bin/bash
# Aliases file
# Command usage: cpRecent/mvRecent -d {dirFrom},{dirTo} -n {numberofFiles} -e {editTheNames}
# Error codes
NO_ARGS="You need to pass in an argument"
INVALID_OPTION="Invaild option:"
NO_DIRECTORY="No directory found"
# Return values
fullpath=
directories=
numfiles=
interactive=
typeset -a files
typeset -A filelist
# Advise that you use relative paths
__returnFullPath(){
local npath
if [[ -d $1 ]]; then
cd "$(dirname $1)"
npath="$PWD/$(basename $1)"
npath="$npath/" #Add a slash
npath="${npath%.*}" #Delete .
fi
fullpath=${npath:=""}
}
__usage(){
wall <<End-Of-Message
________________________________________________
<cpRecent/mvRecent> -d "<d1>,<d2>" -n <num> [-i]
-d First flag: Takes two arguments
-n Second flag: Takes one argument
-i Takes no arguments. Interactive mode
d1 Directory we are reading from
d2 Directory we are writing to
num Number of files
________________________________________________
End-Of-Message
}
__processOptions(){
while getopts ":d:n:i" opt; do
case $opt in
d ) IFS=',' read -r -a directories <<< "$OPTARG";;
n ) numfiles=$OPTARG;;
i ) interactive=1;;
\? ) echo "$INVALID_OPTION -$OPTARG" >&2 ; return 1;;
: ) echo "$NO_ARGS"; __usage; return 1;;
* ) __usage; return 1;;
esac
done
}
__getRecentFiles(){
# Check some conditions
(( ${#directories[#]} != 2 )) && echo "$INVALID_OPTION Number of directories must be 2" && return 2
#echo ${directories[0]} ${directories[1]}
# Get the full paths of the directories to be read from/written to
__returnFullPath "${directories[0]}"
directories[0]="$fullpath"
__returnFullPath "${directories[1]}"
directories[1]="$fullpath"
if [[ -z ${directories[0]} || -z ${directories[1]} ]]; then
echo $NO_DIRECTORY
return 3
fi
[[ numfiles != *[!0-9]* ]] && echo "$INVALID_OPTION Number of files cannot be a string" && return 4
#numfiles=$(($numfiles + 0))
(( $numfiles == 0 )) && echo "$INVALID_OPTION Number of files cannot be zero" && return 4
local num="-"$numfiles""
# Get the requested files in directory(skips directories)
if [[ -n "$(ls -t ${directories[0]} | head $num)" ]]; then
# For some reason using local -a or declare -a does not seem to split the string into two
local tempfiles=($(ls -t ${directories[0]} | head $num))
#IFS=' ' read -r -a tempfiles <<< "$string"
#echo ${tempfiles[#]}
for index in "${!tempfiles[#]}"; do
echo $index ${tempfiles[index]}
[[ -f "${directories[0]}${tempfiles[index]}" ]] && files+=("${tempfiles[index]}")
done
fi
}
####################################
# The problem is this piece of code
__processLines(){
local name
local answer
local dirFrom
local dirTo
if [[ -n $interactive ]]; then
for (( i=0; i< ${#files[#]}; i++ )); do
name=${files[i]}
read -n 1 -p "Old name: $name. Do you wish to change the name(y/n)?" answer
[[ answer="y" ]] && read -p "Enter new name:" name
dirFrom="${directories[0]}${files[i]}"
dirTo="${directories[1]}$name"
fileslist["$dirFrom"]="$dirTo"
done
else
for line in $files; do
dirFrom="${directories[0]}$line"
echo $dirFrom # => /home/reclusiarch/Documents/test
dirTo="${directories[1]}$line"
echo $dirTo # => /home/reclusiarch/test
fileslist["$dirFrom"]="$dirTo" # This is the offending line
done
fi
}
###########################################################
cpRecent(){
__processOptions $*
__getRecentFiles
__processLines
for line in "${!filelist[#]}"; do
cp $line ${filelist[$line]}
done
echo "You have copied ${#fileList[#]} files"
unset files
unset filelist
return
}
mvRecent(){
__processOptions $*
__getRecentFiles
__processLines
for line in "${!filelist[#]}"; do
mv $line ${filelist[$line]}
done
echo "You have copied ${#fileList[#]} files"
unset files
unset filelist
return
}
cpRecent "$*"
I have tried a lot of things. To run the script,
$ bash -x ./testing.sh -d "Documents,." -n 2
But nothing seems to work:
The error is this(when using bash -x):
./testing.sh: line 119: /home/reclusiarch/Documents/test: syntax error: operand expected (error token is "/home/reclusiarch/Documents/test")
If I run that section on the command line, it works:
$ typeset -A filelist
$ filelist["/home/reclusiarch/Documents/test"]=/home/reclusiarch/test
$ echo ${filelist["/home/reclusiarch/Documents/test"]}
/home/reclusiarch/test
Thanks for your help!!
Edit: I intially pared down the script to the piece of offending code but that might make it not run. Again, if you want to test it, you could run the bash command given. (The script ideally would reside in the user's $HOME directory).
Edit: Solved (Charles Duffy solved it) It was a simple mistake of forgetting which name was which.
Your declaration is:
typeset -A filelist
However, your usage is:
fileslist["$dirFrom"]="$dirTo"
fileslist is not filelist.

if condition inside function is not working as desired when function called with command line arguments inside find statement

#!/bin/bash
# Code to generate script usage
if [[ "$#" -ne 1 ]] && [[ "$#" -ne 2 ]]; then
flag=1;
elif ! [[ "$1" == "abcd" || "$1" == "dcba" ]]; then
echo "Invalid"
flag=1;
fi
while [ $# -gt 1 ]
do
case $2 in
'streams')
;;
*)
echo "unrecognised optional arg $2"; flag=1;
;;
esac
shift
done
if [ "$flag" == "1" ]; then
echo "Usage:"
exit
fi
function main {
arg1=$1
streams=$2
if [ "${streams}" == "streams" ]; then
echo entering here
else
echo entering there
fi
}
parent_dir=`pwd`
find $parent_dir -name "*" -type d | while read d; do
cd $denter code here
main $1 $2
done
Why the code does not enter "entering here" when script run with arguments "abcd" and "streams" ?
I feel that function having two arguments is causing the problem, code was working fine with one argument
Several things you might want to fix in your code, before attempts are made to find the specific problem. It is possible that it will disappear after modifying your script accordingly. If the problem is still alive, I'll edit my answer with a solution. If you decide to apply the following changes, please update your code in the question.
Consistent usage of either [[ or [. [[ is a Bash keyword similar to (but more powerful than) the [ command.
See
Bash FAQ 31
Tests And Conditionals
Unless you're writing for POSIX sh, I recommend [[.
Use (( for arithmetic expressions. ((...)) is an arithmetic command, which returns an exit status of 0 if the expression is nonzero, or 1 if the expression is zero. Also used as a synonym for let, if assignments are needed. See Arithmetic Expression.
Use the variable PWD instead of pwd. PWD is a builtin variable in all POSIX shells that contains the current working directory. pwd(1) is a POSIX utility that prints the name of the current working directory to stdout. Unless you're writing for some non-POSIX system, there is no reason to waste time executing pwd(1) rather than just using PWD.
The function keyword is not portable. I suggest you to avoid using it and simply write function_name() { your code here; } # Usage
$parent_dir is not double-quoted. "Double quote" every literal that contains spaces/metacharacters and every expansion: "$var", "$(command "$var")", "${array[#]}", "a & b". See
Quotes
Arguments
ShellCheck your code before uploading.
Replace the while condition logic with an if condition, so that shift is no longer required. Shift was the devil I was facing I found.
#!/bin/bash
# Code to generate script usage
if [[ "$#" -ne 1 ]] && [[ "$#" -ne 2 ]]; then
flag=1;
elif ! [[ "$1" == "abcd" || "$1" == "dcba" ]]; then
echo "Invalid"
flag=1;
fi
#while [[ $# -gt 1 ]]
#do
# case $2 in
# 'streams')
# ;;
# *)
# echo "unrecognised optional arg $2"; flag=1;
# ;;
# esac
# shift
#done
if [[ $2 == "streams" ]]; then
:
elif [[ (-z $2) ]]; then
:
else
echo "unrecognised optional arg $2"; flag=1;
fi
if [[ "$flag" == "1" ]]; then
echo "Usage:"
exit
fi
function main {
streams=$2
if [[ "${streams}" == "streams" ]]; then
echo entering here
else
echo entering there
fi
}
parent_dir=`pwd`
find $parent_dir -name "*" -type d | while read d; do
cd $d
main $1 $2
done

sh: Test for existence of files

How does one test for the existence of files in a directory using bash?
if ... ; then
echo 'Found some!'
fi
To be clear, I don't want to test for the existence of a specific file. I would like to test if a specific directory contains any files.
I went with:
(
shopt -s dotglob nullglob
existing_files=( ./* )
if [[ ${#existing_files[#]} -gt 0 ]] ; then
some_command "${existing_files[#]}"
fi
)
Using the array avoids race conditions from reading the file list twice.
From the man page:
-f file
True if file exists and is a regular file.
So:
if [ -f someFileName ]; then echo 'Found some!'; fi
Edit: I see you already got the answer, but for completeness, you can use the info in Checking from shell script if a directory contains files - and lose the dotglob option if you want hidden files ignored.
I typically just use a cheap ls -A to see if there's a response.
Pseudo-maybe-correct-syntax-example-ahoy:
if [[ $(ls -A my_directory_path_variable ) ]] then....
edit, this will work:
myDir=(./*) if [ ${#myDir[#]} -gt 1 ]; then echo "there's something down here"; fi
You can use ls in an if statement thus:
if [[ "$(ls -a1 | egrep -v '^\.$|^\.\.$')" = "" ]] ; then echo empty ; fi
or, thanks to ikegami,
if [[ "$(ls -A)" = "" ]] ; then echo empty ; fi
or, even shorter:
if [[ -z "$(ls -A)" ]] ; then echo empty ; fi
These basically list all files in the current directory (including hidden ones) that are neither . nor ...
If that list is empty, then the directory is empty.
If you want to discount hidden files, you can simplify it to:
if [[ "$(ls)" = "" ]] ; then echo empty ; fi
A bash-only solution (no invoking external programs like ls or egrep) can be done as follows:
emp=Y; for i in *; do if [[ $i != "*" ]]; then emp=N; break; fi; done; echo $emp
It's not the prettiest code in the world, it simply sets emp to Y and then, for every real file, sets it to N and breaks from the for loop for efficiency. If there were zero files, it stays as Y.
Try this
if [ -f /tmp/foo.txt ]
then
echo the file exists
fi
ref: http://tldp.org/LDP/abs/html/fto.html
you may also want to check this out: http://tldp.org/LDP/abs/html/fto.html
How about this for whether directory is empty or not
$ find "/tmp" -type f -exec echo Found file {} \;
#!/bin/bash
if [ -e $1 ]; then
echo "File exists"
else
echo "Files does not exist"
fi
I don't have a good pure sh/bash solution, but it's easy to do in Perl:
#!/usr/bin/perl
use strict;
use warnings;
die "Usage: $0 dir\n" if scalar #ARGV != 1 or not -d $ARGV[0];
opendir my $DIR, $ARGV[0] or die "$ARGV[0]: $!\n";
my #files = readdir $DIR;
closedir $DIR;
if (scalar #files == 2) { # . and ..
exit 0;
}
else {
exit 1;
}
Call it something like emptydir and put it somewhere in your $PATH, then:
if emptydir dir ; then
echo "dir is empty"
else
echo "dir is not empty"
fi
It dies with an error message if you give it no arguments, two or more arguments, or an argument that isn't a directory; it's easy enough to change if you prefer different behavior.
# tested on Linux BASH
directory=$1
if test $(stat -c %h $directory) -gt 2;
then
echo "not empty"
else
echo "empty"
fi
For fun:
if ( shopt -s nullglob ; perl -e'exit !#ARGV' ./* ) ; then
echo 'Found some!'
fi
(Doesn't check for hidden files)

Resources