Skip to main content
added 273 characters in body
Source Link
Stéphane Chazelas
  • 584.8k
  • 96
  • 1.1k
  • 1.7k
#! /bin/sh -
for f in "$@"; do
    file -- "$f"
done

Or, even simpler and more portable as for loops over the positional parameters by default:

#! /bin/sh -
for f do
    file -- "$f"
done
#! /bin/bash -
files=()
while getopts "i:" arg; do
  case $arg in
    (i) files+=("$OPTARG");;
  esac
done

for f in "${files[@]}"; do file -- "$f"; done
#! /bin/bash -

unset -v mode
while getopts "ie" arg; do
  case $arg in
    (i) mode=i;;
    (e) mode=e;;
    (*) : handle usage error;;
  esac
done
shift $"$(($OPTINDOPTIND - 1))"

if [case "$mode" = i ]; thenin
   (i) for f in "$@"; do file -- "$f"; donedone;;
elif [ "$mode" = (e ]; then
   ) echo "do something else with the files"
elsefiles";;
   (*) echo "error: invalidmode mode"not >&2specified" >&2;;
fiesac

and then run it as script -i "foo bar" "another file" or script -i -- -file-starting-with-dash- -other-file- or script -i -- *.

Also if your loop only calls file on the files, you could just run file -- "${files[@]}" or file -- "$@" and skip the loop.

and have the script to do one thing for the files foo and bar, and another thing for doo and daa, then that's a bit of a different issue. It can be done, sure, but getopts might not be the tool for that.

#!/bin/sh
for f in "$@"; do
    file "$f"
done
#!/bin/bash
files=()
while getopts "i:" arg; do
  case $arg in
    i) files+=("$OPTARG");;
  esac
done

for f in "${files[@]}"; do file "$f"; done
#!/bin/bash

while getopts "ie" arg; do
  case $arg in
    i) mode=i;;
    e) mode=e;;
  esac
done
shift $(($OPTIND - 1))

if [ "$mode" = i ]; then
    for f in "$@"; do file "$f"; done
elif [ "$mode" = e ]; then
    echo "do something else with the files"
else
    echo "error: invalid mode" >&2
fi

and then run it as script -i "foo bar" "another file".

Also if your loop only calls file on the files, you could just run file "${files[@]}" or file "$@" and skip the loop.

and have the script to do one thing for the files foo and bar, and another thing for doo and daa, then that's a bit of a different issue. It can be done, sure, but getopts might not be the tool for that.

#! /bin/sh -
for f in "$@"; do
    file -- "$f"
done

Or, even simpler and more portable as for loops over the positional parameters by default:

#! /bin/sh -
for f do
    file -- "$f"
done
#! /bin/bash -
files=()
while getopts "i:" arg; do
  case $arg in
    (i) files+=("$OPTARG");;
  esac
done

for f in "${files[@]}"; do file -- "$f"; done
#! /bin/bash -

unset -v mode
while getopts "ie" arg; do
  case $arg in
    (i) mode=i;;
    (e) mode=e;;
    (*) : handle usage error;;
  esac
done
shift "$((OPTIND - 1))"

case "$mode" in
  (i) for f do file -- "$f"; done;;
  (e) echo "do something else with the files";;
  (*) echo "error: mode not specified" >&2;;
esac

and then run it as script -i "foo bar" "another file" or script -i -- -file-starting-with-dash- -other-file- or script -i -- *.

Also if your loop only calls file on the files, you could just run file -- "${files[@]}" or file -- "$@" and skip the loop.

and have the script do one thing for the files foo and bar, and another thing for doo and daa, then that's a bit of a different issue. It can be done, sure, but getopts might not be the tool for that.

added 804 characters in body
Source Link
ilkkachu
  • 147.9k
  • 16
  • 268
  • 441

Either have the user use the -i option repeatedly, collecting the filenames to an array, so:

and run as script -i "foo bar" -i "another file". (Running script -i file1 file2 would have file2 ignored.) Similarly you could add another array to collect filenames given through another option.

I'm assuming here that you are also doing something else with getopts other than taking the -i in, since otherwise you could just drop the flag entirely. :) But what other options you have, somewhat affects what the most sensibly (or customary) solution is.

