sed not parsing variable - bash

I'm stuck with a bash script here...
I have variables:
hostname1="sxxx" hostname2="vbbbb" hostname3="sggg" hostname4="aaa" ...
I'm trying to change the 12th line of every files in a folder with the host-name variables.
The files are server1.txt server2.txt server3.txt server4.txt ...
I'm trying to do this with a while loop:
i=1
imax=1
while [[ $i -le 20 ]]
do
sed -i "12s/.*/$hostname$imax/" server$((imax)).txt
(( i++ ))
(( imax++ ))
if [[ imax -eq 21 ]]
then
imax=1
fi
done
what I want to do with sed is to concatenate the word host-name with imax and then use it as variable.
Maybe with this I'm clear enough:
$hostname=hostname$imax; //for exammple
sed -i "12s/.*/$hostname/" server$((imax)).txt // i need here the variable $hostname to have the content "sxxx"

You achieve this by using indirect parameter expansion,
${!var}
Here, var - is a dynamically generated variable name.
Try this:
sed -i "12s/.*/${!hostname}/" server$((imax)).txt
Example:
$ hostname1="sat"
$ i=1
$ hostval="hostname$i"
$ echo ${!hostval}
sat

I'd use the following. Use change, instead of switch in sed. Then strong quote my sed, and unquote my variables.
for i in {1..20}; do
eval h='$'hostname$i
sed -i '12c'$h'' server$i.txt
done
Bash 3 and over supports number ranges in a for looop, easier than your while loop. I also have no idea what you are doing with imax instead of just i, b/c you exit at 20, but change imax value to 1... which it will never use.
edit: b/c I misread

Basically, your problem comes from a variable interpretation.
Try using sed >=4.2.2, should work with your code.

Related

continue <n> not skipping <n> iterations forward in shell script

