How run AWK command in Shell Script? - shell

I am trying to create files using AWK with in a for loop but getting empty file. It looks like AWK is not getting executed. Can you please advise, what is incorrect here?
I have a file $LOG/order_"$id".txt which has some lines between 2 strings CUT:1 and CUT2 etc.. I just need these lines in separate files so I wrote this for loop.
for (( i=1; i<=$CNTLN; i++ ))
do
j=`expr $i - 1`
/usr/bin/awk '/CUT:$j/, /CUT:$i/' $LOG/order_"$id".txt > $LOG/order_"$id"_"$i".txt
done
This generate blank file however if I copy and past this command on shell, it works.
Please advise.

Your quoting is correct, however you should use variable passing instead of trying to embed shell variables within the AWK script.
for (( i=1; i<=CNTLN; i++ ))
do
(( j = i - 1 ))
/usr/bin/awk -v i="$i" -v j="$j" '$0 ~ "CUT:" j, $0 ~ "CUT:" i' "$LOG/order_$id.txt" > "$LOG/order_${id}_$i.txt"
done
When using AWK variables, you have to use the tilde match operator instead of the slash-delimited form.
You don't say which shell you're using, but based on the for statement, I'd guess Bash, Korn or Z shell. If it's one of those, then you can do integer arithmetic as I've shown without using the external expr utility. In the case of ksh or zsh, you can do float math, too.
You could eliminate the line that calculates j and include it in the for statement.
for (( i=1, j=0, i<=CNTLN; i++, j++ ))
In a couple of places you use all-caps variable names. I recommend making a habit of not doing this in order to avoid name collisions with shell or environment variables. It's not an issue in this specific case, but it's a good habit to be in.
Actually, now that I think about it. The shell loop could be eliminated and the whole thing written in AWK.

Related

Shell Script error: command not found

