5

I'm writing a small script that I'd like to make it working from a piped input or else from the command-line arguments.

my_function() {
  for i in "$@"; do
    echo "$i"
    echo -----
  done
}

if [ -t 0 ]; then
  my_function "$@"    # command-line arguments
else
  my_function $(cat)  # pipe
fi

These options work:

$ my_script 1 2 3 4
$ echo 1 2 3 4 | my_script
1
-----
2
-----
3
-----
4
-----

This also works fine:

$ my_script 1 "2 3" 4
1
-----
2 3
-----
4
-----

The problem comes when I'm trying to keep positional parameters from a pipe:

$ echo 1 "2 3" 4 | my_script
1
-----
2
-----
3
-----
4
-----

$ echo 1 "'2 3'" 4 | my_script
1
-----
'2
-----
3'
-----
4
-----

Instead of my_function $(cat), I've tried to use read without good results, something like:

read input
my_function $input

Any ideas on this? I know word splitting is hard.

  • You quote $@, so why not quote $(cat)? – Kusalananda May 1 '17 at 20:00
  • 3
    It still wouldn't work. The answer is "use eval", which means the answer is "rethink what you're doing". – Michael Homer May 1 '17 at 20:02
  • 1
    I agree with @MichaelHomer however if the use case is narrow, xargs might do the trick - check out the output from echo 1 '"2 3"' 4 | xargs -n 1 – iruvar May 1 '17 at 20:06
  • Your first stdin example (echo 1 "2 3" 4 | my_script) can not be made to work. Your command just receives 1 2 3 4 on stdin; it has absolutely no way to know that echo was passed 3 arguments instead of 4. Only your last example stands a chance. – derobert May 1 '17 at 20:48
  • @iruvar Probably you should make that an answer. Especially if you include in that answer an explanation of why it's hard. – derobert May 1 '17 at 20:49
3

Have a look in this technique with bash 4.4:

function myf { 
  if ! [ -t 0 ];then 
    declare -a args=\($(</dev/stdin)\);
    set -- "${args[@]}"; 
  fi;
  for i in "$@";do 
    echo "$i";
    echo -----;
  done 
}

Testing:

$ function myf { if ! [ -t 0 ];then declare -a args=\($(</dev/stdin)\);set -- "${args[@]}"; fi;for i in "$@";do echo "$i";echo -----;done }
$ myf 1 "2 3" 4
1
-----
2 3
-----
4
-----

$ echo $'1 "2 3" 4'|myf
1
-----
2 3
-----
4
-----

Same result can be achieved also with:

$ echo 1 \"2 3\" 4 |myf
$ echo 1 "'2 3'" 4 |myf

But echo 1 "2 3" 4 is not going to work because double quotes are ignored by bash in this syntax:

$ echo 1 "2 3" 4
1 2 3 4
|improve this answer|||||

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.