Does a second (inner) loop open a subprocess? - linux

I have come to an interesting topic, while I was experimenting
recursive loops in a shell script.
First I came across interesting functionality of
for x in a b c d; do
for x in e f d h; do
a=test
done
echo $x
done
Always outputting letter h as variable $x. Which makes sense, as inner loop uses x as variable name too, and h would be the last one picked in such loop.
My concern is, does inner loops open a subprocess or how is the functionality guaranteed?
for x in a b c d; do
for x in e f d h; do
pstree $$
done
pstree $$
done
.. suggest in the output that no sub-processes are opened on nested loops.
I am probably a little bit stuck in a loop, but is there a good documentation how a shell operates loops? I would like to know how does shell interpret the loops so it's kept within one layer of instructions.

Modify your example in this way:
#! /bin/bash
set -x
for x in a b; do
: x=$x
for x in c d; do
: x=$x
a=test
done
echo $x
done
This outputs:
+ for x in a b
+ : x=a
+ for x in c d
+ : x=c
+ a=test
+ for x in c d
+ : x=d
+ a=test
+ echo d
d
+ for x in a b
+ : x=b
+ for x in c d
+ : x=c
+ a=test
+ for x in c d
+ : x=d
+ a=test
+ echo d
d
Now you can see, the inner loop modifies x after the outer loop. When you print x it has always the last value of the inner loop.

Related

Restarting a job for supercomputer while looping a command for millions of files?

