Script with arguments
Accept an argument in a script and write it to a file.
A script that always does the same thing is rarely useful. Usually you need
to pass it something from outside: a file name, an environment (staging or
prod), a server address, a version. That's what command-line arguments are
for. You write them after the script's name, separated by spaces, and the
script receives them in special variables $1, $2, and so on.
Where to practice
The examples below work in an ubuntu:24.04 container. You can start the
same one yourself:
docker run --rm -it ubuntu:24.04 bash
On Linux, macOS and in WSL2 the commands also work in your own terminal.
There are no editors in the container, so we create files through cat and
a heredoc: everything between the line with <<'EOF' and the EOF line goes
into the file. The quotes around EOF keep the shell from substituting $1
and other variables while the file is being created.
Positional parameters
Create a script that shows what it got:
cat > /tmp/args.sh <<'EOF'
#!/bin/bash
echo "script: $0"
echo "arguments: $#"
echo "first: $1"
echo "second: $2"
echo "all: $@"
EOF
chmod +x /tmp/args.sh
Run it with two arguments:
/tmp/args.sh web 8080
script: /tmp/args.sh
arguments: 2
first: web
second: 8080
all: web 8080
What's what here:
$1,$2, ... are the arguments in order, as they were written in the command. Order matters:args.sh 8080 webputs8080in$1.$#is the number of arguments.$@is all the arguments at once.$0is the name the script was run with. It's handy for messages likeusage: $0 ....
An argument that wasn't passed isn't an error, it's an empty string. Run it without arguments:
/tmp/args.sh
script: /tmp/args.sh
arguments: 0
first:
second:
all:
The script ran as if nothing happened, just with empty values. That's why
scripts break silently: an empty $1 ends up in a path or a file, and
nobody sees the error.
Starting from the tenth, the number is written in curly braces: ${10}. The
shell reads $10 as $1 followed by the character 0. With the
arguments a b c d e f g h i j, the line echo "$10 ${10}" prints a0 j.
Quotes
The shell splits a string into arguments by spaces. To pass a value with a space as a single argument, wrap it in quotes:
/tmp/args.sh "hello world" x
script: /tmp/args.sh
arguments: 2
first: hello world
second: x
all: hello world x
Inside the script, the same rule applies a second time. If you write $1
without quotes, the shell splits the value by spaces again and expands *
into a list of files. Example: a script writes $1 to a file three
different ways, and we run it in a directory that has f1.txt and f2.txt,
with the argument 'build 42' (two spaces) and then '*.txt'.
| command in the script | build 42 | *.txt |
|---|---|---|
printf '%s' $1 | build42 | f1.txtf2.txt |
echo $1 | build 42 and \n | f1.txt f2.txt and \n |
printf '%s' "$1" | build 42 | *.txt |
The rule is simple: always put "$1", "$2", "$@" in double quotes,
unless there's a specific reason not to.
The same goes for $@. "$@" passes the arguments as they are, $@
without quotes splits them again. Here's that loop in a script run with the
arguments "a b" and c:
for a in "$@"; do echo "[$a]"; done
[a b]
[c]
Without quotes, the same loop would print three lines: [a], [b], [c].
Newline: echo and printf
echo adds a newline \n at the end. You won't see it on screen, but it's
there in a file, and a program that reads the whole file gets the value with
an extra character. printf '%s' "$1" prints the string as is, with no
\n. You can see every byte of a file with od -c:
printf '%s' "build 42" > /tmp/out
od -c /tmp/out
0000000 b u i l d 4 2
0000011
With echo "build 42" > /tmp/out, a \n would show up at the end, and the
size would be 10 bytes instead of 9 (od prints the offset in octal, so 9
there looks like 0000011).
There's also echo -n, but it trips up on values that look like flags: a
value of -n would be treated as a flag and print nothing. printf '%s'
doesn't have this problem.
Checking the number of arguments
Since an empty argument isn't an error, you have to check it yourself.
Good practice: at the start of the script, compare $# to the number you
need, and if there aren't enough arguments, print a hint and exit with an
error:
cat > /tmp/usage.sh <<'EOF'
#!/bin/bash
[ $# -lt 1 ] && { echo "usage: $0 <arg>" >&2; exit 1; }
echo "ok: $1"
EOF
bash /tmp/usage.sh; echo "rc=$?"
usage: /tmp/usage.sh <arg>
rc=1
There are three details here:
-ltmeans "less than". On the right, put however many arguments the script actually requires.>&2sends the hint to stderr, the stream for errors. If the script's output gets redirected to a file, the hint still shows up on screen.exit 1returns a nonzero code. That's how another script or CI knows the run failed.
The check should come before any work with files, or the script might damage something first.
Sometimes an argument is optional and has a default value. That's what
${1:-value} is for:
cat > /tmp/def.sh <<'EOF'
#!/bin/bash
ENV=${1:-staging}
echo "env=$ENV"
EOF
bash /tmp/def.sh; bash /tmp/def.sh prod
env=staging
env=prod
How to run a script
/tmp/args.sh runs the file directly: it needs permission to run
(chmod +x) and a first line #!/bin/bash, which tells the system which
shell to run it with. bash /tmp/args.sh runs it through bash explicitly,
so execute permission isn't needed then. sh /tmp/args.sh runs it in sh:
on Ubuntu that's dash, not bash. This lesson's examples work there too, but,
for example, [[ ... ]] from bash isn't something dash knows.
Common mistakes
$1without quotes: spaces collapse or vanish,*turns into a list of files.echoinstead ofprintf '%s'when the file needs a value with no newline.- Arguments in the wrong order: the script reads
$2where it expects the first value. There's no error, the result is just wrong. - No check on
$#: without arguments, the script works with empty values and silently does the wrong thing. - The usage hint goes to regular output instead of stderr, and the script exits with code 0.
- An unquoted heredoc (
<<EOF):$1gets substituted while the file is created, and the script ends up with an empty string.
What you'll need for the tasks
- Read values from
$1,$2and check their count through$#. - Put
"$1"in double quotes. - Write a value to a file without an extra
\nusingprintf '%s', and check the bytes withod -c. - Print usage to stderr and exit with a nonzero code.
- Create a script file through
cat > file <<'EOF'and give it permission to run.
Practice in your browser
Linux runs right here. Solve the task and we'll save your progress automatically.
The script accepts an argument
Create the script /root/echo.sh, which takes one argument and writes it (just it, with no trailing newline) to the file /tmp/last-arg. For example, ./echo.sh hello → /tmp/last-arg will contain hello.
0%
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson terminal-scripts-02-arg-script
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress