AWK NR Variable Syntax Issue - bash

I am new to AWK and trying to write some code where I can delete all files in a directory apart from the newest N number.
My code works if I use a hard coded number instead of a variable.
Works:
delete=`ls -t | awk 'NR>3'`
echo $delete
Does Not Work:
amount=3
delete=`ls -t | awk 'NR>$amount'`
echo $delete
I know the problem lies somewhere with the bash variable not being recognised as an awk variable however I do not know how to correct.
I have tried variations of code to fix this such as below, however I am still at a loss.
amount=3
delete=`ls -t | awk -v VAR=${amount} 'NR>VAR'`
echo $delete
Could you please advise what the correct code is ?

Shells don't expand anything inside single quotes.
Either:
amount=3
delete=$(ls -t | awk "NR>$amount")
or:
amount=3
delete=$(ls -t | awk -v amount=$amount 'NR > amount')
Be aware that parsing the output of ls is fraught if your file names are not limited to the portable file name character set. Spaces, newlines, and other special characters in the file name can wreck the parsing.

The simplest fix is to use double quotes instead of single. Single quotes prevent the shell from interpolating the variable $amount in the quoted string.
amount=3
ls -t | awk "NR>$amount"
I would not use a variable to capture the result. If you do use one, you need to quote it properly when interpolating it.
amount=3
delete=$(ls -t | awk -v VAR="$amount" 'NR>VAR')
echo "$delete"
Note that this is basically identical to your second attempt, which should have worked, modulo the string quoting issues.

Related

Extract specific string from line with standard grep,egrep or awk