I have created a hex to ASCII converter for strings in bash. The application I'm on changes characters (anything but [0-9],[A-Z],[a-z]) , in a string to its corresponding %hexadecimal. Eg: / changes to %2F in a string
I want to retain the ASCII characters as it is. Below is my code:
NAME=%2fhome%40%21%23
C_NAME=""
for (( i=0; i<${#NAME}; i++ )); do
CHK=$(echo "{NAME:$i:1}" | grep -v "\%" &> /dev/null;echo $?)
if [[ ${CHK} -eq 0 ]]; then
C_NAME=`echo "$C_NAME${NAME:$i:1}"`
else
HEX=`echo "${NAME:$i:3}" | sed "s/%//"`
C_NAME=`echo -n "$C_NAME";printf "\x$HEX"`
continue 2
fi
done
echo "$C_NAME"
OUTPUT:
/2fhome#40!21#23
EXPECTED:
/home#!#
So basically the conversion is happening, but not in place. Its retaining the hex values as well, which tells me the continue 2 statement is probably not working as I expect in my code. Any workarounds please.
You only have one loop so I assume you expected that continue 2 skips the current and next iteration of the current loop, however, the documentation help continue clearly states
continue [n]
[...]
If N is specified, resumes the Nth enclosing loop.
There is no built-in to skip the current and also the next iteration of the current loop, but in your case you can use (( i += 2 )) instead of continue 2.
Using the structure of your script with some simplifications and corrections:
#!/bin/bash
name=%2fhome%40%21%23
c_name=""
for (( i=0; i<${#name}; i++ )); do
c=${name:i:1}
if [[ $c != % ]]; then
c_name=$c_name$c
else
hex=${name:i+1:2}
printf -v c_name "%s\x$hex" "$c_name"
(( i += 2 )) # stolen from Dudi Boy's answer
fi
done
echo "$c_name"
Always use lower case or mixed case variables to avoid the chance of name collisions with shell or environment variables
Always use $() instead of backticks
Most of the echo commands you use aren't necessary
You can avoid using sed and grep
Variables should never be included in the format string of printf but it can't be avoided easily here (you could use echo -e "\x$hex" instead though)
You can do math inside parameter expansions
% doesn't need to be escaped in your grep command
You could eliminate the $hex variable if you used its value directly:
printf -v c_name "%s\x${name:i+1:2}" "$c_name"
I really enjoyed your exercise and decided to solve it with awk (my current study).
Hope you like it as well.
cat script.awk
BEGIN {RS = "%[[:xdigit:]]+"} { # redefine record separtor to RegEx (gawk specific)
decNum = strtonum("0x"substr(RT, 2)); # remove prefix # from record separator, convert hex num to dec
outputStr = outputStr""$0""sprintf("%c", decNum); # reconstruct output string
}
END {print outputStr}
The output
echo %2fhome%40%21%23 |awk -f script.awk
/home#!#

Change a Number inside a Variable

I have the following problem: (Its about dates)
The user will set the following variable.
variable1=33_2016
now I Somehow want to to automatically set a second variable which sets the "33" +1
that I get
variable2=34_2016
Thanks for any advice.
My first choice would be to break the first variable apart with read, then put the (updated) pieces back together.
IFS=_ read f1 f2 <<< "$variable1"
# Option 1
variable2=$((f1 + 1))_$f2
# Option 2
printf -v variable2 '%s_%s" "$((f1 + 1))" "$f2"
You can also use parameter expansion to do the parsing:
f1=${variable%_*}
f2=${variable#*_}
You can also use a regular expression, which is more readable for parsing but much longer to put the pieces back together (BASH_REMATCH could use a shorter synonym).
[[ $variable1 =~ (.*)_(.*) ]] &&
f1=$((${BASH_REMATCH[1]}+1)) f2=${BASH_REMATCH[2]}
The first and third options also allow the possibility of working with an array:
# With read -a
IFS=_ read -a f <<< "$variable1"
variable2=$(IFS=_; echo "${f[*]}")
# With regular expression
[[ $variable1 =~ (.*)_(.*) ]]
variable2=$(IFS=_; echo "${BASH_REMATCH[*]:1:2}")
You can use awk:
awk 'BEGIN{FS=OFS="_"}{$1+=1}1' <<< "${variable1}"
While this needs an external process to spawn (a bit slower) it's easier to read/write. Decide for yourself what is more important for you here.
To store the return value in a variable, use command substitution:
variable2=$(awk 'BEGIN{FS=OFS="_"}{$1+=1}1' <<< "${variable1}")
You can do somewhat the same thing with parameter expansion with substring substitution, e.g.
$ v1=33_2016
$ v2=${v1/${v1%_*}/$((${v1%_*}+1))}
$ echo $v2
34_2016
It's six to one, a half-dozen to another.

Increment with bash

I'm stuck trying to increment a variable in an .xml file. The tag may be in a file 100 times or just twice. I am trying to add a value that will increment the amount several times. I have included some sample code I am working on, but when I run the script it will only place a one and not increment further. Advice would be great on what I'm doing wrong.
for xmlfile in $(find $DIRECTORY -type f -name \*.xml); do
TFILE="/tmp/$directoryname.$$"
FROM='><process>'
TO=' value\=""><process>'
i=0
while [ $i -lt 10 ]; do
i=`expr $i + 1`
FROM='value\=""'
TO='value\="'$i'"'
done
sed "s/$FROM/$TO/g" "$xmlfile" > $TFILE && mv $TFILE "$xmlfile"
done
The while loop was something I just placed to test the code. It will insert the <process> but it will not insert the increment.
My end goal:
<process>value="1"</process>
<process>value="2"</process>
<process>value="3"</process>
<process>value="4"</process>
And so on as long as <process> is present in the file it needs to increment.
I just tested your code and it seems to correctly increment i.
You could try changing your increment syntax from:
i=`expr $i + 1`
To
i=$((i+1))
For a proper increment in bash, use a for loop (C style) :
n=10
for ((i=1; i<=n; i++)) {
printf '<process>value="%d"</process>\n' $i
}
OUTPUT
<process>value="1"</process>
<process>value="2"</process>
<process>value="3"</process>
<process>value="4"</process>
<process>value="5"</process>
<process>value="6"</process>
<process>value="7"</process>
<process>value="8"</process>
<process>value="9"</process>
<process>value="10"</process>
NOTE
expr is a program used in ancient shell code to do math. In Posix shells like bash, use $(( expression )). In bash and ksh93, you can also use (( expression )) or let expression if you don't need to use the result in an expansion.
EDIT
If I misunderstood your needs and you have a file with blank values like this :
<process>value=""</process>
try this :
$ perl -i -pe '$c++; s/<process>value=""/<process>value"$c"/g' file.xml
<process>value"1"</process>
<process>value"2"</process>
<process>value"3"</process>
<process>value"4"</process>
<process>value"5"</process>
<process>value"6"</process>
<process>value"7"</process>
-i switch edit the file for real, so take care.
This is the simplest way to increment a variable in bash:
i=0
((i++))
This also works.
Declaring the variable as an integer.
declare -i i=0
Then later you can increment like so:
i+=1
Use awk:
awk '{gsub( "value=\"\"", "value=" i++ ); print }' i=1 input-file
This will replace the string value="" with value="1", value="2", etc. You can easily change the start value and the increment ( eg ..."value=" i ); i+=5; print )

SED, using variables and in with an array

What I am trying to do is run the sed on multiple files in the directory Server_Upload, using variables:
AB${count}
Corresponds, to some variables I made that look like:
echo " AB1 = 2010-10-09Three "
echo " AB2 = 2009-3-09Foo "
echo " AB3 = Bar "
And these correspond to each line which contains a word in master.ta, that needs changing in all the text files in Server_Upload.
If you get what I mean... great, I have tried to explain it the best I can, but if you are still miffed I'll give it another go as I found it really hard to convey what I mean.
cd Server_Upload
for fl in *.UP; do
mv $fl $fl.old
done
count=1
saveIFS="$IFS"
IFS=$'\n'
array=($(<master.ta))
IFS="$saveIFS"
for i in "${array[#]}"
do
sed "s/$i/AB${count}/g" $fl.old > $fl
(( count++ ))
done
It runs, doesn't give me any errors, but it doesn't do what I want, so any ideas?
Your loop should look like this:
while read i
do
sed "s/$i/AB${count}/g" $fl.old > $fl
(( count ++ ))
done < master.ta
I don't see a reason to use an array or something similar. Does this work for you?
It's not exactly clear to me what you are trying to do, but I believe you want something like:
(untested)
do
eval repl=\$AB${count}
...
If you have a variable $AB3, and a variable $count, $AB${count} is the concatenation of $AB and $count (so if $AB is empty, it is the same as $count). You need to use eval to get the value of $AB3.
It looks like your sed command is dependent on $fl from inside the first for loop, even though the sed line is outside the for loop. If you're on a system where sed does in-place editing (the -i option), you might do:
count=1
while read i
do
sed -i'.old' -e "s/$i/AB${count}/g" Server_Upload/*.UP
(( count ++ ))
done < master.ta
(This is the entire script, which incorporates Patrick's answer, as well.) This should substitute the text ABn for every occurrence of the text of the nth line of master.ta in any *.UP file.
Does it help if you move the first done statement from where it is to after the second done?

How to iterate over positional parameters in a Bash script?

Where am I going wrong?
I have some files as follows:
filename_tau.txt
filename_xhpl.txt
filename_fft.txt
filename_PMB_MPI.txt
filename_mpi_tile_io.txt
I pass tau, xhpl, fft, mpi_tile_io and PMB_MPI as positional parameters to script as follows:
./script.sh tau xhpl mpi_tile_io fft PMB_MPI
I want grep to search inside a loop, first searching tau, xhpl and so on..
point=$1 #initially points to first parameter
i="0"
while [$i -le 4]
do
grep "$str" ${filename}${point}.txt
i=$[$i+1]
point=$i #increment count to point to next positional parameter
done
Set up your for loop like this. With this syntax, the loop iterates over the positional parameters, assigning each one to 'point' in turn.
for point; do
grep "$str" ${filename}${point}.txt
done
There is more than one way to do this and, while I would use shift, here's another for variety. It uses Bash's indirection feature:
#!/bin/bash
for ((i=1; i<=$#; i++))
do
grep "$str" ${filename}${!i}.txt
done
One advantage to this method is that you could start and stop your loop anywhere. Assuming you've validated the range, you could do something like:
for ((i=2; i<=$# - 1; i++))
Also, if you want the last param: ${!#}
See here, you need shift to step through positional parameters.
Try something like this:
# Iterating through the provided arguments
for ARG in $*; do
if [ -f filename_$ARG.txt]; then
grep "$str" filename_$ARG.txt
fi
done
args=$#;args=${args// /,}
grep "foo" $(eval echo file{$args})

Resources