Shell script -How to group test file records based on column value and send email to corresponding receipents.? - bash

I have a comma separated csv file named "file1" with below details and the headers are.. incident number, date, person name and email id. The requirement is to group records by person name and send email listing all records by his or her name.
So in this example Sam, Mutthu, Andrew, Jordan will receive one email each and in that email they will see all records on their name.
10011,5-Jan,Sam,Sam#companydomain.com
10023,8-Jan,Mutthu,Mutthu#companydomain.com
10010,8-Jan,Mutthu,Mutthu#companydomain.com
10026,15-Jan,Sam,Sam#companydomain.com
10050,10-Jan,Jordan,Jordan#companydomain.com
10021,12-Jan,Andrew,Andrew#companydomain.com
I have searched the forum for solution but not able to map which solution to go with, all I can find below command to create separate files based in person name which will not fit in our requirement.
awk -F\, '{print>$3}' file1
talking about our existing script, it sends email one by one using below command so it will send multiple emails to Mutthu and Sam which we don't want.
/usr/sbin/sendmail -v $MAILTO $MAILCC |grep Sent >> "$HOME/maillog.txt"
Any Help will be appreciated
Thanks
Shan

As the question is tagged "bash", here a possible solution as a pure shell script. Note that this was not tested.
#!/bin/bash
MAILCC=x#y.com
in_file="file1"
# 1. grep all lines containing a '#' (contained in email address)
# 2. Cut field 4 (the email address)
# 3. sort uniq (remove duplicate email addresses)
#
# Loop over that list
#
for email in $(grep '#' $in_file | cut -d, -f 4 | sort -u); do
# only if $email is a non-empty string
if [ -n "$email" ]; then
# grep for email in source file and mail found lines
{
echo "From: sender#example.net"
echo "To: $email"
echo "Subject: Your test file records"
echo ""
grep "$email" $in_file | while read -r line; do
echo "$line"
done
} | /usr/sbin/sendmail -v $email $MAILCC
fi
done | grep Send >>"$HOME/maillog.txt"

Here is an Awk script which does what you request. We collect the input into an array, where each element contains the lines for one recipient, and the key is the recipient's email address ($4). Finally we loop over the keys and send one message each.
awk -F , '{ a[$4] = a[$4] ORS $0 }
END {
for (user in a) {
cmd = "/usr/sbin/sendmail -v " $4
print "From: sender#example.net" | cmd
print "To: " $4 | cmd
print "Subject: stuff from CSV" | cmd
# Neck between headers and email body
print "" | cmd
# Skip initial ORS
print substr(a[user],2) | cmd
close(cmd) } }' file.csv |
grep Sent >>"$HOME/maillog.txt"
I can't guess what's in MAILCC so I just left it off. If you always want to Cc: a static address, adding that back should be easy.

Related

Is there any effective & fast way to catch two match in a log file?

