Separating Bourne / POSIX Shell Parameters

Suppose I have some kind of utility that can take several options, each of which follows a file name. For example, I could call it myutil , myutil -o somefile , myutil -p anotherfile , myutil -o somefile -p anotherfile , etc. I want to write a POSIX shell shell script that can call myutil with arbitrary combinations of options (depending on some conditions internal to the shell script that are not relevant to this issue).

I was thinking of something like:

 #!/bin/sh file1=somefile file2=anotherfile if [ somecriteria = true ]; then OPT1="-o $file1" fi if [ othercriteria = true ]; then OPT2="-p $file2" fi myutil $OPT1 $OPT2 

This works great if no file name has spaces. Assuming both myutil are true, myutil gets $ 1 = [-o], $ 2 = [somefile], $ 3 = [-p] and $ 4 = [anotherfile]. However, if there are spaces, for example, if file1="some file" , $ 1 = [-o], $ 2 = [some], $ 3 = [file], etc. Of course, I want it to be for $ 2 = [some file].

Putting another set of quotes around the file name in OPT1 and OPT2 does not help; for example, if I change it to OPT1="-o \"$file1\"" , it just gets me 2 $ = ["some] and $ 3 = [file"]. And the inclusion of quotes around $ OPT1 and $ OPT2 in the call to myutil does not work either: if I do this, $ 1 = [-o some file].

So, is there any trick to make this work, or is there some other approach that will do what I want? I would like this to adhere to the standard shell functions, so not bash-ism or ksh-isms, please :) See this for a description of what is in the standard.

+4
source share
7 answers

Looks like you found a decent solution for POSIX. However, you can use set to maintain the invocation of your program as myutil " $@ " . Your decision becomes a little cumbersome as the number of possible parameters increases.

 #!/bin/sh file1=somefile file2=anotherfile if [ somecriteria = true ]; then set -- "-o" "$file1" fi if [ othercriteria = true ]; then set -- " $@ " "-p" "$file2" fi myutil " $@ " 

Example

 #!/bin/sh file1="some file" file2="another file" # Default to '1' if not overwritten : ${x:=1} : ${y:=1} if [ $x -eq 1 ]; then set -- "-o" "$file1" fi if [ $y -eq 1 ]; then set -- " $@ " "-p" "$file2" fi printf "[%s]" " $@ " echo 

Output

 $ x=0 y=0 ./opt.sh [] $ x=0 y=1 ./opt.sh [-p][another file] $ x=1 y=0 ./opt.sh [-o][some file] $ x=1 y=1 ./opt.sh [-o][some file][-p][another file] 
+2
source

After I started working with him more, I found another approach that seems to do what I want:

 #!/bin/sh file1="some file" file2=anotherfile if [ somecriteria = true ]; then OPT1="$file1" fi if [ othercriteria = true ]; then OPT2="$file2" fi myutil ${OPT1:+-o "$OPT1"} ${OPT2:+-p "$OPT2"} 

The $ {parameter: + word} construct will be replaced with the word if $ {parameter} is specified; if it is not installed, it leaves. Therefore, if $OPT1 not specified, ${OPT1:+-o "$OPT1"} disappears - in particular, it does not turn into an empty string in argv . If $OPT1 set to some file , the above expression is replaced with -o "some file" and myutil gets $ 1 = [-o], $ 2 = [some file] as I want.

Please note that myutil ${OPT1:+-o} "$OPT1" ${OPT2:+-p} "$OPT2" does not do exactly what I want, because if $OPT1 not set, -o disappears, but "$OPT1" turns into an empty string - $ 1 = [], $ 2 = [- p], $ 3 = [anotherfile]

(Edited at the suggestion of Dennis)

+3
source

First of all, you need to specify the options on this line sh myutil.sh "$OPT1" "$OPT2"

And here is a working implementation without any special-changes using getopts on the side of myutil.sh.

This script calls myutil.sh:

 #!/bin/sh somecriteria=true othercriteria=true file1="some file" file2="other file" if [ $somecriteria = true ]; then OPT1="-o$file1" fi if [ $othercriteria = true ]; then OPT2="-p$file2" fi sh myutil.sh "$OPT1" "$OPT2" 

And here is what myutil.sh might look like:

 #!/bin/sh OPTIND=1 while getopts "o:p:" opt; do case "$opt" in o) file1=$OPTARG ;; p) file2=$OPTARG ;; esac done shift $((OPTIND-1)) echo 'File 1: "'$file1'"' echo 'File 2: "'$file2'"' 

As you can see in the output of myutil.sh, spaces are saved in the file names:

 File 1: "some file" File 2: "other file" 
+2
source

Why use a simple quote instead of a double quote?

 if [ somecriteria = true ]; then OPT1="-o '$file1'" fi if [ othercriteria = true ]; then OPT2="-p '$file2'" fi 
+1
source

I think your own solution using $ {OPT1 + -o "$ OPT1"} is good, and I don’t see any problems with this utility, but there is another approach using eval that no one has mentioned, which is even closer to your source code:

 #!/bin/sh FILE1='some file' FILE2='another file' if [ somecriteria = true ]; then OPT1="-o '$FILE1'" fi if [ othercriteria = true ]; then OPT2="-p '$FILE2'" fi eval myutil "$OPT1" "$OPT2" 

This will lead to what you want.

But you need to be careful if your file names contain single quotes as part of the literal file name string.

If you know exactly what your names look like when writing a script, just make sure that your literals with file names do not come out because you avoid placing the file name.

But this is even more important when you are processing user input or otherwise gaining access to the environment. - for example, if $ FILE1 is defined as abc'; /tmp/malicious_program ' abc'; /tmp/malicious_program ' and then you do eval, it will parse myutil string into:

 myutil -o 'abc'; /tmp/malicious_program '' -p 'another file' 

.. which is two separate commands and can be a massive security hole, depending on how exactly this script is executed relative to the object that created / tmp / malicious _program, and set $ FILE1.

In these cases, if you want to introduce a dependency on sed, you can do something like this in the first place:

 FILE1=\'`printf %s "$FILE1" | sed "s/'/'\\\\''/g"`\' 

.. this will lead to a good single-line shielded file name, while any quotation marks inside it will also be escaped.

Since in the Bourne / POSIX shell, nothing but a single quote can escape from the same line with quotation marks, so I use a single quote in my example. A double quote avoiding such things is possible, but your sed command should be much more complicated, as there are several other characters that you need to avoid inside double quotes (from my head: instead of just running away from quotes, you avoid double quotes , backslashes, backslashes, dollar signs, and possibly other things that I don’t think about).

PS: because I found that this "porting to the escaping shell and then eval later" is suitable in several cases, at least in one where it was absolutely necessary, I wrote a tiny C program (and an equivalent shell function using sed), which wraps all of its arguments in single shell caching, as described above, in case someone wants to use it instead of implementing their own: esceval

+1
source

A possible implementation below is the bash arrays tutorial here .

 #!/bin/sh function myutil { local a1=$1; shift; local a2=$1; shift; local a3=$1; shift; local a4=$1; shift; echo "a1=$a1,a2=$a2,a3=$a3,a4=$a4" } file1="some file" file2="another file" OPT1=(-o "$file1") OPT2=(-p "$file2") myutil "${OPT1[@]}" "${OPT2[@]}" 
0
source

Use getopts http://mywiki.wooledge.org/BashFAQ/035

"The POSIX shell (and others) offers getopts, which is safe to use."

0
source

Source: https://habr.com/ru/post/1336285/


All Articles