nested if: too many arguments? - bash

For some reason this code creates problems:
source="/foo/bar/"
destination="/home/oni/"
if [ -d $source ]; then
echo "Source directory exists"
if [ -d $destination ]; then
echo "Destination directory exists"
rsync -raz --delete --ignore-existing --ignore-times --size-only --stats --progress $source $destination
chmod -R 0755 $destination
else
echo "Destination directory does not exists"
fi
else
echo "Source directory does not exists"
fi
It errors out with:
Source directory exists
/usr/bin/copyfoo: line 7: [: too many arguments
Destination directory does not exists
I used nested if statements in bash before without a problem, what simple mistake am I overlooking?
Thanks!

The syntax really looks correct. Works in dash/bash.
Did you change the name of the destination directory for this example? If your real name contains e.g. whitespace you're better off quoting the tested variable.
if [ -d "$destination" ]; then
(I would leave the destination-directory-check anyway as rsync will create the directory if its missing. If you copy on the same computer and not over a network, I would leave the -z compression parameter of rsync also.)
Update:
Does this work for you? (You'll have to change paths)
#!/bin/bash
source="/tmp/bar/"
destination="/tmp/baz/"
test -d "$source" || {
echo "$source does not exist"
exit
}
rsync -ra \
--delete \
--ignore-existing --ignore-times --size-only \
--stats --progress "$source" "$destination"
if [ "$?" -gt 0 ]; then
echo "Failure exit value: $?"
fi

I suspect your destination is set to something different than you showed us above, probably something containing whitespace.
You should put double quotes around it in the [ ] block too, e.g. [ -d "$destination" ].

Related

Run a command only once in a for loop in Bash

I have the following pattern of bash code from a function:
for folder in ${FOLDER[#]}; do
if [ $# -ne 0 ]; then
rsync -rvh --delete-after ${folder} ${folder_dest}
else
local zipFile=$(stamp-files file.zip)
unzip -q ${zipFile} "${folder}"* -d "${folder_src}"
rsync -rvh --delete-after ${folder_src}${folder} ${folder_dest}
rsync -rvh --delete-after ${folder_src}${folder} ${folder_dest2}
fi
done
So in this function I have an if statement in a for loop. And as you can see in the else part there is a call to another function stamp-files. This call to the others functions is needed just in the first iteration and even if it is not causing any issue, it could be definitely improved by just calling it once.
Do you know how could I do that simply and keeping that shape of code (the if statement in the for loop)?
You could set a variable and change it in the first iteration:
first='yes'
for folder in "{folders[#]}"; do
if [[ $first == 'yes' ]]; then
local files=$(stamp-files "$folder")
rsync -rvh --delete-after "$files" "$folder_dest"
first='no'
else
rsync -rvh --delete-after "$folder" "$folder_dest"
fi
done
The way you tried to use $# wouldn't work: that's the number of positional parameters and has nothing to do with the iteration number or similar.
I've also changed a few things:
folders instead of FOLDER; uppercase variable names are more likely to clash with shell and environment variables
All expansions quoted to prevent word splitting and globbing (the left-hand side in [[...]] is automatically quoted)
I use $folder instead of ${folder}, but that's really just personal taste
If you need to keep structure of your for loop and inside if :
unset stamp_files_done
for folder in ${FOLDER[#]}; do
if [ $# -ne 0 ]; then
rsync -rvh --delete-after ${folder} ${folder_dest}
else
if [[ -z ${stamp_files_done+x} ]]; then local files=$(stamp-files ${folder}); stamp_files_done=""; fi
rsync -rvh --delete-after ${files} ${folder_dest}
fi
done
You could also explain overall task to have more accurate solution.
I know you've said you'd like to keep the shape of the code, but stylistically I'm always hesitant to structure code with an if inside a loop just to handle the first element of an array - I prefer to hoist the code out to emphasise that the first element is being treated specially. The other option is to add a state variable, which never makes code any clearer.
In your case, how about
if [ ${#FOLDER[#]} -ne 0 ]; then
local files=$(stamp-files ${FOLDER[0]})
rsync -rvh --delete-after ${files} ${folder_dest}
for folder in ${FOLDER[#]:1}; do
rsync -rvh --delete-after ${folder} ${folder_dest}
done
fi

Zip I/O error: No such file or directory in bash script

I've been writing some code to (mostly) automate password protecting and compressing/archiving folders and files. However, the code seems to hate me.
I've run it line by line, and it always chokes on the zip command, even when the $file and $source are correct and valid.
Any ideas?
Here's the source code:
#!/bin/bash
echo "Drag in the source file"
read source
echo
echo "Drag in the destination file, or press enter to select the Desktop"
read destination
echo
if [ -z "$destination" ]
then destination="$PWD/Desktop"
echo "Destination is set to the desktop"
fi
echo "Type the name of the file to make"
read file
if [ -z "$file" ]
then file="archive"
echo "File name set to archive.zip"
fi
file="${destination}/${file}"
if [ -d $"source" ]
then zip -erj "$file" "$destination"
else zip -ej "$file" "$destination"
fi
There are a couple of problems in your code:
if [ -z "$destination" ]
then destination="$PWD/Desktop"
echo "Destination is set to the desktop"
fi
$PWD is the current working directory. It is your home directory when you open a Terminal but it changes everytime you run cd.
The Desktop directory is $HOME/Desktop.
If you don't run the script from your home directory, most probably $PWD/Desktop doesn't exist and this is a cause for errors; zip doesn't attempt to create the destination directory for the archive you ask it to build. If the directory doesn't already exist it displays an error message and exits.
Another problem is on the invocation of zip:
if [ -d $"source" ]
then zip -erj "$file" "$destination"
else zip -ej "$file" "$destination"
fi
You probably want to archive the file $source or the files in the $source directory (if it is a directory) but you mistakenly put $destination as the file/directory to archive in the zip command line.
if [ -d $"source" ] -- it should be "$source", otherwise the quote are useless and if $source contains spaces the script will exit with a syntax error.
One last thing: zip doesn't mind receiving -r in the command line when it is asked to archive only one file. You can replace the entire if/else block above with a single command:
zip -erj "$file" "$source"

Bash : Concatenate wildcard to a variable in a for loop

Don't be to hard on me this is my first bash attempt since school (like 9 years ago).
I'm trying to create a script that will rsync remote git repos locally.
I want to store my local directory into a variable because I'm using it several times but I don't find how to do it with the wildcard I use in the for loop.
I tried to concatenate the "/*/" and some other things that I didn't really understood but nothing worked.
Here is my script :
#!/bin/bash
if [ "$#" -lt 1 ]; then
echo "Usage :"
echo "syncDown <repo1> <repo2> ..."
echo "syncDown --all"
exit 1;
fi
LOCAL_PATH="/Volumes/Case Sensitive/repos"
# Sync all local repos
if [ $1 = "--all" ]; then
# for dir in "$LOCAL_PATH/*/";do <-- What I tried
for dir in /Volumes/Case\ Sensitive/repos/*/;do # <-- What works
folder=$(echo $dir | rev | cut -d'/' -f2 | rev);
rsync -avzh --delete-after devweb:$folder --exclude ".git" --exclude ".idea" "$LOCAL_PATH"
done
# Sync specific repos
else
for repo in "$#"; do
rsync -avzh --delete-after devweb:$repo --exclude ".git" --exclude ".idea" "$LOCAL_PATH"
done
fi
Thanks for your help.
You should be using:
for dir in "$LOCAL_PATH"/*/; do
Make sure to not keep glob character in quote and quote only the variable.

Pass dynamically generated parameters to command inside script

I have a script which calls the rsync command with some dynamically generated parameters but I'm having trouble passing them correctly.
Here's some excerpt:
logfile="$logDir/$(timestamp) $name.log"
echo "something" >> "$logfile"
params="-aAXz --stats -h --delete --exclude-from $exclude --log-file=$logfile $src $dest"
if [ "$silent" = "" ]; then
params="-v $params --info=progress2"
fi
rsync $params
If the logfile is e.g. /tmp/150507 test.log, the something statement is actually written to /tmp/150507 test.log, but rsync writes its logs to /tmp/150507 (everything after the first blank removed).
If I explicitly quote the name of the logfile in the params, rsync throws an exception:
params="-aAXz --stats -h --delete --exclude-from $exclude --log-file=\"$logfile\" $src $dest"
The error:
rsync: failed to open log-file "/tmp/150507: No such file or directory (2)
Ignoring "log file" setting.
How can I generate the params dynamically without losing the ability to use blanks in the filenames?
More quoting needed around log file name:
declare -a params
params=(-aAXz --stats -h --delete --exclude-from "$exclude" --log-file="$logfile" "$src" "$dest")
if [[ "$silent" = "" ]]; then
params=( -v "${params[#]}" --info=progress2 )
fi
rsync "${params[#]}"
This is the case where you should consider using BASH arrays to constitute a dynamic command line.

Recursively copying a file into multiple directories, if a directory does not exist in Bash

so I need to copy the file /home/servers/template/craftbukkit.jar into every folder inside of /home/servers, Ex. /home/servers/server1, /home/servers/server2, etc.
But I only want to do it if /home/servers/whateverserveritiscurrentlyon/mods does not exsist. This is what I came up with and was wondering if it will work:
echo " Script to copy a file to all server directories, only if mods does not exist in that directory"
for i in /home/servers/*/; do
if [ ! -d "$i/mods" ]; then
cp -f /home/servers/template/craftbukkit.jar "$i"
fi
done
echo " completed script ..."
Looks like it should work. To non-destructively test, change the cp -f ... line to say echo cp -f ... and review the output.
It could also be somewhat shortened, but it wouldn't affect efficiency much:
for i in /home/servers/*/
do
[[ -d "${i}/mods" ]] || cp -f /home/servers/template/craftbukkit.jar "${i}/."
done

Resources