Assing a variable and use it inside of if statement shell scripting - shell

I am new to shell scripting and trying to make a few small scripts. I got stuck when i tried to write if condition. In the code below i am trying to get the $5 value from df and trying to use it in if condition. However the code doesn't work.
#!/bin/sh
temp = $(df -h | awk '$NF=="/"{$5}')
if [ $temp > 60 ] ; then
df -h | awk '$NF=="/" {printf("%s\n"),$5}'
(date -d "today" +"Date:%Y.%m.%d"" Hour: %H:%M")
fi
#end
So i've figured out something and changed my code into this:
temp=$(df -h | awk '$NF=="/"{$5}')
if [ "$((temp))" -gt 0 ] ; then
df -h | awk '$NF=="/" {printf("%s\n"),$5}'
(date -d "today" +"Date:%Y.%m.%d"" Hour: %H:%M")
fi
#end
And now , i'm trying to get the integer value of the $5 variable. It returns a percentage and i want to compare this percentage with %60. How can i do that ?

Let's see what shellcheck.net has to tell us:
Line 1:
#!/bin/sh
^-- SC1114: Remove leading spaces before the shebang.
Line 3:
temp = $(df -h | awk '$NF=="/"{$5}')
^-- SC1068: Don't put spaces around the = in assignments.
Line 4:
if [ $temp > 0 ] ; then
^-- SC2086: Double quote to prevent globbing and word splitting.
^-- SC2071: > is for string comparisons. Use -gt instead.
^-- SC2039: In POSIX sh, lexicographical > is undefined.
Um, ok, after a little fixing:
#!/bin/sh
temp=$(df -h | awk '$NF=="/"{$5}')
if [ "$temp" -gt 0 ] ; then
df -h | awk '$NF=="/" {printf("%s\n"),$5}'
(date -d "today" +"Date:%Y.%m.%d"" Hour: %H:%M")
fi
The [ ... ] command is the same as test command. Test does not have < comparison for numbers. It has -gt (greater then). See man test.
This will run now, but definitely not do what you want. You want the fifth column of df output, ie. the use percents. Why do you need -h/human readable output? We dont need that. Which row of df output do you want? I guess you don't want the header, ie. the first row: Filesystem 1K-blocks Used Available Use% Mounted on. Let's filter the columns with the disc name, I choose /dev/sda2. We can filter the row that has the first word equal to /dev/sda2 with grep "^/dev/sda2 ". The we need to get the value on the fifth column with awk '{print $5}'. We need to get rid of the '%' sign too, otherwise shell will not interpret the value as a number, with sed 's/%//' or better with tr -d '%'. Specifying date -d"today" is the same as just date. Enclosing a command in (...) runs it in subshell, we don't need that.
#!/bin/sh
temp=$(df | grep "^/dev/sda2 " | awk '{print $5}' | tr -d '%')
if [ "$temp" -gt 0 ]; then
echo "${temp}%"
date +"Date:%Y.%m.%d Hour: %H:%M"
fi
This is a simple, that if use percentage on disc /dev/sda2 is higher then 0, then it will print the use percentage and print current date and time in a custom format.

Assuming you're using GNU tools, you can narrow the df output to just what you need:
pct=$( df --output=pcent / | grep -o '[[:digit:]]\+' )
if [[ $pct -gt 60 ]]; then ...

Related

Need help for string manipulation in a bash script

