Bash Extract first number of a three digit version - awk

I would like to extract number 10 from 10.3.0 for Makefile to add specific CFLAGS
Below code is printing only 1030
echo "gcc.exe (Rev5, Built by MSYS2 project) 10.3.0"|sed -r 's/.* ([0-9])/\1/g' | sed -r 's/\.//g'
1030
How to get the 10

A simple awk:
echo "gcc.exe (Rev5, Built by MSYS2 project) 10.3.0" | awk '{print int($NF)}'
10
Or if you must use sed only then:
echo "gcc.exe (Rev5, Built by MSYS2 project) 10.3.0" |
sed -E 's/.* ([0-9]+).*/\1/'
10

Just a tiny tweak to your own solution would do:
echo "gcc.exe (Rev5, Built by MSYS2 project) 10.3.0"|sed -r 's/.* ([0-9])/\1/g' | sed -r 's/\..*//g'
10
Actually the second sed is not needed here:
echo "gcc.exe (Rev5, Built by MSYS2 project) 10.3.0"|sed -r 's/.* ([0-9]+).*/\1/g'
10
What happened is that you replaced things before 10 but not after it, which can be easily fixed.

This solution using the awk functions match() and substr():
echo 'gcc.exe (Rev5, Built by MSYS2 project) 10.3.0' | awk 'match($0, /[[:digit:]]+\./) {print substr($0,RSTART,RLENGTH-1)}'
10

You code using sed gives 1030 as a result because s/.* ([0-9])/\1/g will leave 10.3.0 and then s/\.//g will remove all the dots leaving 1030.
You could match the format ^[0-9]+\.[0-9]+\.[0-9]+$ of the last field $NF, and if it matches split on a dot and print the first part.
echo "gcc.exe (Rev5, Built by MSYS2 project) 10.3.0" | awk 'match($NF, /^[0-9]+\.[0-9]+\.[0-9]+$/) {
split($NF,a,"."); print a[1]
}
'
Output
10

With shown samples, you could try following. Simple explanation would be, make . and ) as field separators and print 3rd field if NF is greater than 2 for that line, to get required output as per shown samples.
echo "gcc.exe (Rev5, Built by MSYS2 project) 10" |
awk -F'\\.|\\) ' 'NF>=2{print $3}'

Related

Running an awk command with $SHELL -c returns different results

I am trying to use awk to print the unique lines returned by a command. For simplicity, assume the command is ls -alh.
If I run the following command in my Z shell, awk shows all lines printed by ls -alh
ls -alh | awk '!seen[$0]++'
However, if I run the same command with $SHELL -c while escaping the ! with backslash, I only see the first line of the output printed.
$SHELL -c "ls -alh | awk '\!seen[$0]++'"
How can I ensure the latter command prints the exact same outputs as the former?
EDIT 1:
I initially thought the ! could be the issue. But changing the expression '!seen[$0]++' to 'seen[$0]++==0' has the same problem.
EDIT 2:
It looks like I should have escaped $ too. Since I do not know the reason behind it, I will not post an answer.
In the second form, $0 is being treated as a shell variable in the double-quoted string. The substitution creates an interestingly mangled awk command:
> print $SHELL -c "ls -alh | awk '\!seen[$0]++'"
/bin/zsh -c ls -alh | awk '!seen[-zsh]++'
The variable is not substituted in the first form since it is inside single quotes.
This answer discusses how single- and double-quoted strings are treated in bash and zsh:
Difference between single and double quotes in Bash
Escaping the $ so that $0 is passed to awk should work, but note that quoting in commands that are parsed multiple times can get really tricky.
> print $SHELL -c "ls -alh | awk '\!seen[\$0]++'"
/bin/zsh -c ls -alh | awk '!seen[$0]++'

Adding an empty line between first and second line in a text file

