variable substitution in if statement - unix

I am trying to execute the below
if [[ $1 == 'R' ]]
then
echo "Running the recovery steps..."
for i in 1 2 3 4 5 6
do
head -${i} cons.txt | tail -1 | read -r r${i}f1 r${i}f2 r${i}f3 r${i}f4 r${i}f5 r${i}f6 r${i}f7 r${i}f8 r${i}f9;
if (( ${Time} >= ${r${i}f1} && ${Time} < ${r${i}f2} ))
then
sed "s/$r$if3}/`echo $r$if3 | cut -c1-4`/;s/$r$if4/`echo $r$if4 | cut -c1-4`/;s/$r$if5/`echo $r$if5 | cut -c1-4`/;s/$r$if6/`echo $r$if6 | cut -c1-4`/;s/$r$if7/`echo $r$if7 | cut -c1-4`/;s/$r$if8/`echo $r$if8 | cut -c1-4`/;s/$r$if9/`echo $r$if9 | cut -c1-4`/" cons.txt > cons.txt.tmp && mv cons.txt.tmp cons.txt
fi
done
fi
but the inside if condition gives me error. I believe I am using wrong set of braces here but can't seem to figure out the correct way
trim.sh[6]: " ${Time} >= ${r${i}f1} && ${Time} < ${r${i}f2} ": 0403-011 The specified substitution is not valid for this command.

Parameter expansion is not recursive (or repeated, or inside-out) in < ${r${i}f2}, so this can't work.
You could use some convoluted code using eval to construct variable names before expansion, but that's a can of worms. What about simply unrolling the six element loop?

You are not able to do directly the variable inside the variable reference.
${r${i}f2}
You have to use the indirect reference. Try the below code it will work. Using eval we can done this.
if [[ $1 == 'R' ]]
then
echo "Running the recovery steps..."
for i in 1 2 3 4 5 6
do
head -${i} cons.txt | tail -1 | read -r r${i}f1 r${i}f2 r${i}f3 r${i}f4 r${i}f5 r${i}f6 r${i}f7 r${i}f8 r${i}f9;
eval var1=r${i}f1
eval var2=r${i}f2
eval val1=\$$var1
eval val2=\$$var2
if (( ${Time} >= $val1 && ${Time} < $val2 ))
then
sed "s/$r$if3}/`echo $r$if3 | cut -c1-4`/;s/$r$if4/`echo $r$if4 | cut -c1-4`/;s/$r$if5/`echo $r$if5 | cut -c1-4`/;s/$r$if6/`echo $r$if6 | cut -c1-4`/;s/$r$if7/`echo $r$if7 | cut -c1-4`/;s/$r$if8/`echo $r$if8 | cut -c1-4`/;s/$r$if9/`echo $r$if9 | cut -c1-4`/" cons.txt > cons.txt.tmp && mv cons.txt.tmp cons.txt
fi
done
fi

Related

awk $4 column if column = value with characters thereafter

I have a file with the following data within for example:
20 V 70000003d120f88 1 2
20 V 70000003d120f88 2 2
20x00 V 70000003d120f88 2 2
10020 V 70000003d120f88 1 5
I want to get the sum of the 4th column data.
Using the the below command, I can acheive this, however the row 20x00 is excluded. I want to everything to start with 20 must be sumed and nothing before that, so 20* for example:
cat testdata.out | awk '{if ($1 == '20') print $4;}' | awk '{s+=$1}END{printf("%.0f\n", s)}'
The output value must be:
5
How can I achieve this using awk. The below I attempted also does not work:
cat testdata.out | awk '$1 ~ /'20'/ {print $4;}' | awk '{s+=$1}END{printf("%.0f\n", s)}'
There is no need to use 3 processes, anything can be done by one AWK process. Check it out:
awk '$1 ~ /^20/ { a+=$4 } END { print a }' testdata.out
explanation:
$1 ~ /^20/ checks to see if $1 starts with 20
if yes, we add $4 in the variable a
finally, we print the variable a
result 5
EDIT:
Ed Morton rightly points out that the result should always be of the same type, which can be solved by adding 0 to the result.
You can set the exit status if it is necessary to distinguish whether the result 0 is due to no matches
(output status 0) or matching only zero values ​​(output status 1).
The exit code for different input data can be checked e.g. echo $?
The code would look like this:
awk '$1 ~ /^20/ { a+=$4 } END { print a+0; exit(a!="") }' testdata.out
Figured it out:
cat testdata.out | awk '$1 ~ /'^20'/ {print $4;}' | awk '{s+=$1}END{printf("%.0f\n", s)}'
The above might not work for all cases, but below will suffice:
i=20
cat testdata.out | awk '{if ($1 == "'"$i"'" || $1 == ""'"${i}"'"x00") print $4;}' | awk '{s+=$1}END{printf("%.0f\n", s)}'

