I am trying to do a loop similar to this basic one:
storage-33:~# echo {a..z}
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
However I have two variables I set on my script called $first_sd and $last_sd
I have been unsuccessful when running it on my shell. The behaviour is not the loop from A to Z I expected. Its actually not doing the loop at all, do I need to convert my variable or is there a trick to make this work? See my bad example here of what I am trying to do (this does not work)
storage-33:~# for i in {$first_sd..$last_sd}; do echo HI $i; done
HI {a..h}
Thanks!
Use eval
for i in $(eval echo "{$first_sd..$last_sd}"); do echo HI $i; done
You can use eval remembering to escape the appropriate characters:
eval "for i in {$first_sd..$last_sd}; do echo HI \$i; done"
Related
Background
I have a .xyz file from which I need to remove a specific set of lines from. As well as do some text replacements. I have a separate .txt file that contains a list of integers, corresponding to line numbers that need to be removed, and another for the lines which need replacing. This file will be called atomremove.txt and looks as follows. The other file is structured similarly.
Just as a preemptive TL;DR: The tabs in my input file that happen to have one extra whitespace (because they justify to a certain position regardless of one extra whitespace), end up being converted to a single whitespace in the output file.
14
13
11
10
4
The xyz file from which I need to remove lines will look like something like this.
24
Comment block
H 18.38385 15.26701 2.28399
C 19.32295 15.80772 2.28641
O 16.69023 17.37471 2.23138
B 17.99018 17.98940 2.24243
C 22.72612 1.13322 2.17619
C 14.47116 18.37823 2.18809
C 15.85803 18.42398 2.20614
C 20.51484 15.08859 2.30584
C 22.77653 3.65203 2.19000
H 20.41328 14.02079 2.31959
H 22.06640 8.65013 2.27145
C 19.33725 17.20040 2.26894
H 13.96336 17.42048 2.19342
H 21.69450 3.68090 2.22196
C 23.01832 9.16815 2.25575
C 23.48143 2.42830 2.16161
H 22.07113 11.03567 2.32659
C 13.75496 19.59644 2.16380
O 23.01248 6.08053 2.20226
C 12.41476 19.56937 2.14732
C 16.54400 19.61620 2.20021
C 23.50500 4.83405 2.17735
C 23.03249 10.56089 2.28599
O 17.87129 19.42333 2.22107
My Code
I am successful in doing the line removal, and the replacements, although the output is not as expected. It appears to replace some of the tabs with the whitespace, specifically for lines that have a 'y' coordinate with only 5 decimals. I am going to share the resulting output first, and then my code.
Here is the output
19
Comment Block
H 18.38385 15.26701 2.28399
C 19.32295 15.80772 2.28641
O 16.69023 17.37471 2.23138
H 22.72612 1.13322 2.17619
C 14.47116 18.37823 2.18809
C 15.85803 18.42398 2.20614
C 20.51484 15.08859 2.30584
C 22.77653 3.65203 2.19000
C 19.33725 17.20040 2.26894
C 23.01832 9.16815 2.25575
C 23.48143 2.42830 2.16161
H 22.07113 11.03567 2.32659
C 13.75496 19.59644 2.16380
O 23.01248 6.08053 2.20226
C 12.41476 19.56937 2.14732
C 16.54400 19.61620 2.20021
C 23.50500 4.83405 2.17735
H 23.03249 10.56089 2.28599
O 17.87129 19.42333 2.22107
Here is my code.
atomstorefile="./extract_internal/atomremove.txt"
atomchangefile="./extract_internal/atomchange.txt"
temp="temp.txt"
tempp="tempp.txt"
temppp="temppp.txt"
filestoreloc="./"$basefilename"_xyzoutputs/chops"
#get number of files in directory and set a loop for that # of files
numfiles=$( ls "./"$basefilename"_xyzoutputs/splits" | wc -l )
numfiles=$(( numfiles/2 ))
counter=1
while [ $counter -lt $(( numfiles + 1 )) ];
do
#set a loop for each split half
splithalf=1
while [ $splithalf -lt 3 ];
do
#storing the xyz file in a temp file for edits (non destructive)
cat ./"$basefilename"_xyzoutputs/splits/split"$splithalf"-geometry$counter.xyz > $temp
#changin specified atoms
while read line;
do
line=$(( line + 2 ))
sed -i "${line}s/C/H/" $temp
done < $atomchangefile
# removing specified atoms
while read line;
do
line=$(( line + 2 ))
sed -i "${line}d" $temp
done < $atomstorefile
remainatoms=$( wc -l $temp | awk '{print $1}' )
remainatoms=$(( remainatoms - 2 ))
tail -n $remainatoms $temp > $tempp
echo $remainatoms > "$filestoreloc"/split"$splithalf"-geometry$counter.xyz
echo Comment Block >> "$filestoreloc"/split"$splithalf"-geometry$counter.xyz
cat $tempp >> "$filestoreloc"/split"$splithalf"-geometry$counter.xyz
splithalf=$(( splithalf + 1 ))
done
counter=$(( counter + 1 ))
done
I am sure the solution is simple. Any insight into what is causing this issue would be very appreciated.
Not sure what you are doing but you file can be fixed using column -t < filename command.
Example :
❯ cat test
H 18.38385 15.26701 2.28399
C 19.32295 15.80772 2.28641
O 16.69023 17.37471 2.23138
H 22.72612 1.13322 2.17619
C 14.47116 18.37823 2.18809
C 15.85803 18.42398 2.20614
C 20.51484 15.08859 2.30584
C 22.77653 3.65203 2.19000
C 19.33725 17.20040 2.26894
C 23.01832 9.16815 2.25575
C 23.48143 2.42830 2.16161
H 22.07113 11.03567 2.32659
C 13.75496 19.59644 2.16380
O 23.01248 6.08053 2.20226
C 12.41476 19.56937 2.14732
C 16.54400 19.61620 2.20021
C 23.50500 4.83405 2.17735
H 23.03249 10.56089 2.28599
O 17.87129 19.42333 2.22107
~
❯ column -t < test
H 18.38385 15.26701 2.28399
C 19.32295 15.80772 2.28641
O 16.69023 17.37471 2.23138
H 22.72612 1.13322 2.17619
C 14.47116 18.37823 2.18809
C 15.85803 18.42398 2.20614
C 20.51484 15.08859 2.30584
C 22.77653 3.65203 2.19000
C 19.33725 17.20040 2.26894
C 23.01832 9.16815 2.25575
C 23.48143 2.42830 2.16161
H 22.07113 11.03567 2.32659
C 13.75496 19.59644 2.16380
O 23.01248 6.08053 2.20226
C 12.41476 19.56937 2.14732
C 16.54400 19.61620 2.20021
C 23.50500 4.83405 2.17735
H 23.03249 10.56089 2.28599
O 17.87129 19.42333 2.22107
~
❯
The reason you wreck your whitespace is that you need to quote your strings. But a much superior solution is to refactor all of this monumentally overcomplicated shell script to a simple sed or Awk script.
Assuming the line numbers all indicate line numbers in the original input file, try this.
tmp=$(mktemp -t atomtmpXXXXXXXXX) || exit
trap 'rm -f "$tmp"' ERR EXIT
( sed 's%$%s/C/H/%' extract_internal/atomchange.txt
sed 's%$%d%' extract_internal/atomremove.txt ) >"$tmp"
ls -l "$tmp"; nl "$tmp" # debugging
for file in "$basefilename"_xyzoutputs/splits/*; do
dst= "$basefilename"_xyzoutputs/chops/${file#*/splits/}
sed -f "$tmp" "$file" >"$dst"
done
This combines the two input files into a new sed script (remarkably, by way of sed); the debugging line lets you inspect the result (probably remove it once you understand how this works).
Your question doesn't really explain how the input files relate to the output files so I had to guess a bit. One of the important changes is to avoid sed -i when you are not modifying an existing file; but above all, definitely avoid repeatedly overwriting the same file with sed -i.
first of all, i've read the question for loop with multiple conditions in Bash scripting but it does not work for what i intend to do. In the following script, a first for loop assign f quantity of arrays to a pair of variables (CON_PERC and CON_NAME)
f=0
for i in "${container[#]}"; do
CON_PERC[$f]=$(awk '{print int($2+0)}' <<< ="${container[$f]}") #CON_PERC[0] = 2; CON_PERC[1] = 0
CON_NAME[$f]=$(awk '{print $1}' <<< "${container[$f]}") #CON_NAME[0] = hi; CON_NAME[1] = bye
((f++))
done
what i need to do now, is in a separate loop, check every array of both variables and print themm. what would be the best way to do it?
what i tough is something like this
e=0
for ((x in "$CON_PERC[#]" && z in "$CON_NAME[#]")); do
echo "${CON_NAME[$e]} ${CON_PERC[$e]}"
((e++))
done
but it seems that for ((i in "$CON_PERC[#]" && e in "$CON_NAME[#]")) isnt valid in bash.
The question is, what is the best way to approach this, should i exclusively use a nested loop or is other way around it?
Here you have one way :
#!/bin/bash
CON_PERC=(1 2 3)
CON_NAME=("Hello" "Hallo" "Hola")
for item in "${CON_PERC[#]}" "${CON_NAME[#]}"
do
printf "Item : $item\n"
done
This will print :
Item : 1
Item : 2
Item : 3
Item : Hello
Item : Hallo
Item : Hola
Hope it helps!
Edit :
If you want you want you can use a traditional for loop as well. Im assuming both arrays will have the same size :
#!/bin/bash
CON_PERC=(1 2 3)
CON_NAME=("Hello" "Hallo" "Hola")
for (( i=0 ; i < "${#CON_PERC[#]}"; i++ ))
do
echo "${CON_PERC[i]} : ${CON_NAME[i]}"
done
You need to nest them like this (untested in your examples)
for x in "$CON_PERC[#]";
do
for z in "$CON_NAME[#]";
do
echo ${CON_NAME[$e]} ${CON_PERC[$e]}
((e++))
done
done
e.g.:
for x in {a..b};
do
for y in {c..d};
do
echo $x $y
done
done
result:
a c
a d
b c
b d
You could loop through the array keys of one of your arrays and use this key to get the value:
CON_PERC=( 01 02 03 04 )
CON_NAME=( one two three four )
for i in "${!CON_NAME[#]}"; do
printf '%s %s\n' "${CON_NAME[i]}" "${CON_PERC[i]}"
done
Output:
one 01
two 02
three 03
four 04
Taking count from file, say if count = 5, I want to print 5 variables. i.e. A B C D E.
If count = 2, Print 2 variables A B, etc.
I have tried using the ASCII values but couldn't go through it.
for i in {1..5}; do
count=5; a=0;
printf "\x$(printf %x '65+$a')";
count=count+1;
done
if count = 5, I want to print 5 variables. i.e. A B C D E. If count = 2, Print 2 variables A B, etc.
Here's a program that matches your style that does what you are looking for:
a=0
for i in {1..5}; do
printf "\x$(printf %x $(( 65 + a )) )";
a=$((a+1));
done
The first thing to note is that in order to do math in bash, you'll need to use the $(( )) operation. Above, you can see I replaced you '65+$a' with $(( 65 + a )) . That's the big news that you need to get math done.
There were a couple of other little issues, but you were stuck on the $(()) stuff so they weren't clear yet. Incidentally, the 'a' variable can be completely removed from the program to just use the 'i' variable like this:
for i in {1..5}; do
printf "\x$(printf %x $(( 64 + i )) )";
done
I had to change the constant to 64 since we are now counting starting at 1.
The {1..5} expression is a good short cut for 1 2 3 4 5, but you won't be able to put a variable into it. So, if you need to add a count variable back in, consider using the seq program instead like this:
count=$1
for i in $(seq 1 $count); do
printf "\x$(printf %x $(( 64 + i )) )";
done
Note that $() is different than the math operator $(()). $() runs a subcommand returning the results.
method 1: simple brace expansion
#!/bin/bash
# generate a lookup table
vars=( - $(echo {A..Z}) )
# use the elements
for i in {1..5}; do
echo ${vars[$i]}
done
{A..Z} generates 26 strings: A, B, ..., Z
which get stored in an array variable by vars=(...)
we prepend a - that we'll ignore
we can then do 1-based indexing into the array
limited to 26 variables (or whatever range we choose)
method 2: multiple brace expansion to generate arbitrary long variables
#!/bin/bash
if [[ ! $1 =~ ^[0-9]+$ ]]; then
echo "Usage: $0 count"
exit
fi
cmd='{A..Z}'
for (( i=$1; i>26; i=i/26 )); do
cmd="${A..Z}$cmd"
done
vars=( $(eval echo $cmd) )
for (( i=0; i<$1; i++ )); do
echo ${vars[$i]}
done
i/26 does integer division (throws away the remainder)
I'm lazy and generate "more than enough" variables rather than attempting to calculate how many is "exactly enough"
{a..b}{a..b}{a..b} becomes aaa aab aba abb baa bab bba bbb
using eval lets us do the brace expansion without knowing in advance how many sets are needed
Sample output:
$ mkvar.sh 10000 |fmt -64 | tail -5
ORY ORZ OSA OSB OSC OSD OSE OSF OSG OSH OSI OSJ OSK OSL OSM
OSN OSO OSP OSQ OSR OSS OST OSU OSV OSW OSX OSY OSZ OTA OTB
OTC OTD OTE OTF OTG OTH OTI OTJ OTK OTL OTM OTN OTO OTP OTQ
OTR OTS OTT OTU OTV OTW OTX OTY OTZ OUA OUB OUC OUD OUE OUF
OUG OUH OUI OUJ OUK OUL OUM OUN OUO OUP
The variable x in the first example doesn't get decremented, while in the second example it works. Why?
Non working example:
#!/bin/bash
x=100
f() {
echo $((x--)) | tr 0-9 A-J
# this also wouldn't work: tr 0-9 A-J <<< $((x--))
f
}
f
Working example:
#!/bin/bash
x=100
f() {
echo $x | tr 0-9 A-J
((x--))
# this also works: a=$((x--))
f
}
f
I think it's related to subshells since I think that the individual commands in the pipeline are running in subshells.
It does decrement if you don't use a pipeline (and avoid a sub shell forking):
x=10
f() {
if ((x)); then
echo $((x--))
f
fi
}
Then call it as:
f
it will print:
10
9
8
7
6
5
4
3
2
1
Since decrement is happening inside the subshell hence current shell doesn't see the decremented value of x and goes in infinite recursion.
EDIT: You can try this work around:
x=10
f() {
if ((x)); then
x=$(tr 0-9 A-J <<< $x >&2; echo $((--x)))
f
fi
}
f
To get this output:
BA
J
I
H
G
F
E
D
C
B
What I'm after is to have the most compact expression that expands the special parameter # with an offset of 2 or else to a default value of foobar if the subscript expands to the empty string or null. I tried the following notations but without luck:
"$#:2:-foobar"
"${#:2:-foobar}"
"${#:2: -foobar}"
Is there such a compact notation? Alternatively what would be a similar solution; ideally without temporary variables?
You may combine the expansion for the second parameter or its default value, followed by the expansion from the next offset.
Assuming your array is the program or function's argument array $# then,
#!/bin/bash
echo A "${#:2}"
echo B "${#:2:}" # your attempt #1
echo C "${#:2-foobar}" # your attempt #2
echo D "${#:2: -foobar}" # your attempt #3
echo E "${2:-foobar}"
echo F "$1" "${2:-foobar}" ${#:3}
G=("$1" "${2:-foobar}" ${#:3})
echo G "${G[#]}"
Will yield the desired result for line F and G (G uses a temp variable though).
Ex:
$ bash expand.sh 1
A
B
C
D
E foobar
F 1 foobar
G 1 foobar
$ bash expand.sh 1 2 3 4
A 2 3 4
B
C 2 3 4
D
E 2
F 1 2 3 4
G 1 2 3 4
If you're trying to do this with a different array than "$#", say H=(1 2 3), providing defaults to index expansions ("${H[2]:-foobar}") doesn't seem to work. Your best bet in this case, assuming you don't want to introduce temporary variables is to use a function or eval. But at that point you might be better off just adding a conditional e.g.,
# assuming that H wasn't sparse. redefine H based on its values
H=(
"${H[0]}"
$([[ -n "${H[1]}" ]] && echo "${H[1]}" || echo "foobar")
${H[#]:2}
)
But, readability will suffer.