Differences between revisions 48 and 49
Revision 48 as of 2009-12-28 17:41:42
Size: 6232
Editor: GreyCat
Comment: restructuring the guide
Revision 49 as of 2009-12-28 17:49:25
Size: 16272
Editor: GreyCat
Comment: merge C&A and ArgSplit and Scripts from the Intro; prev/next links, local TOC; fix error in shebang section
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
## page was renamed from BashGuide/TheBasics/CommandsAndArguments
<<Anchor(Commands_And_Arguments)>>
== Commands And Arguments ==
[[BashGuide|<- Contents]] | [[BashGuide/SpecialCharacters|Special Characters ->]]
----
<<TableOfContents>>

= Commands And Arguments =
Line 78: Line 80:

= Argument Splitting =

Commands in [[BASH]] can take multiple arguments. These arguments are used to tell the command exactly what it's supposed to do. In [[BASH]], you separate these arguments by whitespace (spaces and tabs).

Assume you're in an empty directory. (If you want to try this code out, you can create and go into an empty directory called `test` by running: `mkdir test; cd test`.)

{{{
    $ ls # List files in the current directory (no output: no files).
    $ touch a b c # Create files 'a', 'b' and 'c'.
    $ ls # List all files again; this time the output shows 'a', 'b' and 'c'.
    a b c
}}}
`touch` is an application that changes the 'Last Modified'-time of a certain file to the current time. If the filename that it's given does not exist yet, it simply creates that file, as a new and empty file. In this example, we passed three arguments. `touch` creates a file for each argument. `ls` shows us that three files have been created.

{{{
    $ rm * # Remove all files in the current directory.
    $ ls # List files in the current directory (no output: no files).
    $ touch a b c # Create files 'a', 'b' and 'c'.
    $ ls # List all files again; this time the output shows 'a', 'b' and 'c'.
    a b c
}}}
`rm` is an application that removes all the files that it was given. ''*'' is a ''glob''. It basically means ''all files in the current directory''. You will read more about this later on.

Now, did you notice that there are several spaces between `a` and `b`, and only one between `b` and `c`? Also, notice that the files that were created by `touch` are no different than the first time. You now know that the amount of whitespace between arguments does not matter. This is important to know. For example:

{{{
    $ echo This is a test.
    This is a test.
    $ echo This is a test.
    This is a test.
}}}
In this case, we provide the `echo` command with four arguments. 'This', 'is', 'a' and 'test.'. `echo` takes these arguments, and prints them out one by one with a space in between. In the second case, the exact same thing happens. The extra spaces make no difference. If we actually want the extra whitespace, we need to pass the sentence as one single argument. We can do this by using quotes:

{{{
    $ echo "This is a test."
    This is a test.
}}}
Quotes group everything inside them into a single argument. This argument is '`This is a test.`', properly spaced. `echo` prints this single argument out just like it always does.

Be very careful to avoid the following:

