I am using UNIX Korn shell. I am trying to create a program in Unix that will search a text file which is shown below:
Last Name:First Name:City:State:Class:Semester Enrolled:Year First Enrolled
Gilman:Randy:Manhattan:KS:Junior:Spring:2010
Denton:Jacob:Rochester:NY:Senoir:Fall:2009
Goodman:Joshua:Warren:MI:Freshman:Summer:2014
Davidson:Blair:Snohomish:WA:Sophmore:Fall:2013
Anderson:Neo:Seattle:WA:Senoir:Spring:2008
Beckman:John:Ft. Polk:LA:Freshman:Spring:2014
Then take that line and cut it out and display it vertically. So if I search Gilman. It would produce:
Gilman
Randy
Manhattan
KS
Junior
Spring
2010
However included in this I should also be able to produce the following layout:
Last Name: Gilman
First Name: Randy
City: Manhattan
State: KS
Class: Junior
Semester Enrolled: Spring
Year First Enrolled: 2010
Now I have figured out most of it which I will display in my code below:
cat<<MENULIST
A - Add Student Information
D - Delete Student Information
M - Modify Student Information
I - Inquiry on a Student
X - Exit
MENULIST
echo -en '\n'
echo -en '\n'
echo " Pleasa choose one of the following: "
#take input from operation
read choice
case $choice in
a|A) ;;
d|D) ;;
m|M) ;;
i|I)
#Create Inguiry Message
clear
echo " Record Inquiry "
echo -en '\n'
echo -en '\n'
echo "What is the last name of the person"
#Gather search parameter
read last_name
grep -i "$last_name" $1 | cut -f 1-7 -d ':' ;;
x|X) echo "The program is ending" ; exit 0;;
*) echo -en '\n' ;echo "Not an acceptable entry." ;echo "Please press enter to try again"; read JUNK | tee -a $2 ; continue
esac
I am just really having trouble on redirecting input to the proper output. There is much more to the program but this is the relevant portion for this problem. Any help would be appreciated.
EDIT:
Ok the final answer is shown below:
while IFS=: read c1 c2 c3 c4 c5 c6 c7 rest ; do
case "$c1" in
"$last_name" )
echo -e "Last Name: $c1\nFirst Name: $c2\nCity: $c3\nState: $c4\nClass: $c5\nSemester Enrolled: $c6\nYear First Enrolled: $c7\n\n"
;;
esac
done < $1
This performed the intended outcome correctly thank you everyone for your help. I still have some other questions but I will make a new topic for that so it doesn't get to jumbled together.
Replace your line
grep -i "$last_name" $1 | cut -f 1-7 -d ':' ;;
with
awk -F: -vnameMatch="$last_name" \
'$1==nameMatch{
printf("LastName:%s\nFirstName:%s\nCity:%s\nState:%s\nClass:%s\nSemester Enrolled:%s\nYear First Enrolled:%s\n\n", \
$1, $2, $3, $4, $5, $6, $7)
}' $1
;;
It's pretty much the same idea in ksh.
while IFS=: read c1 c2 c3 c4 c5 c6 c7 rest ; do
case "$c1" in
"$last_name" )
printf "LastName:%s\nFirstName:%s\nCity:%s\nState:%s\nClass:%s\nSemester Enrolled:%s\nYear First Enrolled:%s\n\n",
"$c1" "$c2" "$c3" "$c4" "$c5" "$c6" "$c7"
;;
esac
done < $1
I think I've got all the syntax right, but don't have the energy tonight to test :-/ ... If you can use this, and there are problems, post a comment and I'll clean it up.
IHTH.
EDIT:
#Case statement for conducting an inquiry of the file
i|I)
#Create Inguiry Message
clear
echo " Record Inquiry "
echo -en '\n'
echo -en '\n'
echo "What is the last name of the person:"
#Gather search parameter
read last_name
while IFS=: read c1 c2 c3 c4 c5 c6 c7 rest ; do
case "$c1" in
"$last_name" )
printf "Last Name:%s\nFirst Name:%s\nCity:%s\nState:%s\nClass:%s\nSemester Enrolled:%s\nYear First Enrolled:%s\n\n", "$c1", "$c2", "$c3", "$c4", "$c5", "$c6", "$c7"
;;
esac
done < $2
#Case statement for ending the program
x|X) echo "The program is ending" ; exit 0;;
The error message is:
./asg7s: line 26: syntax error at line 93: `)' unexpected
Line 93 is
x|X) echo "The program is ending" ; exit 0;;
Kind of weird because I didn't mess with that part of the program so I know it has to be something in the portion I changed.
RLG
Adding something extra so I can save RLG edit without other's "approval"
This awk should do:
last_name="Gilman"
awk -F: -v name="$last_name" 'NR==1 {for(i=1;i<=NF;i++) h[i]=$i} $1==name {for(i=1;i<=NF;i++) print h[i]FS,$i}' file
Last Name: Gilman
First Name: Randy
City: Manhattan
State: KS
Class: Junior
Semester Enrolled: Spring
Year First Enrolled: 2010
It stores the first line in array h (header).
Then if it finds the pattern, print out array and data.
Here I post an alternative to a shell script, in perl:
perl -F':' -lne '
BEGIN { $name = pop; }
$. == 1 and do { #header = #F; next; };
next if m/^\s*$/;
if ( $F[0] eq $name ) {
for ( $i = 0; $i < #F; $i++ ) {
printf qq|%s: %s\n|, $header[$i], $F[$i];
}
exit 0;
}
' infile Gilman
It uses -F swith to split fields with colon, -l removes last newline character and -n opens input file and process it line by line.
The script accepts two arguments, but I extract the last one before processing assuming it's the name you want to search.
First line is saved in an array called #header and for next lines it compares first field, and in a match, prints each header followed by each field of current line and aborts the program.
It yields:
Last Name: Gilman
First Name: Randy
City: Manhattan
State: KS
Class: Junior
Semester Enrolled: Spring
Year First Enrolled: 2010
Related
I have a UNIX script written in korn shell. I need to make it so that this statement:
while true
do
echo "What is the last name of the person you would like to modify:"
read last_name
if line=$(grep -i "^${last_name}:" "$2")
then
IFS=: read c1 c2 c3 c4 rest <<< "$line"
echo -e "Last Name: $c1\nFirst Name: $c2\nState: $c4"
while true
do
echo "What would you like to change the state to?:"
read state
if [[ $state -eq [A-Z] ]];then
echo "State: $state"
echo "This is a valid input"
break
else
echo "Not a valid input:"
fi
done
else
echo "ERROR: $last_name is not in database"
echo "Would you like to search again (y/n):"
read delete_choice
case $delete_choice in [Nn]) break;; esac
fi
done
;;
Specifically, I am having trouble with this code:
if [[ $state -eq [A-Z] ]];then
The point of this program is to modify a record in a text file but will only take the input of state abbreviations such as (MI, WA, KS, ....).
Try something like:
if echo $state | egrep -q '^[A-Z]{2}$'
then
...
fi
^[A-Z]{2}$ means your state starts and ends with CAPS alphabets of length two.
I am having trouble with the code below:
IFS=: read c1 c2 c3 c4 rest <<< "$line"
Don't get me wrong this code works good but it doesn't seem to be used for ksh. I basically need to write the same code without the "<<<". There is not much info on the "<<<" online. If anybody has any ideas it would be much appreciated.
EDIT:
Ok code is as follows for the entire portion of programming:
m|M)
#Create Modify Message
clear
echo " Modify Record "
echo -en '\n'
echo -en '\n'
while true
do
echo "What is the last name of the person you would like to modify:"
read last_name
if line=$(grep -i "^${last_name}:" "$2")
then
oldIFS=$IFS
IFS=:
set -- $line
IFS=$oldIFS
c1=$1
c2=$2
c3=$3
c4=$4
shift; shift; shift; shift
rest="$*"
echo -e "Last Name: $1\nFirst Name: $2\nState: $4"
while true
do
echo "What would you like to change the state to?:"
read state
if echo $state | egrep -q '^[A-Z]{2}$'
then
echo "State: $state"
echo "This is a valid input"
break
else
echo "Not a valid input:"
fi
done
echo -e "Last Name: $c1\nFirst Name: $c2\nState: $state"
echo "State value changed"
break
else
echo "ERROR: $last_name is not in database"
echo "Would you like to search again (y/n):"
read modify_choice
case $modify_choice in [Nn]) break;; esac
fi
done
;;
Ok so everything works except for the
echo -e "Last Name: $c1\nFirst Name: $c2\nState: $state"
It will just show:
Last Name:
First Name:
State:
So I can see it is not adding it to my echo correctly.
FINAL EDIT
CODE:
#Case statement for modifying an entry
m|M)
#Create Modify Message
clear
echo " Modify Record "
echo -en '\n'
echo -en '\n'
while true
do
echo "What is the last name of the person you would like to modify:"
read last_name
if line=$(grep -i "^${last_name}:" "$2")
then
echo "$line" |
while IFS=: read c1 c2 c3 c4 rest; do
echo -e "Last Name: $c1\nFirst Name: $c2\nState: $c4"
last=$c1
first=$c2
done
while true
do
echo "What would you like to change the state to?:"
read state
if echo $state | egrep -q '^[A-Z]{2}$'
then
echo "State: $state"
echo "This is a valid input"
break
else
echo "Not a valid input:"
fi
done
echo -e "Last Name: $last\nFirst Name: $first\nState: $state"
echo "State value changed"
break
else
echo "ERROR: $last_name is not in database"
echo "Would you like to search again (y/n):"
read modify_choice
case $modify_choice in [Nn]) break;; esac
fi
done
;;
A here string in Bash
command <<<"string"
is basically equivalent to
echo "string" | command
with the obvious exception that the latter uses a pipeline, which means you cannot meaningfully use it with read in particular. A common workaround is to use the set builtin to capture tokens from a string or an external command:
oldIFS=$IFS
IFS=:
set -- $line # no quotes
IFS=$oldIFS
c1=$1
c2=$2
c3=$3
c4=$4
shift; shift; shift; shift
rest="$*" # loses spacing / quoting
Another workaround is to use a loop which iterates just once; this may seem elegant at first, but can lead to rather clunky code if the body of the pseudo-loop is long or complex.
echo "$line" |
while IFS=: read c1 c2 c3 c4 rest; do
: stuff which uses those variables
done
This works around the problem that echo stuff | read variable will run read in a child process and thus immediately forget the value of variable -- the body of the while loop is all the same process in which the read happened, and so the values of the variables it initialized are visible inside the loop.
Another, similar workaround is to delegate the reading and procesIng to a function;
process () {
IFS=: read c1 c2 c3 c4 rest
: stuff which uses those variables
}
echo "$line" | process
Whether this is clunky or elegant depends a lot on what happens in the function. If it's neatly encapsulated, it can be rather attractive; but if you end up passing in a bunch of unrelated variables (or worse, modifying globals inside the function!) it can be quite the opposite.
I have posted the same code for another question recently. I was helped with that but when trying to implement an If-Then statement, it returned erroneous results. So within this topic I will have 2 questions.
First Question:
When performing a search within a file, how do you implement the search so uppercase or lower case has no basis on returning the desired record?
Second Question:
In my If-Then statement no matter what I type it shows the else part of the If-Then Statement. For example, the name Smith is in the file, but no matter what I type "Smith" or "smith" yields the same result as "echo "Error: $last_name is not in file"".
Additionally:
This has to be written in korn shell programming.
The code is as follows:
#Gather search parameter
read last_name
if grep -Fxq "$last_name" $1
then
while IFS=: read c1 c2 c3 c4 c5 c6 c7 rest ; do
case "$c1" in
"$last_name" )
echo -e "Last Name: $c1\nFirst Name: $c2\nCity: $c3\nState: $c4\nClass: $c5\nSemester Enrolled: $c6\nYear First Enrolled: $c7\n\n"
;;
esac
done < $1
else
echo "Error: $last_name is not in file"
fi
;;
Any insight into the coding of this would be much appreciated.
EDIT:
User Input 1
User input: Smith
Expected Output:
Last Name: Smith
First Name: John
....
Actual Output:
Error: Smith is not in file
User Input 2
User input: smith
Expected Output:
Error: smith is not in file
Actual Output:
Error: smith is not in file
You are using grep's -x option: your user input must exactly match a complete line. I assume you don't want this behaviour.
Use -i for case insensitivity
read "last_name?Enter a last name: "
matches=0
grep -Fi "$last_name" "$1" |
while IFS=: read c1 c2 c3 c4 c5 c6 c7 rest; do
echo -e "Last Name: $c1\nFirst Name: $c2\nCity: $c3\nState: $c4\nClass: $c5\nSemester Enrolled: $c6\nYear First Enrolled: $c7\n\n"
((matches++))
done
if (( matches == 0 )); then
echo "Error: $last_name is not in file"
fi
In this case, I'm looping over the grep output directly, assuming that more than one line may match
Of course, it's misleading to be searching for "last name" with grep -- I could enter a city or a year, or a colon, and grep could find a match. If you're interested in this line of thought, let me know.
To test for string equality of the first field, awk would be a good choice
read "last_name?Enter a last name: "
awk -v name="$last_name" '
tolower($1) == tolower(name) {
print "Last Name:", $1
print "First Name:", $2
print "City:", $3
print "State:", $4
print "Class:", $5
print "Semester Enrolled:", $6
print "Year First Enrolled:", $7
matches++
}
END {if (matches == 0) print "No matches for:", name }
' "$1"
I am trying to search for a particular string in a Unix file from each and every line and error out those records. Can someone let me how can I improve my code which is as below. Also please share your thoughts if you have a better solution.
v_filename=$1;
v_new_file="new_file";
v_error_file="error_file";
echo "The input file name is $var1"
while read line
do
echo "Testing $line"
v_cnt_check=`grep ',' $line | wc -l`
echo "Testing $v_cnt_check"
# if [ $v_cnt_check > 2 ]; then
# echo $line >> $v_error_file
# else
# echo $line >> $v_new_file
# fi
done < $v_filename
Input:
1,2,3
1,2,3,4
1,2,3
Output:
(New file)
1,2,3
1,2,3
(Error file)
1,2,3,4
awk -F ',' -v new_file="$v_new_file" -v err_file="$v_error_file" \
'BEGIN { OFS="," }
NF == 3 { print >new_file }
NF != 3 { print >err_file }' $v_filename
The first line sets the file name variables and sets the field separator to comma. The second line sets the output field separator to comma too. The third line prints lines with 3 fields to the new file; the fourth line prints lines with other than 3 fields to the error file.
Note that your code would be excruciatingly slow on big files because it executes two processes per line. This code has only one process operating on the whole file — which will be really important if the input grow to thousand or millions or more lines.
From the grep manpage:
General Output Control
-c, --count
Suppress normal output; instead print a count of matching lines for each input file. With the -v, --invert-match option (see below), count non-
matching lines. (-c is specified by POSIX.)
You could do something like:
grep --count "your pattern" v_filename
to get the number of occurrences. If you just want the number of lines with your pattern, replace the grep shown above with:
grep "your pattern" v_filename | wc -l
I have a file in following format:
B: that
I: White
I: House
B: the
I: emergency
I: rooms
B: trauma
I: centers
What I need to do is to read line by line from the top, if the line begin with B then remove B:
If it begin with I: then remove I: and connect to the previous one (the previous one is processed in the same rule).
Expected Output:
that White House
the emergency rooms
trauma centers
What I tried:
while read line
do
string=$line
echo $string | grep "B:" 1>/dev/null
if [ `echo $?` -eq 0 ] //if start with " B: "
then
$newstring= echo ${var:4} //cut first 4 characters which including B: and space
echo $string | grep "I:" 1>/dev/null
if [ `echo $?` -eq 0 ] //if start with " I: "
then
$newstring= echo ${var:4} //cut first 4 characters which including I: and space
done < file.txt
What I don't know is how to put it back to the line (in the file) and how to connect the line to the previous processed one.
Using awk print the second field of I: and B: records. The variable first is used to control the newline output.
/B:/ searches for the B: pattern. This pattern marks the start of the record. If the record is NOT the first, then a newline is printed, then the data $2 is printed.
If the pattern found is I: the data $2 (the second field which follows I: is printed.
awk 'BEGIN{first=1}
/B:/ { if (first) first=0; else print ""; printf("%s ", $2); }
/I:/ { printf("%s ", $2) }
END {print ""}' filename
awk -F":" '{a[NR]=$0}
/^ B:/{print line;line=$2}
/^ I:/{line=line" "$2}
END{
if(a[NR]!~/^B/)
{print line}
}' Your_file
awk '/^B/ {printf "\n%s",$2} /^I/ {printf " %s",$2}' file
that White House
the emergency rooms
trauma centers
Shorten it some
awk '/./ {printf /^B/?"\n%s":" %s",$2}' file
There is an interesting solution using awk auto-split on RS patterns. Note that this is a bit sensitive to variations in the input format:
<infile awk 1 RS='(^|\n)B: ' | awk 1 RS='\n+I: ' ORS=' ' | grep -v '^ *$'
Output:
that White House
the emergency rooms
trauma centers
This works at least with GNU awk and Mikes awk.
This might work for you (GNU sed):
sed -r ':a;$!N;s/\n$//;s/\n\s*I://;ta;s/B://g;s/^\s*//;P;D' file
or:
sed -e ':a' -e '$!N' -e 's/\n$//' -e 's/\n\s*I://' -e 'ta' -e 's/B://g' -e 's/^\s*//' -e 'P' -e 'D' file