0

I am trying to set the IFS to ':', but it seems to not work. Here is my code,

FILE="/etc/passwd"

while IFS=':' read -r line; do
        set $line
        echo $1
done < $FILE

When I run my code, it seems to give me the entire line. I have used the set command to assign positional parameters, to extract only the username, but it output the entire line when I try to print the first positional argument $1. What am I doing wrong?

Leuel Asfaw
  • 316
  • 1
  • 14
  • `while IFS=: read -r user_name _; do printf '%s\n' "$user_name"; done – Jetchisel Oct 14 '22 at 17:23
  • @Jetchisel Can you explain you answer? I am new to bash programming. And also why can't I use `set`? – Leuel Asfaw Oct 14 '22 at 17:25
  • 2
    `while IFS=: read -ra gecos_field; do printf '%s\n' "${gecos_field[0]}"; done – Jetchisel Oct 14 '22 at 17:26
  • @Jetchisel I want to extract other information other than username, like group id and user id. Isn't `set` more appropriate for that? – Leuel Asfaw Oct 14 '22 at 17:27
  • `read` builtin in bash has the `a` flag which put the input in an array – Jetchisel Oct 14 '22 at 17:28
  • 2
    Something like this would've been the best available way to do this back in the 1980s. But it was confusing (as you've discovered), and tended to be buggy even when done "correctly", so most modern shells have added features like arrays that give you better options and more control. Word splitting (i.e referencing variable without double-quotes to split it into "words", as you do in `set $line`) also has a lot of problems, and is best avoided; anytime you see a variable or parameter reference without double-quotes around it, it's a sign that something unwise is being done. – Gordon Davisson Oct 14 '22 at 17:43

2 Answers2

2
  • IFS=':' read -r line reads the complete line
  • IFS=':' read -r name remainder puts the first field in the variable name and the rest of the line in remainder
  • IFS=':' read -r name password remainder puts the first field in the variable name, the second field in the variable password and the rest of the line in remainder.
  • etc...

In bash you can use read -a array_var for getting an array containing all the fields, and that's probably the easiest solution for your purpose:

#!/bin/bash

while IFS=':' read -r -a fields
do
    echo "${fields[0]}"
done < /etc/passwd
Fravadona
  • 13,917
  • 1
  • 23
  • 35
  • You have explained it well, but can you tell me why you have used `{ }`? I am new to bash programming – Leuel Asfaw Oct 14 '22 at 17:46
  • @LeuelAsfaw, that is an array , fields is the name of the array and inside of the `[` `]` is the index, counting starts at zero. – Jetchisel Oct 14 '22 at 17:53
  • The `-a fields` says read into indexed array `fields`. The `${fields[index]}` is how you access an element of the array. Generically it is `${array[index]}` where if `index=0`, inside `{..}` either `${array[index]}` or `${array[$index]}` are allowed. – David C. Rankin Oct 14 '22 at 20:00
1

You're only setting IFS for the read command. When you expand the $line variable, it's set back to the original value. You need to set IFS there, not when reading.

oldIFS="$IFS" # save IFS
IFS=:

while read -r line; do
    set $line
    echo $1
done < $FILE

IFS="$oldIFS" # restore original IFS
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • What is oldIFS? and why are you setting it to IFS and resetting it back to IFS? I am new to bash programming. – Leuel Asfaw Oct 14 '22 at 17:33
  • I'm saving it so that `IFS` is restored to its original value after the loop iss done. – Barmar Oct 14 '22 at 17:34
  • Why? I mean I want it to be ':' all the time? Why do I need to set it back to its original value? – Leuel Asfaw Oct 14 '22 at 17:36
  • In case you expand variables later in the script and they should use the normal IFS. If you don't need that, you can leave it out. – Barmar Oct 14 '22 at 17:37
  • Thank you for the comprehensive answer @Barmar. – Leuel Asfaw Oct 14 '22 at 17:38
  • Note that this answer contains a [potential pitfall](http://mywiki.wooledge.org/BashPitfalls#OIFS.3D.22.24IFS.22.3B_.2BICY.3B_IFS.3D.22.24OIFS.22), because an empty IFS and an unset IFS have different behaviors and this solution cannot preserve an unset IFS as written. – tjm3772 Oct 14 '22 at 17:56
  • @tjm3772 I don't think we need to worry much about that. `IFS` is practically always set. But if it's a concern the workaround in that link can be used. – Barmar Oct 14 '22 at 18:51
  • Using word-splitting to separate fields won't work well here. For a line like "root:*:0:0:System Administrator:/var/root:/bin/sh", the "*" will expand to a list of files in the current working directory. – Gordon Davisson Oct 14 '22 at 22:14
  • Good point. That's why Fravadona's answer is better. – Barmar Oct 14 '22 at 22:16