4

I have a list of values, separated by ':' and I want to process them one by one.

When the delimiter is space, there are no problems:

nuclear@korhal:~$ for a in 720 500 560 130; do echo $a; done
720
500
560
130

But after settings IFS (Internal Field Separator) to : , strange things start to happen:

nuclear@korhal:~$ IFS=":" for a in 720:500:560:130; do echo $a; done;
bash: syntax error near unexpected token `do'

If I skip all semicolons, when IFS is set:

nuclear@korhal:~$ IFS=":" for a in 720:500:560:130 do echo $a done;
Command 'for' not found, did you mean:
  command 'vor' from deb vor (0.5.8-1)
  command 'fop' from deb fop (1:2.5-1)
  command 'tor' from deb tor (0.4.4.5-1)
  command 'forw' from deb mailutils-mh (1:3.9-3.2)
  command 'forw' from deb mmh (0.4-2)
  command 'forw' from deb nmh (1.7.1-7)
  command 'sor' from deb pccts (1.33MR33-6build1)
  command 'form' from deb form (4.2.1+git20200217-1)
  command 'fox' from deb objcryst-fox (1.9.6.0-2.2)
  command 'fort' from deb fort-validator (1.4.0-1)
  command 'oor' from deb openoverlayrouter (1.3.0+ds1-3)
Try: sudo apt install <deb name>

Bash does not recognize the for command at all. If there was no IFS set in this case, it will show the prompt, because it expects more output (normal behaviour)

What is happening when the IFS is set to custom character? Why the for loop does not work with it?

I am using Kubuntu 20.10 Bash version 5.0.17

Nuclear
  • 174
  • 1
  • 1
  • 6

3 Answers3

9

Keywords aren't recognized after an assignment. So, the for in IFS=blah for ... just runs a regular command called for, if you have one:

$ cat > ./for
#!/bin/sh  
echo script for
$ chmod +x ./for 
$ PATH=$PATH:.
$ for x in a b c
> ^C
$ foo=bar for x in a b c
script for

But because Bash parses the whole input line before running it, the keyword do causes a syntax error before that happens.

This is similar with redirections in place of the assignment: Can I specify a redirected input before a compound command? And also see Why can't you reverse the order of the input redirection operator for while loops? for the gory details about how the syntax is defined.

Also see: How do I use a temporary environment variable in a bash for loop?

My Zsh is stricter:

$ zsh -c 'foo=bar for x in a b c'
zsh:1: parse error near `for'

But Zsh does allow redirections there before a compound command. This outputs the three lines to test.txt:

$ zsh -c '> test.txt for x in a b c ; do echo $x; done '

Besides, note that IFS won't be used to split a static string like 720:500:560:130, word splitting only works for expansions. So:

$ IFS=":"
$ for a in 720:500:560:130; do echo "$a"; done;
720:500:560:130

but,

$ IFS=":"
$ s=720:500:560:130
$ for a in $s; do echo "$a"; done;
720
500
560
130
ilkkachu
  • 138,973
0

for

As required by POSIX some words (including for) must be either:

  1. The first word of a command
  2. The first word following one of the reserved words other than case, for, or in
  3. The third word in a case command (only in is valid in this case)
  4. The third word in a for command (only in and do are valid in this case)

That is not the case here. That's why for is not understood by the shell and an error is reported.

This question (about temporary IFS) is quite similar to When can I use a temporary IFS for field splitting?. But that answer only covers simple commands (This is a compound command).

Additionally:

IFS

The value of IFS only affects expansions (expand a variable).

The value of IFS must be set in a previous command before it has an effect:

$ a="one:two:three"
$ echo $a
one:two:three

$ ( > IFS=":" > echo $a > ) one two three

$ ( IFS=":"; echo $a ) one two three

But NOT :

$ ( IFS=":" echo $a )
one:two:three

For that to work you need to delay the execution of the command:

$ ( IFS=":" eval echo '$a' )
one two three

So, for your script , you will need to do something like:

b="720:500:560:130"
( IFS=":";
  for      a in $b   ;
  do       echo "$a" ;
  done
)

Note that since $b is being expanded unquoted any glob character (*,[, ? and others with extglob set) will be also expanded. Maybe generating a list of files instead of only one.

Isaac
  • 1
  • 1
-1

Rather than struggle with IFS magic, use Stephen Collyer's "path" functions. They allow a new type of data in bash: the colon-separated list. addpath, delpath, listpath, etc. I use these functions on my PATH, MANPATH, to allow locate to search offline media, and in general bash tasks.

Here's how, from an AskUbuntu answer of mine:

I use Stephen Collyer's bash_path_funcs, described in Linux Journal way back in 2000:

https://www.linuxjournal.com/article/3645 https://www.linuxjournal.com/article/3768 https://www.linuxjournal.com/article/3935

The addpath function adds an entry to a path only if it is not there in the first place. delpath -n deletes all non-existent directories from a path.

You can get the pathfunc.tgz file from https://web.archive.org/web/20061210054813/http://www.netspinner.co.uk:80/Downloads/pathfunc.tgz

waltinator
  • 4,865