Bash for loop end condition given as script argument - bash

I'm using this script to copy virtual machines in my ESXI 6.5. The first argument of the script is the name of the directory to copy.
I would like to have a second argument, which would be the number of vms I want to copy. For now on, I need to modify the for loop every time I want to copy different number of vms. The below script creates 20 vms by copying the directory of a vm given as the first script argument. I run it like this: ./copy.sh CentOS1 but would like to have something like this: ./copy.sh CentOS1 x where x is the end condition in my for loop.
#!/bin/sh
for i in $(seq 1 1 20)
do
mkdir ./$1_$i/
cp $1/* $1_$i/
echo "Copying machine '$1_$i' ... DONE!"
done
NOTE: Please do not suggest other for solutions, like those given, for instance, here: https://www.cyberciti.biz/faq/bash-for-loop/ because I checked them and they didn't work.
Thanks.

Use a C-style for loop, if you are using bash.
for ((i=1; i<=$2; i++))
do
mkdir "./$1_$i/"
cp "$1"/* "$1_$i/"
echo "Copying machine '$1_$i' ... DONE!"
done
If you need POSIX compatibility (as implied by your shebang), then you probably can't rely on seq being available either; use a while loop.
i=1
while [ "$i" -le "$2" ]; do
mkdir ./"$1_$i"
cp "$1"/* "$1_$i"
i=$((i+1))
done

In spite of your protestations to the contrary, one of the solutions in your link would work fine:
for ((i=1; i<=$2; i++)); do
# body of loop goes here
done
would loop from 1 to the number given in the second argument

Related

Multiple bash script with different parameters

I have the following bash script, that I launch using the terminal.
dataset_dir='/home/super/datasets/Carpets_identification/data'
dest_dir='/home/super/datasets/Carpets_identification/augmented-data'
# if dest_dir does not exist -> create it
if [ ! -d ${dest_dir} ]; then
mkdir ${dest_dir}
fi
# for all folder of the dataset
for folder in ${dataset_dir}/*; do
curr_folder="${folder##*/}"
echo "Processing $curr_folder category"
# get all files
for item in ${folder}/*; do
# if the class dir in dest_dir does not exist -> create it
if [ ! -d ${dest_dir}/${curr_folder} ]; then
mkdir ${dest_dir}/${curr_folder}
fi
# for each file
if [ -f ${item} ]; then
# echo ${item}
filename=$(basename "$item")
extension="${filename##*.}"
filename=`readlink -e ${item}`
# get a certain number of patches
for i in {1..100}
do
python cropper.py ${filename} ${i} ${dest_dir}
done
fi
done
done
Given that it needs at least an hour to process all the files.
What happens if I change the '100' with '1000' in the last for loop and launch another instance of the same script?
Will the first process count to 1000 or will continue to count to 100?
I think the file will be readonly when a bash process executes it. But you can force the change. The already running process will count to its original value, 100.
You have to take care about the results. You are writing in the same output directory and have to expect side effects.
"When you make changes to your script, you make the changes on the disk(hard disk- the permanent storage); when you execute the script, the script is loaded to your memory(RAM).
(see https://askubuntu.com/questions/484111/can-i-modify-a-bash-script-sh-file-while-it-is-running )
BUT "You'll notice that the file is being read in at 8KB increments, so Bash and other shells will likely not load a file in its entirety, rather they read them in in blocks."
(see https://unix.stackexchange.com/questions/121013/how-does-linux-deal-with-shell-scripts )
So, in your case, all your script is loaded in the RAM memory by the script interpretor, and then executed. Meaning that if you change the value, then execute it again, the first instance will still have the "old" value.

Loop though an array after ssh to a host in shell script

In the part of my script, I need to make ssh to a host and delete the elements of an array. In my current code for each element of the array I need to make ssh to host which takes time.
I want to make ssh to the host at one time and then delete all elements of the array.
How can I improve my below code from performance point of view?
for x in $Array
do
echo "Value of array are : $x"
ssh user#abc.host.com "rm -rf $x"
done
Why the loop at all? Using * as subscript gives all elements of an array.
ssh user#example.com "rm -rf ${Array[*]}"
Note that either way (with or without loop) will break if file names contain whitespaces.
You must input the commands in a file on your local, then upload the file, and finally run the script. Here is how it goes:
echo > rmscript.sh
for x in $Array
do
echo "Value of array are : $x"
echo "rm -rf $x" >> rmscript.sh
done
#upload
scp rmscript.sh user#abc.host.com:~
#run script
ssh user#abc.host.com "sh ~/rmscript.sh"

Monitor folders using unix bash

I need a shell script that will monitor all the folders given in the command
and will notify the user if a certain file will be created inside them (the name of
the file will be read from keyboard).
I am allowed to use simple commands, so not inotify...
this is what i managed to do so far:
#!/bin/bash
echo "Please enter a file you want to monitor: "
read file_monitor
#this is an infinite while
while [ 1 ] ; do
#using test -e to search for the file
test -e $file_monitor && echo "The file has been created!"
sleep 5
done
I have to find a way to stop the while when the file has been created, and also to search for the file in the folders given in the command line. Can someone help me, please?
To exit the loop, use break:
test -e $file_monitor && echo "The file has been created!" && break
I would prefer to break first, and echo after the loop, or as #mkemp6 suggested, directly use the test as the condition for the loop.
To check the folders, simply loop through them, and check the file in each one.
break [n]
Exit from within a for, while, until, or select loop. If n is specified, break n
levels. n must be ≥ 1. If n is greater than the number of enclosing loops, all
enclosing loops are exited.
while ! test -e "$file_monitor"; do sleep 5; done
But you are much better off using something like inotify to monitor the appropriate directories.
#!/bin/bash
arr=$#
for i in $arr; do
if [ ! -d $i ]
then echo "The parameter $i is no a directory!"
exit 1
fi
done
echo -n "Please give file you want to monitor: "
read file_monitor
a=1
while [ $a -eq 1 ]; do
for i in $arr; do
cd $i
test -e $file_monitor && echo "The file has been created" && a=$((a+1))
cd ..
done
done
So this is what I have managed to do.

Bash function - return parent script file path

I have a bash script containing a function which is sourced by a number of different bash scripts. This function may fail based on its input, and I'd like to create logging within the function to identify what script(s) are causing failures.
E.g.,
source /path/to/function.sh
The closest I've come is this:
ps --no-heading -ocmd -p $$
This works well enough if the full file path is used to run the parent script, returning:
/bin/bash /path/to/parent.sh
But it fails to provide the full path if the parent script is run from a relative path, returning:
/bin/bash ./parent.sh
Ideally, I'd like a way to reliably return the parent script file path for both cases.
I suppose I could have each parent script pass its file path to the function (via $0 or similar), but that seems hard to enforce and not terribly elegant.
Any ideas, or alternative approaches? Should I not worry about the relative path case, and just use full/absolute file paths for everything?
Thanks!
I'm using Centos 5.9.
Bash version -
GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu)
You can use readlink to follow all symbolic links to get an absolute path.
echo $(readlink -f $0)
As soon as the parent script starts export
"`pwd`/$0"
or so, into an env variable, say ORIG_SCRIPT, then in the function just use ORIG_SCRIPT.
You need to do this as soon as the script starts because $0 may be relative to the PWD and if you later change PWD before you need the value of ORIG_SCRIPT, it gets unnecessarily complicated.
Update:
Since you know the pid by $$, you may get something from /proc/<PID>/cmdline but I don't know how exactly this one works right now.
You could use ${BASH_SOURCE[1]} to get the script that calls the function but that is not always on absolute path form. You could get the absolute path of it by readlink -m, realpath, or other shell-script based solutions, but if your script changes directory from time to time, conversion of relative paths to absolute paths would no longer be accurate as those tools base from the current directory to get the actual form.
There's a workaround however but this requires that you won't change directories in your scripts before calling (sourcing) the script that contains the function. You would have to save the current directory in that script itself then base forming of absolute paths through that directory. You are free to change directories after the script has already been included. As an example:
ORIGINAL_PWD=$PWD
function x {
local CALLING_SCRIPT="${BASH_SOURCE[1]}"
if [[ -n $CALLING_SCRIPT ]]; then
if [[ $CALLING_SCRIPT == /* ]]; then
CALLING_SCRIPT=$(readlink -m "$CALLING_SCRIPT")
else
CALLING_SCRIPT=$(readlink -m "$ORIGINAL_PWD/$CALLING_SCRIPT")
fi
echo "Calling script: $CALLING_SCRIPT"
else
echo "Caller is not a script."
fi
}
Or
ORIGINAL_PWD=$PWD
function getabspath {
local -a T1 T2
local -i I=0
local IFS=/ A
case "$1" in
/*)
read -r -a T1 <<< "$1"
;;
*)
read -r -a T1 <<< "/$PWD/$1"
;;
esac
T2=()
for A in "${T1[#]}"; do
case "$A" in
..)
[[ I -ne 0 ]] && unset T2\[--I\]
continue
;;
.|'')
continue
;;
esac
T2[I++]=$A
done
case "$1" in
*/)
[[ I -ne 0 ]] && __="/${T2[*]}/" || __=/
;;
*)
[[ I -ne 0 ]] && __="/${T2[*]}" || __=/.
;;
esac
}
function x {
local CALLING_SCRIPT="${BASH_SOURCE[1]}"
if [[ -n $CALLING_SCRIPT ]]; then
if [[ $CALLING_SCRIPT == /* ]]; then
getabspath "$CALLING_SCRIPT"
else
getabspath "$ORIGINAL_PWD/$CALLING_SCRIPT"
fi
echo "Calling script: $__"
else
echo "Caller is not a script."
fi
}
You could also play around with FUNCNAME and BASH_LINENO to be more specific with the errors. I'm just not sure if they're already supported in Bash 3.2.
If you actually had Bash 4.0+ you could make use of associative arrays to map absolute paths with it but if there are two scripts with the same names or are called with almost similar names, one value could be overridden. There's no fix to that since we can't choose our keys from BASH_SOURCE.
Added Note: You could also prevent your script from being unnecessarily sourced multiple times as it only requires to be once through a solution like Shell Script Loader. You might find convenience through it as well.

Shell script to browse one or more directories passed as parameters

I made this script that should receive one or more parameter, and those parameter are all directories, and it has to browse those directories (one by one) and do some operations.
The operations work fine if the parameter is 1 (only one directory),
How should I modify my script to make it works if more than 1 parameter is passed
Example if I want it to do the same operations in 2 or 3 directories at the same time?
Thanks
#!/bin/sh
cd $1
for file in ./* # */
do
if [[ -d $file ]]
then
ext=dir
else
ext="${file##*.}"
fi
mv "${file}" "${file}.$ext"
done
First, if you are using bash use bash shebang (#! /bin/bash).
Then use
#! /bin/bash
for d in "$#"
do
echo "Do something with $d"
done
to iterate over the command line arguments (dirs in your case)
#!/bin/sh
for dir in "$#"; do
for file in "$dir"/*; do
echo "Doing something with '$file'"
done
done

Resources