Duplicates in an unix text file based on multiple fields

I have a requirement to find duplicates based on three columns in a .txt file in unix which is delimited by ,.
Input:
a,b,c,d,e,f,gf,h
a,bd,cg,dd,ey,f,g,h
a,b,df,d,e,fd,g,h
a,b,ck,d,eg,f,g,h
Let's take we are finding dupliactes based on 1,2,5 fields.
Expected output:
a,b,c,d,e,f,gf,h
a,b,df,d,e,fd,g,h
Can anyone help to write a script for this or is there a command already available?
I tried like this:
awk -F, '!x[$1,$2,$3]++' file.txt but did not work
One way using awk:
awk -F, 'FNR==NR { x[$1,$2,$5]++; next } x[$1,$2,$5] > 1' a.txt a.txt
This is simple, but reads the file two times. On the first pass (FNR==NR), it maintains counts based on key fields. During the second pass, if prints the line if its key was found more than once.
Another way using awk:
awk -F, '{if (x[$1$2$5]) { y[$1$2$5]++; print $0; if (y[$1$2$5] == 1) { print x[$1$2$5] } } x[$1$2$5] = $0}' a.txt
Explanation:
1 awk -F,
2 '{if (x[$1$2$5])
3 { y[$1$2$5]++; print $0;
4 if (y[$1$2$5] == 1)
5 { print x[$1$2$5] }
6 } x[$1$2$5] = $0
7 }'
Line 2: If x has $1$2$5, this key was seen before, do steps 3-5
Line 3: Increment the count and print the line because it is a dup
Line 4: This means, We are seeing this key for the 2nd time, so we need to print the first line with this key. Last time we saw this key we did not know whether it was a dup or not. So we print the first line in step 5.
Line 6: Store the current line against the key so we can use it in step 2
Another way using sort, uniq and awk
Note: uniq command has an option '-f' to skip the specified number of fields before it starts comparison.
sort -t, -k1,1 -k2,2 -k5,5 a.txt | awk -F, 'BEGIN { OFS = " "} {print $0, $1, $2, $5}' | sed 's/,/ /g' | uniq -f7 -D | sed 's/ /,/g' | cut -d',' -f 1-7
This sorts based on fields 1,2,5. awk prints the original line and appends fields 1,2,5 . sed changes the delimiter because uniq does not have an option to specify delimiter. uniq skips first 7 fields and works on rest of the line and prints duplicate lines.
I had a similar issue
I needed to eliminate duplicate detail records while preserving flat file record formatting and seqence of the records.
The duplication caused by a time expansion of the date field in column 2 of the detail only.
Receiving system was reporting duplication on columns 4 and 5.
I cobbled together this quick hack to resolve it.
First read the file data into an array
Then we can read and manipulate the individual records (crudely with a counter) as demonstrated in this snippet integrating a case statement to logically treat the various record types.
Cheers!
readarray inrecs < [input file name]
filebase=echo "[input file name] | cut -d '.' -f1
i=1
for inrec in "${inrecs[#]}";do
field1=echo ${inrecs[$i-1]} | cut -d',' -f1
field2=echo ${inrecs[$i-1]} | cut -d',' -f2
field3=echo ${inrecs[$i-1]} | cut -d',' -f3
field4=echo ${inrecs[$i-1]} | cut -d',' -f4
field5=echo ${inrecs[$i-1]} | cut -d',' -f5
field6=echo ${inrecs[$i-1]} | cut -d',' -f6
field7=echo ${inrecs[$i-1]} | cut -d',' -f7
field8=echo ${inrecs[$i-1]} | cut -d',' -f8
case $field1 in
'H')
echo "$field1,$field2,$field3">${filebase}.new
;;
'D')
dupecount=0
dupecount=`zegrep -c -e "${field4},${field5}" ${infile}`
if [[ "$dupecount" -gt 1 ]];then
writtencount=0
writtencount=`zegrep -c -e "${field4},${field5}" ${filebase}.new`
if [[ "${writtencount}" -eq 0 ]];then
echo "$field1,$field2,$field3,$field4,$field5,$field6,$field7,$field8,">>${filebase}.new
fi
else
echo "$field1,$field2,$field3,$field4,$field5,$field6,$field7,$field8,">>${filebase}.new
fi
;;
'T')
dcount=`zegrep -c '^D' ${filebase}.new`
echo "$field1,$field2,$dcount,$field4">>${filebase}.new
;;
esac
((i++))
done

