<- Contents | Special Characters ->


Commands and Arguments

BASH reads commands from its input (which is usually either a terminal or a file). Each line of input that it reads is treated as a command -- an instruction to be carried out. (There are a few advanced cases, such as commands that span multiple lines, but we won't worry about that just yet.)

Bash divides each line into words at each whitespace character (spaces and tabs). The first word it finds is the name of the command to be executed. All the remaining words become arguments to that command (options, filenames, etc.).

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

The ls command prints out the names of the files in the current directory. The first time we run ls, we get no output, because there are no files yet.

The # character, when it is at the beginning of a word, indicates a comment. Comments are ignored by the shell; they are meant only for humans to read. Everything from the # to the end of the current line is considered a comment, and ignored. If you're running these examples in your own shell, you don't have to type the comments; but even if you do, things will still work.

touch is an application that changes the Last Modified time of a 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 globs later.

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.

echo is a command that writes its arguments to standard output (which in our case is the terminal). In this example, 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. Note that the quotes are not part of the argument; BASH removes them before handing the argument to echo. echo prints this single argument out just like it always does.

Be very careful to avoid the following:

$ ls                                          # There are two 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             # But your file 'secret' is now gone!

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 your filenames have spaces and you do not quote them, BASH thinks each word is a separate argument. BASH hands each argument to rm separately, like individually wrapped slices of processed cheese. rm treats each argument as a separate file.

The above example tried to delete a file for each word in the filename of the song, instead of keeping the filename intact. That caused our file secret to be deleted, and our song to remain behind!

This is what we should have done:

$ rm "The secret voice in your head.mp3"

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 ] need to be separated by spaces. The [ command expects its last argument to be ]. The correct command separates all arguments with spaces:

$ [ -f file ]

(We'll cover the [ command in more detail later. We see a lot of people who are confused by it, and think that they can omit the white space between it and its arguments, so we need to present this particular example very early.)

And of course, if your filename contains whitespace or other special characters, it should be quoted:

$ [ -f "my file" ]

NOTE: Please have a good look at Arguments, Quotes, WordSplitting and http://wiki.bash-hackers.org/syntax/words if all this isn't very clear to you yet. It is important that you have a good grasp of how the shell interprets the statements you give it before you continue this guide.





Strings

The term string refers to a sequence of characters which is treated as a single unit. The term is used loosely throughout this guide, as well as in almost every other programming language.

In BASH programming, almost everything is a string. When you type a command, the command's name is a string, and each argument is a string. Variable names are strings, and the contents of variables are strings as well. A filename is a string, and most files contain strings. They're everywhere!

An entire command can also be considered a string. This is not normally a useful point of view, but it illustrates the fact that parts of strings can sometimes be considered strings in their own right. A string which is part of a larger string is called a substring.

Strings do not have any intrinsic meaning. Their meaning is defined by how and where they are used.

Let's try another example. With your favorite editor, write a shopping list and save it with the file name "list", and use cat to show it:

$ cat list
shampoo
tissues
milk (skim, not whole)

We typed a command: cat list. The shell reads this command as a string, and then divides it into the substrings cat and list. As far as the shell is concerned, list has no meaning. It's just a string with four characters in it. cat receives the argument list, which is a string that it interprets as a filename. The string list has become meaningful because of how it was used.

The file happens to contain some text, which we see on our terminal. The entire file content, taken as a whole, is a string, but that string is not meaningful. However, if we divide the file into lines (and therefore treat each line as a separate string), then we see each individual line has meaning.

We can divide the final line into words, but these words are not meaningful by themselves. We can't buy (skim at the store, and we might get the wrong kind of milk. Dividing the lines into words is not a useful thing to do in this example. But the shell doesn't know any of this -- only you do!

So, when you are dealing with commands, and data, and variables -- all of which are just strings to the shell -- you have all the responsibility. You need to be sure everything that needs to be separated can be separated properly, and everything that needs to stay together stays together properly.

We'll touch on these concepts repeatedly as we continue.

Types of Commands

Bash understands several different types of commands: aliases, functions, builtins, keywords, and executables.






Scripts

A script is basically a sequence of commands in a file. Bash reads the file and processes the commands 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. Just make a new file, and put this in it at the top:

#!/usr/bin/env bash

This header (also called the hashbang or shebang) makes sure that whenever your script is executed, Bash will be used as its interpreter. The way it works, is that when the kernel executes a non-binary, it looks at the first line of the file. If the line begins with #!, the kernel uses the line to determine the interpreter that the code should be passed to. (There are other valid ways to do this, as well -- see below.) The #! must be at the very start of the file, with no spaces or blank lines before them. Your script's commands should all appear on separate lines below this.

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 call it like this:

$ bash myscript

In this example, we execute bash and tell it to read our script. When we do this, the #! line is just a comment. Bash does not do anything at all with it.

Alternatively, you can give your script executable permissions. When you do this, you can actually execute the script as an application instead of calling Bash manually:

$ chmod +x myscript
$ ./myscript

When executed in this way, the #! line tells the operating system (OS) what interpreter to use. The OS runs /usr/bin/env, which in turn runs bash, which reads our script. BASH itself ignores the # header line, just like last time.

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"
$ exec bash

The first command will make a directory called bin in your home directory. It is traditional for directories that contain commands to be named bin, even when those commands are scripts and not compiled ("binary") programs. 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. Finally, the third line replaces our current instance of Bash with a new one, which reads the .bashrc file.

Changes to DotFiles (such as .bashrc) never have an immediate effect. You have to take some step to re-read the files. In the example above, we used exec bash to replace the running shell. If you wanted, you could close your existing terminal and open a new one. Bash would then initialize itself again by reading .bashrc (and possibly other files). Or, you could 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" .

In any case, 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





<- Contents | Special Characters ->

BashGuide/CommandsAndArguments (last edited 2013-11-18 12:57:34 by 46)