and have the script to do one thing for the files foo and bar, and another thing for doo and daa, then that's a bit of a different issue. It can be done, sure, but getopts might not be the tool for that.

See also:

And of course:

for the issues with trying to deal with multiple distinct arbitrary strings (filenames) within a single variable.

Either have the user use the -i option repeatedly, so:

and run as script -i "foo bar" -i "another file". (Running script -i file1 file2 would have file2 ignored.)

I'm assuming here that you are also doing something else with getopts other than taking the -i in, since otherwise you could just drop the flag entirely. :)

and have the script to do one thing for the files foo and bar, and another thing for doo and daa, then that's a bit of a different issue. It can be done, sure, but getopts might not be the tool for that.

Either have the user use the -i option repeatedly, collecting the filenames to an array, so:

and run as script -i "foo bar" -i "another file". (Running script -i file1 file2 would have file2 ignored.) Similarly you could add another array to collect filenames given through another option.

I'm assuming here that you are also doing something else with getopts other than taking the -i in, since otherwise you could just drop the flag entirely. :) But what other options you have, somewhat affects what the most sensibly (or customary) solution is.

and have the script to do one thing for the files foo and bar, and another thing for doo and daa, then that's a bit of a different issue. It can be done, sure, but getopts might not be the tool for that.

See also:

And of course:

for the issues with trying to deal with multiple distinct arbitrary strings (filenames) within a single variable.

Source Link
ilkkachu
  • 147.9k
  • 16
  • 268
  • 441

There's a bit of a difference between the four script invocations you posted.

./script.sh -i "'foo bar' 'another file'"
./script.sh -i "foo\ bar another\ file"

The above two both pass to the script -i as the first argument, and a single string as the second. In the first one, that's 'foo bar' 'another file', and in the second, it's foo\ bar another\ file. In the shell language, both are valid ways to present the two strings (or filenames) foo bar and another file. But the quote and backslash processing only applies when the strings are on a raw command line, not when they're inside a variable, as they end up in the string.

./script.sh -i foo\ bar another\ file
./script.sh -i 'foo bar' 'another file'

On the other hand, these two pass a total of three arguments: -i, foo bar, and another file.

The difference is somewhat important in that it's much easier to deal safely with distinct arguments. You just need to keep them intact, and don't have to process the quotes and escapes embedded within.

Also, importantly, running something like script ./*.txt will pass the filenames as distinct arguments.

E.g. this would just call file on both files if called as script 'foo bar' another\ file:

#!/bin/sh
for f in "$@"; do
    file "$f"
done

But you have the getopts there, too. And with the filenames as distinct arguments, only the first would appear as the argument to -i. Here, there's basically two common options.

Either have the user use the -i option repeatedly, so:

#!/bin/bash
files=()
while getopts "i:" arg; do
  case $arg in
    i) files+=("$OPTARG");;
  esac
done

for f in "${files[@]}"; do file "$f"; done

and run as script -i "foo bar" -i "another file". (Running script -i file1 file2 would have file2 ignored.)

Or, have the option set the "mode" the script works in, and take the filenames as a list distinct from the options. getopts leaves all the arguments intact, you'll just have to drop the ones it processed with shift. So:

#!/bin/bash

while getopts "ie" arg; do
  case $arg in
    i) mode=i;;
    e) mode=e;;
  esac
done
shift $(($OPTIND - 1))

if [ "$mode" = i ]; then
    for f in "$@"; do file "$f"; done
elif [ "$mode" = e ]; then
    echo "do something else with the files"
else
    echo "error: invalid mode" >&2
fi

and then run it as script -i "foo bar" "another file".

I'm assuming here that you are also doing something else with getopts other than taking the -i in, since otherwise you could just drop the flag entirely. :)

Also if your loop only calls file on the files, you could just run file "${files[@]}" or file "$@" and skip the loop.


However, if you want to be able to this:

script -i foo bar -e doo daa

and have the script to do one thing for the files foo and bar, and another thing for doo and daa, then that's a bit of a different issue. It can be done, sure, but getopts might not be the tool for that.