A file (foo.csv) contain entries (four columns) as follows:
A 5.3 3.2 1.2
A 2.1 3.4 6.7
A 3.4 2.1 5.6
A 0.4 2.2 4.2
In this file, I want to add the total number of lines in the first line followed by an empty line.
I want the output to be as follow.
4
A 5.3 3.2 1.2
A 2.1 3.4 6.7
A 3.4 2.1 5.6
A 0.4 2.2 4.2
Here is what I tried.
#to get the total number of lines in the file foo.csv
t=$((wc -l foo.csv | cut -d" " -f1))
#to add an empty line
sed -i "1i\\" foo.csv
#to insert the total number at the top; this works fine.
sed -i "1i $t" foo.csv
I need to do this for a bunch of files. So, script will be useful. The problem seems to be in sed -i "1i\\" foo.csv. How to correct this?
do the line counting with awk as well.
$ awk 'NR==FNR{next} FNR==1{print NR-1 ORS}1' file{,}
or, with tac...tac
$ tac file | awk '1; END{print ORS NR}' | tac
If you are ok with awk could you please try following.
awk -v line=$(wc -l < Input_file) 'FNR==1{print line ORS} 1' Input_file
In case you want to add output into Input_file itself then append > temp_file && mv temp_file Input_file to above code then.
Explanation: Adding explanation for above code too now.
awk -v line=$(wc -l < Input_file ) ' ##Creating variable line whose value is bash command wc -l to get line count for Input_file as per OP request.
FNR==1{ ##Checking if line number is 1 here then do following.
print line ORS ##Printing variable line here with ORS whose value is new line here.
} ##Closing FNR block here.
1 ##awk works on method of pattern and action mentioning 1 making condition TRUE and no action will make print to happen.
' Input_file ##Mentioning Input_file name here.
You can do it quite simply using sed with the 0,addr2 form (see man sed under "Addresses") with general substitution, e.g.
$ sed '0,/^/s/^/4\n\n/' file
4
A 5.3 3.2 1.2
A 2.1 3.4 6.7
A 3.4 2.1 5.6
A 0.4 2.2 4.2
The sed expression simply finds the first occurrence of the beginning of the line 0,/^/ and then substitutes the beginning of the line with 4\n\n, using s/^/4\n\n/
Add the -i option to edit-in-place (or -i.bak to create a back of the original (e.g. file.bak) while editing in place.
If you are interested in setting the number of lines, then you can simply get the lines with wc -l using command substitution, e.g.
$ sed "0,/^/s/^/$(wc -l <file2)\n\n/" file2
8
A 5.3 3.2 1.2
A 2.1 3.4 6.7
A 3.4 2.1 5.6
A 0.4 2.2 4.2
A 5.3 3.2 1.2
A 2.1 3.4 6.7
A 3.4 2.1 5.6
A 0.4 2.2 4.2
(note: the use of double-quotes instead of single-quotes to allow expansion of the command substitution)
This might work for you (GNU sed):
sed -e '1e wc -l <file' -e '1H;1g' file
or to do everything in sed:
sed -e '1e sed "$=;d" file' -e '1H;1g' file
This uses the e command to evaluate unix commands. Normally this is done using the e flag to the s command, but it can be used following an address, as in this situation.
An alternative, using a pipe:
wc -l <file | sed '1G' - file
or:
sed '$=;d' file | sed '1G' - file
Use the result of a wc or sed command as the first input file.
On retrospect, the easiest solution (although not the most efficient):
sed 'H;$!d;=;x' file
Which slurps the file into the hold space and inserts the number of lines and a blank line before printing out the hold space.

"awk" Command Behaves Differently On SuSE 11 vs. Solaris 10

Friends,
I'm trying to extract the last part of following path in a ksh script:
TOOL_HOME=/export/fapps/mytool/mytool-V2-3-4
I want to extract the version # (i.e., 2-3-4) from the above.
awk runs fine on SuSE:
echo $TOOL_HOME | awk -F'mytool-V' '{print $2}'
#2-3-4
However, on Solaris 10, it produces the following:
#ytool
So on Solaris, awk is ignoring everything after the first character in -F'mytool-V'
What should i do to get the same output on both OS's?
On Solaris use /usr/xpg4/bin/awk, not /bin/awk (aka "old, broken awk").
Solaris awk is broken...
$ echo "$TOOL_HOME" | awk '{sub(/.*mytool-V/,"")}1'
2-3-4
or simply with sed
$ echo "$TOOL_HOME" | sed 's/.*mytool-V//'
2-3-4
No need to use awk or any other external program. ksh can do that:
echo ${TOOL_HOME##*mytool-V}

Grep / awk, match exact string

I need to find the ID of some container docker, but some containers have similar names:
$ docker images
REPOSITORY TAG IMAGE ID
app-node latest 620350b79c5a
app-node-temp latest 461c5143a985
If I run:
$ docker images | grep -w app-node-temp | awk -e '{print $3}'
461c5143a985
If I run instead:
$ docker images | grep -w app-node | awk -e '{print $3}'
620350b79c5a
461c5143a985
How can I match the exact name?
I'd say just use awk with exact string matching:
docker images | awk '$1 == "app-node" { print $3 }'
Dashes are considered non-word characters, so grep -w won't work when the difference is marked by a dash.
In context, grep '^app-node[[:space:]]' would work. It looks for the required name followed by a space.
Of course, grep | awk is an anti-pattern most of the time; it would be better to use:
docker images | awk '/^app-node[[:space:]]/ { print $3 }'
Or, an easier solution with awk again uses equality — as suggested by Tom Fenech in his answer:
for server in app-node app-node-temp
do
docker images | awk -v server="$server" '$1 == server { print $3 }'
…
done
If running docker images is too expensive, you can run it once and capture the output in a file and then scan the file. This shows how to pass a shell variable into the awk script.
The chances are the pipeline would be run to capture the container's image ID information:
image_id=$(docker images | awk -v server="$server" '$1 == server { print $3 }')
docker images -q is good for your case

How to put this command in a Makefile?

I have the following command I want to execute in a Makefile but I'm not sure how.
The command is docker rmi -f $(docker images | grep "<none>" | awk "{print \$3}")
The command executed between $(..) should produce output which is fed to docker rmi but this is not working from within the Makefile I think that's because the $ is used specially in the Makefile but I'm not sure how to modify the command to fit in there.
Any ideas?
$ in Makefiles needs to be doubled to prevent substitution by make:
docker rmi -f $$(docker images | grep "<none>" | awk "{print \$$3}")
Also, it'd be simpler to use use a singly-quoted string in the awk command to prevent expansion of $3 by the shell:
docker rmi -f $$(docker images | grep "<none>" | awk '{print $$3}')
I really recommend the latter. It's usually better to have awk code in single quotes because it tends to contain a lot of $s, and all the backslashes hurt readability.