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 )
Related
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#!#
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.
I'm trying to treat a bunch of files (five) with an awk command and name the output files using an incrementation.
The input files have complicated names. I know how to reuse the files' basenames to rename the outputs but I want to simplify the file names.
this is my code:
for f in *.txt; do
for i in {1..5}; do
echo processing file $f
awk '
{ if ($1=="something" && ($5=="60" || $5=="61"|| $5=="62"|| $5=="63"|| $5=="64"|| $5=="65"|| $5=="66"|| $5=="67"|| $5=="68"|| $5=="69"|| $5=="70"))
print }' $b.txt>"file_treated"$i.txt
echo processing file $f over
done
done
I understand that the error is in the second line because what I wrote runs the second loop for each value of the first one. I want each value of the first loop to correspond to one value of the second one.
Hope this was clear enough
How about:
i=0
for f in *.txt; do
let i++;
awk '$1=="something" && ($5 >= 60 && $5 <=70)' "$f" > file_treated"${i}".txt
done
I simplified your awk command and straightened out your various quoting issues. I also removed the $b.txt since you were simply recreating $f. I left the echo $b etc in case you actually wanted that but it could just as easily be replaced with echo "$f".
Use a counter:
i=1
for f in *.txt
do
echo "$f is number $((i++))"
done
well i just dont know what happened, my grep results counter used to work and now
it seems that no matter what i do it doesn't count my results and stay on the
initial value of 0, at the first line of the script i initiating it:
TotalResults=0
even if i define it in that way:
typeset -i TotalResults=0
it won't work , that's the while loop which in it the counter should grow and it actually doing the other commands, it's doing the printf stuff but just not increasing the counter, i checked it with echo and also when i want to use it, it stays on 0!
export URL="$CurrentURL"
grep -n -o -a $ExpressionValue $INDEX | while read line ; do
printf "%s\t%s" "${URL} ${line}"
printf "\n"
let TotalResults+=1
done
what is the problem? I have other counter that defined the same and he is working great, I'm tired of that, please help.
You are incrementing the counter in a subshell, after the |. The variable does not change in the parent shell. Change your code to
while read line ; do
printf "%s\t%s" "${URL} ${line}"
printf "\n"
let TotalResults+=1
done < <(grep -n -o -a $ExpressionValue $INDEX)
I would suggest to use counter in c-style, as code became more readable and works faster:
while read line ; do
printf "%s\t%s" "${URL} ${line}"
printf "\n"
(( ++TotalResults))
done < <(grep -n -o -a $ExpressionValue $INDEX)
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?