1

I'm not a bash expert but I've been working on quite a long a bash script lately and it's been getting really bothersome quoting my parameters every single time. Is there any way to change this shell behavior? I would like my strings to not be split even when I don't quote them

P.S - setting IFS to null doesn't do the job because then all arguments gets mangled. what I want is that when writing:

func1() {
    echo $1 $2 $3
}

func2() {
    echo "number of arguments func2 got: $#"
} 

func2 $(func1 firstarg "second arg is a multi word string" "third arg is also a multi word string")

would print:

number of arguments func2 got: 3

Edit, thanks for comments for pointing out: since my example is using echo the code would anyway print it got either 1 or 14 arguments depending if you quote the argument to func2 or not. However what I was looking for is a way to return multiple values from a function, without them being mangled, as in the expected result I mentioned

Thanks!

11
  • IMO what you are saying is essentially that writing the language correctly is too troublesome for you so you want to go out of your way to be able to write it incorrectly. Also your test case almost certainly has no practical purpose and is hardly an issue with word splitting, echo would print that all out the same whether quotes are used or not.
    – jesse_b
    Mar 23, 2022 at 15:02
  • @jesse_b You're right that I don't understand why this was chosen as the default behavior in the language, I can't see when this would be useful, and having to quote everything reduces readability and is a source of common omission mistakes. regarding what you mentioned about echo, you're right, I didn't necessarily meant using echo, but was asking if there's some way I could pass return values "as is" without needing to worry exactly about this kind of things. I could define a global variable for return values but it looks like it would be a bad practice.. Mar 23, 2022 at 15:17
  • 1
    How should func2 know how many arguments func1 got, when func1 just returns a string ? That won't be possible in any language I guess.
    – pLumo
    Mar 23, 2022 at 15:58
  • 1
    @zombieParrot You wrote that you don't want to quote your parameters each time, but in your example quoting would not help at all.
    – Philippos
    Mar 24, 2022 at 8:57

2 Answers 2

5

To answer the question in the title, the way to disable word splitting is to set IFS to the empty string.

But what you described in the text was harder, to keep more than one multi-word string distinct after passing them through a command substitution. And there's really no simple way to do that.

The root issue here is that the result of a command substitution (~ the output to stdout of the function called within it) is just a single string (stream of bytes), and you're trying to fit multiple strings in it. In the general case, with arbitrary contents in those strings, that's a rather fundamental problem.

The also isn't too different from storing a command in a variable, just we have command substitution here instead.


A workaround would be to reserve some character to use as a separator, and set IFS to that. With the default IFS, you'd have space, tab and newline as such separators, which obviously doesn't work for strings with whitespace. With IFS set to the empty string, you'd have no such separator, so you'd always get just one field.

But you could set IFS to e.g. just the newline (assuming your strings aren't multi-line):

#!/bin/bash
IFS=$'\n'
foo() {
    printf "%s\n" "$@"
}

nargs() {
    echo "number of arguments nargs got: $#"
} 

nargs $(foo firstarg "multi word string" "also a multi word string")

(You can't use empty strings at the end of the list, though, since the command substitution removes all trailing newlines regardless of IFS.)

Another way is to not use the standard output of a function, but instead pass the name of a variable to the function, and have the function write to the named array via a nameref:

#!/bin/bash
bar() {
    declare -n _out="$1"
    shift
    _out=("$@")
}

nargs() {
    echo "number of arguments nargs got: $#"
} 

bar tmparr firstarg "multi word string" "also a multi word string"
nargs "${tmparr[@]}"

(There's some issues with variable scoping here if a variable called _out exists outside the function too.)

Also note that if you have unquoted expansions (variables or command substitutions), their results will also be subject to filename globbing, so an output like 12 * 34 would get badly mangled, unless you also disable globbing with set -f.

1

What you originally wanted was to disable word splitting after parameter expansion, so instead of writing

mcd() {
  mkdir -p "$1"
  cd "$1"
}

you could write

mcd() {
  mkdir -p $1
  cd $1
}

and it would work with mcd "foo bar" anyhow.

This is the preferred behaviour for most use cases, that's why zsh made it the default behaviour (you can disable it globally with the SH_SPLIT_WORD option or individually by writing ${=1}).

I know you asked for a bash solution, but "use zsh instead of bash" was the answer to so many of my questions, that I really switched to zsh one day and got rid of many problems. Thus, I gladly pass that recommendation to anyone else. IMHO there are few reasons for sticking to bash.

7
  • Are you missing the IFS="" assignment from the second code block? With the default IFS, calling that function as mcd "foo bar" would create the two directories foo and bar, and probably give an error from cd. Or is there something else you meant here?
    – ilkkachu
    Mar 23, 2022 at 23:14
  • @ilkkachu Maybe I did not write this clear enough: bash would do as you describe, zsh would create the foo bar folder and enter it by default (without SH_SPLIT_WORD option).
    – Philippos
    Mar 24, 2022 at 8:14
  • the question title asks for "Any way to disable word splitting in bash?" and it's tagged with bash, and in that context, the code in your second code block just will not work. I have no idea where zsh came into this.
    – ilkkachu
    Mar 24, 2022 at 9:36
  • @ilkkachu It's written in my answer, see the last paragraph. I suspect an X-Y-problem. You have my respect for your solution, but if someone asks me how to cut a tree with a knife, my first suggestion is to use a saw instead. Too many people use bash just as a placeholder for shell.
    – Philippos
    Mar 24, 2022 at 10:29
  • Hiding in the last paragraph the fact that you're suggesting another tool entirely doesn't really help, though. In the context given, the statement "you could write [...] instead and it would work with [...] anyhow" is just untrue.
    – ilkkachu
    Mar 24, 2022 at 10:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .