Unterminated string / Syntax Error with Bash - linux

I'm trying to get the follow code to read in variables from the user; files to search, a search string, wanted whitespace for output and the amount of fields to be output.
First issue is with the AWK command. If I enter a valid white space such as " " (A single space" or "\t" I am given the Unterminated string and syntax error, which only occurs if I request more than one field to be output (otherwise no whitespace is added on).
Secondly GREP seems to be a bit picky when using the search string. I've had to add double quotation marks to the start and finish of the variable in order for the entire string to be used.
#!/bin/bash
#****************************************************
#Name: reportCreator.sh
#Purpose: Create reports from log files
#Author:
#Date Written: 11/01/2013
#Last Updated: 11/01/2013
#****************************************************
clear
#Determine what to search for
printf "Please enter input file name(s): "
read inputFile
printf "Please enter your search query: "
read searchQuery
printf "Please enter the whitespace character: "
IFS= read whitespace
printf "Please enter the amount of fields to be displayed: "
read fieldAmount
#Add quotation marks to whitespace and searchQuery
whitespace=\""$whitespace"\"
searchQuery=\""$searchQuery"\"
#Declare variables
declare -i counter=0
declare -a fields[$fieldAmount]
declare -a fieldInsert[$fieldAmount]
#While loop for entering fields
while [[ "$counter" -ne "$fieldAmount" ]]
do
#Ask for field numbers
printf "Please enter number for required field $((counter+1)): "
read fields[$counter]
((counter++))
done
#Create function to add '$' before every field and the whitespace characters
function fieldFunction
{
for (( counter=0; counter <= ($fieldAmount-1); counter++ ))
do
fieldInsert[$fieldAmount]="$""${fields[$counter]}"
if (( counter!=($fieldAmount-1) ))
then
printf "${fieldInsert[*]}$whitespace"
else
printf "${fieldInsert[*]}"
fi
done
}
printf "%b\n"
tac $inputFile | grep "$searchQuery" | less #| awk '{print $(fieldFunction)}'
exit 0
Any help would be appreciated.

Grep doesn't understand quotes, so delete the line that adds them to $searchQuery.
Use double quotes instead of single quotes for awk, so $(fieldFunction) will expand.
Fixing this (as well as uncommenting the awk, of course), it works:
user#host 15:00 ~ $ cat script
#!/bin/bash
#****************************************************
#Name: reportCreator.sh
#Purpose: Create reports from log files
#Author:
#Date Written: 11/01/2013
#Last Updated: 11/01/2013
#****************************************************
clear
#Determine what to search for
printf "Please enter input file name(s): "
read inputFile
printf "Please enter your search query: "
read searchQuery
printf "Please enter the whitespace character: "
IFS= read whitespace
printf "Please enter the amount of fields to be displayed: "
read fieldAmount
#Add quotation marks to whitespace and searchQuery
whitespace=\""$whitespace"\"
#Declare variables
declare -i counter=0
declare -a fields[$fieldAmount]
declare -a fieldInsert[$fieldAmount]
#While loop for entering fields
while [[ "$counter" -ne "$fieldAmount" ]]
do
#Ask for field numbers
printf "Please enter number for required field $((counter+1)): "
read fields[$counter]
((counter++))
done
#Create function to add '$' before every field and the whitespace characters
function fieldFunction
{
for (( counter=0; counter <= ($fieldAmount-1); counter++ ))
do
fieldInsert[$fieldAmount]="$""${fields[$counter]}"
if (( counter!=($fieldAmount-1) ))
then
printf "${fieldInsert[*]}$whitespace"
else
printf "${fieldInsert[*]}"
fi
done
}
printf "%b\n"
tac $inputFile | grep "$searchQuery" | awk "{print $(fieldFunction)}"
exit 0
user#host 15:01 ~ $ cat file
foo two three four
foo two2 three2 four2
bar two three four
user#host 15:01 ~ $ bash script
Please enter input file name(s): file
Please enter your search query: foo
Please enter the whitespace character:
Please enter the amount of fields to be displayed: 2
Please enter number for required field 1: 4
Please enter number for required field 2: 2
four2 two2
four two
user#host 15:01 ~ $

Let's take a look at this section of code:
#Create function to add '$' before every field and the whitespace characters
function fieldFunction
{
for (( counter=0; counter <= ($fieldAmount-1); counter++ ))
do
fieldInsert[$fieldAmount]="$""${fields[$counter]}"
if (( counter!=($fieldAmount-1) ))
then
printf "${fieldInsert[*]}$whitespace"
else
printf "${fieldInsert[*]}"
fi
done
}
printf "%b\n"
tac $inputFile | grep "$searchQuery" | awk "{print $(fieldFunction)}"
It can be re-written more simply and robustly as (untested):
tac "$inputFile" |
awk -v fieldsStr="${fields[*]}" -v searchQuery="$searchQuery" -v OFS="$whitespace" '
BEGIN{ numFields = split(fieldsStr,fieldsArr) }
$0 ~ searchQuery {
for ( i=1; i <= numFields; i++ )
printf "%s%s", (i==1?"":OFS), $(fieldsArr[i])
print "\b"
}
'
I don't know why you need "tac" to open the file but I assume you have your reasons so I left it in.

Related

Unix shell code. How to count the number of letters on each line from a text file

I have the following code in Linux shell file:
I need to replace the "..." with the number of letters of the coresponding line.
#!/bin/sh
echo "Filename is: $1\n"
nr_lines=$(wc -l <$1)
echo "Number of lines in files is: $nr_lines\n"
for line in $(seq 1 $nr_lines);
do
echo "Line $line has ... letters"
done
The general pattern for iterating over all lines of a file is something like:
i=0; while read line; do
printf "Line $((++i)) has %d letters\n" \
"$(echo "$line" | tr -dc a-zA-Z | wc -c)";
done < input
but using while read ...; do ... done < input in a shell is often better done with awk:
awk '{gsub("[^a-zA-Z]", ""); printf "Line %d has %d letters\n", NR, length}' input

Shell script to read values from a file and to compare them with another value

I need a shell script, where I read in a number and it compares the number with the numbers in another file.
Here is an example:
I have a file called numbers.txt, which contains the following:
name;type;value;description
samsung;s5;1500;blue
iphone;6;1000;silver
I read in a number for example 1200. And it should print out the values from the file which are lesser than 1200(in my example it should print out 1000)
Here is the code that I started to write but I don't know how to finish it.
echo " Enter a number"
read num
if [ $numbersinthefile -le $num ]; then
echo "$numbersinthefile"
I hope I defined my question properly. Can somebody help me?
Use:
#!/bin/bash
echo -n "Enter the number: "
read num
awk -F\; '$3 < '$num' {print $0;}' myfile
Try this, first you use sed to remove first line then you use cut to get the actual number from line and you compare that number to the input.
echo " Enter a number"
read num
sed '1d' numbers.txt | while read line; do
numbersinthefile=$(echo $line | cut -d';' -f3);
if [ $numbersinthefile -lt $num ]; then
echo $line;
fi
done

Converting user input to uppercase

I am attempting to create a program in Unix that accesses a data file, adding, deleting, and searching within the file for names and usernames. With this if statement, I am attempting to allow the user to search for data in the file by the first field.
All of the data in the file uses uppercase letters, so I first must convert any text the user input from lowercase to uppercase letters. For some reason, this code is not working with both converting to uppercase and searching and printing the data.
How can I fix it?
if [ "$choice" = "s" ] || [ "$choice" = "S" ]; then
tput cup 3 12
echo "Enter the first name of the user you would like to search for: "
tput cup 4 12; read search | tr '[a-z]' '[A-Z]'
echo "$search"
awk -F ":" '$1 == "$search" {print $3 " " $1 " " $2 }'
capstonedata.txt
fi
This: read search | tr '[a-z]' '[A-Z]' will not assign anything to variable search.
It should be something like
read input
search=$( echo "$input" | tr '[a-z]' '[A-Z]' )
and it is better to use parameter expansion for case modification:
read input
search=${input^^}
If you use Bash, you can declare a variable to convert to uppercase:
$ declare -u search
$ read search <<< 'lowercase'
$ echo "$search"
LOWERCASE
As for your code, read doesn't have any output, so piping to tr doesn't do anything, and you can't have a newline before the file name in the awk statement.
Edited version of your code, minus all the tput stuff:
# [[ ]] to enable pattern matching, no need to quote here
if [[ $choice = [Ss] ]]; then
# Declare uppercase variable
declare -u search
# Read with prompt
read -p "Enter the first name of the user you would like to search for: " search
echo "$search"
# Proper way of getting variable into awk
awk -F ":" -v s="$search" '$1 == s {print $3 " " $1 " " $2 }' capstonedata.txt
fi
Alternatively, if you want to use only POSIX shell constructs:
case $choice in
[Ss] )
printf 'Enter the first name of the user you would like to search for: '
read input
search=$(echo "$input" | tr '[[:lower:]]' '[[:upper:]]')
awk -F ":" -v s="$search" '$1 == s {print $3 " " $1 " " $2 }' capstonedata.txt
;;
esac
Awk is not shell (google that). Just do:
if [ "$choice" = "s" ] || [ "$choice" = "S" ]; then
read search
echo "$search"
awk -F':' -v srch="$search" '$1 == toupper(srch) {print $3, $1, $2}' capstonedata.txt
fi

