Meaning of this shell script line with awk - bash

I read this line of script in book [linux device drivers]. What does it do?
major=$(awk "\\$2= =\"$module\" {print \\$1}" /proc/devices)
as in context:
#!/bin/sh
module="scull"
device="scull"
mode="664"
# invoke insmod with all arguments we got
# and use a pathname, as newer modutils don't look in . by default
/sbin/insmod ./$module.ko $* || exit 1
# remove stale nodes
rm -f /dev/${device}[0-3]
major=$(awk "\\$2= =\"$module\" {print \\$1}" /proc/devices)
mknod /dev/${device}0 c $major 0
....

A better way to write this would be :
major=$(awk -v mod=$module '$2==mod{print $1}' /proc/devices)

I read this too but that line was not working for me. I had to modify it to
major=$(awk "\$2 == \"$module\" {print \$1}" /proc/devices)
The first part \$2 == \"$module\" is the pattern. When this is satisfied, that is, the second column is equal to "scull", the command print \$1 is executed which prints the first column. This value is stored in the variable major.
The $ needs to be escaped as they need to be passed as it is to awk.

/proc/devices contains the currently configured character and block devices for each module.
Expanding a few variables in your context, and fixing the syntax error in the equality, the command looks like this:
awk '$2=="scull" {print $1}' /proc/devices
This means "if the value of the second column is scull, then output the first column."
This command is run in a subshell — $(...) — and the output is assigned to the variable $major.
The explanation of the purpose is in the book:
The script to load a module that has been assigned a dynamic number can, therefore, be written using a tool such as awk to retrieve information from /proc/devices in order to create the files in /dev.
Note that in the distributed examples, the line in scull_load matches Vivek's correction.

Related

Parameter expansion not working when used inside Awk on one of the column entries

System: Linux. Bash 4.
I have the following file, which will be read into a script as a variable:
/path/sample_A.bam A 1
/path/sample_B.bam B 1
/path/sample_C1.bam C 1
/path/sample_C2.bam C 2
I want to append "_string" at the end of the filename of the first column, but before the extension (.bam). It's a bit trickier because of containing the path at the beginning of the name.
Desired output:
/path/sample_A_string.bam A 1
/path/sample_B_string.bam B 1
/path/sample_C1_string.bam C 1
/path/sample_C2_string.bam C 2
My attempt:
I did the following script (I ran: bash script.sh):
List=${1};
awk -F'\t' -vOFS='\t' '{ $1 = "${1%.bam}" "_string.bam" }1' < ${List} ;
And its output was:
${1%.bam}_string.bam
${1%.bam}_string.bam
${1%.bam}_string.bam
${1%.bam}_string.bam
Problem:
I followed the idea of using awk for this substitution as in this thread https://unix.stackexchange.com/questions/148114/how-to-add-words-to-an-existing-column , but the parameter expansion of ${1%.bam} it's clearly not being recognised by AWK as I intend. Does someone know the correct syntax for that part of code? That part was meant to mean "all the first entry of the first column, except the last part of .bam". I used ${1%.bam} because it works in Bash, but AWK it's another language and probably this differs. Thank you!
Note that the paramter expansion you applied on $1 won't apply inside awk as the entire command
body of the awk command is passed in '..' which sends content literally without applying any
shell parsing. Hence the string "${1%.bam}" is passed as-is to the first column.
You can do this completely in Awk
awk -F'\t' 'BEGIN { OFS = FS }{ n=split($1, arr, "."); $1 = arr[1]"_string."arr[2] }1' file
The code basically splits the content of $1 with delimiter . into an array arr in the context of Awk. So the part of the string upto the first . is stored in arr[1] and the subsequent split fields are stored in the next array indices. We re-construct the filename of your choice by concatenating the array entries with the _string in the filename part without extension.
If I understood your requirement correctly, could you please try following.
val="_string"
awk -v value="$val" '{sub(".bam",value"&")} 1' Input_file
Brief explanation: -v value means passing shell variable named val value to awk variable variable here. Then using sub function of awk to substitute string .bam with string value along with .bam value which is denoted by & too. Then mentioning 1 means print edited/non-edtied line.
Why OP's attempt didn't work: Dear, OP. in awk we can't pass variables of shell directly without mentioning them in awk language. So what you are trying will NOT take it as an awk variable rather than it will take it as a string and printing it as it is. I have mentioned in my explanation above how to define shell variables in awk too.
NOTE: In case you have multiple occurences of .bam then please change sub to gsub in above code. Also in case your Input_file is TAB delmited then use awk -F'\t' in above code.
sed -i 's/\.bam/_string\.bam/g' myfile.txt
It's a single line with sed. Just replace the .bam with _string.bam
You can try this way with awk :
awk -v a='_string' 'BEGIN{FS=OFS="."}{$1=$1 a}1' infile

