LinuxQuestions.org
Download your favorite Linux distribution at LQ ISO.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 05-07-2012, 09:56 AM   #1
dsartain
LQ Newbie
 
Registered: Feb 2007
Posts: 22

Rep: Reputation: 0
BASH Arrays


I'm running BASH version 3.2.25(1)-release

I need to be able to read a line of user input and store that into an array. Currently, what I have technically works, but not the way I want it to.

Code:
 echo "How many documents do you want to create?"
        read COUNT

        echo "Enter the names of the documents you wish to build surrounded by single quotes, separated by spaces."
        echo "See the following format: '01 Financial Statement ct' '02 Insurances ct' '03 Tax Returns ct'"


read -ea DOCUMENTS

        echo "${DOCUMENTS[0]}"
        echo "${DOCUMENTS[1]}"
THE PROBLEM:

1) Even when I surround what I'd like each element of the array to be by single quotes, I still have to use a backslash to escape the space between words within the single quotes. And then when I view the output, it shows the single quotes at the beginning and end, which is not desirable.

INPUT 1:
Code:
'01\ Financial\ Statement\ ct' '02\ Insurances\ ct'
OUTPUT 1:
Code:
'01 Financial Statement ct'
'02 Insurances ct'
Problem 2
In attempts to not have the single quotes show up, I didn't use them and only escapes backslashes between spaces of the same elements. This output is better, but still not practical as I'm sure other users will put slashes in the wrong place, etc.


INPUT 2:
Code:
01\ Financial\ Statement\ ct 02\ Insurances\ ct
OUTPUT 2:
Code:
01 Financial Statement ct
02 Insurances ct

Is there a way to separate array elements by using a comma instead, negating the need for backslashes? Or a way to use the single quotes to isolate the elements from each other, without requiring the backslashes as well??
 
Old 05-07-2012, 10:40 AM   #2
catkin
LQ 5k Club
 
Registered: Dec 2008
Location: Tamil Nadu, India
Distribution: Debian
Posts: 8,578
Blog Entries: 31

Rep: Reputation: 1208Reputation: 1208Reputation: 1208Reputation: 1208Reputation: 1208Reputation: 1208Reputation: 1208Reputation: 1208Reputation: 1208
Maybe read -ead',' DOCUMENTS
 
Old 05-07-2012, 11:05 AM   #3
dsartain
LQ Newbie
 
Registered: Feb 2007
Posts: 22

Original Poster
Rep: Reputation: 0
Changed to

