0

I understand that in Bourne shell and derivates, IFS= sets IFS to null, so the shell won't perform any field splitting.

I recall reading about:

  • echo "$*"
  • IFS= echo "$*"

When I ran both commands, once in CentOS and once in WSL (uname -o = GNU/Linux only so I don't know the distribution) I only got empty lines.

What does the above test command mean and is it really a good case to learn about IFS (if not, what simple, two lines, test would you suggest)?

6
  • 1
    This is not a good test case. First, because in an interactive shell there generally aren't any arguments, so "$*" expands to nothing, and second because using IFS=something doesn't affect how that command's arguments (like "$*") are expanded, it only takes effect on the command as it runs, and echo isn't affected by IFS. Mar 19 at 5:58
  • @GordonDavisson much thanks ! What you wrote is in my opinion the perfect first part for an answer, if you just add one more passage about a simple way about how to test default IFS versus null IFS than this would make a great answer. I have even edited the question to make it fitted for such an answer. Mar 19 at 6:05
  • Furthermore @GordonDavisson comments can be deleted automatically with time and it would be a shame for such a beautiful didactic explanation to go to waste. Mar 19 at 6:06
  • 2
    Note that in the Bourne shell, "$*" joins the positional parameters with SPC regardless of the value of $IFS. The joining with the first character of $IFS is a Korn shell invention. Mar 19 at 7:17
  • 1
    SPC is a name for the U+0020 character. Also known as space (though there's a plethora of spacing characters, so using a code name like SPC for the most common one makes it potentially less ambiguous). That's usually the character sent by your terminal or terminal emulator when you press the "space bar" key on English keyboards in English locales at least. Mar 19 at 7:30
6

Before I answer, here's a warning: changing IFS tends to have really confusing effects. There are a few places where it's relatively safe and has well-defined effects (like setting it for the read command, as in IFS= read -r line and IFS=, read -r field1 field2 field3), but trying to understand its behavior in general is, in my opinion, more work than it's worth. Also, if you do change it, either isolate it to a particular command by doing the assignment as a prefix to the command (again, as in IFS= read -r line) or by setting it back to normal as soon as possible after whatever you need to change it for.

Now to the question: these aren't good examples of IFS effects, first because $* expands to the current argument list, and interactive shells don't generally have arguments, so it's going to expand to the empty string. You can change that with the set command if you want. Here's an example:

$ echo "$*"

$ set -- "foo bar" "baz/quux"
$ echo "$*"
foo bar baz/quux

Second, because changing IFS as a prefix to a particular command (as in IFS= echo "$*") only affects the command as it runs, not while the shell is expanding its arguments (which happens before the command itself runs), and echo itself isn't affected by IFS (it always sticks its arguments together with spaces, no matter what IFS is set to). For example:

$ IFS= echo "$*"    # No effect, because IFS is only set for echo, which ignores it
foo bar baz/quux

Contrast that with setting IFS for the shell itself:

$ saveIFS=$IFS      # Save the normal IFS, so we can set it back later
$ IFS=              # Set IFS for the entire shell
$ echo "$*"         # Now we'll see an effect
foo barbaz/quux
$ IFS='+-*/'        # Let's try another value
$ echo "$*"         # Now the arguments get merged, first char of IFS beween them
foo bar+baz/quux
$ echo $*           # See below
foo bar baz quux
$ printf '%s\n' $*  # See below
foo bar
baz
quux
$ IFS=$saveIFS      # Set it back to normal, to avoid trouble later

So what happened with echo $* when IFS was "+-*/"? Well, IFS is used both to merge and split arguments, so just like with echo "$*", the $* bit expanded to foo bar+baz/quux, but then since it wasn't double-quoted it then immediately got split into words (using all of the characters in IFS), so it got split into three arguments to echo: "foo bar" "baz", and "quux", and echo then stuck those all together with spaces in between. Essentially the same thing happens with the printf command, except the format string makes it print each following argument on a separate line, so you can see that "foo bar" is just one argument.

BTW, some people use the saveIFS approach like I used here, some prefer to unset IFS afterward, which makes the shell act as if IFS was back to its normal space-tab-newline value. Both work, but not together. If you unset IFS, and then later try to save & restore it, it'll come out set to the empty string, and things will be weird. You could also explicitly reset it with IFS=$' \t\n', but that's not portable to all shells (and will cause really weird effects in shells that don't support $' '). Sigh.

EDIT: See this question for more options & discussion of how to save & restore the value of IFS.

BTW2, in most situations you don't want the arguments either merged or split, so what you want is "$@". Unlike $*, $@ doesn't merge the arguments, and with double-quotes it doesn't split them either (or expand them as filename wildcards) -- it just passes them straight through. But don't use args="$@", because that actually does merge them like "$*" (or maybe with " " instead of the first char of IFS, depending on the shell) (it has to merge them to save them in a plain variable). Use args=("$@") instead, to save them as an array.

8
  • 1
    You make it sound like "$@" is the same as "$*" in a scalar assignment. It is not. The bash shell would not use the first character of $IFS as delimiter with "$@", but space (always).
    – Kusalananda
    Mar 19 at 9:43
  • 1
    Another way to temporarily "save and restore" IFS: IFS=_$IFS (do stuff) IFS=${IFS#?}. (This sets the first character of IFS to an _, and then removes it).
    – Kusalananda
    Mar 19 at 9:48
  • @Kusalananda Hah, I didn't know that about args="$@". From a quick test, bash and ksh use "` ", but zsh and dash use the first char of IFS`. Fortunately, this isn't really something you should do, so not an incompatibility I'm going to worry about. Mar 19 at 10:05
  • Yeah, using "$@" in a scalar context doesn't really make much sense. That's where you use "$*" after all.
    – Kusalananda
    Mar 19 at 10:07
  • 1
    Luckily (/s), you can always do something like unset savedIFS; [ "${IFS+set}" ] && savedIFS=$IFS;, then do your thing, and then unset IFS; [ "${savedIFS+set}" ] && IFS=$savedIFS; ...
    – ilkkachu
    Mar 19 at 10:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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