743

I want my script to be able to take an optional input,

e.g. currently my script is

#!/bin/bash
somecommand foo

but I would like it to say:

#!/bin/bash
somecommand  [ if $1 exists, $1, else, foo ]
Amir
  • 10,600
  • 9
  • 48
  • 75
Abe
  • 12,956
  • 12
  • 51
  • 72
  • 3
    See also http://unix.stackexchange.com/questions/122845/using-a-b-for-variable-assignment-in-scripts – Vadzim Mar 28 '16 at 10:12
  • 2
    ...and http://stackoverflow.com/questions/2013547/assigning-default-values-to-shell-variables-with-a-single-command-in-bash – Vadzim Mar 28 '16 at 10:26
  • I want to say that the this subject is not about the optional argument but a positional argument with default value. This terminology gives much confusion. "Optional argument" means it would be ok whether those arguments exist in the command line or not. – Joonho Park Feb 16 '21 at 03:40

10 Answers10

1056

You could use the default-value syntax:

somecommand ${1:-foo}

The above will, as described in Bash Reference Manual - 3.5.3 Shell Parameter Expansion [emphasis mine]:

If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter is substituted.

If you only want to substitute a default value if the parameter is unset (but not if it's null, e.g. not if it's an empty string), use this syntax instead:

somecommand ${1-foo}

Again from Bash Reference Manual - 3.5.3 Shell Parameter Expansion:

Omitting the colon results in a test only for a parameter that is unset. Put another way, if the colon is included, the operator tests for both parameter’s existence and that its value is not null; if the colon is omitted, the operator tests only for existence.

Kenny Evitt
  • 9,291
  • 5
  • 65
  • 93
Itay Perl
  • 12,844
  • 3
  • 18
  • 12
  • 77
    Please note the semantic difference between the above command, "return `foo` if `$1` is unset *or an empty string*", and `${1-foo}`, "return `foo` if `$1` is unset". – l0b0 Feb 21 '12 at 15:12
  • 17
    Can you explain why this works? Specially, what's the function/purpose of the ':' and '-'? – jwien001 Sep 05 '14 at 21:11
  • 11
    @jwein001: In the answer submitted above, a substitution operator is used to return a default value if the variable is undefined. Specifically, the logic is "If $1 exists and isn't null, return its value; otherwise, return foo." The colon is optional. If it's omitted, change "exists and isn't null" to only "exists." The minus sign specifies to return foo without setting $1 equal to 'foo'. Substitution operators are a subclass of expansion operators. See section 6.1.2.1 of Robbins and Beebe's *Classic Shell Scripting* [O'Reilly] (http://shop.oreilly.com/product/9780596005955.do) – Jubbles Sep 23 '14 at 20:36
  • 6
    @Jubbles or if you don't want to buy an entire book for a simple reference... http://www.tldp.org/LDP/abs/html/parameter-substitution.html – Ohad Schneider Feb 08 '17 at 14:39
  • 3
    This answer would be even better if it showed how to make the default be the result of running a command, as @hagen does (though that answer is inelegant). – sautedman Mar 17 '17 at 17:47
  • 1
    If the subcommand possible takes multiple paramters, then: `somecommand ${@-foo}` – DanielW Apr 09 '19 at 14:05
  • I am frustrated why this is optional argument. If you use $1 for a variable, it is positional argument not optional argument. If a command can receive an optional argument of -a -b -c then it should work with "command -a 10", "command -b ten", "command -c abc" and "command -a 7 -c aaa". If it works always with three arguments such as "command 10 ten abc", it is not a optional argument. – Joonho Park Feb 16 '21 at 03:36
  • @OhadSchneider, please reconsider linking to the ABS -- it's a source of enough bad-practice examples that [entire alternative reference sources](https://mywiki.wooledge.org/BashGuide) have been written to replace it after the #bash IRC support channel got fed up with trying to get people to unlearn bad habits they picked up there. (https://wiki.bash-hackers.org/syntax/pe is another good reference on the specific topic at hand; the Wooledge wiki also has parameter expansion coverage in [BashFAQ #100](https://mywiki.wooledge.org/BashFAQ/100) and [#73](https://mywiki.wooledge.org/BashFAQ/073)) – Charles Duffy Oct 09 '22 at 23:26
  • Is it possible to "return `foo` if `$1` is set"? – Qwerty Apr 26 '23 at 19:25
530

You can set a default value for a variable like so:

somecommand.sh

#!/usr/bin/env bash

ARG1=${1:-foo}
ARG2=${2:-'bar is'}
ARG3=${3:-1}
ARG4=${4:-$(date)}

echo "$ARG1"
echo "$ARG2"
echo "$ARG3"
echo "$ARG4"

Here are some examples of how this works:

$ ./somecommand.sh
foo
bar is
1
Thu 19 May 2022 06:58:52 ADT

$ ./somecommand.sh ez
ez
bar is
1
Thu 19 May 2022 06:58:52 ADT

$ ./somecommand.sh able was i
able
was
i
Thu 19 May 2022 06:58:52 ADT

$ ./somecommand.sh "able was i"
able was i
bar is
1
Thu 19 May 2022 06:58:52 ADT

$ ./somecommand.sh "able was i" super
able was i
super
1
Thu 19 May 2022 06:58:52 ADT

$ ./somecommand.sh "" "super duper"
foo
super duper
1
Thu 19 May 2022 06:58:52 ADT