comparing floating numbers in unix

I am facing problem in comparing big floating variables in unix
Code:
error message: syntax error on line 1 teletype
I got to know from one of the old posts in the forum this is because
"the script is trying to do a calculation with bc by echoing an expression into it. But one of the variables has an illegal number"
Below is the script which is giving the error
Code:
#! /bin/bash -xv
a=`cat abc.csv | sed '1d' | tr -s ' ' | cut -d, -f3`
echo $a
-180582621617.24
b=`sed '1d' def.csv | cut -d',' -f7 | awk '{s+=$1}END{ printf("%.2f\n",s)}'`
echo $b
-180582621617.37
Result=`echo "if($a !=$b) 1" | bc `
if [ $Result -eq 1 ]; then
echo "both values not equal"
else
echo " both values equal"
fi
But I was able to compare it when hard-coded
Code:
a=`echo "-180582621617.24,222.555,333.333" | awk -F"," '{print $1}'`
b=`echo "-180582621617.24,222.555,333.333" | awk -F"," '{print $1}'`
Result=`echo "if($a !=$b) 1" | bc `
if [ $Result -eq 1 ]; then
echo "both values not equal"
else
echo " both values equal"
fi
Your test in bc is return 1 if true and nothing when false.
$Result will be then either undefined or numeric (1). test with -eq only works with two operands both numeric. Just return 0 for the else case
Result=`echo "if($a !=$b) 1 else 0" | bc `
if [ $Result -eq 1 ] ; then
echo "both values not equal"
else
echo " both values equal"
fi
Use bc for dealing with floating numbers in shell:
$ bc <<< '-180582621617.24 == -180582621617.37'
0
$ bc <<< '-180582621617.24 != -180582621617.37'
1
In your case, it is going to be bc <<< "$a != $b", e.g.:
[[ bc <<< "$a != $b" ]] && Result=1 || Result=0
Thanks for all the suggestions.
I was able to compare by creating two temp files and using the diff -w command.
#! /bin/bash -xv
rm -f triger_cksum.txt data_cksum.txt
a=`cat ab.csv | sed '1d' | tr -s ' ' | cut -d, -f3`
echo $a > triger_cksum.txt
b=`sed '1d' cd.csv | cut -d',' -f61 | awk '{s+=$1}END{ printf("%.6f\n",s)}'`
echo $b > data_cksum.txt
diff_files=`diff -w triger_cksum.txt data_cksum.txt | wc -l | tr -s ' '`
if [ $diff_files -eq 0 ]
then
echo "cksum equal"
else
echo "cksum not equal"
fi

Creating an nxn alternating matrix using unix commands

Anyone know how to create an nxn matrix of alternating 0's and 1's using unix commands?
ex output:
I can create a non-alternating matrix by $ yes 1010101 | head -7 but I'm not sure how to alternate the rows
This will do the trick:
if [ $((n%2)) -eq 0 ]; then
yes 'echo 1; echo 0' | sh | head -$((n*n+n)) | pr -$n -s' ' -t | head -$n
else
yes 'echo 1; echo 0' | sh | head -$((n*n)) | pr -$n -s' ' -t
fi
Basically it prints out alternating 1s and 0s in a single column, then uses pr to wrap this column into multiple columns for output.
A year too late, but here you go:
$ yes "1 0" | fmt -10 |head -7

Advanced grep unix

