1

Using dash (0.5.10.2), I can do this:

% dash
$ set -- x hello world
$ echo "<${*#x }>"
<hello world>

This is the behavior I expect. The contents of $* (which are x hello world as assigned by set and delimited by spaces) are run through shell parameter expansion to remove any leading for echo, thus resulting in hello world, which I'm echoing with surrounding brackets to demonstrate the lack of surrounding white space.

I can't replicate that in bash (5.0.2(1)-release). It appears the space, a delimiter, is inaccessible:

% bash
$ set -- x hello world
$ echo "<${*#x }>"
<x hello world>
$ echo "<${@#x }>"     # trying $@ instead of $*
<x hello world>
$ echo "<${*#x}>"      # without the space works but now I have a space
< hello world>
$ echo "<${*#x?}>"     # trying the `?` wildcard for a single character
<x hello world>
$ echo "<${*#x\ }>"    # trying to escape the space
<x hello world>
$ echo "<${*/#x /}>"   # using bash pattern substitution instead
<x hello world>
$ echo "<${*#x$IFS}>"  # trying the input field separator variable
<x hello world>

Is there a solution here? Perhaps some way of modifying $* or changing the output field separator?

My current workaround is to assign it to a temporary variable, but that's pretty ugly. (I need bash or else I'd stick with /bin/sh, which is dash.)

Adam Katz
  • 14,455
  • 5
  • 68
  • 83
  • 1
    As I read http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02: *If parameter is `#`, `*`, or `@`, the result of the expansion is unspecified.* -- so both are equally correct, and a script that wants to work on all POSIX platforms shouldn't rely on either behavior. (`var=$*` and `${var// /}`, and you're set). – Charles Duffy Mar 08 '19 at 22:36
  • @CharlesDuffy - Err... Um.. isn't `${var// /}` *parameter expansion with substring replacement* .. a bashism? POSIX only provides *suffix* and *prefix* removal as I read it. – David C. Rankin Mar 08 '19 at 23:12
  • @DavidC.Rankin, quite right. Still, the point that `var=$*` avoids relying on undefined behavior stands. – Charles Duffy Mar 08 '19 at 23:14
  • @DavidC.Rankin – This is a bash question, so the bashism is okay ... though I was using `var="$*"; echo "${var#x }"` to the same effect because I so rarely script in bash and I try to limit my bash scripts' bashisms to where they're absolutely necessary. – Adam Katz Mar 08 '19 at 23:15
  • Agreed, I was just confused by your apparent use of `dash`. – David C. Rankin Mar 08 '19 at 23:15
  • If you just want to discard the first argument before concatenating those that remain, `shift` is your friend; to avoid changing `$1`, you can scope it to a function: `myfunc() { shift; printf '%s\n' "$*"; }; concatenationOfAllArgsButTheFirst=$(myfunc "$@")"` leaves the original value of `"$@"` alone. – Charles Duffy Mar 08 '19 at 23:16
  • @CharlesDuffy – If I didn't care about preserving that array, I could indeed `shift`. The function is a bit unwieldy in the face of that other guy's more elegant (bashism) `${*:2}` (see the comment to his answer). – Adam Katz Mar 08 '19 at 23:18
  • Yes, I'd definitely suggest the bashism if portability isn't a priority. – Charles Duffy Mar 08 '19 at 23:22
  • This whole mess started when I had to ~upgrade my script from /bin/sh (dash) to bash just for `read -t` to allow easier skipping of a `sleep` call. (This should answer @DavidC.Rankin's confusion.) – Adam Katz Mar 08 '19 at 23:27

1 Answers1

2

For array-like operands, the string operation is applied for each element before they are joined on spaces. Therefore, you can't apply them to the joining space.

Here's an example showing this:

$ set -- hello world "hello world with spaces"
$ echo "${*// /<SPACE>}"
hello world hello<SPACE>world<SPACE>with<SPACE>spaces

Spaces within each argument is replaced just fine, but the spaces between them as inserted by $* are not affected.

The workaround is indeed a temporary variable.

that other guy
  • 116,971
  • 11
  • 170
  • 194
  • Ah, nice demonstration. `echo "<${*#hello }>"` (using your `set` line) better fits what I'm doing, and sure enough it gives me `` because of the implicit _for each_ that you noted. – Adam Katz Mar 08 '19 at 22:37
  • 3
    If your real question is "How do I concatenate all arguments except the first one?" then the answer is `"${*:2}"` – that other guy Mar 08 '19 at 22:41
  • Actually, yes: that's the literal answer to my (oversimplified) question, though I think your first paragraph and Charles Duffy's comment to the question combine for a more informative answer. (Also, I had a whole bunch of conditionals like `[ "$*" != "${*#* foo* bar}" ]` which I've had to convert to `case` patterns like `( *\ foo*\ bar* )` but that didn't make it to the simplified question I asked here.) – Adam Katz Mar 08 '19 at 22:50