$ ./somecommand.sh "" "super duper" hi you
foo
super duper
hi
you
Brad Parks
  • 66,836
  • 64
  • 257
  • 336
  • 8
    Ah, ok. The `-` confused me (is it negated?). – Raffi Khatchadourian Mar 28 '18 at 20:58
  • 15
    Nope - that's just a weird way bash has of doing the assignment. I'll add some more examples to clarify this a bit... thanks! – Brad Parks Mar 29 '18 at 11:09
  • It looks this is the limit of bash argument. Using python terminology, this is not optional, it is positional. It is just a positional argument with default values. Is there no really optional argument in bash? – Joonho Park Feb 17 '22 at 08:20
  • you may be able to use getopts to get what you want - Here's 2 stackoverflow answers that go into greater detail: [dfeault values](https://stackoverflow.com/q/22058316/26510) and [inside a function](https://stackoverflow.com/a/16655341/26510) – Brad Parks Feb 17 '22 at 12:47
  • Note also, that to confuse even more (with JSON/python dicts) no space is permitted anywhere, e.g. after the colon and/or after/before the dash... where is the principle of least astonishment here?:) – mirekphd May 19 '22 at 08:39
  • good point @mirekphd - I added an example showing how to put spaces in a default value. You can use single quotes if you want a literal value, or double quotes if you want variables to be interpolated – Brad Parks May 19 '22 at 10:03
66
if [ ! -z $1 ] 
then 
    : # $1 was given
else
    : # $1 was not given
fi
sorpigal
  • 25,504
  • 8
  • 57
  • 75
Irit Katriel
  • 3,534
  • 1
  • 16
  • 18
  • 2
    Technically, if you pass in an empty string '' that might count as a parameter, but your check will miss it. In that case $# would say how many parameters were given – vmpstr Feb 17 '12 at 17:40
  • 30
    `-n` is the same as `! -z`. – l0b0 Feb 21 '12 at 15:15
  • 1
    I get different results using `-n` and `! -z` so I would say that is not the case here. – Eliezer Dec 19 '19 at 21:31
  • 5
    because you failed to quote the variable, `[ -n $1 ]` *will always be true*. If you use bash, `[[ -n $1 ]]` will behave as you expect, otherwise you must quote `[ -n "$1" ]` – glenn jackman Mar 19 '20 at 12:33
  • 3
    For ones like me: forget the ":" in the code, it's not required, replace it with your real commands! – Anh-Thi DINH Nov 12 '21 at 13:52
31

You can check the number of arguments with $#

#!/bin/bash
if [ $# -ge 1 ]
then
    $1
else
    foo
fi
Dennis
  • 32,200
  • 11
  • 64
  • 79
12

please don't forget, if its variable $1 .. $n you need write to a regular variable to use the substitution

#!/bin/bash
NOW=$1
echo  ${NOW:-$(date +"%Y-%m-%d")}
Vadzim
  • 24,954
  • 11
  • 143
  • 151
hagen
  • 121
  • 1
  • 2
  • 2
    Brad's [answer](http://stackoverflow.com/a/33419280/603516) above proves that argument variables can also be substituted without intermediate vars. – Vadzim Mar 28 '16 at 10:05
  • 2
    +1 for noting the way to use a command like date as the default instead of a fixed value. This is also possible: `DAY=${1:-$(date +%F -d "yesterday")}` – Garren S Jan 06 '17 at 21:07
9

This allows default value for optional 1st arg, and preserves multiple args.

 > cat mosh.sh
   set -- ${1:-xyz} ${@:2:$#} ; echo $*    
 > mosh.sh
   xyz
 > mosh.sh  1 2 3
   1 2 3 
mosh
  • 1,402
  • 15
  • 16
5

For optional multiple arguments, by analogy with the ls command which can take one or more files or by default lists everything in the current directory:

if [ $# -ge 1 ]
then
    files="$@"
else
    files=*
fi
for f in $files
do
    echo "found $f"
done

Does not work correctly for files with spaces in the path, alas. Have not figured out how to make that work yet.

Jesse Glick
  • 24,539
  • 10
  • 90
  • 112
4

It's possible to use variable substitution to substitute a fixed value or a command (like date) for an argument. The answers so far have focused on fixed values, but this is what I used to make date an optional argument:

~$ sh co.sh
2017-01-05

~$ sh co.sh 2017-01-04
2017-01-04

~$ cat co.sh

DAY=${1:-$(date +%F -d "yesterday")}
echo $DAY
Garren S
  • 5,552
  • 3
  • 30
  • 45
1
while getopts a: flag
do
    case "$flag" in
        a) arg1=${OPTARG};;
    esac
done

echo "Print optional argument: $arg1"

if [[ -z "$arg1" ]]; then
    ARG=DEFAULT_VAL
else
    ARG=$arg1
fi  

#Run using below command (eg: file name : runscript.sh)
bash runscript.sh -a argument_val &  
shadyyx
  • 15,825
  • 6
  • 60
  • 95
-1

When you use ${1: } you can catch the first parameter(1) passed to your function or(:) you can catch a blank space like a default value.

For example. To be able to use Laravel artisan, I put this into my .bash_aliases file:

artisan() {
    docker exec -it **container_name** php artisan ${1: } ${2: } ${3: } ${4: } ${5: } ${6: } ${7: }
}  

and now, I can just type in command line:

  • artisan -- and all parameters(${1: } ${2: } ${3: } ${4: } ${5: } ${6: } ${7: }) will be just blank spaces
  • artisan cache:clear -- and the first parameter ( ${1: } ) will be cache:clear and all the others will be just blank spaces

So, in this case I can pass 7 parameters optionally.

I hope it can help someone.