LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (http://www.linuxquestions.org/questions/programming-9/)
-   -   bash: format select options (http://www.linuxquestions.org/questions/programming-9/bash-format-select-options-884200/)

dokkalf 06-02-2011 04:10 PM

bash: format select options
 
I have a formatted file of tab-separated fields, four fields per line, a field can contain spaces. I want to use this to generate a menus in a bash script using select. However, since an entire line is too long, I want to format the line across two lines without select making each individual line an option.

Here's what I've got:

data.txt
Header1<tab>Header2<tab>Header3<tab>Header4
field11<tab>field12<tab>field13<tab>field14<newline>
field21<tab>field22<tab>field23<tab>field24<newline>
field31<tab>field32<tab>field33<tab>field34<newline>
field41<tab>field42<tab>field43<tab>field44<newline>

script.sh
#!/bin/bash

formatline()
{
current=$1
field2=$(echo $1 | cut -f 2)
field3=$(echo $1 | cut -f 3)
field4=$(echo $1 | cut -f 4)

echo -e "Field 2 = $field2\tField 4 = $field4\n\tField 3 = $field3\n"
}

OLDIFS=$IFS
IFS=$'\012'

select lchoice in $(for i in $(awk (NR > 1)); do formatline $i; done)
do
echo $lchoice
done
IFS=$OLDIFS
#end of script

What I want to see:
1) Field 2 = field12 <tab> Field 4 = field14
<tab> Field 3 = field13
2) Field 2 = field22 <tab> Field 4 = field24
<tab> Field 3 = field23
3) Field 2 = field32 <tab> Field 4 = field34
<tab> Field 3 = field33
4) Field 2 = field42 <tab> Field 4 = field44
<tab> Field 3 = field43

What I actually see:
1) Field 2 = field12 <tab> Field 4 = field14
2) <tab> Field 3 = field13
3) Field 2 = field22 <tab> Field 4 = field24
4) <tab> Field 3 = field23
5) Field 2 = field32 <tab> Field 4 = field34
6) <tab> Field 3 = field33
7) Field 2 = field42 <tab> Field 4 = field44
8) <tab> Field 3 = field43

Is there any way to get select to do what I want - maybe by playing games with IFS?

David the H. 06-02-2011 10:48 PM

The problem is that the entries you want to print contain newlines also, so you get one entry for each newline-separated field, not each line output from the function.

One solution is to specify a unique delimiter that can be used instead of newline.

Here's a version that appears to do what you want. I've also taken the liberty of rearranging things to make it cleaner and removed the need to call on external tools like awk and cut.
Code:

formatline() {

local IFS=$'\t'

while read line ; do

    [[ $line == Header* ]] && continue
    local farray=( $line )
    echo -en "Field 2 = ${farray[1]}\tField 4 = ${farray[3]}\n\tField 3 = ${farray[2]}@"
    #last character echoed is the delimiter you want to use.
   
done <"$1"

}

IFS="@"

select lchoice in $( formatline data.txt ); do

    echo "$lchoice"

done

Edit: I almost forgot...please use [code][/code] tags around your code, to preserve formatting and to improve readability.

dokkalf 06-03-2011 01:29 PM

This works - thanks.

I knew there had to be something better than that ugly awk/cut combo, but once I had it, I couldn't think of anything else. That'll probably be more useful to me.

As for the code tags - oops.

grail 06-04-2011 06:38 AM

Just another alternative for you:
Code:

#!/usr/bin/awk -f

BEGIN{  OFS=FS="\t" }

NR > 1{
    n = NR - 1
    print line[n] = sprintf("%d) Field 2 = %s\tField 4 = %s\n\tField 3 = %s",n,$2,$4,$3)
}

END{
    printf "Please select the corresponding number # "
    getline choice < "-"
   
    print "Your choice was:\n"line[choice]

}


David the H. 06-05-2011 07:43 AM

When all you need to do is print certain fields in a string, bash arrays and/or parameter substitution can often be good alternatives to awk, sed, grep, and cut. The external tools are generally better (and often more efficient) when more complex processing is required, such as when complex regex patterns are required to match text or when targeting only certain lines in a file or such.

By the way, I was wondering why you were using IFS=$'\012' in your script instead of IFS=$'\n'. That seemed a bit odd to me, since you used \n elsewhere in the script.


@grail: As usual, another nice solution. But he did mention that this was for inclusion in a larger script. It would have to be converted to a stand-alone command for in-script use, and the output likely captured in a bash variable for further use.

grail 06-05-2011 10:18 AM

Quote:

But he did mention that this was for inclusion in a larger script.
Missed that bit :( The awk could of course be shortened to just the essentials to return the desired output, but if we stay with bash I would employ
your solution but ditch the select all together and have the while loop handle it.

Guess ultimately it will depend on the overall algorithm the OP is searching for :)

dokkalf 06-06-2011 02:52 PM

@grail: several years ago, I created a database for my personal library using Carlo Strozzi's NoSQL database and tools. Periodically, I attempt to create complex query scripts for it that are shell-based (in keeping with NoSQL's theory of close integration with UNIX and the idea of the shell as a 4GL). This particular script will, when finished (or if), allow me to get information on a series based on just a few title words and/or author.

@David: I developed the habit of using octal notation when assigning a value like newline or tab to variable like IFS because many of the sample scripts in the Advanced Bash-scripting Guide by Mendel Cooper did so.

grail 06-06-2011 09:45 PM

Thanks for the information ... if we can help any further just ask away :)


All times are GMT -5. The time now is 10:32 PM.