I'm using a supercomputer which is using the famous #PBS. My walltime is 48 hours and this is not enough to process million files.
My file names are like :
AAAAAA.pdb
DAAAAA.pdb
EAAAAA.pdb
FAAAAA.pdb
...
All possibles letters are "A D E F G H I K L M N P Q R S T V W Y".
I want to use a script like this :
for file in /dir/*
do
cmd [option] $file >> results.out
done
But I must use a restart for the wall time. With numbers I would have put a counter but with specific letters I don't know how to write the last file to start from this checkpoink. Like :
if [ -f next.seq ]; then
seq=`cat next.seq`
else
...
for file in /dir/seq
do
cmd [option] $file >> results.out
done
...
let seq=seq+1
echo $seq > next.seq

bash order-of-operations in math context: Wrong value assigned

Given the linux shell code,
~$ (( b = a, (a += 3) + $((a = 1)), b++ ))
~$ echo $b
2
Why does $b equal 2? I split the code into three steps:
~$ ((b = a))
~$ (((a += 3) + $((a = 1))))
~$ ((b++))
~$ echo $b
1
$b equals 1 this time, why?
P.S. Neither a nor b is initialized.
Your two examples are not equivalent. The arithmetic expansion $((...)) is performed before the (( ... )) statement is evaluated, so the following are equivalent:
(( b = a, (a += 3) + $((a = 1)), b++ ))
and
a=1
(( b = a, (a += 3) + a, b++ ))
Your attempt at breaking it into three parts is equivalent to
((b = a)) # b = 0 since uninitialized a is treated as 0
a=1
(((a += 3) + a))
((b++)) # b = 1
The difference is when, in the sequence of evaluation, a is first assigned the value of 1.
Because you set b = a, then bash waits for last assigning of a. In b will be assigned same value as the value assigned to a.
~$ (( b = a, (a += 3) + $((a = 5)), b++ ))
~$ echo $b
6
~$ echo $a
8
EDIT
1) column-separated expressions are treated sequentially
~$ echo $((1+1, 2+2, 3+3))
6
2) $((...)) expressions are treated first
which gives result:
(( b = a, (a += 3) + $((a = 1)), b++ ))
$((a = 1)) #a=1
b = a #a=b=1
a += 3 #a=4, b=1
b++ #a=4, b=2

How to add a single special character randomly anywhere inside a string? Bash

I have this script that generates a random character between 8-16. I am confused as to how I would add a single random special character from a bank [! # # $ % ^ & * ( ) _ + ] anywhere randomly inside this string?
if [ $# -eq 0 ] then
pwdlen=$(((RANDOM % 9 ) +8))
spclen=$((RANDOM % 1))
char=(0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V X W Y Z)
chars=(~ ! # # $ % ^ & * - +)
#rand2=$random % 11
max=${#char[*]}
for i in `seq 1 $pwdlen`
do
let "rand=$RANDOM % $max"
str="${str}${char[$rand]}"
done
echo $str
exit 0
fi
teststring=foobarspam
specialchars='!##$%^&*()_+'
randomchar=${specialchars:RANDOM % ${#specialchars}:1}
randompos=$(( RANDOM % ( ${#teststring} + 1 ) ))
newstring=${teststring:0:randompos}${randomchar}${teststring:randompos}
You can use the following code.
#!/bin/bash
if [ $# -eq 0 ]; then
pwdlen=$(((RANDOM % 9 ) +8))
spclen=$((RANDOM % 1))
char=(0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V X W Y Z)
chars=('~' '!' '#' '#' '$' '%' '^' '&' '*' '-' '+')
#rand2=$random % 11
max=${#char[*]}
for i in `seq 1 $pwdlen`
do
let "rand=$RANDOM % $max"
str="${str}${char[$rand]}"
done
A=$(echo $str|wc -c) ## To get the count of
P=$((RANDOM % $A)) ## To get a random position of where to insert the character.
I=$((RANDOM % 11)) ## To get a random index number of chars array
C=${chars[$#]}
echo $str | sed 's!^\(.\{'$P'\}\).!\1\'"$C"'!' ## Inserting the special character to string in defined position
exit 0
fi
Output:
$ for i in `seq 1 10`;do ./test1;done
j^eh8BmD2H
0B01^1AN6EVw
Wu2$LLTILuDN8fSV
e^90gmHjksDo
eB7wa\#fmwf
NVAtJkmfqx~
JaHvD%uyO3rB
ncFrgyyz~UkZ
q0LLRHUNATM8DL
X%ARcXgyC1Do
I am not sure what script language are you using. I wrote a solution for you using PHP. If PHP is not what you are using, you should be able to convert the same logic to other languages and get the same results.
<?php
//This is the original string where you want to add a random character to
$org_string = 'This is My original String';
//calculates the length of the string
$org_length = strlen($org_string);
//find a random position
$pos = rand(0, $org_length-1);
//concatenate the first part of the string, random character, the remaining string
$final = substr($org_string, 0, $pos) . getOne() . substr($org_string, $pos);
//print the final value
echo $final;
//return a random string
function getOne(){
//the following string is 12 characters in length. it is all available characters that you want to select from
$str = '!##$%^&*()_+';
//return a random character
return $str[rand(0, 11)];
}
?>

while loop variable reading issue

I have a text file a.txt like this:
1 1 a
2 b
4 4 d
Now I read that text file and feed it into a while loop:
cat a.txt | while read k l o
do
echo " ${k} ${l} ${o} "
if [ "${k}" == "${l}" ]; then
echo " success ; X: ${k} Y : ${l} Z : ${o} "
else
echo " failed ; X: ${k} Y : ${l} Z : ${o} "
fi
done
In my text file 2nd line first value is empty. That's why I'm getting y value in to x and z value in to y.
How can I handle such empty values and get all values printed at their correct positions?
why don't you just insert 0 for empty x value?
anyway you would need 3 values.. /bin/sh read command assigns the first value to the first name..

Variable as bash array index?

#!/bin/bash
set -x
array_counter=0
array_value=1
array=(0 0 0)
for number in ${array[#]}
do
array[$array_counter]="$array_value"
array_counter=$(($array_counter + 1))
done
When running above script I get the following debug output:
+ array_counter=0
+ array_value=1
+ array=(0 0 0)
+ for number in '${array[#]}'
+ array[$array_counter]=1
+ array_counter=1
+ for number in '${array[#]}'
+ array[$array_counter]=1
+ array_counter=2
+ for number in '${array[#]}'
+ array[$array_counter]=1
+ array_counter=3
Why does the variable $array_counter not expand when used as index in array[]?
Bash seems perfectly happy with variables as array indexes:
$ array=(a b c)
$ arrayindex=2
$ echo ${array[$arrayindex]}
c
$ array[$arrayindex]=MONKEY
$ echo ${array[$arrayindex]}
MONKEY
Your example actually works.
echo ${array[#]}
confirms this.
You might try more efficient way of incrementing your index:
((array_counter++))

Resources