3

Let's say I have a file named tmp.out that contains the following:

c:\My files\testing\more files\stuff\test.exe
c:\testing\files here\less files\less stuff\mytest.exe

I want to put the contents of that file into an array and I do it like so:

ARRAY=( `cat tmp.out` )

I then run this through a for loop like so

for i in ${ARRAY[@]};do echo ${i}; done

But the output ends up like this:

c:\My
files\testing\more
files\stuff\test.sas
c:\testing\files
here\less
files\less
stuff\mytest.sas

and I want the output to be:

c:\My files\testing\more files\stuff\test.exe
c:\testing\files here\less files\less stuff\mytest.exe

How can I resolve this?

1
  • For your simplified question you can avoid an array and use the loop of @choroba with echo "${line}". I hope you can implement a solution for your more complex real life quest without an array!
    – Walter A
    Commented Nov 25, 2015 at 22:19

3 Answers 3

4

In order to iterate over the values in an array, you need to quote the array expansion to avoid word splitting:

for i in "${values[@]}"; do 

Of course, you should also quote the use of the value:

  echo "${i}"
done

That doesn't answer the question of how to get the lines of a file into an array in the first place. If you have bash 4.0, you can use the mapfile builtin:

mapfile -t values < tmp.out

Otherwise, you'd need to temporarily change the value of IFS to a single newline, or use a loop over the read builtin.

2

You can use the IFS variable, the Internal Field Separator. Set it to empty string to split the contents on newlines only:

while IFS= read -r line ; do
    ARRAY+=("$line")
done < tmp.out

-r is needed to keep the literal backslashes.

12
  • 1
    IFS is for internal line whitespace. You don't need it for this. You do want to set it to blank though IFS= to prevent word-splitting collapsing runs of multiple spaces though. Commented Nov 25, 2015 at 21:17
  • 1
    That said for bash 4+ mapfile is better as Bash FAQ 005 says. Commented Nov 25, 2015 at 21:17
  • @chepner: The reaction was relevant for the previous version of my reply which was indeed wrong.
    – choroba
    Commented Nov 25, 2015 at 21:35
  • Doesn't Bash have read -a ARRAY for reading into an array? Commented Nov 25, 2015 at 21:39
  • 1
    @JonathanLeffler: read -a A puts the individual words taken from one line of stdin into the array A. mapfile -t A puts the individual lines from stdin into the array.
    – rici
    Commented Nov 25, 2015 at 21:41
2

Another simple way to control word-splitting is by controlling the Internal Field Separator (IFS):

#!/bin/bash

oifs="$IFS"  ## save original IFS
IFS=$'\n'    ## set IFS to break on newline

array=( $( <dat/2lines.txt ) )  ## read lines into array

IFS="$oifs"  ## restore original IFS

for ((i = 0; i < ${#array[@]}; i++)) do
    printf "array[$i] : '%s'\n" "${array[i]}"
done

Input

$ cat dat/2lines.txt
c:\My files\testing\more files\stuff\test.exe
c:\testing\files here\less files\less stuff\mytest.exe

Output

$ bash arrayss.sh
array[0] : 'c:\My files\testing\more files\stuff\test.exe'
array[1] : 'c:\testing\files here\less files\less stuff\mytest.exe'
4
  • Assigning a shell variable in a subshell isn't going to be of much use.
    – rici
    Commented Nov 25, 2015 at 21:25
  • @rici - the solution in this case is to move the process out of the subshell. Fixed. Commented Nov 25, 2015 at 21:52
  • Unless you turn off shell globbing, a line containing only * would be replaced with a list of filenames. Commented Nov 25, 2015 at 22:24
  • That is something I did not consider given the sample input, but a very valid point. So if that is a possibility with the data, we would need a set -f prior to redirecting the file into the array and a set +f following. Commented Nov 25, 2015 at 22:31

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