3

Say I have the file foo.txt

"The" "quick brown" "fox" "jumps over" "the" "lazy dog."

I would like to read these "fields" from the file into an array. However my attempt is failing if the field has a space

$ read -a bar < foo.txt

$ echo ${bar[0]}
"The"

$ echo ${bar[1]}
"quick
3
  • 2
    Do you want to keep the quotation marks?
    – chepner
    Commented Oct 16, 2012 at 23:51
  • Do you want to keep the spaces as field separator or can you replace them and use a suitable IFS?
    – micke
    Commented Oct 16, 2012 at 23:56
  • @Ignacio Vazquez-Abrams: FYI. Commented Oct 17, 2012 at 0:19

4 Answers 4

5

Use declare instead of eval:

declare -a "bar=( $( < foo.txt ) )"

This forces everything in the file to be treated as the right-hand side of the assignment. Using eval, the contents of the file can be interpreted as code. For example, if the file contains

Some text ); echo "you just erased all your files"; ( true

then the following is executed by eval:

bar=( Some text ); echo "you just erased all your files"; ( true )

The parentheses in the file balance the parentheses used outside the command substitution, resulting in the text between them in the file being executed as code rather than being used to populate the array.

2
  • It doesn't work. echo ${bar[0]} : ( "The" "quick brown" "fox" "jumps over" "the" "lazy dog." ). check my solution Commented Oct 17, 2012 at 0:12
  • Obviously, I spent more time on the counterexample than the original :) The given file works with "-a", but I am having trouble finding a good example and bad example that both work with the same amount of quoting.
    – chepner
    Commented Oct 17, 2012 at 2:39
1

This works

$ read < foo.txt

$ eval bar=($REPLY)

$ echo ${bar[0]}
The

$ echo ${bar[1]}
quick brown
1
  • 1
    declare "bar=( $(< tmp.txt))" is a little safer; try the eval if the contents of the file are something like "The" ); echo malicious code; ( true.
    – chepner
    Commented Oct 16, 2012 at 23:56
1

Your solution fails because bash normally uses whitespaces for input field separation IFS (and that's also why you get the quotes).

You can use plain read, if you prepare your text for processing:

IFS='"'
read -a bar < <(sed 's/"\([^"]*\)" */\1"/g' foo.txt)
echo ${bar[1]}

quick brown

Note: the sed command transforms the file into this format: The"quick brown"fox"jumps over"the"lazy dog.". I use " as delimiter as that's definitely not used in the words.

1
  • Only works if your separator is a double-quote
    – superk
    Commented Oct 25, 2021 at 16:35
1

Here's one way using GNU awk and the FPAT variable:

IFS=$'\n'
array=($(awk 'BEGIN { FPAT = "([^ ]+)|(\"[^\"]+\")" } { for (i=1; i<=NF; i++) print $i }' file.txt))

echo "${array[1]}"

Result:

"quick brown"
1
  • This actually works and is more elegant than other responses. Break (with the field separator) at the newline character and then read the array as whole lines. I've used IFS=$'\n' array=( `whatever_command_that_emits_lines_of_text` ) many times.
    – superk
    Commented Oct 25, 2021 at 16:35

Not the answer you're looking for? Browse other questions tagged or ask your own question.