Change specific line of text using variable line number in bash

I'm working on a project for a home server i'm running where I need to change the text on a specific line in a configuration file. The line i need to change has a unique attribute name with varying values however sometimes new lines will be added to the file causing the position of the line I want to change to move from time to time.
To write and test the script i picked a random config file on my pc which looks like this
# cat kvirc4.ini
# KVIrc configuration file
[Main]
LocalKvircDirectory=C:\Users\Aulus\AppData\Roaming\KVIrc4\
SourcesDate=538186288
I wrote a script to change the content on the 3rd line to "changed". If I write the script like this specifying the line number directly in the script it works as expected wit the output file showing what was in the source file with that one line changed.
# cat bashscript.sh
#!/bin/bash
awk '{ if (NR == 3) print "different"; else print $0}' kvirc4.ini.bak > output
However once I add a command to find the line number and save that to a variable which I use in awk to specify the line script no longer makes the change to that line leaving the original version of the file. I added the echo you see below to confirm my variable has the correct value and it does. It seems to me that the problem is likley that my awk command is not correctly reading the line number causing the if statement to remain false but i'm still a bit new to bash and awk so i'm not sure how to fix it. Can anyone offer advice that will help me find a solution?
# cat bashscript.sh
#!/bin/bash
lineno=$(awk '/LocalKvircDirectory/{ print NR; exit }' kvirc4.ini.bak);lineno=$(printf '%b' $lineno)
echo $lineno
# awk 'NR==$lineno {$0="changed"} { print }' kvirc4.ini.bak
awk '{ if (NR == "$lineno") print "different"; else print $0}' kvirc4.ini.bak > output
You are using bash parameter expansion style
("$lineno") in awk code, which have nothing to do.
Your "$lineno" is never expanded because it is single quoted.
You have several options:
You can use different quoting to make the "$lineno" work:
awk '{ if (NR == '"$lineno"') print ···
Use awk variable initialization in the command line
awk -v lineno="$lineno" '{ if (NR==lineno) print ···
Get the value of a bash variable which has been exported (i.e., in the environment) using the ENVIRON awk array :
export lineno
awk '{ if (NR==ENVIRON["lineno"]) print ···

sed or awk for matching a pattern with a variable from a previous script?

In continuation of this:
Matching a pattern with sed and getting an integer out at the same time
Having gotten the number out of a pattern in an xml file, I have a var_number=some number ,
var_number=6 in this case.
In the same xml file, developers ought to enter the following line:
NewC_server_list=C_SME_DEV6,C_WEB_DEV6
I need to verify that the pattern after the = is C_SME_DEV${var_number},C_WEB_DEV${var_number}
Meaning two things:
That the guys entered the server name correctly: C_WEB_DEV and C_SME_DEV,
And that the number is the same as var_number (in this case, 6)
sed or awk? My concerns are: shorter script and speed (cpu allocation is lowsy on this machine)
Adding to my previous answer to OP's previous question linked to the question above. Test file:
$ cat file
#Env=DEV2,DEV3,DEV5,DEV6
#Enter your required DEV environment after the ENV= in the next line.
Env=DEV6
NewC_server_list=C_SME_DEV6,C_WEB_DEV6
Solution:
$ awk 'sub(/^Env=DEV/,"") && /^[1-9][0-9]?$/ {a=$0;b="NewC_server_list=C_SME_DEV" $0 ",C_WEB_DEV" $0} $0==b {print a}' file
6
Due to lack of better knowledge it only supports 1 match per file assuming the new requirement data exists after the old.
Assuming your number from the earlier command is stored in a variable, myVar, you can do something like this in perl.
Also you can directly run the commands from command-line.
#!/bin/bash
myVar=$(perl -nle 'print $1 if /ENV=DEV.*?(\d+)/' file) # stored from your previous question.
# myVar=6 (The value from your previous question)
if (( myVar == $(perl -nle 'print $1 if /.*=C_SME_DEV.*?(\d+)/' file) )) && \
(( myVar == $(perl -nle 'print $1 if /.*,C_WEB_DEV.*?(\d+)/' file) )); then
echo "Match found"
fi