Retrieve string between characters and assign on new variable using awk in bash

I'm new to bash scripting, I'm learning how commands work, I stumble in this problem,
I have a file /home/fedora/file.txt
Inside of the file is like this:
[apple] This is a fruit.
[ball] This is a sport's equipment.
[cat] This is an animal.
What I wanted is to retrieve words between "[" and "]".
What I tried so far is :
while IFS='' read -r line || [[ -n "$line" ]];
do
echo $line | awk -F"[" '{print$2}' | awk -F"]" '{print$1}'
done < /home/fedora/file.txt
I can print the words between "[" and "]".
Then I wanted to put the echoed word into a variable but i don't know how to.
Any help I will appreciate.
Try this:
variable="$(echo $line | awk -F"[" '{print$2}' | awk -F"]" '{print$1}')"
or
variable="$(awk -F'[\[\]]' '{print $2}' <<< "$line")"
or complete
while IFS='[]' read -r foo fruit rest; do echo $fruit; done < file
or with an array:
while IFS='[]' read -ra var; do echo "${var[1]}"; done < file
In addition to using awk, you can use the native parameter expansion/substring extraction provided by bash. Below # indicates a trim from the left, while % is used to trim from the right. (note: a single # or % indicates removal up to the first occurrence, while ## or %% indicates removal of all occurrences):
#!/bin/bash
[ -r "$1" ] || { ## validate input is readable
printf "error: insufficient input. usage: %s filename\n" "${0##*/}"
exit 1
}
## read each line and separate label and value
while read -r line || [ -n "$line" ]; do
label=${line#[} # trim initial [ from left
label=${label%%]*} # trim through ] from right
value=${line##*] } # trim from left through '[ '
printf " %-8s -> '%s'\n" "$label" "$value"
done <"$1"
exit 0
Input
$ cat dat/labels.txt
[apple] This is a fruit.
[ball] This is a sport's equipment.
[cat] This is an animal.
Output
$ bash readlabel.sh dat/labels.txt
apple -> 'This is a fruit.'
ball -> 'This is a sport's equipment.'
cat -> 'This is an animal.'

how to insert separate lines ( elements ) in parameter

I have the following csv file
more file.csv
1,yes,yes,customer1,1,2,3,4
2,no,yes,customer5,34,56,33,2
3,yes,yes,customer11
4,no,no,customer14
5,yes,no,customer15
6,yes,yes,customer21
7,no,yes,customer34
8,no,yes,customer89
The following (awk) line was written in order to manipulate and take line from the csv and put each element (line) in to the parameter - LINES
declare LINES=` awk -F, 'BEGIN{IGNORECASE=1} $2=="yes" {printf "\"Line number %d customer %s\"\n", $1, $4}' file.csv `
.
echo $LINES
"Line number 1 customer customer1" "Line number 3 customer customer11" "Line number 5 customer customer15" "Line number 6 customer 21”
but when I want to print the number of elemnt in parameter LINES I get 1 ??
echo ${#LINES[*]}
1
While actually I need to get 4 elements ( lines )
Please advice how to fix the awk line in order to get 4 elements?
remark:
please see this example , when I edit manual the LINES , the elements should be 4
declare LINES=( "Line number 1 customer customer1" "Line number 3 customer customer11" "Line number 5 customer customer15" "Line number 6 customer 21” )
echo ${#LINES[*]}
4
The awk output isn't being stored in an array. You’d need declare -a LINES=($(...)) to do that. But even then, bash splits array elements on any whitespace, not only newlines. And if you were to wrap the process substitution in quotes like LINES=("$(...)") you would only have a single element containing the entire output from the command.
You could do the necessary text manipulation with a read loop to preserve the number of elements that contain whitespace.
declare -a lines
while IFS=, read -r line_number answer _ customer _; do
if [[ $answer == #(yes|YES) ]]; then
lines+=("Line number $line_number customer $customer")
fi
done < file.csv
As noted in the comments, depending on the bash version, usage of #(...) inside [[ ... ]] may require shopt -s extglob.
Alternatively, the if could be replaced with a case:
case $answer in
yes|YES)
LINES+=("Line number $line_number customer $customer")
;;
esac
Try this:
a=$(awk -F, 'BEGIN{IGNORECASE=1} $2=="yes" {printf "Line number %d customer %s;", $1, $4}' file.csv)
IFS=';' read -a LINES <<< "${a}"
As #JohnB mentioned, you are populating LINES as a scalar variable, not an array. Try this:
$ IFS=$'\n' LINES=( $(awk 'BEGIN{for(i=1;i<=3;i++) printf "\"Line number %d\"\n", i}') )
$ echo ${#LINES[*]}
3
$ echo "${LINES[0]}"
"Line number 1"
$ echo "${LINES[1]}"
"Line number 2"
$ echo "${LINES[2]}"
"Line number 3"
and tweak to suit your real input/output which would probably result in:
IFS=$'\n' LINES=( $(awk -F, 'BEGIN{IGNORECASE=1} $2=="yes"{printf "\"Line number %d customer %s\"\n", $1, $4}' file.csv) )
If you're using bash, you can just use the mapfile builtin:
$ mapfile -t LINES < \
<(awk -F, 'BEGIN{IGNORECASE=1}
$2=="yes" {printf "\"Line number %d customer %s\"\n", $1, $4}' file.csv)
$ echo "${#LINES[*]}"
4
$ echo "${LINES[#]}"
"Line number 1 customer customer1" "Line number 3 customer customer11" "Line number 5 customer customer15" "Line number 6 customer customer21"

Resources