Here is my code aiming to diff .in and .out file in batches. Both .in and .out files are in same directory and sorted by name. So, two files needed to test should be adjacent in directory. But when I want to use outFile=${arr[$(i++)]} to get the .out file, it shows i++: command not found. What's the error in my script?
#!/bin/sh
dir=$PATH
arr=($dir/*)
for((i=0;i<${#arr[#]};i++));do
inFile=${arr[$i]}
outFile=${arr[$(i++)]}
if diff $inFile $outFile > /dev/null; then
echo Same
else
echo $inFile
fi
done
Use $(( i++ )). That's two (not one) parenthesis.
$( ) runs shell commands.
$(( )) evaluates arithmetic expressions.
Also:
Your script uses bash features (arrays), it's best to use #!/bin/bash to avoid confusion.
I'm not sure what you expect dir=$PATH to do? $PATH is a special environment variable that is used for the lookup of commands. This is probably not what you want, if I understand the intent of the script correctly.
i++ will increment the value after being used; so here inFile and outFile are in fact the same! You probably want to use ++i (which will modify the variable and then use it), or just i + 1 (which will not modify the variable).
The stuff in brackets is already evaluated in arithmetic context, like within $(( ... )). So you can do:
for (( i=0; i < ${#arr[#]}; ));do
inFile=${arr[i++]}
outFile=${arr[i++]}
References:
https://www.gnu.org/software/bash/manual/bashref.html#Arrays
The subscript is treated as an arithmetic expression ...
https://www.gnu.org/software/bash/manual/bashref.html#Shell-Arithmetic
Within an expression, shell variables may also be referenced by name without using the parameter expansion syntax.

Tricky bash to try to run program with different params

I want to run a program multiple times with different parameters, and then put the results piped into files that use parameters in their names. Here is what I've come up with:
#!/bin/bash
for i in 'seq 1 5';
do
for j in 'seq 1 8';
do
for m in 'seq 1 8';
do
./program -s i -v j -k m ../input_files/input_file1.txt < results_ijm.txt
done
done
done
This doesn't work. It says "no file results_ijm.txt".... I know that - I want it to create this file implicitly.
Otherwise, I also doubt it will assign ijm in the filename correctly - how does it know whether I want the VARIABLES ijm.... or just the characters? It's ambiguous.
You must use variable $i, $j, $m etc.
Better to use ((...)) construct in BASH.
In BASH you can do:
#!/bin/bash
for ((i=1; i<=5; i++)); do
for ((j=1; h<=8; j++)); do
for ((m=1; m<=8; m++)); do
./program -s $i -v $j -k $m ../input_files/input_file1.txt > "results_${i}${j}${m}.txt"
done
done
done
Two problems. As I mentioned in the comments, your arrow is backwards. We want the results of the program to go from stdout to the file so flip that thing around. Second, variables when used gain a dollar sign in front of them... so it won't be ambiguous.
Edited to add: Third thing, use backticks instead of single quotes for seq 1 5 You want the results of that command, not the text "seq 1 5". Thanks #PSkocik
#!/bin/bash
for i in `seq 1 5`;
do
for j in `seq 1 8`;
do
for m in `seq 1 8`;
do
./program -s $i -v $j -k $m ../input_files/input_file1.txt > results_${i}${j}${m}.txt
done
done
done
./program -s i -v j -k m ../input_files/input_file1.txt < results_ijm.txt
I believe the less than symbol should be flipped to greater than, so as to input to file, instead of from file. I've not worked too much with bash, but it seems logical.

For loop with an argument based range

I want to run certain actions on a group of lexicographically named files (01-09 before 10). I have to use a rather old version of FreeBSD (7.3), so I can't use yummies like echo {01..30} or seq -w 1 30.
The only working solution I found is printf "%02d " {1..30}. However, I can't figure out why can't I use $1 and $2 instead of 1 and 30. When I run my script (bash ~/myscript.sh 1 30) printf says {1..30}: invalid number
AFAIK, variables in bash are typeless, so how can't printf accept an integer argument as an integer?
Bash supports C-style for loops:
s=1
e=30
for i in ((i=s; i<e; i++)); do printf "%02d " "$i"; done
The syntax you attempted doesn't work because brace expansion happens before parameter expansion, so when the shell tries to expand {$1..$2}, it's still literally {$1..$2}, not {1..30}.
The answer given by #Kent works because eval goes back to the beginning of the parsing process. I tend to suggest avoiding making habitual use of it, as eval can introduce hard-to-recognize bugs -- if your command were whitelisted to be run by sudo and $1 were, say, '$(rm -rf /; echo 1)', the C-style-for-loop example would safely fail, and the eval example... not so much.
Granted, 95% of the scripts you write may not be accessible to folks executing privilege escalation attacks, but the remaining 5% can really ruin one's day; following good practices 100% of the time avoids being in sloppy habits.
Thus, if one really wants to pass a range of numbers to a single command, the safe thing is to collect them in an array:
a=( )
for i in ((i=s; i<e; i++)); do a+=( "$i" ); done
printf "%02d " "${a[#]}"
I guess you are looking for this trick:
#!/bin/bash
s=1
e=30
printf "%02d " $(eval echo {$s..$e})
Ok, I finally got it!
#!/bin/bash
#BSD-only iteration method
#for day in `jot $1 $2`
for ((day=$1; day<$2; day++))
do
echo $(printf %02d $day)
done
I initially wanted to use the cycle iterator as a "day" in file names, but now I see that in my exact case it's easier to iterate through normal numbers (1,2,3 etc.) and process them into lexicographical ones inside the loop. While using jot, remember that $1 is the numbers amount, and the $2 is the starting point.

What is the range of typeset -i variable

Below is my script
#!/bin/sh
typeset resl=$(($1+$2))
echo $resl
when i am passing two value 173591451 and 2000252844 to shell script, it is returning negative value.
./addvalue.sh 173591451 2000252844
output ---> -2121123001
Please let me know how we can fix this problem?
Dropping into a friendly programming calculator application to look at your values in hex I see you are into 32-bits of precision. Once you hit 32-bits (8'th digit >= 8) you have exceeded the size of integer your shell was compiled with and entered the land of negative numbers (but that's another post).
0x81923B47 = 0xA58CB9B + 0x77396FAC
Two workarounds, without having to worry about getting a 64-bit shell, follow.
1. awk
The success of this depends on how your awk as compiled and which awk you are using.
awk 'END {print 173591451 + 2000252844}' </dev/null
Also do all your relational testing in awk.
2. dc
The "dc" program (desk calculator) uses arbitrary precision so you never need to worry about integer bit-size again. To put it into a variable:
$ sum="$( echo 173591451 2000252844 + p | dc )"; echo $sum
2173844295
And avoid typeset -i with dc as the shell needs to see strings. Properly checking relationships (if $a < $b) gets a little tricky, but can be done ($a -lt $b is wrong).

print a variable using another

I have my script setup like this, i want to print switch names using loop.
swn1="something"
swn2="somethingelse"
for (( i=1; i<="$ii"; i++ ))
do
echo "$swn$i "
done
I have searched and searched but no luck, can anyone help me how to print swn using for loop ?
Sounds like you want an array...
swn=("something" "something else")
for i in "${swn[#]}"
do
echo $i
done
The solution for that is variable indirection.
swn1="something"
swn2="somethingelse"
for (( i=1; i<="$ii"; i++ ))
do
var="swn$i"
echo "${!var}"
done
Although normally you could solve your problem with arrays, but the way to print a variable using another is through it.
As explained in the bash manual:
If the first character of parameter is an exclamation point (!), a
level of variable indirection is introduced. Bash uses the value of
the variable formed from the rest of parameter as the name of the
variable; this variable is then expanded and that value is used in the
rest of the substitution, rather than the value of parameter itself.
This is known as indirect expansion. The exceptions to this are the
expansions of ${!prefix} and ${!name[#]}. The
exclamation point must immediately follow the left brace in order to
introduce indirection.
You can't do this. swn1 is one token, swn2 is another. It looks like you want an array, and if you want an array in a bash script you either want to process the values as separate lines in a file, or switch to using Perl.
Separate lines:
echo "something" > swnfile;
echo "somethingelse" >> swnfile;
cat file | while read line; do
echo "$line";
done

Resources