I would like to get some ideas.
My situation: there are tons of logs on my Linux server that are big and they are also have tons of things in them. I would like to catch ONLY the login with a timestamp and ONLY the email address from the log and collect them to a .txt file.
An example log:
[...]
2019-07-21 03:13:06.939 login
[things not needed between the two]
(mail=>example#mail.com< method=>email< cmd=>login<)
[...]
An example output:
************** 2019-07-21 **************
2019-07-21 03:13:06.939 login
example#mail.com
2019-07-21 06:22:19.424 login
example#mail.com
2019-07-21 12:10:23.665 login
example#mail.com
2019-07-21 14:26:19.068 login
example#mail.com
************** 2019-07-22 **************
2019-07-22 08:01:50.157 login
example#mail.com
2019-07-22 08:12:35.504 login
example#mail.com
2019-07-22 09:10:35.416 login
example#mail.com
To achieve this I am using this right now:
for i in $(ls); do echo "" && printf "************** " && cat $i | head -c 10 && printf " **************\n"; while read line; do echo $line | grep "login"; echo "$line" | grep -h -o -P '(?<=mail=>).*?(?=<)'; done < $i; done >> ../logins.txt
The for loop is going through the files, cat $i | head -c 10 will get the date (because that is the first thing in every log), the while loop is reading the file line-by-line and greps login and ONLY the mail address (grep between "mail=>" "<"). And at the end it is outputting to logins.txt.
While this is working I find it very-very slow because it's executing a lots of commands. (And we are talking about 2 years of logs here) And it is also looks really dirty.
I really think that there is an effective way to do this but I don't really get what would that be.
With awk use the -F for selecting the mail account:
sep='************************'
awk -v sep="$sep" -F '(mail=>|<)' '
FNR==1 { printf("%s %s %s\n", sep, substr($0,0,10), sep)}
/mail=>/ {print $2}
/login *$/ {print}
' *
When you have additional requirements and want to use a loop, consider
for f in *; do
sed -nr '
1s/(.{10}).*/********* \1 **********/p;
/login *$/p;
s/.*mail=>([^<]*).*/\1/p
' "${f}"
done
awk would do a nice job of this. You can tell it to print the line only when the line matches a particular regex. Something like:
awk '$0~/[0-9]{4}-[0-9]{2}-[0-9]{2}|\(mail=>/{print $0}' * > output.log
Updated: Noticed you just want the email. In the case, two blocks will suffice. In the second block we split by characters < or > and then retrieve the email from index 2 of the resulting array.
awk '$1~/^[0-9]{4}-[0-9]{2}-[0-9]{2}/{print $0}$1~/^\(mail=>/{split($1,a,"[<>]");print a[2]}' * > output.log
This awk says:
If the first field (where the field is delimited by awk's default of a space character) of the row we are reading starts with a date of format nnnn-nn-nn: $1~/^[0-9]{4}-[0-9]{2}-[0-9]{2}/
Then print the entire line {print $0}
If the first field of the row we are reading starts with the characters (mail=>: $1~/^\(mail=>/
Then split the first field by either characters < or > into an array named a: split($1,a,"[<>]")
Then print the 3rd item in the array (index 2): print a[2]
For all of the files in this current directory: *
Instead of printing to the command line, send the output to a file: > output.log

Assistance needed with bash script parsing data from a file

Would first like to thank everyone for taking the time and reviewing this, and providing some assistance.
I am stuck on this bash script project I have been working on. This script is supposed to pull data from this file, export it to a csv, and then email it out. I was able to grab the required data and email it to myself but the problem is that the groups in the file have special characters. I need to have the lsgroup command executed on those groups in order to retrieve the users and then have it exported to the csv file.
For example, below is sample data that are in the file and how it looks like:
[Skyrim]
comment = Elder Scrolls
path = /export/skyrim/elderscrolls
valid users = #dawnstar nords #riften
invalid users = #lakers
[SONY]
comment = PS4
path = /export/Sony/PS4
valid users = #insomniac #activision
invalid users = peterparker controller #pspro
The script is supposed to be gathering the name, comment, path, valid users, invalid users, and exporting them to the csv
So far this is what I have that works,
out="/tmp/parsed-report.csv"
file="/tmp/file.conf"
echo "name,comment,path,valid_users,invalid_users" > $out;
scp -q server:/tmp/parse/file.conf $out
grep "^\[.*\]$" $file |grep -Ev 'PasswordPickup|global' | while read shr ; do
shr_regex=$(echo "$shr" | sed 's/[][]/\\&/g')
shr_print=$(echo "$shr"|sed 's/[][]//g')
com=$(grep -p "$shr_regex" $file|grep -v "#"| grep -w "comment"| awk -F'=' '{print $2}'|sed 's/,/ /g')
path=$(grep -p "$shr_regex" $file|grep -v "#"| grep -w "path"| awk -F'=' '{print $2}')
val=$(grep -p "$shr_regex" $file|grep -v "#"| grep -w "valid users"| awk -F'=' '{print $2}')
inv=$(grep -p "$shr_regex" $file|grep -v "#"| grep -w "invalid users"| awk -F'=' '{print$2}')
echo "$shr_print,$com,$path,$val,$inv" >> $out
done
exit 0
The text with '#' are considered groups so if $var3='#' then run the lsgroup command and export the data to csv file under the correct category, else if $vars3!='#' then export users to the csv file.
This is what I tried to come up with:
vars3="$val$inv"
Server="server_1"
for lists in $(echo "$vars3"); do
if [[ $lists = *[!\#]* ]]; then
ssh -q $Server "lsgroup -a users $(echo "$lists"|tr -d /#/)|awk -
F'=' '{print $1}'" > print to csv file as valid or invalid users
else [[ $lists != *[!\#]* ]]; then
echo "users without #" > to csv file as valid or invalid users
With the right commands the output should look like this
: skyrim
Comment: Elder Scrolls
Path: /export/skyrim/elderscrolls
Valid Users: dragonborn argonian kajit nords
Invalid Users : Shaq Kobe Phil Lebron
: SONY
Comment: PS4
Path: /export/Sony/PS4
Valid Users: spiderman ratchet&clank callofduty spyro
Invalid Users : peterparker controller 4k
Create a file file.sed with this content:
s/\[/: / # replace [ with : and one space
s/]// # remove ]
s/^ // # remove leading spaces
s/ = /: /
s/#lakers/Shaq Kobe Phil Lebron/
s/^comment/Comment/
# Can be completed by you here.
and then use
sed -f file.sed your_sample_data_file
Output:
: Skyrim
Comment: Elder Scrolls
path: /export/skyrim/elderscrolls
valid users: #dawnstar nords #riften
invalid users: Shaq Kobe Phil Lebron
: SONY
Comment: PS4
path: /export/Sony/PS4
valid users: #insomniac #activision
invalid users: peterparker controller #pspro
Parsing things is a hard problem and, in my opinion, writing your own parser is unproductive.
Instead, I highly advise you to take your time and learn about grammars and parsing generators. Then you can use some battle tested library such as textX to implement your parser.

Storing data in shell

I have a need to store a list of e-mails in a shell script. This script will get called and passed a customer number. depending on the customer number I want to populate a variable based on the passed in customer number.
I am not sure how to accomplish this and have been looking.
command example
gcb "type" "customernumber" "date"
I want to pull an e-mail associated with that customer number and populate a variable with it.
I would prefer this get stored in the script and not in a separate file if possible.
#shellter
So as you can see above my command has the customer number as $2, i am trying to get the email finder to work with that in mind. So I created a script to test the e-mail finder function with. It works fine as you have it below, but if i want it to look for $2 == cust_id it returns nothing. Here is my code below.
#!/bin/sh
#case $# in 0 ) echo "usage: myEmailFinder2 CustID" ; exit 1 ;; esac
cfgDir="/verification"
# given cust file like
# cust_id "\t" email_addr
fn_myEmailFinder() {
awk -F"\t" -v cust_id="$2" '{if ($2 == cust_id) {print $3}}' /verification/custlist.cfg
}
emailAddr=$( fn_myEmailFinder "$1")
echo $emailAddr
The command I run to test this is this
sh emailtest.sh test 90624
My config file is layed out like this, tab delimited
CustomerNumber CustomerName Email
I am going to store more data in this file to populate other variables, I'm sure once i get this figured out, I can sort out the other data.
I appreciate all of your help.
This script will get called and passed a customer number.
myEmailFinder "$CustID"
I want to populate a variable based on the passed in customer number.
emailAddr=$( myEmailFinder "$CustID")
I want to pull an e-mail associated with that customer number and populate a variable with it.
I would prefer this get stored in teh script and not in a separate file if possible.
Using a database is preferred, but .... per your written specification, try this
cat myEmailFinder
#!/bin/bash
case $# in 0 ) echo "usage: myEmailFinder CustID" ; exit 1 ;; esac
# given cust file like
# cust_id "\t" email_addr
fn_myEmailFinder() {
awk -F"\t" -v cust_id="$1" '{
if ($1 == cust_id) {
print $2
}
}' <<-EOF
1 user1#myCorp.com
2 user2#myCorp.com
5 user3#myCorp.com
EOF
#--^tabCh^---make sure you put a real tab char between custID and emailAddr
#tabCh-TabCh--- data indented with TabChars. EOS indented with **only** tabCh.
#send an email to cust in $1
emailAddr=$( fn_myEmailFinder "$1")
mailx -S "Test Email" "$emailAddr" <<-EOM
Here is the body of an email addressed to $emailAddr with CustID=$custID
EOM
#end of script
The block delimited by EOF is the place to store your custID and associated email Addresses. One per line, tab-delimited. The Indents on each line should be done with tab chars. The closing EOF line must be done ONLY with tab chars.
A preferable solution would be to store the "lookup table" in a separate file. That would look like
cat myEmailFinder2
#!/bin/bash
case $# in 0 ) echo "usage: myEmailFinder2 CustID" ; exit 1 ;; esac
cfgDir="/usr/local/data"
# given cust file like
# cust_id "\t" email_addr
fn_myEmailFinder() {
awk -F"\t" -v cust_id="$1" '{
if ($1 == cust_id) {
print $2
}
}' "$cfgDir"/emaillist.cfg
#send an email to cust in $1
emailAddr=$( fn_myEmailFinder "$1")
mailx -S "Test Email" "$emailAddr" <<-EOM
Here is the body of an email addressed to $emailAddr with CustID=$custID
EOM
where emaillist.cfg is laid out as above, tab-delimited.
IHTH
#!/bin/bash -
''''echo "Customer number: $1"
X=$(/bin/env python $0 $1)
echo $X
exit
'''
customers = {
42: 'customerA'
,43: 'customerB'
}
import sys
print customers.get(int(sys.argv[1]), '')
sys.exit(0)
:-|
if [ "$1" = "42" ]; then X="CustomerA" ; fi
if [ "$1" = "43" ]; then X="CustomerB" ; fi

How to search a text file based on columns using Bash Script

I am creating a bash script contact list system and this is how it prints out.
=================
Menu
=================
Enter 1 for Insert new contact
Enter 2 for Print current contact list
Enter 3 for Search contact list
Enter 4 for Exit
Enter your selection:
When 2 is selected, basically it prints out the following:
Name Email Phone
Test test#aol.com 102-123-1234
Data data#yahoo.com 345-345-5555
Sally sally#yahoo.com 344-555-4930
To display this I use
$ awk -F, 'BEGIN{printf "%-12s %-15s %-12s\n","Name"," Email"," Phone"} {printf "%-12s %-15s %-12s\n",$1,$2,$3}' contacts.txt
I am having trouble with the option number 3 (searching contact list).
It prompts for:
Enter in data that you would like to search for: aol
Then the code behind is:
echo -e "Enter in data that you would like to search for: \c"
read search
grep "$search" contacts.txt
It prints out:
Test,test#aol.com,102-123-1234
This is because the text file contacts.txt stores the data in a comma separated list.
I want the search results to display in the columns like option number 2. So when "aol" is the search it should print out:
Name Email Phone
Test test#aol.com 102-123-1234
How would I do this?
use read and IFS, eg:
echo -e "Enter in data that you would like to search for: \c"
read search
printf "%16s%16s%16s\n\n" Name Email Phone
grep "$search" contacts.txt | while IFS="," read name email phone etc ; do
printf "%16s%16s%16s\n" "$name" "$email" "$phone"
done
awk -v pattern=$search '/Name/{print $0} $0~pattern{print $0}' input
will output:
Name Email Phone
Test test#aol.com 102-123-1234
what does it?
-v option creates a awk variable pattern assigns it with $search
/Name/ selects the title line
$0~pattern matches the search patters
A much simpler version would be
awk -v pattern=$search '/Name/; $0~pattern' input
since print $0 is the default action.

How to display text file in columns using bashscript in unix?

I am making a bash script contact list system. This is what it prints out.
=================
Menu
=================
Enter 1 for Insert new contact
Enter 2 for Print current contact list
Enter 3 for Search contact list
Enter 4 for Exit
Enter your selection:
For "1" it ask for name, email, and phone and stores them for variables then stores them in a text file.
case "$answer" in
1) echo -e "Enter in a new contact name: \c"
read name
echo -e "Enter in new contact email address: \c"
read email
echo -e "Enter in new contact phone number: \c"
read phone
echo "$name, $email, $phone" >> contacts.txt ;;
For 2 is where I am having trouble. I want to display the text in three columns so I can sort them by name, email, or phone number. This is my code for case 2.
2) cat contacts.txt ;;
Obviously it only spits out:
Test Name, Test#data.com, 123-123-1234
Blank Data, Data#aol.com, 234-555-5555
I want it to read:
Name Email Phone
Test Name Test#data.com 123-123-1234
Blank Data Data#aol.com 234-555-5555
How would I do that? And how would I be able to sort it later on?
Change
echo "$name, $email, $phone" >> contacts.txt ;;
to
echo "$name,$email,$phone" >> contacts.txt ;;
and try this:
(echo Name,Email,Phone; cat contacts.txt) | column -s , -t
$ cat contacts.txt
Test Name, Test#data.com, 123-123-1234
Blank Data, Data#aol.com, 234-555-5555
$ awk -F, 'BEGIN{printf "%-12s %-15s %-12s\n","Name"," Email"," Phone"} {printf "%-12s %-15s %-12s\n",$1,$2,$3}' contacts.txt
Name Email Phone
Test Name Test#data.com 123-123-1234
Blank Data Data#aol.com 234-555-5555
How it works:
The printf statement allows custom formatting of output. Above the format string %-12s %-15s %-12s\n was used. Taking %-12s, for example, the 12s part means that we want to format a string to a width of 12 columns. The minus sign means that we want that field left-justified.
Looking at each piece of the awk code separately:
-F,
This tells awk to use a comma as the field separator on each line.
BEGIN{printf "%-12s %-15s %-12s\n","Name"," Email"," Phone"}
The BEGIN block is executed before the first line of the file is read. It is used here to print the header.
printf "%-12s %-15s %-12s\n",$1,$2,$3
awk implicitly loops through every line in the file. For each line, we print out the first three fields as per the format statement.

Resources