I'm not use to the syntax of bash script. I'm trying to read a file. For each line I want to keep only the part of the string before the delimiter '/' and put it back into a new file if the word respect a perticular length. I've download a dictionary, but the format does not meet my expectation. Since there is 84000 words, I don't really want to manualy remove what after the '/' for each word. I though it would be an easy thing and I follow couple of idea in other similar question on this site, but it seem that I'm missing something somewhere because it still doesn't work. I can't get the length right. The file Test_Input contains one word per line. Here's the code:
#!/usr/bin/bash
filename="Test_Input.txt"
while read -r line
do
sub= echo $line | cut -d '/' -f1
length= echo ${#sub}
if $length >= 4 && $length <= 10;
then echo $sub >> Test_Output.txt
fi
done < "$filename"
Several items:
I assume that you have been using single back-quotes in the assignments, and not literally sub= echo $line | cut -d '/' -f1, as this would have certainly failed. Alternatively, you can also use sub=$(), as in $(echo $line | cut -d '/' -f1)
The conditions in an if clause need to be encompassed by single or double [], like this: if [[ $length -ge 4 ]] && [[ $length -le 10 ]];
Which brings me to the next point: <= doesn't reliably work in bash. Just use -ge for "greater or equal" and -le for "less or equal".
If your line does not contain any / characters, in your version sub will contain the whole line. This might not be what you want, so I'd advise to also add the -s flag to cut.
You don't need somevar=$(echo $someothervar). Just use somevar=$someothervar
Here's a version that works:
#!/usr/bin/env bash
filename="Test_Input.txt"
while read -r line
do
sub=$(echo $line | cut -s -d '/' -f 1)
length=${#sub}
if [[ $length -ge 4 ]] && [[ $length -le 10 ]];
then echo $sub >> Test_Output.txt
fi
done < "$filename"
Of course, you could also just use sed:
sed -n -r '/^[^/]{4,10}\// s;/.*$;;p' Test_Input.txt > Test_Output.txt
Explanation:
-n Don't print anything unless explicitly marked for printing.
-r Use the extended regex
/<searchterm>/ <operation> Search for lines that match a certain criteria, and perform this operation:
Searchterm is: ^[^/]{4,10}\/ From the beginning of the line, there should be between 4 and 10 non-slash characters, followed by the slash
Operation is: s;/.*$;;p replace everything between the first slash and the end of the line with nothing, then print.
awk is the best tool for this
awk -F/ 'length($1) >= 4 && length($1) <= 10 {print $1} > newfile

Find number of files with prefixes in bash

I've been trying to count all files with a specific prefix and then if the number of files with the prefix does not match the number 5 I want to print the prefix.
To achieve this, I wrote the following bash script:
#!/bin/bash
for filename in $(ls); do
name=$(echo $filename | cut -f 1 -d '.')
num=$(ls $name* | wc -l)
if [$num != 5]; then
echo $name
fi
done
But I get this error (repeatedly):
./check_uneven_number.sh: line 5: [1: command not found
Thank you!
The if statement takes a command, runs it, and checks its exit status. Left bracket ([) by itself is a command, but you wrote [$num. The shell expands $num to 1, creating the word [1, which is not a command.
if [ $num != 5 ]; then
Your code loops over file names, not prefixes; so if there are three file names with a particular prefix, you will get three warnings, instead of one.
Try this instead:
# Avoid pesky ls
printf '%s\n' * |
# Trim to just prefixes
cut -d . -f 1 |
# Reduce to unique
sort -u |
while IFS='' read -r prefix; do
# Pay attention to quoting
num=$(printf . "$prefix"* | wc -c)
# Pay attention to spaces
if [ "$num" -ne 5 ]; then
printf '%s\n' "$prefix"
fi
done
Personally, I'd prefer case over the clunky if here, but it takes some getting used to.

take ping test average change output

Here is my script I wanto change out put second one:
#!/bin/bash
declare -a arr=("8.8.8.8" "8.8.4.4" "192.168.1.28")
x=0
DATE=`date +%Y-%m-%d:%H:%M:%S`
echo $DATE > denemesh.txt
while [ $x -le 2 ]
do
echo " ${arr[x]}" >> denemesh.txt
ping -c 4 ${arr[x]} | tail -1| awk ' {print $4 }' | cut -d '/' -f 2 >> denemesh.txt
x=$(( $x + 1 ))
done
Currently, the output looks like this:
2014-12-22:20:22:37
8.8.8.8
18.431
8.8.4.4
17.758
192.168.1.28
0.058
Is it possible to change to output to look like this instead?
2014-12-22:20:22:37
8.8.8.8 18.431
8.8.4.4 17.758
192.168.1.28 0.058
You really just need to modify one line:
echo -n " ${arr[x]}" >> denemesh.txt
Using the -n flag suppresses the trailing newline, and so your next statement should append to the current line. You can then adjust the formatting as you please.
Sure it is. Try something like this:
declare -a arr=("8.8.8.8" "8.8.4.4" "192.168.1.28")
d=$(date +%Y-%m-%d:%H:%M:%S)
echo "$d" > denemesh.txt
for ip in "${arr[#]}"
do
printf ' %-12s' "$ip"
ping -c 4 "$ip" | awk 'END{split($4,a,"/"); printf "%12s\n", a[2]}'
done >> denemesh.txt
I've used printf with format specifiers to align the output. The %-12s left-aligns the first column with a fixed width of 12 characters and the %12s in awk right-aligns the second column. Rather than use a while loop, I got rid of your variable x and have looped through the values in the array directly. I have also changed the old-fashioned backtick syntax in your script to use $( ) instead. awk is capable of obtaining the output directly by itself, so I removed your usage of tail and cut too. Finally, you can simply redirect the output of the loop rather than putting >> on the end of each line.

How to delete all lines containing more than three characters in the second column of a CSV file?

How can I delete all of the lines in a CSV file which contain more than 3 characters in the second column? E.g.:
cave,ape,1
tree,monkey,2
The second line contains more than 3 characters in the second column, so it will be deleted.
awk -F, 'length($2)<=3' input.txt
You can use this command:
grep -vE "^[^,]+,[^,]{4,}," test.csv > filtered.csv
Breakdown of the grep syntax:
-v = remove lines matching
-E = extended regular expression syntax (also -P is perl syntax)
bash stuff:
> filename = overwrite/create a file and fill it with the standard out
Breakdown of the regex syntax:
"^[^,]+,[^,]{4,},"
^ = beginning of line
[^,] = anything except commas
[^,]+ = 1 or more of anything except commas
, = comma
[^,]{4,} = 4 or more of anything except commas
And please note that the above is simplified and would not work if the first 2 columns contained commas in the data. (it does not know the difference between escaped commas and raw ones)
No one has supplied a sed answer yet, so here it is:
sed -e '/^[^,]*,[^,]\{4\}/d' animal.csv
And here's some test data.
>animal.csv cat <<'.'
cave,ape,0
,cat,1
,orangutan,2
large,wolf,3
,dog,4,happy
tree,monkey,5,sad
.
And now to test:
sed -i'' -e '/^[^,]*,[^,]\{4\}/d' animal.csv
cat animal.csv
Only ape, cat and dog should appear in the output.
This is a filter script for your type of data. It assumes your data is in utf8
#!/bin/bash
function px {
local a="$#"
local i=0
while [ $i -lt ${#a} ]
do
printf \\x${a:$i:2}
i=$(($i+2))
done
}
(iconv -f UTF8 -t UTF16 | od -x | cut -b 9- | xargs -n 1) |
if read utf16header
then
px $utf16header
cnt=0
out=''
st=0
while read line
do
if [ "$st" -eq 1 ] ; then
cnt=$(($cnt+1))
fi
if [ "$line" == "002c" ] ; then
st=$(($st+1))
fi
if [ "$line" == "000a" ]
then
out=$out$line
if [[ $cnt -le 3+1 ]] ; then
px $out
fi
cnt=0
out=''
st=0
else
out=$out$line
fi
done
fi | iconv -f UTF16 -t UTF8

How to verify information using standard linux/unix filters?

I have the following data in a Tab delimited file:
_ DATA _
Col1 Col2 Col3 Col4 Col5
blah1 blah2 blah3 4 someotherText
blahA blahZ blahJ 2 someotherText1
blahB blahT blahT 7 someotherText2
blahC blahQ blahL 10 someotherText3
I want to make sure that the data in 4th column of this file is always an integer. I know how to do this in perl
Read each line, Store value of 4th column in a variable
check if that variable is an integer
if above is true, continue the loop
else break out of the loop with message saying file data not correct
But how would I do this in a shell script using standard linux/unix filter? My guess would be to use grep, but I am not sure how?
cut -f4 data | LANG=C grep -q '[^0-9]' && echo invalid
LANG=C for speed
-q to quit at first error in possible long file
If you need to strip the first line then use tail -n+2 or you could get hacky and use:
cut -f4 data | LANG=C sed -n '1b;/[^0-9]/{s/.*/invalid/p;q}'
awk is the tool most naturally suited for parsing by columns:
awk '{if ($4 !~ /^[0-9]+$/) { print "Error! Column 4 is not an integer:"; print $0; exit 1}}' data.txt
As you get more complex with your error detection, you'll probably want to put the awk script in a file and invoke it with awk -f verify.awk data.txt.
Edit: in the form you'd put into verify.awk:
{
if ($4 !~/^[0-9]+$/) {
print "Error! Column 4 is not an integer:"
print $0
exit 1
}
}
Note that I've made awk exit with a non-zero code, so that you can easily check it in your calling script with something like this in bash:
if awk -f verify.awk data.txt; then
# action for success
else
# action for failure
fi
You could use grep, but it doesn't inherently recognize columns. You'd be stuck writing patterns to match the columns.
awk is what you need.
I can't upvote yet, but I would upvote Jefromi's answer if I could.
Sometimes you need it BASH only, because tr, cut & awk behave differently on Linux/Solaris/Aix/BSD/etc:
while read a b c d e ; do [[ "$d" =~ ^[0-9] ]] || echo "$a: $d not a numer" ; done < data
Edited....
#!/bin/bash
isdigit ()
{
[ $# -eq 1 ] || return 0
case $1 in
*[!0-9]*|"") return 0;;
*) return 1;;
esac
}
while read line
do
col=($line)
digit=${col[3]}
if isdigit "$digit"
then
echo "err, no digit $digit"
else
echo "hey, we got a digit $digit"
fi
done
Use this in a script foo.sh and run it like ./foo.sh < data.txt
See tldp.org for more info
Pure Bash:
linenum=1; while read line; do field=($line); if ((linenum>1)); then [[ ! ${field[3]} =~ ^[[:digit:]]+$ ]] && echo "FAIL: line number: ${linenum}, value: '${field[3]}' is not an integer"; fi; ((linenum++)); done < data.txt
To stop at the first error, add a break:
linenum=1; while read line; do field=($line); if ((linenum>1)); then [[ ! ${field[3]} =~ ^[[:digit:]]+$ ]] && echo "FAIL: line number: ${linenum}, value: '${field[3]}' is not an integer" && break; fi; ((linenum++)); done < data.txt
cut -f 4 filename
will return the fourth field of each line to stdout.
Hopefully that's a good start, because it's been a long time since I had to do any major shell scripting.
Mind, this may well not be the most efficient compared to iterating through the file with something like perl.
tail +2 x.x | sort -n -k 4 | head -1 | cut -f 4 | egrep "^[0-9]+$"
if [ "$?" == "0" ]
then
echo "file is ok";
fi
tail +2 gives you all but the first line (since your sample has a header)
sort -n -k 4 sorts the file numerically on the 4th column, letters will rise to the top.
head -1 gives you the first line of the file
cut -f 4 gives you the 4th column, of the first line
egrep "^[0-9]+$" checks if the value is a number (integers in this case).
If egrep finds nothing, $? is 1, otherwise it's 0.
There's also:
if [ `tail +2 x.x | wc -l` == `tail +2 x.x | cut -f 4 | egrep "^[0-9]+$" | wc -l` ] then
echo "file is ok";
fi
This will be faster, requiring two simple scans through the file, but it's not a single pipeline.
#OP, use awk
awk '$4+0<=0{print "not ok";exit}' file

Resources