#!/bin/sh
BACKUPDIR=$1
for argnum in {2..$#};do
echo ${"$argnum"}
done
I have tried this but it gives me this error:
./backup.sh: 10: ./backup.sh: Bad substitution
Use the shift command to remove $1 from the argument list after you're done reading it (thus renumbering your old $2 to $1, your old $3 to $2, etc):
#!/bin/sh
backupdir=$1; shift
for arg; do
echo "$arg"
done
To provide a literal (but not-particularly-good-practice) equivalent to the code in the question, indirect expansion (absent such security-impacting practices as eval) looks like the following:
#!/bin/bash
# ^^^^-- This IS NOT GUARANTEED TO WORK in /bin/sh
# not idiomatic, not portable to baseline POSIX shells; this is best avoided
backupdir=$1
for ((argnum=2; argnum<=$#; ++argnum)); do
echo "${!argnum}"
done
Related
I'm using getopt to parse command line arguments for a script.
All optional arguments (-o and --long) are handled by getopt, but there are also mandatory arguments. Argument number 4 can be a path or a sequence of values which may be negative.
script.sh mandatory/path/one mandatory/path/two mandatory/file.txt -200.12,32.7,-18.7
If argument 4 begins with a negative value, the - will trigger getopt. Obviously, this breaks the script as getopt will complain about undefined options.
There are two workarounds that I can think of, but both are quite hacky:
do a sed on $# to replace the - with some dummy string and then sed after getopt is done with the parsing to replace the dummy with - again.
ARGS=$(echo "$#" | sed -E "s/-([0-9])/%dummy%\1/g")
set -- "$ARGS"
ARGS=`getopt -o n -l no-act -n $0 -- "$#"`
eval set -- "$ARGS"
while :; do
case "$1" in
-n|--no-act)
norun=1 ;;
--)
shift; break ;;
*)
break
esac
shift
done
ARGS=$(echo "$#" | sed -E "s/%dummy%([0-9])/-\1/g")
eval set -- "$ARGS"
define options with arguments in getopt -1234567890. This will split the mandatory argument 4 into -2and 00.12,32.7,-18.7, which can be combined into an intermediate variable. In the end, the mandatory arguments 1-3 and 4 need to be set again.
ARGS=`getopt -o n1:2:3:4:5:6:7:8:9:0: -l no-act -n $0 -- "$#"`
eval set -- "$ARGS"
while :; do
case "$1" in
-n|--no-act)
norun=1 ;;
-1|-2|-3|-4|-5|-6|-7|-8|-9|-0)
TEMPVAR=$1$2
shift ;;
--)
shift; break ;;
*)
break
esac
shift
done
There must be a more elegant way to do this. Getopt is doing a great job otherwise and I would like to keep it for the command line parsing.
(There are a few more options, I'm only mentioning the -n option in the example to keep the code block short)
EDIT: Mandatory argument 4 should stay $4, as there are further checks down the line.
There is - sort of. The problem here is that the getopt command doesn't really understand anything other than options that start with a hyphen, nor can you convince it to use anything other than a hyphen as the leader to an option. There isn't a way to tell getopt not to process an option in any other way.
However, getopt doesn't process arguments. It just passes that into the $OPTARG variable without any parsing whatsoever. So you have three options here:
If your options are positional, there's no reason to use getopt here. Just use $1, $2 and $3 as usual and fail the script on an incorrect count and/or an incorrectly-formatted number list. You can always count the number of arguments and treat the final one as your list, if you wish.
On the other hand, if your options are not positional, then you really should consider giving the numeric list its own option and then interpret $OPTARG specially.
Create your own option-parsing loop if what you're doing is very special. getopt is meant for relatively simple tasks, and there is such a thing as stretching its usefulness.
Any of the above would be commonly done in useful scripts. I encourage you to not complicate the issue, though. Scripts should be relatively simple, and Bash is best used as a command construction and dispatch language. We're not living in the days where Bash (or maybe just sh?) are the only languages available by default in the distribution. If you are trying to do some fairly complicated argument processing, you should strongly consider whether a general-purpose programming language like Perl, Python, or Ruby is a better choice for what you intend to do.
In bash, this syntax can be used to get list of command line arguments, starting from $2:
echo "${#:2}"
This syntax does not seem to work in sh (/bin/dash).
What would be the best way to emulate this in sh ?
(shift; echo "$#")
Using the subshell created by the parens ensures that "$#" in the outer scope is not modified.
As another approach (uglier, but avoiding the need for a subshell), you can remove the first argument, and re-add it later:
argOne=$1 # put $1 in $argOne
shift # rename $2 to $1, $3 to $2, etc
echo "$#" # Pass the new argument list to echo
set -- "$argOne" "$#" # Create a new argument list, restoring argOne before the rest
I am working on an option driven bash script that will use getopts. The script has cases where it can accept multiple options and specific cases where only one option is accepted. While testing a few cases out I ran into this issue which I have reduced down to pseudo-code for this question.
for arg in "$#"; do
echo ${arg}
done
echo "end"
Running below returns:
$ ./test.sh -a -b
-a
end
I am running bash 4.1.2, why isn't the -b returned on the empty line? I assume this has to do with the '-'.
I cannot reproduce your exact error, but this is the risk of using echo: if $arg is a valid option, it will be treated as such, not a string to print. Use printf instead:
printf '%s\n' "$arg"
Also check if you have applied any "shift" commands that might remove the arguments before you test then (typical in a argument collection block that might include a case statement)
Suppose I have a #!/bin/sh script which can take a variety of positional parameters, some of which may include spaces, either/both kinds of quotes, etc. I want to iterate "$#" and for each argument either process it immediately somehow, or save it for later. At the end of the script I want to launch (perhaps exec) another process, passing in some of these parameters with all special characters intact.
If I were doing no processing on the parameters, othercmd "$#" would work fine, but I need to pull out some parameters and process them a bit.
If I could assume Bash, then I could use printf %q to compute quoted versions of args that I could eval later, but this would not work on e.g. Ubuntu's Dash (/bin/sh).
Is there any equivalent to printf %q that can be written in a plain Bourne shell script, using only built-ins and POSIX-defined utilities, say as a function I could copy into a script?
For example, a script trying to ls its arguments in reverse order:
#!/bin/sh
args=
for arg in "$#"
do
args="'$arg' $args"
done
eval "ls $args"
works for many cases:
$ ./handle goodbye "cruel world"
ls: cannot access cruel world: No such file or directory
ls: cannot access goodbye: No such file or directory
but not when ' is used:
$ ./handle goodbye "cruel'st world"
./handle: 1: eval: Syntax error: Unterminated quoted string
and the following works fine but relies on Bash:
#!/bin/bash
args=
for arg in "$#"
do
printf -v argq '%q' "$arg"
args="$argq $args"
done
eval "ls $args"
This is absolutely doable.
The answer you see by Jesse Glick is approximately there, but it has a couple of bugs, and I have a few more alternatives for your consideration, since this is a problem I ran into more than once.
First, and you might already know this, echo is a bad idea, one should use printf instead, if the goal is portability: "echo" has undefined behavior in POSIX if the argument it receives is "-n", and in practice some implementations of echo treat -n as a special option, while others just treat it as a normal argument to print. So that becomes this:
esceval()
{
printf %s "$1" | sed "s/'/'\"'\"'/g"
}
Alternatively, instead of escaping embedded single quotes by making them into:
'"'"'
..instead you could turn them into:
'\''
..stylistic differences I guess (I imagine performance difference is negligible either way, though I've never tested). The resulting sed string looks like this:
esceval()
{
printf %s "$1" | sed "s/'/'\\\\''/g"
}
(It's four backslashes because double quotes swallow two of them, and leaving two, and then sed swallows one, leaving just the one. Personally, I find this way more readable so that's what I'll use in the rest of the examples that involve it, but both should be equivalent.)
BUT, we still have a bug: command substitution will delete at least one (but in many shells ALL) of the trailing newlines from the command output (not all whitespace, just newlines specifically). So the above solution works unless you have newline(s) at the very end of an argument. Then you'll lose that/those newline(s). The fix is obviously simple: Add another character after the actual command value before outputting from your quote/esceval function. Incidentally, we already needed to do that anyway, because we needed to start and stop the escaped argument with single quotes. You have two alternatives:
esceval()
{
printf '%s\n' "$1" | sed "s/'/'\\\\''/g; 1 s/^/'/; $ s/$/'/"
}
This will ensure the argument comes out already fully escaped, no need for adding more single quotes when building the final string. This is probably the closest thing you will get to a single, inline-able version. If you're okay with having a sed dependency, you can stop here.
If you're not okay with the sed dependency, but you're fine with assuming that your shell is actually POSIX-compliant (there are still some out there, notably the /bin/sh on Solaris 10 and below, which won't be able to do this next variant - but almost all shells you need to care about will do this just fine):
esceval()
{
printf \'
unescaped=$1
while :
do
case $unescaped in
*\'*)
printf %s "${unescaped%%\'*}""'\''"
unescaped=${unescaped#*\'}
;;
*)
printf %s "$unescaped"
break
esac
done
printf \'
}
You might notice seemingly redundant quoting here:
printf %s "${unescaped%%\'*}""'\''"
..this could be replaced with:
printf %s "${unescaped%%\'*}'\''"
The only reason I do the former, is because one upon a time there were Bourne shells which had bugs when substituting variables into quoted strings where the quote around the variable didn't exactly start and end where the variable substitution did. Hence it's a paranoid portability habit of mine. In practice, you can do the latter, and it won't be a problem.
If you don't want to clobber the variable unescaped in the rest of your shell environment, then you can wrap the entire contents of that function in a subshell, like so:
esceval()
{
(
printf \'
unescaped=$1
while :
do
case $unescaped in
*\'*)
printf %s "${unescaped%%\'*}""'\''"
unescaped=${unescaped#*\'}
;;
*)
printf %s "$unescaped"
break
esac
done
printf \'
)
}
"But wait", you say: "What I want to do this on MULTIPLE arguments in one command? And I want the output to still look kinda nice and legible for me as a user if I run it from the command line for whatever reason."
Never fear, I have you covered:
esceval()
{
case $# in 0) return 0; esac
while :
do
printf "'"
printf %s "$1" | sed "s/'/'\\\\''/g"
shift
case $# in 0) break; esac
printf "' "
done
printf "'\n"
}
..or the same thing, but with the shell-only version:
esceval()
{
case $# in 0) return 0; esac
(
while :
do
printf "'"
unescaped=$1
while :
do
case $unescaped in
*\'*)
printf %s "${unescaped%%\'*}""'\''"
unescaped=${unescaped#*\'}
;;
*)
printf %s "$unescaped"
break
esac
done
shift
case $# in 0) break; esac
printf "' "
done
printf "'\n"
)
}
In those last four, you could collapse some of the outer printf statements and roll their single quotes up into another printf - I kept them separate because I feel it makes the logic more clear when you can see the starting and ending single-quotes on separate print statements.
P.S. There's also this monstrosity I made, which is a polyfill which will select between the previous two versions depending on if your shell seems to be capable of supporting the necessary variable substitution syntax (it looks awful though, because the shell-only version has to be inside an eval-ed string to keep the incompatible shells from barfing when they see it): https://github.com/mentalisttraceur/esceval/blob/master/sh/esceval.sh
I think this is POSIX. It works by clearing $# after expanding it for the for loop, but only once so that we can iteratively build it back up (in reverse) using set.
flag=0
for i in "$#"; do
[ "$flag" -eq 0 ] && shift $#
set -- "$i" "$#"
flag=1
done
echo "$#" # To see that "$#" has indeed been reversed
ls "$#"
I realize reversing the arguments was just an example, but you may be able to use this trick of set -- "$arg" "$#" or set -- "$#" "$arg" in other situations.
And yes, I realize I may have just reimplemented (poorly) ormaaj's Push.
Push. See the readme for examples.
The following seems to work with everything I have thrown at it so far, including spaces, both kinds of quotes and a variety of other metacharacters, and embedded newlines:
#!/bin/sh
quote() {
echo "$1" | sed "s/'/'\"'\"'/g"
}
args=
for arg in "$#"
do
argq="'"`quote "$arg"`"'"
args="$argq $args"
done
eval "ls $args"
If you're okay with calling out to an external executable (as in the sed solutions given in other answers), then you may as well call out to /usr/bin/printf. While it's true that the POSIX shell built-in printf doesn't support %q, the printf binary from Coreutils sure does (since release 8.25).
esceval() {
/usr/bin/printf '%q ' "$#"
}
We can use /usr/bin/printf when version of GNU Coreutil is not less than 8.25
#!/bin/sh
minversion="8.25"
gnuversion=$(ls '--version' | sed '1q' | awk 'NF{print $NF}')
printcmd="printf"
if ! [ $gnuversion \< $minversion ]; then
printcmd="/usr/bin/printf"
fi;
params=$($printcmd "%q" "$#")
Is it possible to pass command line arguments to shell script as name value pairs, something like
myscript action=build module=core
and then in my script, get the variable like
$action and process it?
I know that $1....and so on can be used to get variables, but then won't be name value like pairs. Even if they are, then the developer using the script will have to take care of declaring variables in the same order. I do not want that.
This worked for me:
for ARGUMENT in "$#"
do
KEY=$(echo $ARGUMENT | cut -f1 -d=)
KEY_LENGTH=${#KEY}
VALUE="${ARGUMENT:$KEY_LENGTH+1}"
export "$KEY"="$VALUE"
done
# from this line, you could use your variables as you need
cd $FOLDER
mkdir $REPOSITORY_NAME
Usage
bash my_scripts.sh FOLDER="/tmp/foo" REPOSITORY_NAME="stackexchange"
STEPS and REPOSITORY_NAME are ready to use in the script.
It does not matter what order the arguments are in.
Changelog
v1.0.0
In the Bourne shell, there is a seldom-used option '-k' which automatically places any values specified as name=value on the command line into the environment. Of course, the Bourne/Korn/POSIX shell family (including bash) also do that for name=value items before the command name:
name1=value1 name2=value2 command name3=value3 -x name4=value4 abc
Under normal POSIX-shell behaviour, the command is invoked with name1 and name2 in the environment, and with four arguments. Under the Bourne (and Korn and bash, but not POSIX) shell -k option, it is invoked with name1, name2, name3, and name4 in the environment and just two arguments. The bash manual page (as in man bash) doesn't mention the equivalent of -k but it works like the Bourne and Korn shells do.
I don't think I've ever used it (the -k option) seriously.
There is no way to tell from within the script (command) that the environment variables were specified solely for this command; they are simply environment variables in the environment of that script.
This is the closest approach I know of to what you are asking for. I do not think anything equivalent exists for the C shell family. I don't know of any other argument parser that sets variables from name=value pairs on the command line.
With some fairly major caveats (it is relatively easy to do for simple values, but hard to deal with values containing shell meta-characters), you can do:
case $1 in
(*=*) eval $1;;
esac
This is not the C shell family. The eval effectively does the shell assignment.
arg=name1=value1
echo $name1
eval $arg
echo $name1
env action=build module=core myscript
You said you're using tcsh. For Bourne-based shells, you can drop the "env", though it's harmless to leave it there. Note that this applies to the shell from which you run the command, not to the shell used to implement myscript.
If you specifically want the name=value pairs to follow the command name, you'll need to do some work inside myscript.
It's quite an old question, but still valid
I have not found the cookie cut solution. I combined the above answers. For my needs I created this solution; this works even with white space in the argument's value.
Save this as argparse.sh
#!/bin/bash
: ${1?
'Usage:
$0 --<key1>="<val1a> <val1b>" [ --<key2>="<val2a> <val2b>" | --<key3>="<val3>" ]'
}
declare -A args
while [[ "$#" > "0" ]]; do
case "$1" in
(*=*)
_key="${1%%=*}" && _key="${_key/--/}" && _val="${1#*=}"
args[${_key}]="${_val}"
(>&2 echo -e "key:val => ${_key}:${_val}")
;;
esac
shift
done
(>&2 echo -e "Total args: ${#args[#]}; Options: ${args[#]}")
## This additional can check for specific key
[[ -n "${args['path']+1}" ]] && (>&2 echo -e "key: 'path' exists") || (>&2 echo -e "key: 'path' does NOT exists");
#Example: Note, arguments to the script can have optional prefix --
./argparse.sh --x="blah"
./argparse.sh --x="blah" --yy="qwert bye"
./argparse.sh x="blah" yy="qwert bye"
Some interesting use cases for this script:
./argparse.sh --path="$(ls -1)"
./argparse.sh --path="$(ls -d -1 "$PWD"/**)"
Above script created as gist, Refer: argparse.sh
Extending on Jonathan's answer, this worked nicely for me:
#!/bin/bash
if [ "$#" -eq "0" ]; then
echo "Error! Usage: Remind me how this works again ..."
exit 1
fi
while [[ "$#" > "0" ]]
do
case $1 in
(*=*) eval $1;;
esac
shift
done