Bash - How to for-loop last digit of a variable - bash

I am trying to automate creation of folders. Each folder should have a number at beginning of file name (increased by the for-loop [$i]. Also the rest of the folder name should be build up by variables that are constructed as [folder_x] where [x] is supposed to be stepped up also by the loop.
To be more specific. How do you build a string that is combined by the for-loops [$i] and a variable that is called, but should at the end also use the [$i]?
For more details see below:
#!/bin/bash
# Variables to be used
folder_1=1-folderOne
folder_2=2-folderTwo
folder_3=3-folderThree
# This is the folder names that should be created:
# mkdir /tmp2/1-folderOne
# mkdir /tmp2/2-folderTwo
# mkdir /tmp2/3-folderThree
# The for loop should combine the [$i] and above [folder_x],
# where the [x] should also be increased by the loop.
# Below is what i have right now:
# Note! The text "created-by-forloop" is just dummy text,
# and should be replace by the real solution.
for i in 1 2 3
do
if [ ! -d /tmp2/$i-created-by-forloop ]; then
mkdir -p /tmp2/$i-created-by-forloop;
fi
done

Use an array of names instead of distinct variables for each:
numbers=(One Two Three)
for i in "${!numbers[#]}" ; do
mkdir /tmp2/$((i+1))-folder"${numbers[i]}"
done
The loop iterates $i over the indices of the array. We need to add 1 to the index as arrays are zero based, but we want our files to be numbered from 1, not 0.

Related

Assiging a number to a module

I need to be able to assign a number to a module, my current code is below. Gap is where it needs to be assigned.
assignmodules(){
#Assign first module
x=1
modules=$(ls ./modules)
for module in modules; do
echo "$module assigned to slot $x"
x=$(x+1)
done
}
A few things:
Module title has to be printed in a different function, so i cant print it within the for loop.
Module needs to be run this way:
case $choice in
1) module1
2) module2
3) module3
etc.
Needs to be printed as following (If i find a good a solution that doesn't do it right, ill probably still use that.):
[1] Module1 [2] Module2
[3] Module3 [4] Module4
etc.
I tried this in the blank:
[{x}]module=$module
(i don't think this is exactly what i tried, i believe it was slightly different but i cant remember)
I wanted it to do as described above, but i don't think it will.
If you're using Bash, just use an array and some globbing and special parameter expansion.
assignmodules() {
set -- ./modules/*
modules=(0 "${#/#./modules/}")
unset 'modules[0]'
declare -p modules # Optionally show result
}
Somewhere you can print the modules list using
for i in "${!modules[#]}"; do
echo "[$i] ${modules[i]}"
done
Or
for i in "${!modules[#]}"; do
echo -n "[$i] ${modules[i]} "
(( i % 2 == 0 )) && echo
done
(( i % 2 )) && echo
It's also recommended to add shopt -s nullglob at the beginning of the script, especially if
modules directory can sometimes be empty.
Lastly, if you're wanting to ask user for choices, look at the select command. You may not need to display them manually. Run help select.
Just put your modules in an array: Numerically-indexed arrays (the default kind) have indices that are numbered by nature (note that these numbers start at 0, not 1).
assignmodules() {
declare -ga modules # explicitly declare global array (optional)
modules=( modules/* ) # put all modules directory entries in an array
modules=( "${modules[#]#*/}" ) # strip modules/ prefix off each entry
}
assignmodules # calling that function leaves modules assigned
# to print your list of modules in the specific format requested might look like:
print_args=( ) # generate format string argument array
for modules_idx in "${!modules[#]}"; do # iterate over indices in array
module=${modules[$module_idx]} # retrieve corresponding module name
print_args+=( "[$module_idx]" "$module" ) # append to list of stuff to print
done
# format string is repeated until all arguments are consumed
# so this way we get two columns as you requested
printf '%s %s\t%s %s\n' "${print_args[#]}" # actually print our list

bash recursion automatically ends after single level

Why is this make_request function ending just after a single traversal?
make_request(){
path="${1//' '/'%20'}"
echo $path
mkdir -p $HOME/"$1"
$(curl --output $HOME/"$1"/$FILE_NAME -v -X GET $BASE_URL"/"$API_METHOD"/"$path &> /dev/null)
# sample response from curl
# {
# "count":2,
# "items": [
# {"path": "somepath1", "type": "folder"},
# {"path": "somepath2", "type": "folder"},
# ]
# }
count=$(jq ".count" $HOME/"$1"/$FILE_NAME)
for (( c=0; c<$count; c++ ))
do
child=$(jq -r ".items[$c] .path" $HOME/"$1"/$FILE_NAME);
fileType=$(jq -r ".items[$c] .type" $HOME/"$1"/$FILE_NAME);
if [ "$fileType" == "folder" ]; then
make_request "$child"
fi
done
}
make_request "/"
make_request "/" should give the following output:
/folder
/folder/folder1-1
/folder/folder1-1/folder1-2
/folder/foler2-1
/folder/folder2-1/folder2-2
/folder/folder2-1/folder2-3 ...
but I am getting the following:
/folder
/folder/folder1-1
/folder/folder1-1/folder1-2
You are using global variables everywhere. Therefore, the inner call changes the loop variables c and count of the outer call, resulting in bogus.
Minimal example:
f() {
this_is_global=$1
echo "enter $1: $this_is_global"
((RANDOM%2)) && f "$(($1+1))"
echo "exit $1: $this_is_global"
}
Running f 1 prints something like
enter 1: 1
enter 2: 2
enter 3: 3
exit 3: 3
exit 2: 3
exit 1: 3
Solution: Make the variables local by writing local count=$(...) and so on. For your loop, you have to put an additional statement local c above the for.
As currently written all variables have global scope; this means that all function calls are overwriting and referencing the same set of variables, this in turn means that when a child function returns to the parent the parent will find its variables have been overwritten by the child, this in turn leads to the type of behavior seen here.
In this particular case the loop variable c leaves the last child process with a value of c=$count and all parent loops now see c=$count and thus exit; it actually gets a bit more interesting because count is also changing with each function call. The previous comment to add set -x (aka enable debug mode) before the first function call should show what's going on with each variable at each function call.
What OP wants to do is insure each function is working with a local copy of a variable. The easiest approach is to add a local <variable_list> at the top of the function, making sure to list all variables that should be treated as 'local', eg:
local path count c child fileType
change variables to have local scope instead of global.
...
local count; # <------ VARIABLE MADE LOCAL
count=$(jq ".count" $HOME/"$1"/$FILE_NAME)
local c; # <------ VARIABLE MADE LOCAL
for (( c=0; c<$count; c++ ))
do
....
done
...

Iterate between two arrays within a single loop

I have these variables:
bridge_xa_name_list=( "$br_int0_srxa" "$br_int1_srxa" "$br_int2_srxa" "$br_int6_srxa" "$br_int7_srxa" "$br_wan3_srxa1" "$br_wan3_srxa2" )
bridge_xb_name_list=( "$br_int0_srxb" "$br_int1_srxb" "$br_int2_srxb" "$br_int6_srxb" "$br_int7_srxb" "$br_wan3_srxb1" "$br_wan3_srxb2" )
I am trying to use a single loop to iterate all the elements for each array.
At the moment I have a functioning loop but only by referencing the $bridge_xa_name_list
for a in "${bridge_xa_name_list[#]}"; do
shell_echo_textval_green "Network Bridge Name Detected" "$a"
sleep 1
shell_echo_text "Verifying $a network State"
virsh_net_list=$(virsh net-list | grep active | grep $a)
if [[ ! $virsh_net_list == *"active" ]]
then
shell_echo "[Inactive]"
else
shell_echo "[Active]"
shell_echo_green "$a.xml found. Undefining anyway."
virsh net-undefine $a
fi
shell_echo_text "File $a.xml is at $srxa_fld_path"
if [[ -f ${srxa_fld_path}${a}.xml ]]
then
shell_echo "[Yes]"
else
shell_echo "[Not Found]"
shell_echo_text "Attempting to copy $a.xml template to ~/config/$srxa_nm"
cp $xml_file_path $srxa_fld_path${a}.xml
shell_echo ["Copied"]
#Check if Copy was sucessfull
if [[ -f $srxa_fld_path${a}.xml ]]
then
:
else
shell_echo_red "[Failed]"
shell_echo_red "There was an error when trying to copy ${a}.xml"
shell_echo_error_banner "Script Aborted! 1 error(s)"
exit 1
fi
done
$a in my script is iterating all the elements from the 1st array. However, I would like to include the second array as part of the same loop.
These are indexed arrays so you can iterate over the indexes:
for (( i = 0; i < ${#bridge_xa_name_list[#]}; i++ )); do
echo "${bridge_xa_name_list[i]}"
echo "${bridge_xb_name_list[i]}"
done
$a in my script is iterating all the elements from the 1st array. However, I would like to include the second array as part of the same loop.
I think you mean that you want to execute the loop body once for each element of bridge_xa_name_list and also once, separately, for each element of bridge_xb_name_list, without duplicating the body of the loop. Yes, there are at least two easy ways to do that:
Absolutely easiest would be to just specify the additional elements in the loop header:
for a in "${bridge_xa_name_list[#]}" "${bridge_xb_name_list[#]}"; do
# loop body ...
What you need to understand here is that the for loop syntax has nothing in particular to do with accessing an array. The in list of such a command designates zero or more individual values (shell "words") to iterate over, which in the case of your original code are produced by a parameter expansion involving array-valued parameter bridge_xa_name_list. But this is just a special case of the shell's general procedure of expanding each command (path expansion, parameter expansion, command expansion, etc.) before executing it. You can use that however you like.
OR
Make a function around the loop that executes it once for every function argument. Then call that function once for each array:
my_loop() {
for a in "$#"; do
# loop body
done
}
# ...
my_loop "${bridge_xa_name_list[#]}"
my_loop "${bridge_xb_name_list[#]}"
Note that this still exhibits the same expand-then-execute behavior described in the previous item, which is why you have to pass the expansion of each array (to one word per element). There is no direct way to pass the whole array as a single argument.
Note also that the shell supports a special shortcut for iterating over all the elements of $#. For that particular case, you can omit the in list altogether:
my_loop() {
for a; do
# loop body
done
}
Of course, you can also combine the above, by providing the function and calling it once with the elements of both arrays:
my_loop "${bridge_xa_name_list[#]}" "${bridge_xb_name_list[#]}"

use array in bash for change name of created folder in each loop

I am trying to do a script in bash and the idea is that I have an array with text and I use to creat a folder, for example:
#!/bin/bash
dir=(dir1 dir2 dir3)
for i in 0 1 2; do
d=run_${#dir[i]}
echo "Prepare case ${d}..."
done
My problem is that when I do this, it prints:
Prepare case run_4...
Prepare case run_4...
Prepare case run_4...
and the number 4 corresponds to the lenght of each array element, (if I change dir1 to direc1 for example, I get in the first line of the output "Prepare case run_6...")
What i was looking:
Prepare case run_dir1...
Prepare case run_dir2...
Prepare case run_dir3...
What i am missing?
Remove # from this commnd d=run_${#dir[i]}
And you can loop over array values like this:
for i in "${dir[#]}"; do
echo "Prepare case run_$i..."
done

How to rename multiple files reordering name parts and including ordered numbers at the front (bash)

I have a list of files in a folder named based on (a) sample name [sometimes with '_1' or '_2' for different individuals]; (b) job id [1-12]; and (c) chromosome number [chrI-chrXXI].
Ex:
8116_1_chrI.vcf #sample[8116]; jobId[1]; chr[chrI]
8116_1_chrII.vcf #sample[8116]; jobId[1]; chr[chrII]
...
CSC0832_1_7_chrVIII.vcf #sample[CSC0832_1]; jobId[7]; chr[chrVIII]
CSC0832_1_7_chrXIX.vcf #sample[CSC0832_1]; jobId[7]; chr[chrXIX]
...
RNF2887_2_12_chrX.vcf #sample[RNF2887_2]; jobId[2]; chr[chrX]
RNF2887_2_12_chrXI.vcf #sample[RNF2887_2]; jobId[2]; chr[chrXI]
...
Each sample has the same job id number, and separate files for each chromosome.
I am trying to submit a job array, so now I need unique identifiers (job ids) for each single file, and I am trying to rename those by (1) including a unique number in the front; (2) then adding the sample id; (3) and then the chromosome number.
I am trying to do a bash for loop for that, but it is not working. Below is my script:
for FILENAME in `ls $SCRATCH/stickleback/sorelData/indSamplesVcf/splitChr/*.vcf`; do
ROOTNAME=`basename ${FILENAME%%_*}`
CHR=`basename ${FILENAME##*_} .vcf`
for LIST in `seq 279`; do
cp "$FILENAME" $SCRATCH/stickleback/sorelData/indSamplesVcf/splitCopy/${LIST}_${ROOTNAME}_${CHR}.vcf
echo "copying $(basename ${FILENAME}) to ${LIST}_${ROOTNAME}_${CHR}.vcf"
done
done
What I get is a file with unique numbers, but they are always the same sample id, and the same chromosome number:
1_8116_chrIII.vcf
2_8116_chrIII.vcf
...
And one thing I noticed is that when I echo basename ${FILENAME##*_}, it lists the chromosomes in alphabetical order (because they are in romans). Will that affect the renaming also?
Sorry for the long and silly question, but I am a newbie at this.
Thank you!
if it helps ...
directory=$SCRATCH/stickleback/sorelData/indSamplesVcf/splitChr
list=0
for filename in $directory/*.vcf ; do
basename=$( basename ${filename} ) # 8116_1_chrI.vcf
sample=${basename%%_*} # 8116
chr=${basename##*_} # chrI.vcf
list=$(( list+1 ))
cp "$directory/$filename" "$directory/splitCopy/${list}_${sample}_${chr}"
echo "copying $basename to ${list}_${sample}_${chr}"
done
I assume:
that you want to add an unique ID ($list) to filename
filename becomes id_sample_chr.extension
I suggest:
variables of script in lowercase
prefer $( command ), no backticks
do not use $( ls )
use indentations

Resources