How to get the line number of a match? - bash

My aim is to get the line number ($lineof) of a string which matches a line in /etc/crontab.
To give 0 8 * * * Me echo "start working please" and get this is the line number 13 from /etc/crontab.
Given this file /tmp/crontab :
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# m h dom mon dow user command
#
0 17 * * * Me echo "end of work"
0 8 * * * Me echo "start working please"
1 3 2 4 2 Me ls -la
I do something like that for the moment:
cat /etc/crontab | grep -v "#" | grep "Me" > /tmp/task.cron
i=1
while read -r content
do
line=$content
# lineof=$LINENO
nbline=${i}
minute=$(echo "$line" | awk '{print $1}') #0-59
hour=$(echo "$line" | awk '{print $2}') #0-23
dom=$(echo "$line" | awk '{print $3}') #1-31
month=$(echo "$line" | awk '{print $4}') #1-12
dow=$(echo "$line" | awk '{print $5}') #0-6 (0=Sunday)
cmd=$(echo "$line" | awk '{$1=$2=$3=$4=$5=$6=""; print $0}') #command
cmd=$(echo "$cmd" | tr ' ' _)
str=$str' '$nbline' "'$minute'" "'$hour'" "'$dom'" "'$month'" "'$dow'" "'$user'" "'$cmd'" '
i=$(($i+1))
done < /tmp/task.cron
$nbline give me the line of the content in /tmp/task.cron
$LINENO give me the line of the current script (which execute the program)
I want $lineof give me the number of the line in /etc/crontab

To print the line number of your match, use the -n option of grep. Since the pattern contains some special characters, use -F to make them be interpreted as fixed strings and not a regular expression:
grep -Fn 'your_line' /etc/crontab
However, since you want to print some message together with the line number, you may want to use awk instead:
awk -v line='your_line' '$0 == line {print "this is the line number", NR, "from", FILENAME}' /etc/crontab
Test
$ cat a
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# m h dom mon dow user command
#
0 17 * * * Me echo "end of work"
0 8 * * * Me echo "start working please"
1 3 2 4 2 Me ls -la
With awk:
$ awk -v line='0 8 * * * Me echo "start working please"' '$0 == line {print "this is the line number", NR, "from", FILENAME}' a
this is the line number 13 from a
With grep:
$ grep -Fn '0 8 * * * Me echo "start working please"' a13:0 8 * * * Me echo "start working please"
13:0 8 * * * Me echo "start working please"

grep --fixed-strings --line-number "${match}" | cut --delimiter=":" --fields=1

I finally did like this, found alone :
nbline=1
while read -r content
do
line=$content
if [ "${line:0:1}" != "#" ]; then #if this is not a comment
line=$(echo -e "$line" | grep "$user") #$line keep only lines with the $user choose
if [ ! -z "$line" ];then #if this is not a void $line
minute=$(echo -e "$line" | awk '{print $1}') #0-59
hour=$(echo -e "$line" | awk '{print $2}') #0-23
dom=$(echo -e "$line" | awk '{print $3}') #1-31
month=$(echo -e "$line" | awk '{print $4}') #1-12
dow=$(echo -e "$line" | awk '{print $5}') #0-6 (0=Sunday)
cmd=$(echo -e "$line" | awk '{$1=$2=$3=$4=$5=$6=""; print $0}') #command
cmd=$(echo -e "$cmd" | tr ' ' _) #replace space by '_' because it's annoying later
str=$str' "'$nbline'" "'$minute'" "'$hour'" "'$dom'" "'$month'" "'$dow'" "'$user'" "'$cmd'" '
fi
fi
nbline=$(($nbline+1))
done < /etc/crontab
I don't need to create an other file and get in $nbline the number of current line in loop. And count all line, even if they are void or commented. That's what I wanted.
'#' is the line number of the right content in /etc/crontab.

Related

How to grab fields in inverted commas

I have a text file which contains the following lines:
"user","password_last_changed","expires_in"
"jeffrey","2021-09-21 12:54:26","90 days"
"root","2021-09-21 11:06:57","0 days"
How can I grab two fields jeffrey and 90 days from inverted commas and save in a variable.
If awk is an option, you could save an array and then save the elements as individual variables.
$ IFS="\"" read -ra var <<< $(awk -F, '/jeffrey/{ print $1, $NF }' input_file)
$ $ var2="${var[3]}"
$ echo "$var2"
90 days
$ var1="${var[1]}"
$ echo "$var1"
jeffrey
while read -r line; do # read in line by line
name=$(echo $line | awk -F, ' { print $1} ' | sed 's/"//g') # grap first col and strip "
expire=$(echo $line | awk -F, ' { print $3} '| sed 's/"//g') # grap third col and strip "
echo "$name" "$expire" # do your business
done < yourfile.txt
IFS=","
arr=( $(cat txt | head -2 | tail -1 | cut -d, -f 1,3 | tr -d '"') )
echo "${arr[0]}"
echo "${arr[1]}"
The result is into an array, you can access to the elements by index.
May be this below method will help you using
sed and awk command
#!/bin/sh
username=$(sed -n '/jeffrey/p' demo.txt | awk -F',' '{print $1}')
echo "$username"
expires_in=$(sed -n '/jeffrey/p' demo.txt | awk -F',' '{print $3}')
echo "$expires_in"
Output :
jeffrey
90 days
Note :
This above method will work if their is only distinct username
As far i know username are not duplicate