Code:
read -ead',' DOCUMENTS
Resulted in: ./test.sh: line 20: read: `d,': not a valid identifier
 
Old 05-07-2012, 11:41 AM   #4
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,007

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
How about:
Code:
mapfile -t DOCUMENTS
No quotes required, but must advise user to press Ctrl+D after last line and that each record to be on its own line
Code:
01 Financial Statement ct
02 Insurances ct
<Ctrl+D here>
 
Old 05-07-2012, 12:15 PM   #5
dsartain
LQ Newbie
 
Registered: Feb 2007
Posts: 22

Original Poster
Rep: Reputation: 0
That looks like a great option, but I'm running BASH 3.2.25. Mapfile wasn't introduced until BASH 4
 
Old 05-07-2012, 12:23 PM   #6
David the H.
Bash Guru
 
Registered: Jun 2004
Location: Osaka, Japan
Distribution: Arch + Xfce
Posts: 6,852

Rep: Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037
Yes, mapfile would've been a good choice.

But really, the big problem here is that a single iteration of read only works on a single "line" of input (as delimited by the "-d" option). Then to split the line into an array, you have to change the IFS variable to define the field breaks.

More properly, to capture multiple lines like this you should use a loop.


And it's no use adding quotes to the input. Once the text is stored in the variable, the shell parsing order ensures that they will always be treated as literal. You'd have to parse the line manually in order to handle them.


I think what I would do is something more like this:

Code:
echo "Enter the names of the documents you wish to build."
echo "Press 'enter' after each entry.  When finished, enter the word 'done'."

n=0
while true; do

	read -e -p 'Enter a document name or "done": ' name

	case $name in

		[Dd][oO][nN][eE]) break ;;

		*) documents[n++]=$name ;;

	esac

done

echo "${documents[@]}"
This also adds another benefit in that you can also add tests to validate each entry by other criteria (e.g. file exists, or matches a certain pattern) before continuing.

P.S. Environment variables are generally all upper-case. So while not critical, it's good practice to keep your own user variables in lower-case or mixed-case, to help differentiate them.

Last edited by David the H.; 05-07-2012 at 12:42 PM. Reason: minor corrections and additions
 
Old 05-07-2012, 12:33 PM   #7
David the H.
Bash Guru
 
Registered: Jun 2004
Location: Osaka, Japan
Distribution: Arch + Xfce
Posts: 6,852

Rep: Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037
By the way, for a simple comma-delimited version, change the IFS setting like I mentioned above:

Code:
echo "Enter the names of the documents you wish to build, separated by commas."

IFS=',' read -e -a documents
The -e option tells bash to use the readline library, btw. This makes filename tab completion available.

Last edited by David the H.; 05-07-2012 at 12:34 PM.
 
Old 05-07-2012, 12:34 PM   #8
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,007

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
@David - I think you mixed up the variable being assigned to the array ... name vs line
 
Old 05-07-2012, 12:41 PM   #9
David the H.
Bash Guru
 
Registered: Jun 2004
Location: Osaka, Japan
Distribution: Arch + Xfce
Posts: 6,852

Rep: Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037
Ah thanks. Minor mistake there. I wanted to change the variable name to something more appropriate, but forgot to change every entry.

It's fixed now, and I've also added a bit of extra info.
 
Old 05-07-2012, 01:08 PM   #10
dsartain
LQ Newbie
 
Registered: Feb 2007
Posts: 22

Original Poster
Rep: Reputation: 0
Ok, the IFS change is going to work best for what I need. Here's the updated code as of now. Thanks to everyone for the help so far.

Code:
      1         echo "How many documents do you want to create?"
      2         read COUNT
      3
      4         echo "Enter the names of the documents you wish to build surrounded by single quotes, separated by spaces."
      5         echo "See the following format: '01 Financial Statement ct' '02 Insurances ct' '03 Tax Returns ct'"
      6
      7         OIFS=$IFS #Saves original field separator
      8         IFS=',' read -ea DOCUMENTS
      9
     10         for i in {0..$((COUNT-1))}
     11         do
     12                 echo "${DOCUMENTS[$i]}"
     13         done
     14
     15         IFS=$OIFS #Restores original field separator
The problem I'm running into now is on line 12.

Input:
Code:
01 Financial Statement ct, 02 Insurances ct, 03 Tax Returns ct
Error:
Code:
./test.sh: line 12: {0..2}: syntax error: operand expected (error token is "{0..2}")
I'm not sure what's causing the problem here, because when I change the $((COUNT-1)) in the for loop to a static number, it works just fine...

Any thoughts?

EDIT:
Scratch that. The issue was brace expansion preceeding variable expansion.

New line for the for loop control:
Code:
for i in $(eval echo {0..$((COUNT-1))})
Consider this one solved, guys. Thanks!

Last edited by dsartain; 05-07-2012 at 01:15 PM. Reason: Update code.
 
Old 05-07-2012, 01:31 PM   #11
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,007

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
Please don't use eval when something easier can be used (here are some reasons not use eval).
Just use a standard for loop construct:
Code:
for (( i = 0; i <= COUNT - 1; i++ ))
 
Old 05-07-2012, 01:34 PM   #12
David the H.
Bash Guru
 
Registered: Jun 2004
Location: Osaka, Japan
Distribution: Arch + Xfce
Posts: 6,852

Rep: Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037
Oh for goodness sakes, no. Never use eval for something like this. That's like using a howitzer to hunt mosquitoes.

There are several (much) more appropriate ways to handle looping through arrays.


Code:
for i in "${DOCUMENTS[@]}"; do
	echo "$i"
done

for i in "${!DOCUMENTS[@]}"; do
	echo "${DOCUMENTS[i]}"
done

for (( i=0; i<=COUNT; i++ )); do
	echo "${DOCUMENTS[i]}"
done


printf '%s\n' "${DOCUMENTS[@]}"

How can I use array variables?
http://mywiki.wooledge.org/BashFAQ/005
 
Old 05-07-2012, 01:35 PM   #13
dsartain
LQ Newbie
 
Registered: Feb 2007
Posts: 22

Original Poster
Rep: Reputation: 0
That works for me. That's the syntax I'm used to anyway, I just didn't see it pop up when I searched for for loops in bash. #bashnewbie
 
Old 05-07-2012, 02:06 PM   #14
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,007

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
Sorry if David and I came across a little harsh ... not trying to pick on you as we are aware that you were new. Just trying to help you move away from starting a bad habit
 
Old 05-07-2012, 02:19 PM   #15
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948
From an user interface perspective, instead of asking how many documents the user intends to specify up front, you could just accept new document names from the user, until they supply an empty line:
Code:
DOCUMENTS=()
while read -p "Document $[${#DOCUMENTS[@]}+1]: " DOCUMENT ; do

    # Break the loop if DOCUMENT is empty
    [ -n "$DOCUMENT" ] || break

    # Append DOCUMENT to DOCUMENTS array.
    DOCUMENTS[${#DOCUMENTS[@]}]="$DOCUMENT"
done

printf 'You specified %d documents:' ${#DOCUMENTS[@]}
printf '\t\047%s\047\n' "${DOCUMENTS[@]}"
 
  


Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
[SOLVED] bash and arrays disca Programming 3 07-27-2010 08:26 AM
Bash - Arrays and the for-loop Orangutanklaus Linux - General 2 10-23-2009 01:04 PM
Dynamic Arrays in Bash custangro Programming 5 06-19-2009 02:13 PM
Bash Arrays Simon256 Programming 2 02-17-2009 01:39 PM
[bash] How do I nest for arrays? blckleprd Programming 3 06-05-2008 10:49 PM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 06:01 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration