32

I have one file with a list of names. I need to loop through all names in this file from an external file with a shell script. How can I do that?

Example files:

scripts/names.txt

alison
barb
charlie
david

scripts/script.sh

NAMES="" #names from names.txt file
for NAME in $NAMES; do
    echo "$NAME"
done

How can I explode the names.txt file into an array in a separate shell script?

oguz ismail
  • 1
  • 16
  • 47
  • 69
Ryan
  • 14,682
  • 32
  • 106
  • 179

5 Answers5

62

One way would be:

while read NAME
do
    echo "$NAME"
done < names.txt

EDIT: Note that the loop gets executed in a sub-shell, so any modified variables will be local, except if you declare them with declare outside the loop.

Dennis Williamson is right. Sorry, must have used piped constructs too often and got confused.

smocking
  • 3,689
  • 18
  • 22
  • 2
    No, since nothing is being piped into `while`, there's no subshell. Redirection into `done` doesn't create a subshell. – Dennis Williamson Jul 05 '12 at 18:24
  • By the way, the issue (bug?) with pipes does not occur with korn shell (but does with pdksh). – cdarke Jul 06 '12 at 08:12
  • Someone should clarify that the file you're looping over should have an empty last line, otherwise the last element won't be considered – Ben May 15 '21 at 17:25
15

You'll be wanting to use the 'read' command

while read name
do
    echo "$name"
done < names.txt

Note that "$name" is quoted -- if it's not, it will be split using the characters in $IFS as delimiters. This probably won't be noticed if you're just echoing the variable, but if your file contains a list of file names which you want to copy, those will get broken down by $IFS if the variable is unquoted, which is not what you want or expect.

If you want to use Mike Clark's approach (loading into a variable rather than using read), you can do it without the use of cat:

NAMES="$(< scripts/names.txt)" #names from names.txt file
for NAME in $NAMES; do
    echo "$NAME"
done

The problem with this is that it loads the whole file into $NAMES, when you read it back out, you can either get the whole file (if quoted) or the file broken down by $IFS, if not quoted. By default, this will give you individual words, not individual lines. So if the name "Mary Jane" appeared on a line, you would get "Mary" and "Jane" as two separate names. Using read will get around this... although you could also change the value of $IFS

Barton Chittenden
  • 4,238
  • 2
  • 27
  • 46
9
cat names.txt|while read line; do
    echo "$line";
done
Arnaud Le Blanc
  • 98,321
  • 23
  • 206
  • 194
5

I know the purists will hate this method, but you can cat the file.

NAMES=`cat scripts/names.txt` #names from names.txt file
for NAME in $NAMES; do
   echo "$NAME"
done
Mike Clark
  • 11,769
  • 6
  • 39
  • 43
  • 5
    This has the advantage of not invoking a sub-shell, but will consider any space in `names.txt` as a delimiter. – smocking Jul 05 '12 at 18:01
  • 2
    The "purists" will helpfully explain what's wrong with this, too. There are multiple issues. See http://mywiki.wooledge.org/DontReadLinesWithFor – tripleee Mar 13 '17 at 12:41
2

This might work for you:

cat <<\! >names.txt
> alison
> barb
> charlie
> david
> !
OIFS=$IFS; IFS=$'\n'; NAMES=($(<names.txt)); IFS=$OIFS
echo "${NAMES[@]}"
alison barb charlie david
echo "${NAMES[0]}"
alison
for NAME in "${NAMES[@]}";do echo $NAME;done
alison
barb
charlie
david
potong
  • 55,640
  • 6
  • 51
  • 83