Usually grep command is used to display the line contaning the specified pattern. Is there any way to display n lines before and after the line which contains the specified pattern?
Can this will be achieved using awk?
Yes, use
grep -B num1 -A num2
to include num1 lines of context before the match, and num2 lines of context after the match.
EDIT:
Seems the OP is using AIX. This has a different set of options which doesn't include -B and -A
this link describes grep on AIX 4.3 (it doesn't look promising)
Matt's perl script might be a better solution.
Here is what I usually do on AIX:
before=2 << The number of lines to be shown Before >>
after=2 << The number of lines to be shown After >>
grep -n <pattern> <filename> | cut -d':' -f1 | xargs -n1 -I % awk "NR<=%+$after && NR>=%-$before" <filename>
If you do not want the extra 2 varialbles you can always use it an a one line:
grep -n <pattern> <filename> | cut -d':' -f1 | xargs -n1 -I % awk 'NR<=%+<<after>> && NR>=%-<<before>>' <filename>
Suppose I have a pattern 'stack' and the filename is flow.txt
I want 2 lines before and 3 lines after. The the command will be like:
grep -n 'stack' flow.txt | cut -d':' -f1 | xargs -n1 -I % awk 'NR<=%+3 && NR>=%-2' flow.txt
I want 2 lines before and only - the the command will be like:
grep -n 'stack' flow.txt | cut -d':' -f1 | xargs -n1 -I % awk 'NR<=% && NR>=%-2' flow.txt
I want 3 lines after and only - the the command will be like:
grep -n 'stack' flow.txt | cut -d':' -f1 | xargs -n1 -I % awk 'NR<=%+3 && NR>=%' flow.txt
Multiple Files - change it for Awk & grep. From above for the pattern 'stack' with the filename is flow.* - 2 lines before and 3 lines after. The the command will be like:
awk 'BEGIN {
before=1; after=3; pattern="stack";
i=0; hold[before]=""; afterprints=0}
{
#Print the lines from the previous Match
if (afterprints > 0)
{
print FILENAME ":" FNR ":" $0
afterprints-- #keep a track of the lines to print after - this can be reset if a match is found
if (afterprints == 0) print "---"
}
#Look for the pattern in current line
if ( match($0, pattern) > 0 )
{
# print the lines in the hold round robin buffer from the current line to line-1
# if (before >0) => user wants lines before avoid divide by 0 in %
# and afterprints => 0 - we have not printed the line already
for(j=i; j < i+before && before > 0 && afterprints == 0 ; j++)
print hold[j%before]
if (afterprints == 0) # print the line if we have not printed the line already
print FILENAME ":" FNR ":" $0
afterprints=after
}
if (before > 0) # Store the lines in the round robin hold buffer
{ hold[i]=FILENAME ":" FNR ":" $0
i=(i+1)%before }
}' flow.*
From the tags, it's likely that the system has a grep that may not support providing context (Solaris is one system that doesn't and I can't remember about AIX). If that is the case, there's a perl script that may help at http://www.sun.com/bigadmin/jsp/descFile.jsp?url=descAll/cgrep__context_grep.
If you have sed you could use this shell script
BEFORE=2
AFTER=3
FILE=file.txt
PATTERN=pattern
for i in $(grep -n $PATTERN $FILE | sed -e 's/\:.*//')
do head -n $(($AFTER+$i)) $FILE | tail -n $(($AFTER+$BEFORE+1))
done
What it does is, grep -n prefixes each match with the line it was found at, the sed strips all but the line it was found at. Then you use head to get the lines up to the line it was found on plus an additional $AFTER lines. That's then piped to tail to just get $BEFORE + $AFTER + 1 lines (that is, your matching line plus the number of lines before and after)
Sure there is (from the grep man page):
-B NUM, --before-context=NUM
Print NUM lines of leading context before matching lines.
Places a line containing a group separator (--) between
contiguous groups of matches. With the -o or --only-matching
option, this has no effect and a warning is given.
-A NUM, --after-context=NUM
Print NUM lines of trailing context after matching lines.
Places a line containing a group separator (--) between
contiguous groups of matches. With the -o or --only-matching
option, this has no effect and a warning is given.
and if you want the same amount of lines before AND after the match, use:
-C NUM, -NUM, --context=NUM
Print NUM lines of output context. Places a line containing a
group separator (--) between contiguous groups of matches. With
the -o or --only-matching option, this has no effect and a
warning is given.
you can use awk
awk 'BEGIN{t=4}
c--&&c>=0
/pattern/{ c=t; for(i=NR;i<NR+t;i++)print a[i%t] }
{ a[NR%t]=$0}
' file
output
$ more file
1
2
3
4
5
pattern
6
7
8
9
10
11
$ ./shell.sh
2
3
4
5
6
7
8
9

Resources