Bash Script: Grabbing First Item Per Line, Throwing Into Array

I'm fairly new to the world of writing Bash scripts and am needing some guidance. I've begun writing a script for work, and so far so good. However, I'm now at a part that needs to collect database names. The names are actually stored in a file, and I can grep them.
The command I was given is cat /etc/oratab which produces something like this:
# This file is used by ORACLE utilities. It is created by root.sh
# and updated by the Database Configuration Assistant when creating
# a database.
# A colon, ':', is used as the field terminator. A new line terminates
# the entry. Lines beginning with a pound sign, '#', are comments.
#
# The first and second fields are the system identifier and home
# directory of the database respectively. The third filed indicates
# to the dbstart utility that the database should , "Y", or should not,
# "N", be brought up at system boot time.
#
OEM:/software/oracle/agent/agent12c/core/12.1.0.3.0:N
*:/software/oracle/agent/agent11g:N
dev068:/software/oracle/ora-10.02.00.04.11:Y
dev299:/software/oracle/ora-10.02.00.04.11:Y
xtst036:/software/oracle/ora-10.02.00.04.11:Y
xtst161:/software/oracle/ora-10.02.00.04.11:Y
dev360:/software/oracle/ora-11.02.00.04.02:Y
dev361:/software/oracle/ora-11.02.00.04.02:Y
xtst215:/software/oracle/ora-11.02.00.04.02:Y
xtst216:/software/oracle/ora-11.02.00.04.02:Y
dev298:/software/oracle/ora-11.02.00.04.03:Y
xtst160:/software/oracle/ora-11.02.00.04.03:Y
I turn turned around and wrote grep ":/software/oracle/ora" /etc/oratab so it can grab everything I need, which is 10 databases. Not the most elegant way, but it gets what I need:
dev068:/software/oracle/ora-10.02.00.04.11:Y
dev299:/software/oracle/ora-10.02.00.04.11:Y
xtst036:/software/oracle/ora-10.02.00.04.11:Y
xtst161:/software/oracle/ora-10.02.00.04.11:Y
dev360:/software/oracle/ora-11.02.00.04.02:Y
dev361:/software/oracle/ora-11.02.00.04.02:Y
xtst215:/software/oracle/ora-11.02.00.04.02:Y
xtst216:/software/oracle/ora-11.02.00.04.02:Y
dev298:/software/oracle/ora-11.02.00.04.03:Y
xtst160:/software/oracle/ora-11.02.00.04.03:Y
So, if I want to grab the name, such as dev068 or xtst161, how do I? I think for what I need to do with this project moving forward, is storing them in an array. As mentioned in the documentation, a colon is the field terminator. How could I whip this together so I have an array, something like:
dev068
dev299
xtst036
xtst161
dev360
dev361
xtst215
xtst216
dev298
xtst160
I feel like I may be asking for too much assistance here but I'm truly at a loss. I would be happy to clarify if need be.
It is much simpler using awk:
awk -F: -v key='/software/oracle/ora' '$2 ~ key{print $1}' /etc/oratab
dev068
dev299
xtst036
xtst161
dev360
dev361
xtst215
xtst216
dev298
xtst160
To populate a BASH array with above output use:
mapfile -t arr < <(awk -F: -v key='/software/oracle/ora' '$2 ~ key{print $1}' /etc/oratab)
To check output:
declare -p arr
declare -a arr='([0]="dev068" [1]="dev299" [2]="xtst036" [3]="xtst161" [4]="dev360" [5]="dev361" [6]="xtst215" [7]="xtst216" [8]="dev298" [9]="xtst160")'
We can pipe the output of grep to the cut utility to extract the first field, taking colon as the field separator.
Then, assuming there are no whitespace or glob characters in any of the names (which would be subject to word splitting and filename expansion), we can use a command substitution to run the pipeline, and capture the output in an array by assigning it within the parentheses.
names=($(grep ':/software/oracle/ora' /etc/oratab| cut -d: -f1;));
Note that the above command actually makes use of word splitting on the command substitution output to split the names into separate elements of the resulting array. That is why we must be sure that no whitespace occurs within any single database name, otherwise that name would be internally split into separate elements of the array. The only characters within the command substitution output that we want to be taken as word splitting delimiters are the line feeds that delimit each line of output coming off the cut utility.
You could also use awk for this:
awk -F: '!/^#/ && $2 ~ /^\/software\/oracle\/ora-/ {print $1}' /etc/oratab
The first pattern excludes any commented-out lines (starting with a #). The second pattern looks for your expected directory pattern in the second field. If both conditions are met it prints the first field, which the Oracle SID. The -F: flag sets the field delimiter to a colon.
With your file that gets:
dev068
dev299
xtst036
xtst161
dev360
dev361
xtst215
xtst216
dev298
xtst160
Depending on what you're doing you could finesse it further and check the last flag is set to Y; although that is really to indicate automatic start-up, it can sometime be used to indicate that a database isn't active at all.
And you can put the results into an array with:
declare -a DBS=(`awk -F: -v key='/software/oracle/ora' '$2 ~ key{print $1}' /etc/oratab`)
and then refer to ${DBS[1]} (which evaluates to dev299) etc.
If you'd like them into a Bash array:
$ cat > toarr.bash
#!/bin/bash
while read -r line
do
if [[ $line =~ .*Y$ ]] # they seem to end in a "Y"
then
arr[$((i++))]=${line%%:*}
fi
done < file
echo ${arr[*]} # here we print the array arr
$ bash toarr.bash
dev068 dev299 xtst036 xtst161 dev360 dev361 xtst215 xtst216 dev298 xtst160

shell: write integer division result to a variable and print floating number

I'm trying to write a shell script and plan to calculate a simple division using two variables inside the script. I couldn't get it to work. It's some kind of syntax error.
Here is part of my code, named test.sh
awk '{a+=$5} END {print a}' $variable1 > casenum
awk '{a+=$5} END {print a}' $variable2 > controlnum
score=$(echo "scale=4; $casenum/$controlnum" | bc)
printf "%s\t%s\t%.4f\n", $variable3 $variable4 $score
It's just the $score that doesn't work.
I tried to use either
sh test.sh
or
bash test.sh
but neither worked. The error message is:
(standard_in) 1: syntax error
Does anyone know how to make it work? Thanks so much!
You are outputting to files, not to vars. For this, you need var=$(command). Hence, this should make it:
casenum=$(awk '{a+=$5} END {print a}' $variable1)
controlnum=$(awk '{a+=$5} END {print a}' $variable2)
score=$(echo "scale=4; $casenum/$controlnum" | bc)
printf "%s\t%s\t%.4f\n", $variable3 $variable4 $score
Note $variable1 and $variable2 should be file names. Otherwise, indicate it.
First your $variable1 and $variable2 must expand to a name of an existing file; but that's not a syntax error, it's just a fact that makes your code wrong, unless you mean really to cope with files containing numbers and accumulating the sum of the fifth field into a file. Since casenum and controlnum are not assigned (in fact you write the awk result to a file, not into a variable), your score computation expands to
score=$(echo "scale=4; /" | bc)
which is wrong (Syntax error comes from this).
Then, the same problem with $variable3 and $variable4. Are they holding a value? Have you assigned them with something like
variable=...
? Otherwise they will expand as "". Fixing these (including assigning casenum and controlnum), will fix everything, since basically the only syntax error is when bc tries to interpret the command / without operands. (And the comma after the printf is not needed).
The way you assign the output of execution of a command to a variable is
var=$(command)
or
var=`command`
If I understand your commands properly, you could combine calculation of score with a single awk statement as follows
score=$(awk 'NR==FNR {a+=$5; next} {b+=$5} END {printf "%.4f", a/b}' $variable1 $variable2)
This is with assumption that $variable1 and $variable2 are valid file names
Refer to #fedorqui's solution if you want to stick to your approach of 2 awk and 1 bc.

Resources