2

I want to execute a shell script that require 3 arguments.

The argument number 2 contains a string with space

I want to put all arguments in one variable like this:

Linux:~# kk="\"111\" \"222 222\" \"333\""
Linux:~# echo $kk
"111" "222 222" "333"

Now If I call a function:

func() {
  echo ---$1---
  echo ---$2---
  echo ---$3---
}

with the $kk variable in this way

func $kk

Then it will return

Linux:~# func $kk
---"111"---
---"222---
---222"---

And I was expecting to get this result

---111---
---222 222---
---333---

How to solve this issue without using eval ?

I know that the eval solve this issue but I do not want to use it (since it takes time if I execute many time a such call).

Lesmana
  • 25,663
  • 9
  • 82
  • 87
MOHAMED
  • 41,599
  • 58
  • 163
  • 268
  • 3
    You can't do this without `eval`. See http://mywiki.wooledge.org/BashFAQ/050 for details. – Etan Reisner Feb 16 '15 at 13:49
  • It can be done with arrays if targeting bash rather than POSIX (why is this tagged with two different shells?) – Charles Duffy Feb 16 '15 at 13:56
  • BTW, performance is a relatively minor reason to avoid `eval` -- the potential security impact is a much larger concern. – Charles Duffy Feb 16 '15 at 13:58
  • 1
    ...even in ash, where there aren't proper arrays supported, this can be done using a function's command line argument as temporary safe array storage (or completely overriding the global `"$@"`, if one doesn't need it and doesn't want to use a function for scoping). – Charles Duffy Feb 16 '15 at 13:59
  • OP, you originally tagged the question [tag:bash]. Do you want a [tag:bash] answer or a POSIX shell solution? – gniourf_gniourf Feb 16 '15 at 14:00
  • 1
    Putting more than one argument in a single scalar variable, by the way, is impossible (without explicit parsing) for a simple reason: A scalar variable can store any value other than NUL. An argument can likewise store any value other than NUL. Thus, there's no way to tell if `"111" "222 222" "333"` is meant to be exactly one argument containing literal quotes, four arguments containing literal quotes but split on whitespace, or three arguments with no quotes -- but imposing parsing rules on expansion would be a rat's nest of security bugs, so Not Doing It is safer, thus shells behave thus. – Charles Duffy Feb 16 '15 at 14:05
  • (indeed, the existing simple string-splitting and glob matching done on unquoted expansions are confusing enough, and it's understandable why zsh chooses to break POSIX by not doing them by default). – Charles Duffy Feb 16 '15 at 15:22
  • 1
    "I want to put all arguments in one variable like this"... No, you don't. Really. What you want to do is redesign the assumptions being made elsewhere that make you think you want to do this... – twalberg Feb 16 '15 at 19:51

2 Answers2

3

OP originally tagged question , then removed the tag. Please refer to Charles Duffy's great answer for a POSIX shell solution! the solution below uses arrays and is only suitable for .


You should not put your data in a string like this, but in an array. By the way, you're missing some quotes in your function:

func() {
    echo "---$1---"
    echo "---$2---"
    echo "---$3---"
}

or better yet (printf is preferable to echo):

func() {
    printf -- '---%s---\n' "$1"
    printf -- '---%s---\n' "$2"
    printf -- '---%s---\n' "$3"
}

$ kk=( "111" "222 222" "333" )
$ func "${kk[@]}"

will happily print:

---111---
---222 222---
---333---

You can even include newlines and such in your arguments:

$ kk=( $'one\none' "222 222" $' *   three\nthree' )
$ func "${kk[@]}"
---one
one---
---222 222---
--- *   three
three---
gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
3

If features (like arrays) didn't make bash more expressive than POSIX sh, there would have been no reason to add them. :)

That said, you can work around it by overriding "$@" for your needs:

set -- 111 "222 222" 333
printf '%s\n' "$@"

...will print...

111
222 222
333

If you need to build the arguments list step by step, you can make several calls to set with "$@" as the first arguments (make sure you observe the quotes!):

set -- 111
set -- "$@" "222 222"
set -- "$@" 333
printf '%s\n' "$@"

...will print...

111
222 222
333
gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441