1

I have two specific questions about the IFS. I'm aware that changing the internal field separator, IFS, changes what the bash script iterates over.

So, why is it that the length of the array doesn't change?

Here's my example:

delimiter=$1
strings_to_find=$2

OIFS=$IFS
IFS=$delimiter
echo "the internal field separator is $IFS"
echo "length of strings_to_find is ${#strings_to_find[@]}"

for string in ${strings_to_find[@]}
do
    echo "string is separated correctly and is $string"
done

IFS=$OIFS

But why does the length not get affected by the new IFS?

The second thing that I don't understand is how to make the IFS affect the input arguments.

Let's say I'm expecting my input arguments to look like this:

./executable_shell_script.sh first_arg:second_arg:third_arg

And I want to parse the input arguments by setting the IFS to :. How do I do this? Setting the IFS doesn't seem to do anything. I must be doing this wrong....?

Thank you.

makansij
  • 9,303
  • 37
  • 105
  • 183

2 Answers2

4

Bash arrays are, in fact, arrays. They are not strings which are parsed on demand. Once you create an array, the elements are whatever they are, and they won't change retroactively.

However, nothing in your example creates an array. If you wanted to create an array out of argument 2, you would need to use a different syntax:

strings_to_find=($2)

Although your strings_to_find is not an array, bash allows you to refer to it as though it were an array of one element. So ${#strings_to_find[@]} will always be one, regardless of the contents of strings_to_find. Also, your line:

for string in ${strings_to_find[@]}

is really no different from

for string in $strings_to_find

Since that expansion is not quoted, it will be word-split, using the current value of IFS.

If you use an array, most of the time you will not want to write for string in ${strings_to_find[@]}, because that just reassembles the elements of an array into a string and then word-splits them again, which loses the original array structure. Normally you will avoid the word-splitting by using double quotes:

strings_to_find=(...)
for string in "${strings_to_find[@]}"

As for your second question, the value of IFS does not alter the shell grammar. Regardless of the value of IFS, words in a command are separated by unquoted whitespace. After the line is parsed, the shell performs parameter and other expansions on each word. As mentioned above, if the expansion is not quoted, the expanded text is then word-split using the value of IFS.

If the word does not contain any expansions, no word-splitting is performed. And even if the word does contain expansions, word-splitting is only performed on the expansion itself. So, if you write:

IFS=:
my_function a:b:c

my_function will be called with a single argument; no expansion takes places, so no word-splitting occurs. However, if you use $1 unquoted inside the function, the expansion of $1 will be word-split (if it is expanded in a context in which word-splitting occurs).

On the other hand,

IFS=:
args=a:b:c
my_function $args

will cause my_function to be invoked with three arguments.

And finally,

IFS=:
args=c
my_function a:b:$args

is exactly the same as the first invocation, because there is no : in the expansion.

rici
  • 234,347
  • 28
  • 237
  • 341
2

This is an example script based on @rici's answer :

#!/bin/bash
fun()
{
  echo "Total Params : " $#
}

fun2()
{
  array1=($1) # Word splitting occurs here based on the IFS ':'
  echo "Total elements in array1 : "${#array1[@]}
  # Here '#' before array counts the length of the array
  array2=("$1") # No word splitting because we have enclosed $1 in double quotes
  echo "Total elements in array2 : "${#array2[@]}

}
IFS_OLD="$IFS"
IFS=$':' #Changing the IFS
fun a:b:c #Nothing to expand here, so no use of IFS at all. See fun2 at last
fun a b c
fun abc
args="a:b:c"
fun $args # Expansion! Word splitting occurs with the current IFS ':' here
fun "$args" # preventing word spliting by enclosing ths string in double quotes
fun2 a:b:c

IFS="$IFS_OLD"

Output

Total Params :  1
Total Params :  3
Total Params :  1
Total Params :  3
Total Params :  1
Total elements in array1 : 3
Total elements in array2 : 1

Bash manpage says :

The shell treats each character of IFS as a delimiter, and splits the results of the other expansions into words on these characters.

Community
  • 1
  • 1
sjsam
  • 21,411
  • 5
  • 55
  • 102
  • which `man` page did you find that quote? I seem unable to find the correct manpage for this. – makansij Jun 20 '16 at 07:47
  • 1
    @Sother : `Manual page bash(1) line 1758`. In case of trouble do a pattern search in `bash`. – sjsam Jun 20 '16 at 07:49
  • That quote is useful (and I would have emphasized "results of the other expansions") but there is a clear statement in the last paragraph of that section ("word splitting"): "Note that if no expansion occurs, no splitting is performed." – rici Jun 20 '16 at 14:32