{{{
    $ ls # List files in the current directory.
    The secret voice in your head.mp3 secret
    $ rm The secret voice in your head.mp3 # Executes rm with 6 arguments; not 1!
    rm: cannot remove `The': No such file or directory
    rm: cannot remove `voice': No such file or directory
    rm: cannot remove `in': No such file or directory
    rm: cannot remove `your': No such file or directory
    rm: cannot remove `head.mp3': No such file or directory
    $ ls # List files in the current directory: It is still there.
    The secret voice in your head.mp3
}}}
You need to make sure you quote filenames properly. If you don't you'll end up deleting the wrong things! `rm` takes filenames as arguments. If you do not quote filenames with spaces, `rm` thinks that each argument is a separate file. Since [[BASH]] splits your arguments at the spaces, `rm` will try to remove each word. The above example tried to delete files for each word in the filename of the song, instead of the filename of the song. That caused our file `secret` to be deleted, and our song to remain behind!

Please have a good look at WordSplitting or http://bash-hackers.org/wiki/doku.php?id=syntax:words if all this isn't very clear to you yet.

--------
 . '''Good Practice: <<BR>> You should ''always'' quote sentences or strings that belong together, even if it's not absolutely necessary. This will keep you alert and reduce the risk of human error in your scripts. <<BR>> For example, you should always quote arguments to the `echo` command.'''
----
 . '''In the FAQ: <<BR>> [[BashFAQ/050|I'm trying to put a command in a variable, but the complex cases always fail!]]'''
----
 . ''Arguments'': These are the optional additional words you can specify when running commands. They are given after the command's name ('`ls -l foo`' executes `ls` with two arguments).
 . ''Quotes'': The two forms of quotes (`'` and `"`) are used to protect certain special characters inside them from being interpreted as special by [[BASH]]. The difference between `'` and `"` will be discussed later.
--------

= Scripts =

A script is basically a sequence of commands that [[BASH]] processes in order. It only moves on to the next command when the current one has ended, unless the current one has been executed asynchronously (in the background). Don't worry too much about the latter case yet -- you'll learn about how that works later on.

Virtually any example that you see in this guide can be used in a script just as well as on the command line.

Making a script is easy. You just make a new file, and put this in it at the top:

{{{
    #!/usr/bin/env bash
}}}
This header makes sure that whenever your script is executed, [[BASH]] will be used as its interpreter. '''Please''' do not be fooled by examples on the Internet that use `/bin/sh` as interpreter. '''`sh` is not `bash`'''. Even though `sh`'s syntax and `bash`'s look very much alike and even though most `bash` scripts will run in `sh`, a lot of the examples in this guide only apply to `bash` and will just break or cause unexpected behaviour in `sh`. Also, please refrain from giving your scripts that stupid `.sh` extension. It serves no purpose, and it's completely misleading (since it's going to be a `bash` script, not an `sh` script).

And by the way, it's perfectly fine if you use ''Windows'' to write your scripts, but if at all possible, '''avoid using ''Notepad'' for writing scripts'''. ''Microsoft Notepad'' can only make files with DOS-style line-endings. That means that each line you make in notepad will be ended by two characters: a ''Carriage Return'' and a ''Newline'' character. [[BASH]] reads lines as terminated by ''Newline'' characters only. As a result, the ''Carriage Return'' character will cause you incredible headache if you don't know it's there (very weird error messages). If at all possible, ''use a decent editor'' like [[http://www.vim.org/|Vim]], [[http://www.gnu.org/software/emacs/|Emacs]], kate, GEdit, GVIM or xemacs. If you don't, then you will need to remove the carriage returns from your scripts before running them.

Once your script file has been made, you can run it like this:

{{{
    $ bash myscript
}}}
In this example, we execute [[BASH]] and tell it to read our script. Alternatively, you can give your script executable permissions. When you do this, you can actually execute the script instead of calling [[BASH]] manually:

{{{
    $ chmod +x myscript
    $ ./myscript
}}}
Some people like to keep their scripts in a personal directory. Others like to keep their scripts somewhere in the `PATH` variable. Most like to do both at once. Here's what I suggest you do:

{{{
    $ mkdir -p "$HOME/bin"
    $ echo 'PATH="$HOME/bin:$PATH"' >> "$HOME/.bashrc"
}}}
The first command will make a directory called `bin` in your home directory. The second command will add a line to your `.bashrc` file which adds the directory we just made to the beginning of the `PATH` variable. Every new instance of [[BASH]] will now check for executable scripts in your `bin` directory.

To apply the changes we added to `.bashrc` we obviously need to actually process `.bashrc` first. You can do that by closing your existing terminal and opening a new one. [[BASH]] will then initialize itself again by reading `.bashrc` among others. Alternatively you can just execute that line of code on the command line (`PATH="$HOME/bin:$PATH"`) or manually process your `.bashrc` file in the running shell by running `source "$HOME/.bashrc"` . Yet another way would be to replace your current [[BASH]] instance with a new one by running `exec bash` .

As a result, we can now put our script in our `bin` directory and execute it as a normal command (we no longer need to prepend our script's name with its path, which was the `./` part in the previous examples):

{{{
    $ mv myscript "$HOME/bin"
    $ myscript
}}}
--------
 . '''Tip: <<BR>> While you're defining the interpreter in your header, you might want to take the time to explain your script's function and expected arguments a little too:'''
{{{
    #! /usr/bin/env bash
    #
    # scriptname argument [argument] ...
    #
    # A short explanation of your script's purpose.
    #
    # Copyright [date], [name]
}}}
----
 . '''Tip: <<BR>> You can use this header to specify up to one word of optional arguments that you want to pass to the interpreter. For example, the following arguments will turn on some verbose debugging:'''
{{{
    #! /bin/bash -xv
}}}
 . '''Unfortunately, you can't use this:'''
{{{
    #! /usr/bin/env bash -xv
}}}
 . '''because that would require two words of arguments in the shebang line. Unix doesn't allow that.'''
----
 . ''Header'': The header of a script determines the application that will function as its interpreter (e.g. `bash`, `sh`, `perl`, ...). Colloquially, this is also called a ''shebang'' -- a slang term derived by combining ''hash'' (`#`) and ''bang'' (`!`). You will probably see the word ''shebang'' more often than ''header''.
--------
[[BashGuide|<- Contents]] | [[BashGuide/SpecialCharacters|Special Characters ->]]

<- Contents | Special Characters ->


Commands And Arguments

BASH reads commands from its input (which is either a terminal or a file). These commands can be aliases, functions, builtins, keywords, or executables.

  • Aliases: Aliases are a way of shortening commands. They are only used in interactive shells, not in scripts. An alias is a name that is mapped to a certain string. Whenever that name is used as a command name, it is replaced by the string before executing the command. So, instead of executing:

      $ nmap -P0 -A --osscan_limit 192.168.0.1
    You could use an alias like this:
      $ alias nmapp='nmap -P0 -A --osscan_limit'
      $ nmapp 192.168.0.1
  • Functions: Functions in BASH are somewhat like aliases, but more powerful. Unlike aliases they can be used in scripts. A function contains shell commands, very much like a small script. When a function is called, the commands in it are executed.

  • Builtins: BASH has some basic commands built into it, such as cd (change directory), if (conditional command execution), and so on. You can think of them as functions that are provided already.

  • Keywords: Keywords are quite like builtins, but the main difference is that special parsing rules apply to them. For example, [ is a bash builtin, while [[ is a bash keyword. They are both used for testing stuff, but since [[ is a keyword rather than a builtin, it benefits from a few special parsing rules which make it a lot easier:

      $ [ a < b ]
     -bash: b: No such file or directory
      $ [[ a < b ]]

    The first example returns an error because bash tries to redirect the file b to the command [ a ] (See File Redirection). The second example actually does what you expect it to. The special character < no longer has its special meaning of File Redirection operator.

  • Executables: The last kind of command that can be executed by bash is executables, also called external commands or applications. Executables are invoked by using a pathname. If the executable is in the current directory, use ./myprogram. If it's in the /usr/local/bin directory, use /usr/local/bin/myprogram.

    To make life a little easier for you, though, BASH uses a variable that tells it where to find applications in case you just know the name of the application but not its full pathname. This variable is called PATH, and it is a set of directory names separated by colons -- for example, /bin:/usr/bin. When a command is specified in BASH without a pathname (e.g. myprgram, or ls), and it isn't an alias, function, builtin or keyword, BASH searches through the directories in PATH, in order from left to right, to see whether they contain an executable of the name you typed.

Each command can be followed by arguments. Arguments are words you specify after the command name. Arguments are separated from the command name and from each other by white space. This is important to remember. For example, the following is wrong:

    $ [-f file]

You want the [ command name to be separated from the arguments -f, file and ]. If you do not separate [ and -f from each other with whitespace, bash will think you are trying to execute the command name [-f and look in PATH for a program named [-f. Additionally, the arguments file and ] also need to be separated by spaces. The [ command expects the last argument to be ]. The correct command separates all arguments with spaces:

    $ [ -f file ]

NOTE:
It is very important that you understand how this works exactly. If you don't grasp these concepts well, the quality of your code will degrade significantly and you will introduce very dangerous bugs. Read Argument Splitting very carefully.

    $ ls
    a  b  c

ls is a command that lists files in the current directory. It's intended to be used only for producing human-readable results. Please don't try to parse, pipe, grep, capture, read, or loop over the output of ls in a script. It's dangerous and there's always a better way. While an invaluable tool on the interactive shell, ls should never be used in scripts. You will understand why as you go through this guide.

    $ mkdir d
    $ cd d
    $ ls

mkdir is a command that creates a new directory. We specified the argument d to that command. This way, the application mkdir is instructed to create a directory called d. After that, we use the builtin command cd to change the shell's current directory to d. ls shows us that the current directory (which is now d) is empty, since it doesn't display any filenames.

In BASH scripts and functions, arguments that were passed to the script or function are saved in 'Positional Parameters'. You can read these by using $1, $2, and so on for the respective argument. You can also use $@ and $* but more about this later on.


  • Tip:
    You can use the type command to figure out the type of a command.
    For example:

    $ type rm
    rm is hashed (/bin/rm)
    $ type cd
    cd is a shell builtin



  • Alias: A name that is mapped to a string. Whenever that name is used as a command, it is replaced by the string it has mapped.

  • Function: A name that is mapped to a script. Whenever that name is used as a command, the script is called with the arguments provided to the function's name on the command line.

  • Builtin: Certain features have been built into BASH. These are handled internally whenever they are executed on the command line (and often do not create a new process).

  • Application: A program that can be executed by referring to its pathname (/bin/ls), or simply by its name if its location is in your PATH variable.


Argument Splitting

Commands in BASH can take multiple arguments. These arguments are used to tell the command exactly what it's supposed to do. In BASH, you separate these arguments by whitespace (spaces and tabs).

Assume you're in an empty directory. (If you want to try this code out, you can create and go into an empty directory called test by running: mkdir test; cd test.)

    $ ls                # List files in the current directory (no output: no files).
    $ touch a b c       # Create files 'a', 'b' and 'c'.
    $ ls                # List all files again; this time the output shows 'a', 'b' and 'c'.
    a  b  c

touch is an application that changes the 'Last Modified'-time of a certain file to the current time. If the filename that it's given does not exist yet, it simply creates that file, as a new and empty file. In this example, we passed three arguments. touch creates a file for each argument. ls shows us that three files have been created.

    $ rm *              # Remove all files in the current directory.
    $ ls                # List files in the current directory (no output: no files).
    $ touch a   b c     # Create files 'a', 'b' and 'c'.
    $ ls                # List all files again; this time the output shows 'a', 'b' and 'c'.
    a  b  c

rm is an application that removes all the files that it was given. * is a glob. It basically means all files in the current directory. You will read more about this later on.

Now, did you notice that there are several spaces between a and b, and only one between b and c? Also, notice that the files that were created by touch are no different than the first time. You now know that the amount of whitespace between arguments does not matter. This is important to know. For example:

    $ echo This is a test.
    This is a test.
    $ echo This    is    a    test.
    This is a test.

In this case, we provide the echo command with four arguments. 'This', 'is', 'a' and 'test.'. echo takes these arguments, and prints them out one by one with a space in between. In the second case, the exact same thing happens. The extra spaces make no difference. If we actually want the extra whitespace, we need to pass the sentence as one single argument. We can do this by using quotes:

    $ echo "This    is    a    test."
    This    is    a    test.

Quotes group everything inside them into a single argument. This argument is 'This    is    a    test.', properly spaced. echo prints this single argument out just like it always does.

Be very careful to avoid the following:

    $ ls                                          # List files in the current directory.
    The secret voice in your head.mp3  secret
    $ rm The secret voice in your head.mp3        # Executes rm with 6 arguments; not 1!
    rm: cannot remove `The': No such file or directory
    rm: cannot remove `voice': No such file or directory
    rm: cannot remove `in': No such file or directory
    rm: cannot remove `your': No such file or directory
    rm: cannot remove `head.mp3': No such file or directory
    $ ls                                          # List files in the current directory: It is still there.
    The secret voice in your head.mp3

You need to make sure you quote filenames properly. If you don't you'll end up deleting the wrong things! rm takes filenames as arguments. If you do not quote filenames with spaces, rm thinks that each argument is a separate file. Since BASH splits your arguments at the spaces, rm will try to remove each word. The above example tried to delete files for each word in the filename of the song, instead of the filename of the song. That caused our file secret to be deleted, and our song to remain behind!

Please have a good look at WordSplitting or http://bash-hackers.org/wiki/doku.php?id=syntax:words if all this isn't very clear to you yet.


  • Good Practice:
    You should always quote sentences or strings that belong together, even if it's not absolutely necessary. This will keep you alert and reduce the risk of human error in your scripts.
    For example, you should always quote arguments to the echo command.



  • Arguments: These are the optional additional words you can specify when running commands. They are given after the command's name ('ls -l foo' executes ls with two arguments).

  • Quotes: The two forms of quotes (' and ") are used to protect certain special characters inside them from being interpreted as special by BASH. The difference between ' and " will be discussed later.


Scripts

A script is basically a sequence of commands that BASH processes in order. It only moves on to the next command when the current one has ended, unless the current one has been executed asynchronously (in the background). Don't worry too much about the latter case yet -- you'll learn about how that works later on.

Virtually any example that you see in this guide can be used in a script just as well as on the command line.

Making a script is easy. You just make a new file, and put this in it at the top:

    #!/usr/bin/env bash

This header makes sure that whenever your script is executed, BASH will be used as its interpreter. Please do not be fooled by examples on the Internet that use /bin/sh as interpreter. sh is not bash. Even though sh's syntax and bash's look very much alike and even though most bash scripts will run in sh, a lot of the examples in this guide only apply to bash and will just break or cause unexpected behaviour in sh. Also, please refrain from giving your scripts that stupid .sh extension. It serves no purpose, and it's completely misleading (since it's going to be a bash script, not an sh script).

And by the way, it's perfectly fine if you use Windows to write your scripts, but if at all possible, avoid using Notepad for writing scripts. Microsoft Notepad can only make files with DOS-style line-endings. That means that each line you make in notepad will be ended by two characters: a Carriage Return and a Newline character. BASH reads lines as terminated by Newline characters only. As a result, the Carriage Return character will cause you incredible headache if you don't know it's there (very weird error messages). If at all possible, use a decent editor like Vim, Emacs, kate, GEdit, GVIM or xemacs. If you don't, then you will need to remove the carriage returns from your scripts before running them.

Once your script file has been made, you can run it like this:

    $ bash myscript

In this example, we execute BASH and tell it to read our script. Alternatively, you can give your script executable permissions. When you do this, you can actually execute the script instead of calling BASH manually:

    $ chmod +x myscript
    $ ./myscript

Some people like to keep their scripts in a personal directory. Others like to keep their scripts somewhere in the PATH variable. Most like to do both at once. Here's what I suggest you do:

    $ mkdir -p "$HOME/bin"
    $ echo 'PATH="$HOME/bin:$PATH"' >> "$HOME/.bashrc"

The first command will make a directory called bin in your home directory. The second command will add a line to your .bashrc file which adds the directory we just made to the beginning of the PATH variable. Every new instance of BASH will now check for executable scripts in your bin directory.

To apply the changes we added to .bashrc we obviously need to actually process .bashrc first. You can do that by closing your existing terminal and opening a new one. BASH will then initialize itself again by reading .bashrc among others. Alternatively you can just execute that line of code on the command line (PATH="$HOME/bin:$PATH") or manually process your .bashrc file in the running shell by running source "$HOME/.bashrc" . Yet another way would be to replace your current BASH instance with a new one by running exec bash .

As a result, we can now put our script in our bin directory and execute it as a normal command (we no longer need to prepend our script's name with its path, which was the ./ part in the previous examples):

    $ mv myscript "$HOME/bin"
    $ myscript


  • Tip:
    While you're defining the interpreter in your header, you might want to take the time to explain your script's function and expected arguments a little too:

    #! /usr/bin/env bash
    #
    #   scriptname argument [argument] ...
    #
    # A short explanation of your script's purpose.
    #
    # Copyright [date], [name]


  • Tip:
    You can use this header to specify up to one word of optional arguments that you want to pass to the interpreter. For example, the following arguments will turn on some verbose debugging:

    #! /bin/bash -xv
  • Unfortunately, you can't use this:

    #! /usr/bin/env bash -xv
  • because that would require two words of arguments in the shebang line. Unix doesn't allow that.


  • Header: The header of a script determines the application that will function as its interpreter (e.g. bash, sh, perl, ...). Colloquially, this is also called a shebang -- a slang term derived by combining hash (#) and bang (!). You will probably see the word shebang more often than header.


<- Contents | Special Characters ->

BashGuide/CommandsAndArguments (last edited 2023-06-20 18:36:11 by larryv)