Using awk to set first character to lowercase UNIX - awk

I've written the following bash script that utilizes awk, the aim is to set the first character to lower case. The script works mostly fine, however I'm adding an extra space when I concat the two values. Any ideas how to remove this errant space?
Script:
#!/bin/bash
foo="MyCamelCaseValue"
awk '{s=tolower(substr($1,1,1))}{g=substr($1,2,length($1))}{print s,g}' <<<$foo
Output:
m yCamelCaseValue
edit:
Please see discussion from Bobdylan and RavinderSingh13 on accepted answer as it highlights issues with default MacOs bash version.
bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin19)
Copyright (C) 2007 Free Software Foundation, Inc.

You were close and good try, you need not get length of line here, substr is intelligent enough to get the rest of the length of you mention a character position and don't give till where it should print(length value). Could you please try following.
(Usually these kind of problems could be solved by bash itself but when OP tried bash solution provided by #bob dylan its having issues because of OLD version of BASH, hence I am undeleting this one which is working for OP)
echo "$foo" | awk '{print tolower(substr($0,1,1)) substr($0,2)}' Input_file
Explanation:
Use substr function of awk to get sub-strings in current line.
Then grab the very first letter by substr($0,1,1) and wrap it inside tolower to make it in small case.
Now print rest of the line(since first character is already being captures by previous substr) by doing `substr($0,2) this will print from 2nd character to last of line.
EDIT by #bob dylan:
https://www.shell-tips.com/mac/upgrade-bash/
MacOS comes with an older version of bash. However if you're on 4+ you should be able to use the native bash function to translate the first character from upper to lower:
$ bash --version
GNU bash, version 4.4.19(1)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ cat foo.sh
#!/bin/bash
foo="MyCamelCaseValue"
echo "${foo,}"
$ bash foo.sh
myCamelCaseValue
Further examples for the whole string, to lower, to upper etc.
$ echo $foo
myCamelCaseValue
echo "${foo,}"
myCamelCaseValue
$ echo "${foo,,}"
mycamelcasevalue
$ echo "${foo^}"
MyCamelCaseValue
$ echo "${foo^^}"
MYCAMELCASEVALUE

Related

execute a system() method in awk if a condition matches

I am trying to check if the gcc version and based on the result
execute another system call (scl enable devtoolset-7 bash).
Below is what I have tried so far
gcc --version | awk '/gcc/ && ($3+0)<7.0{print "Current Version",$3,"is less than 7.5" system("scl enable devtoolset-7 bash")}'
I wish to know how to get this done correctly in awk. Also, I am open to other simpler approaches to do the above, if any.
Don't complicate things by having awk spawn a subshell to call another command if you don't have to:
if [[ gcc --version | awk '/gcc/ && ($3+0)<7.0{print "Current Version",$3,"is less than 7.5"; f=1} END{exit !f}' >&2 ]]; then
scl enable devtoolset-7 bash
fi
Note that your test is <7.0 but message says <7.5. You might want to make it this instead to ensure consistency:
if [[ gcc --version | awk -v minVer='7.0' '/gcc/ && ($3+0)<(minVer+0){print "Current Version",$3,"is less than",minVer; f=1} END{exit !f}' >&2 ]]; then
scl enable devtoolset-7 bash
fi
In general software versions can't be directly compared as either strings or numbers, though, so you might have to think about your comparison. e.g. if you wanted to compare 1.2.3 vs 1.2.4 both would be truncated to 1.2 in a numeric comparison and declared equal, but if you wanted to compare 2 different versions like 12.3 vs 5.7, the 5.7 would be considered larger in a string comparison.

awk --bignum messes with floating point computations

Example input
42 -0.400000000000000022
I want to add 9'000'000'000'000'000'000 to the 1st column, and add 30 to the 2nd column.
$ echo 42 -0.400000000000000022 | awk '{ $1 += 9000000000000000000; $2 += 30 } { print }'
9000000000000000000 29.6
Computation for the 1st column is wrong, but the 2nd column is OK.
From the documentation and the man page, there's a --bignum option which should help me for the big integer computation.
$ echo 42 -0.400000000000000022 | awk --bignum '{ $1 += 9000000000000000000; $2 += 30 } { print }'
9000000000000000042 30
Now the 1st column is OK, but the 2nd one isn't!
Here's my AWK version, running on Ubuntu 16.04:
$ awk -V
GNU Awk 4.1.3, API: 1.1 (GNU MPFR 3.1.4, GNU MP 6.1.0)
Copyright (C) 1989, 1991-2015 Free Software Foundation.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/.
What's even weirder is that I tested this inside an Ubuntu 16.04 docker container, and the output is correct for both column when using --bignum.
I actually don't know what to look for to fix this.
I also recommend this syntax with big numbers.
Use LC_ALL=C:
$ echo 42 -0.400000000000000022 | LC_ALL=C awk --bignum '{ $1 += 9000000000000000000; $2 += 30 } { print }'
9000000000000000042 29.6
Successfully tested with GNU Awk 5.1.0, API: 3.0 (GNU MPFR 4.1.0-p13, GNU MP 6.2.0)

Script to display only comments from /etc/services file

I need to write a bash script that takes service name as a parameter and display only comment that is after hash symbol in /etc/services but I have no idea how to cut only the comment part.
The ,,it's working solution'' for me is to just:
grep "^$1" /etc/services | awk '{print $3,$4 ...
but I don't think this is a good one
I'm searching for something like:
[find the service] -> print only the part from # till the end of the line
I'm still learning so any solution with explanation or just a hint will be very helpful for me.
Chances are this is what you're looking for:
awk -v svc="$1" '($1==svc) && sub(/[^#]+#/,"")' /etc/services
but without sample input/output it's a guess.
The above will work using any awk in any shell on every Unix box.
Try this:
SERVICE_NAME=linuxconf; grep -Po "^$SERVICE_NAME.*# \K.*$" /etc/services
-P tells grep to use perl regex.
-o trims the output so that it only includes the regex match.
\K tells the regex engine to exclude previously matched part of the string from the match, i.e. only the part after \K will be present in the final match.

Understanding A Particular Line of Nawk Command

I am going through shell scripting online lessons as my work requires me to learn shell scripting.
I came across "awk" and "nawk " commands and my learning hasn't yet reached up to it.
In a nutshell, I know that awk/nawk search for a particular pattern and perform an action in case a match has been found.
Even despite of that, I couldn't understand what the following line is meant for:
eval $( cat ${MMORPHYDIR}/${PWFILE} | nawk ' /^#BEGIN '${ENV_NAME}'/,/^#END '${ENV_NAME}'/ { print }' | egrep "${USEFULL_PARAM}" )
Please help me to understand what this line does or is intended to do.
... awk '/start/,/end/'
prints the records between start and end patterns. {print} can be omitted since it's implied. The ^ in your script indicates beginning of a line.
Note that cat and eval are unnecessary, the same can be written as
$ awk '...' "${MMORPHYDIR}/${PWFILE}"
also grep can be included in the awk script as well.
awk is THE standard, general purpose tool for manipulating text on all UNIX-like systems. There are various flavors of awk, all with the same core functionality plus some differences. nawk is the very unfortunately named new awk because it was named that about 30 years ago as the successor to 1977s old awk (e.g. /bin/awk on Solaris, aka old, broken awk which must never be used) and is now actually best avoided as it doesn't even support the minimal awk functionality required by POSIX (e.g. character classes). Important lesson there: never use the word "new" in the name of any software entity!
The best awk to use these days is GNU awk, gawk, as it supports all POSIX functionality plus a ton of useful extensions, is generally available, is extremely well documented, and has a massive user base.
wrt:
eval $( cat ${MMORPHYDIR}/${PWFILE} | nawk ' /^#BEGIN '${ENV_NAME}'/,/^#END '${ENV_NAME}'/ { print }' | egrep "${USEFULL_PARAM}" )
That is a complete mess, doing literally about a dozen things that should never be done in shell or in awk. Trying to explain it would be like trying to explain someone mixing concrete with a screwdriver. Forget you ever saw it and move on.
To learn awk, read the book Effective Awk Programming, 4th Edition, by Arnold Robbins.

AWK: How to extract a word between 2 commas in a certain field

I have a file that looks like this:
name.com,168fcade-fc3d-425e-b67d-7176c80122f6,system,1034567,Red Hat Enterprise Linux Server, Standard (Physical or Virtual Nodes),
I need to extract the part between the 2 commas that start with Red Hat, or just Red so that my output is simply:
Red Hat Enterprise Linux Server
I have tried with this and come close but was not able to achieve it.
awk 'match($0,/,[^Red]*,/) {print substr($0,RSTART+1,RLENGTH-2)}' file
This prints the data between the commas in the field before the one I want.
1034567
I think I am missing something simple but I cant figure it out.
awk -F, '{print $5}' file
Also, note that when you wrote:
match($0,/,[^Red]*,/)
you were specifying a bracket expression ([...]) within which was a negated (^) character list (Red = R or e or d) repeated zero or more times. So the regexp is looking for any characters other than R, e, or d. It is NOT looking for anything related to the string "Red" which I assume is what you were trying to convey. I suspect you were trying to write this:
$ awk 'match($0,/,Red[^,]+,/) { print substr($0,RSTART+1,RLENGTH-2) }' file
Red Hat Enterprise Linux Server
If the position is not know, this also works...
$ awk -v RS=, '/^Red/' file
Red Hat Enterprise Linux Server