my script deletes user accounts that have not been logged in for more than 30 days, in fact it works, but it deletes some system accounts

It works, but also deletes some system accounts...i don't know why
#!/bin/bash
# This script takes everyone with id>1000 from /etc/passwd and removes every user account in case if it hasn't been used for the last 30 days.
# Make sure that script is being executed with root priviligies.
if [[ "${UID}" -ne 0 ]]
then
echo "You should run this script as a root!"
exit 1
fi
# First of all we need to know id limit (min & max)
USER_MIN=$(grep "^UID_MIN" /etc/login.defs)
USER_MAX=$(grep "^UID_MAX" /etc/login.defs)
# Print all users accounts with id>=1000 and <=6000 (default).
awk -F':' -v "min=${USER_MIN##UID_MIN}" -v "max=${USER_MAX##UID_MAX}" ' { if ( $3 >= min && $3 <= max ) print $0}' /etc/passwd
# This function deletes users which hasn't log in in the last 30 days
# Make a color output message
for accounts in ` lastlog -b 30 | sed "1d" | awk ' { print $1 } '`
do
userdel $accounts 2>/dev/null
done
echo -e "\e[36mYou have successfully deleted all user's account which nobody logged in in the past 30 days.\e[0,"
exit 0
You do not apply the awk -F':' -v "min=${USER_MIN##UID_MIN}" -v "max=${USER_MAX##UID_MAX}" ' { if ( $3 >= min && $3 <= max ) print $0}' /etc/passwd filter to lastlog output....
Join the list from lastlog with the list of users from /etc/passwd and filter UID with proper number range.
lastlog -b30 | sed 1d | awk '{print $1}' |
sort | join -t: -11 -21 -o2.1,2.3 - <(sort /etc/passwd) |
awk -F: -v "min=${USER_MIN##UID_MIN}" -v "max=${USER_MAX##UID_MAX}" '$2 >= min && $2 <= max {print $1}' |
xargs -n1 echo userdel
Notes:
Do not use backticks `. Use $(...) instead. Obsolete and deprecated syntax
how to read a stream line by line in bash bashfaq
With bash and GNU grep:
#!/bin/bash
# I left out the check for root user
# get only the numbers from each line
# \K removes the matching part before \K (from left to right)
USER_MIN=$(grep -Po "^UID_MIN *\K.*" /etc/login.defs)
USER_MAX=$(grep -Po "^UID_MAX *\K.*" /etc/login.defs)
# create an associative array, see: help declare
declare -A users
# read all users with its uid to this associative array
while IFS=":" read -r user x uid x; do users[$user]="$uid"; done </etc/passwd
# see output of: declare -p users
# remove all unwanted lines including headline. NR is line number
lastlog -b 30 | awk '! /Never logged in/ && NR>1 {print $1}' |
while read -r user; do
# -ge: greater-than-or-equal
# -le: less-than-or-equal
if [[ ${users[$user]} -ge $USER_MIN ]] && [[ ${users[$user]} -le $USER_MAX ]]; then
echo "delete user $user with uid ${users[$user]}"
# add your code here to delete user $user
fi
done

bash scripting to add users

I created a bash script to read information such as username, group etc., from a text file and create users based on it in linux. The code seems to function properly and creates the users as desired. But the user information in the last line of the text file always gets misinterpreted. Even if i delete it then the next last line gets misinterpreted i.e., the text is read wrongly.
`
#!/bin/bash
userfile="users.txt"
IFS=$'\n'
if [ ! -f "$userfile" ]
then
echo "File does not exist. Specify a valid file and try again. "
exit
fi
groups=(`cut -f 4 "$userfile" | sed 's/ //'`)
fullnames=(`cut -f 1 "$userfile" | sed 's/,//' | sed 's/"//g'`)
username1=(`cut -f 1 "$userfile" |sed 's/,//' | sed 's/"//' | tr [A-Z] [a-z] | awk '{print substr($2,1,1) substr($3,1,1) substr($1,1,1)}'`)
username2=(`cut -f 4 "$userfile" | tr [A-Z] [a-z] | awk '{print substr($1,1,1)}'`)
i=0
n=${#username1[#]}
for (( q=0; q<n; q++ ))
do
usernames[$q]=${username1[$q]}"${username2[$q]}"
done
declare -a usernames
x=0
created=0
for user in ${usernames[*]}
do
adduser -c ${fullnames[$x]} -p 123456789 -f 15 -m -d /home/${groups[$x]}/$user -K LOGIN_RETRIES=3 -K PASS_MAX_DAYS=30 -K PASS_WARN_AGE=3 -N -s /bin/bash $user 2> /dev/null
usermod -g ${groups[$x]} $user
chage -d 0 $user
let created=$created+1
x=$x+1
echo -e "User $user created "
done
echo "$created Users created"
enter image description here`
#!/bin/bash
userfile="./users.txt"; # <-- Config
while read line; do
# FULL NAME
# Capture all between quotes as full name
fullname=$(printf '%s' "${line}" | sed 's/^"\(.*\)".*/\1/')
# Remove spaces and punctuations???:
fullname=$(printf '%s' "${fullname}" | tr -d '[:punct:][:blank:]')
# Right-side names:
partb=$(printf '%s' "${line}" | sed "s/^\".*\"//g")
# CODE 1, capture second row
code1=$(printf '%s' "${partb}" | cut -f 2 )
# CODE 2, capture third row
code2=$(printf '%s' "${partb}" | cut -f 3 )
# GROUP, capture fourth row
group=$(printf '%s' "${partb}" | cut -f 4 )
# Print only for report
echo "fullname: ${fullname}\n code 1: ${code1}\n code 2: ${code2}\n group: ${group}\n"
done <${userfile}
Maybe these are the fields that you want, now you have it in variables for manipulate them: $fullname, $code1, $code2 and $group.
Although maybe the fail that you observed was due to some misplaced quotation mark in the text file or the line breaks, on the attached screenshot I can see one missed quote.

BASH: Remove newline for multiple commands

I need some help . I want the result will be
UP:N%:N%
but the current result is
UP:N%
:N%
this is the code.
#!/bin/bash
UP=$(pgrep mysql | wc -l);
if [ "$UP" -ne 1 ];
then
echo -n "DOWN"
else
echo -n "UP:"
fi
df -hl | grep 'sda1' | awk ' {percent+=$5;} END{print percent"%"}'| column -t && echo -n ":"
top -bn2 | grep "Cpu(s)" | \sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | \awk 'END{print 100 - $1"%"}'
You can use command substitution in your first sentence (notice you're creating a subshell in this way):
echo -n $(df -hl | grep 'sda1' | awk ' {percent+=$5;} END{print percent"%"}'| column -t ):

How to properly parse this scenario in a simple bash script?

I have a file where each key-value pair takes a new line. There is a possibility of having multiple values for each key. I want to return a list of all pairs that have a "special key", where "special" is is defined as some function.
For Example, if "special" is defined as a key that somewhere has a value of 100
A 100
B 400
A hello
B world
C 100
I would return
A 100
A hello
C 100
How to do this in bash?
#!/bin/bash
special=100
awk -v s=$special '
{
a[$1,$2]
if($2 ~ s)
k[$1]
}
END
{
for(key in k)
for(pair in a)
{
split(pair,b,SUBSEP)
if(b[1] == key)
print b[1],b[2]
}
}' ./infile
Proof of Concept
$ special=100; echo -e "A 100\nB 400\nA hello\nB world\nC 100" | awk -v s=$special '{a[$1,$2];if($2 ~ s)k[$1]}END{for(key in k)for(pair in a){split(pair,b,SUBSEP); if(b[1] == key)print b[1],b[2]}}'
A hello
A 100
C 100
This would also work:
id=`grep "\<$special\>$" yourfile | sed -e "s/$special//"`
[ -z "$id" ] || grep "^$id" yourfile
Returns:
If special=100
A 100
A hello
C 100
If special="hello"
A 100
A hello
If special="A"
(nothing)
If special="ello"
(nothing)
Notes
drop the \<\> if you want partial match
add | uniq at the end if there is a possibility of multiple entrances of the same pair (A 100, A 100, ...) but you don't want that in your output.
***** script *****
#!/bin/bash
grep " $1" data.txt | cut -d ' ' -f1 | grep -f /dev/fd/0 data.txt
result:
./test.sh 100
A 100
A hello
C 100
***** inline *****
the first grep must contain the 'special' preceded by a space ' ':
grep " 100" data.txt | cut -d ' ' -f1 | grep -f /dev/fd/0 data.txt
A 100
A hello
C 100
awk -v special="100" '$2==special{a[$1]}($1 in a)' file
Whew! My bash was incredibly rusty! Hope this helps:
FILE=$1
IFS=$'\n' # Internal File Sep, so as to avoid splitting in whitespaces
FIND="100"
KEEP=""
for line in `cat $FILE`; do
key=`echo $line | cut -d \ -f1`;
value=`echo $line | cut -d \ -f2`;
echo "$key = $value"
if [ "$value" == "$FIND" ]; then
KEEP="$key $KEEP"
fi
done
echo "Keys to keep: $KEEP"
# You can now do whatever you want with those keys.

Resources