i'm trying to extract a specific string from a grep output
uci show minidlna
produces a large list
.
.
.
minidlna.config.enabled='1'
minidlna.config.db_dir='/mnt/sda1/usb/db'
minidlna.config.enable_tivo='1'
minidlna.config.wide_links='1'
.
.
.
so i tried to narrow down what i wanted by running
uci show minidlna | grep -oE '\bdb_dir=\S+'
this narrows the output to
db_dir='/mnt/sda1/usb/db'
what i want is to output only
/mnt/sda1/usb/db
without the quotes and without the starting "db_dir" so i can run rm /mnt/sda1/usb/db/file.db
i've used the answers found here
How to extract string following a pattern with grep, regex or perl
and that's as close as i got.
EDIT: after using Ed Morton's awk command i needed to pass the output to rm command.
i used:
| ( read DB; (rm $DB/files.db) .
read DB passes the output into the vairable DB.
(...) combines commands.
rm $DB/files.db deletes the the file files.db.
Is this what you're trying to do?
$ awk -F"'" '/db_dir/{print $2}' file
/mnt/sda1/usb/db
That will work in any awk in any shell on every UNIX box.
If that's not what you want then edit your question to clarify your requirements and post more truly representative sample input/output.
Using sed with some effort to avoid single quotes:
sed -n 's/^minidlna.config.db_dir=\s*\S\(\S*\)\S\s*$/\1/p' input
Well, so you end up having a string like db_dir='/mnt/sda1/usb/db'.
I would first remove the quotes by piping this to
.... | tr -d "'"
Now you end up with a string like db_dir=/mnt/sda1/usb/db.
Say you have this string stored in a variable named confstr, then
${confstr##*=}
gives you just /mnt/sda1/usb/db, since *= denotes everything from the start to the equal sign, and ## denotes removal.
I would do this:
Once you either extracted your line about into file.txt (or pipe it into this command), split the fields using the quote character. Use printf to generate the rm command and pass this into bash to execute.
$ awk -F"'" '{printf "rm %s.db/file.db\n", $2}' file.txt | bash
rm: /mnt/sda1/usb/db.db/file.db: No such file or directory
With your original command:
$ uci show minidlna | grep -oE '\bdb_dir=\S+' | \
awk -F"'" '{printf "rm %s.db/file.db\n", $2}' | bash

awk syntax error in bash. Works fine in zsh

I have written the following script that extracts a number from an rss file.
#!/bin/sh
wget -O selic https://conteudo.bcb.gov.br/api/feed/pt-br/PAINEL_INDICADORES/juros
line=$(grep 'dailyratevalue' selic)
index=$(awk -v var=$line 'BEGIN {print index(var, "dailyratevalue") }')
end=$((index+21))
echo $line | cut -c $index-$end | tail -c 4 | tr ',' '.' > selic
In zsh it works perfectly, but i need it to work in bash, too. I have tried running it on bash but i get the following error
awk: cmd. line:1: <content
awk: cmd. line:1: ^ syntax error
The error pattern <content comes from the line that is being fed as a parameter to awk, which makes no sense to me, since awk is just supposed to get me the position of the pattern i want.
What could this be?
index=$(awk -v var="$line" 'BEGIN {print index(var, "dailyratevalue") }')
should fix it.
awk can do all of the extra steps. You can just
wget -qO - https://conteudo.bcb.gov.br/api/feed/pt-br/PAINEL_INDICADORES/juros | \
awk -F '&[gl]t;' '/dailyratevalue/ {sub(",", ".", $25); print $25;}'
and obtain the value you want.
This is setting the FS and getting the field you want for the line that matches dailyratevalue.
#DiegoTorresMilano's answer is probably better overall, but if you want to do it in bash, the main thing you need to do is double-quote your variable references. Without double-quotes around them, bash (and most shells other than zsh) splits variables into "words", and also expands anything that looks like a wildcard expression into a list of matching filenames. You almost never want this, so use double-quotes. In your case, there are two places they're needed: around $line here:
index=$(awk -v var="$line" 'BEGIN {print index(var, "dailyratevalue") }')
and here:
echo "$line" | cut -c $index-$end | tail -c 4 | tr ',' '.' > selic
Note that you don't need double-quotes around the $( ) expressions, because they're on the right side of an assignment statement, and that isn't subject to word splitting and wildcard expansion. If they occurred elsewhere, you'd probably want double-quotes around them too.
BTW, shellcheck.net is really good at pointing out common mistakes like this, so I recommend running your scripts through it (even when they seem to be working correctly).

How to parse variable to sed command in shell?

I have some variables:
$begin=10
$end=20
how to pass them to sed command.
sed -n '$begin,$endp' filename | grep word
sed -n '10,20p' filename | grep word
The reason this doesn't work is that single quotes in shell code prevent variable expansion. The good way is to use awk:
awk -v begin="$begin" -v end="$end" 'NR == begin, NR == end' filename
It is possible with sed if you use double quotes (in which shell variables are expanded):
sed -n "$begin,$end p" filename
However, this is subject to code injection vulnerabilities because sed cannot distinguish between code and data this way (unlike the awk code above). If a user manages to set, say, end="20 e rm -Rf /;", unpleasant things can happen.

Using a variable in Awk command in script

I am having a little trouble with using a variable and printing the 2nd field with the awk command. I am attempting to grab a number from a value in a file. The value in the file looks like
MAX=10000 (I want the Number only), I am passing this into a variable in a script so in the script I have variables
parm_file=ParmFiles/Parmfile.parm
session=s_session_value
OLD_MAX_SEQ_NR=`awk -F '=' "/$session/ {getline; print $2}" < $parm_file`
because I have double quotes to identify the $session variable, it is taking the $2 as a variable too, and so it is just printing the whole line, instead of the second field.
I've tried also to pass the variable into the awk command like
OLD_MAX_SEQ_NR=`awk -F '=' \
-v var="$session" \
'/var/ {getline; print $2}' < $parm_file`
But it does not seem to be putting the variable where var is. I have even tried hard coding the -v var="s_session_value" and it does not work.
I can't figure out a way to make the command look at the $2 as it normally does instead of a variable. Any help would be greatly appreciated.
Try this:
parm_file=ParmFiles/Parmfile.parm
session=s_session_value
OLD_MAX_SEQ_NR=$(
awk -F'=' -v pat="$session" \
'$0 ~ pat {getline; print $2}' < "$parm_file"
)
You need to pass shell variables to awk by defining an awk variable using -v.
Using variable inside /../ is taken as literal. So use $0~var_name construct.
Using back-ticks is deprecated. Use command substitution $(..)
Quote your variables.
It's a bit tricky without a sample line of the parm file. But I don't understand why you don't use cut, it makes it much easier?
OLD_MAX_SEQ_NR=$(grep "$session" "$parm_file" | cut -d= -f2)

String Manipulation in Bash

I am a newbie in Bash and I am doing some string manipulation.
I have the following file among other files in my directory:
jdk-6u20-solaris-i586.sh
I am doing the following to get jdk-6u20 in my script:
myvar=`ls -la | awk '{print $9}' | egrep "i586" | cut -c1-8`
echo $myvar
but now I want to convert jdk-6u20 to jdk1.6.0_20. I can't seem to figure out how to do it.
It must be as generic as possible. For example if I had jdk-6u25, I should be able to convert it at the same way to jdk1.6.0_25 so on and so forth
Any suggestions?
Depending on exactly how generic you want it, and how standard your inputs will be, you can probably use AWK to do everything. By using FS="regexp" to specify field separators, you can break down the original string by whatever tokens make the most sense, and put them back together in whatever order using printf.
For example, assuming both dashes and the letter 'u' are only used to separate fields:
myvar="jdk-6u20-solaris-i586.sh"
echo $myvar | awk 'BEGIN {FS="[-u]"}; {printf "%s1.%s.0_%s",$1,$2,$3}'
Flavour according to taste.
Using only Bash:
for file in jdk*i586*
do
file="${file%*-solaris*}"
file="${file/-/1.}"
file="${file/u/.0_}"
do_something_with "$file"
done
i think that sed is the command for you
You can try this snippet:
for fname in *; do
newname=`echo "$fname" | sed 's,^jdk-\([0-9]\)u\([0-9][0-9]*\)-.*$,jdk1.\1.0_\2,'`
if [ "$fname" != "$newname" ]; then
echo "old $fname, new $newname"
fi
done
awk 'if(match($9,"i586")){gsub("jdk-6u20","jdk1.6.0_20");print $9;}'
The if(match()) supersedes the egrep bit if you want to use it. You could use substr($9,1,8) instead of cut as well.
garph0 has a good idea with sed; you could do
myvar=`ls jdk*i586.sh | sed 's/jdk-\([0-9]\)u\([0-9]\+\).\+$/jdk1.\1.0_\2/'`
You're needing the awk in there is an artifact of the -l switch on ls. For pattern substitution on lines of text, sed is the long-time champion:
ls | sed -n '/^jdk/s/jdk-\([0-9][0-9]*\)u\([0-9][0-9]*\)$/jdk1.\1.0_\2/p'
This was written in "old-school" sed which should have greater portability across platforms. The expression says:
don't print lines unless they match -n
on lines beginning with 'jdk' do:
on a line that contains only "jdk-IntegerAuIntegerB"
change it to "jdk.1.IntegerA.0_IntegerB"
and print it
Your sample becomes even simpler as:
myvar=`echo *solaris-i586.sh | sed 's/-solaris-i586\.sh//'`

Resources