Bash by example, part 2

Bash by example, part 2

Author: Daniel Robbins From: IBM DW China

Let's take a look at simple techniques for handling command line arguments, and then look at the basic programming structure of bash.

Receive argument

In the introductory article sample program, we used the environment variable "1"To refer to the first command line argument. Similarly, you can use"1 "to refer to the first command line argument. Similarly, you can use" 2", "$ 3" to pass a reference to the second and third arguments scripts. Here is an example :

#!/usr/bin/env bash echo name of script is $0 echo first argument is $1 echo second argument is $2 echo seventeenth argument is $17 echo number of arguments is $# Copy code

Except for the following two details, this example needs no explanation. the first,"0"Will expand to the name of the script called from the command line,"0 "will expand to the name of the script called from the command line," # "will expand to deliver to the number of arguments the script. The above test script, the command transfer different types of line argument to see how it works .

Sometimes you need a reference for all command-line arguments. For this purpose, bash implements the variable "$@", which expands to all command line parameters separated by spaces. You will see examples of using this variable in the "for" loop part later in this article.

Bash programming structure

If you have ever used a procedural language such as C, Pascal, Python or Perl to program, you must be familiar with standard programming structures such as "if" statements and "for" loops. For most of these standard structures, Bash has its own version. In the next few sections, we will introduce several bash structures and demonstrate the differences between these structures and the structures in other programming languages that you are already familiar with. If you didn't have much programming before, don't worry. I have provided enough information and examples so that you can keep up with the progress of this article.

Convenient conditional statement

If you have ever written file-related code in C, you should know that it takes a lot of work to compare whether a particular file is newer than another. That's because C does not have any built-in syntax for this kind of comparison, and two stat() calls and two stat structures must be used for manual comparison. In contrast, bash has built-in standard file comparison operators, so determining "whether/tmp/myfile is readable" is as easy as checking "whether $myvar is greater than 4".

The following table lists the most commonly used bash comparison operators. There are also examples of how to use each option correctly. The example should follow the "if". E.g:

if [-z "$myvar"] then echo "myvar is not defined" fi Copy code
File comparison operator
-e filenameTrue if filename exists[-e/var/log/syslog]
-d filenameTrue if filename is a directory[-d/tmp/mydir]
-f filenamein case True filename is a regular file[-f/usr/bin/grep]
-L filenamein case True filename is a symbolic link[-L/usr/bin/grep]
-r filenamein case True filename is readable[-r/var/log/syslog]
-w filenamein case True filename is writable[-w/var/mytmp.txt]
-x filenamein case filenameTrue executable[-L/usr/bin/grep]
filename1 -nt filename2If filename1 is better than filename2True newer[/tmp/install/etc/services -nt/etc/services]
filename1 -ot filename2If filename1 is better than filename2True older[/boot/bzImage -ot arch/i386/boot/bzImage]
String comparison operators (please pay attention to the use of quotation marks, this is a good way to prevent spaces from disturbing the code)
-z stringIf stringTrue length is zero[-z "$myvar"]
-n stringIf stringTrue length is non-zero[-n "$myvar"]
string1 = string2If string1 and string2True same["$myvar" = "one two three"]
string1 != string2True if string1 is different from string2["$myvar" != "one two three"]
Arithmetic comparison operators
num1 -eq num2equal[3 -eq $mynum]
num1 -ne num2not equal to[3 -ne $mynum]
num1 -lt num2Less than[3 -lt $mynum]
num1 -le num2less than or equal to[3 -le $mynum]
num1 -gt num2more than the[3 -gt $mynum]
num1 -ge num2greater than or equal to[3 -ge $mynum]

Sometimes, there are several different ways to make a specific comparison. For example, the following two code snippets have the same function:

if ["$myvar" -eq 3] then echo "myvar equals 3" fi if ["$myvar" = "3"] then echo "myvar equals 3" fi Copy code

The above two comparisons perform the same function, but the first one uses arithmetic comparison operators and the second one uses string comparison operators.

String comparison description

Most of the time, although you can omit the double quotes that enclose strings and string variables, it is not a good idea. why? Because if there happens to be a space or a tab key in the environment variable, bash will not be able to distinguish it and it will not work properly. Here is an incorrect comparison example:

if [$myvar = "foo bar oni"] then echo "yes" fi Copy code

In the above example, if myvar is equal to "foo", the code will work as expected and will not print. However, if myvar is equal to "foobar oni", the code will fail with the following error:

[: Too many arguments copy the code

under these circumstances,"myvar"(equal"foobaroni") Is confusedbash.bashExpand"The space in "myvar" (equal to "foo bar oni") confuses bash. Bash extension" after myvar ", code is as follows:

[foo bar oni = "foo bar oni" ] Copy code

Because environment variables are not enclosed in double quotes, bash thinks that there are too many arguments in square brackets. You can use double quotes to enclose the string argument to eliminate this problem. Remember, if you develop the habit of enclosing all string arguments in double quotes, many similar programming errors will be eliminated. "foobar oni" comparison should be written as:

if ["$myvar" = "foo bar oni"] then echo "yes" fi Copy code

More citation details

If you want to expand the environment variables, they must be used in double quotes , rather than single quotes. Single quotes disable variable (and history) expansion.

The above code will work as expected, without any unpleasant surprises.

Loop structure: "for"

Now that we have talked about conditional statements, it's time to explore the bash loop structure. We will start with the standard "for" loop. Here is a simple example:

#!/usr/bin/env bash for x in one two three four do echo number $x done Output: number one number two number three number four Copy code

what happened? The "for x" part of the "for" loop defines a name called "x"The new environment variable (also called loop control variable), its value is sequentially set to"one","two","three"with"four". After each assignment, execute a loop body ("do"with"done"Code between). In the loop body, like other environment variables, use standard variable expansion syntax to reference loop control variables"x" new environment variable (also called loop control variable), its value is set to "one", "two", "three" and "four" in turn. After each assignment, the loop body ("do The code between "and "done"). In the loop body, like other environment variables, use standard variable expansion syntax to reference loop control variables." X." Also note that the "for" loop always receives some type of word list after the "in" statement. In this example, four English words are specified, but the word list can also refer to files on disk, or even file wildcards. Take a look at the following example, which demonstrates how to use standard shell wildcards:

#!/usr/bin/env bash for myfile in/etc/r* do if [-d "$myfile"] then echo "$myfile (dir)" else echo "$myfile" fi done Output: /etc/rc.d (dir) /etc/resolv.conf /etc/resolv.conf~ /etc/rpc Copy code

The above code lists every file starting with "r" in/etc. To do this, bash first obtains the wildcard/etc/r* before executing the loop, and then expands it with the string/etc/rc.d/etc/resolv.conf/etc/resolv.conf~/etc/rpc replace. Once in the loop, depending on whether myfile is a directory, the "-d" conditional operator is used to perform two different operations. If it is a directory, append "(dir)" to the output line.

You can also use multiple wildcards or even environment variables in the word list:

for x in/etc/r--?/var/lo*/home/drobbins/mystuff/*/tmp/${MYPATH}/* do cp $x/mnt/mydir done Copy code

Bash will perform wildcard and environment variable expansion in all the correct positions and may create a very long list of words.

While all of the wildcard expansion examples use an absolute path, relative path may also be used, as follows:

for x in ../* mystuff/* do echo $x is a silly file done Copy code

In the above example, bash performs wildcard expansion relative to the current working directory, just like using a relative path on the command line. Investigate wildcard expansion. You will notice that if you use absolute paths in wildcards, bash expands the wildcards into a list of absolute paths. Otherwise, bash will use relative paths in the list of words that follow. If you only refer to files in the current working directory (for example, if you enter "for x in*"), the resulting file list will not have a prefix of path information. Remember, you can use the "basename" executable program to remove the preceding path information, as shown below:

for x in/var/log/* do echo `basename $x` is a file living in/var/log done Copy code

Of course, it is often convenient to execute loops on the command line arguments of the script. Here is an example of how to use the "$@" variable mentioned at the beginning of this article:

#!/usr/bin/env bash for thing in "$@" do echo you typed ${thing}. done Output: $ allargs hello there you silly you typed hello. you typed there. you typed you. you typed silly. Copy code

Shell arithmetic

Before learning another type of loop structure, it is best to be familiar with how to perform shell arithmetic. Yes, it is true: you can use the shell structure to perform simple integer arithmetic. Just enclose a specific arithmetic expression with "$((" and "))" and bash can calculate the expression. Here are some examples:

$ echo $(( 100/3 )) 33 $ myvar="56" $ echo $(( $myvar + 12 )) 68 $ echo $(( $myvar-$myvar )) 0 $ myvar=$(( $myvar + 1 )) $ echo $myvar 57 Copy code

More loop structures: "while" and "until"

As long as the specific condition is true, the "while" statement will be executed, and its format is as follows:

while [condition] do statements done Copy code

The "While" statement is usually used to loop a certain number of times. For example, the following example will loop 10 times:

myvar=0 while [$myvar -ne 10] do echo $myvar myvar=$(( $myvar + 1 )) done Copy code

As you can see, the above example uses an arithmetic expression to make the condition ultimately false and cause the loop to terminate.

"Until" statements provide the "while" statement is the opposite function: as long as certain conditions are false , they are repeated. The following is an "until" loop with the same function as the previous "while" loop:

myvar=0 until [$myvar -eq 10] do echo $myvar myvar=$(( $myvar + 1 )) done Copy code

Case statement

The Case statement is another convenient condition structure. Here is an example snippet:

case "${x##*.}" in gz) gzunpack ${SROOT}/${x} ;; bz2) bz2unpack ${SROOT}/${x} ;; *) echo "Archive format not recognized." exit ;; esac Copy code

In the above example, bash first expands " {x##*.}". In the code, " x" is the name of the file, and " {x##.*}" removes all text except the text after the last period in the file. Then, bash compares the generated string with the value listed to the left of ")". In this example, " {x##. }" is first compared with "gz", then "bz2", and finally " ". If "${x##. }" matches any of these strings or patterns, the line immediately after ")" is executed until ";;", and then bash continues to execute the line after the terminator "esac" Row. If it does not match any pattern or string, no line of code is executed. In this particular code snippet, at least one code block must be executed, because any string that does not match "gz" or "bz2" will be the same as " "Pattern matching.

Functions and namespaces

In bash, you can even define functions similar to other procedural languages (such as Pascal and C). In bash, functions can even receive arguments in a similar way as scripts receive command-line arguments. Let's take a look at the sample function definition, and then continue from there:

tarview() { echo -n "Displaying contents of $1 " if [${1##*.} = tar] then echo "(uncompressed tar)" tar tvf $1 elif [${1##*.} = gz] then echo "(gzip-compressed tar)" tar tzvf $1 elif [${1##*.} = bz2] then echo "(bzip2-compressed tar)" cat $1 | bzip2 -d | tar tvf- fi } Copy code

another situation

You can use the "case" statement to write the above code. Do you know how to write it?

We defined a function named "tarview" above, which receives an argument, which is a certain type of tar file. When executing this function, it determines which tar file type the argument is (uncompressed, gzip compressed or bzip2 compressed), prints a line of informational message, and then displays the contents of the tar file. The above function should be called as follows (after typing, pasting, or finding the function, call it from a script or command line):

$ tarview shorten.tar.gz Displaying contents of shorten.tar.gz (gzip-compressed tar) drwxr-xr-x ajr/abbot 0 1999-02-27 16:17 shorten-2.3a/ -rw-r--r-- ajr/abbot 1143 1997-09-04 04:06 shorten-2.3a/Makefile -rw-r--r-- ajr/abbot 1199 1996-02-04 12:24 shorten-2.3a/INSTALL -rw-r--r-- ajr/abbot 839 1996-05-29 00:19 shorten-2.3a/LICENSE .... Copy code

Use them interactively

Don't forget, you can put functions (such as the functions above) in ~/.bashrc or ~/.bash_profile so that you can use them at any time in bash.

As you can see, you can use the same mechanism as quoting command line arguments to refer to arguments inside function definitions. In addition, the " #" macro will be expanded to include the number of arguments. The only thing that may not be exactly the same is the variable " 0", which will expand to the string "bash" (if the function is run interactively from the shell) or the name of the script that calls the function.

Name space

It is often necessary to create environment variables in functions. Although it is possible, there is one technical detail that should be understood. In most compiled languages (such as C), when a variable is created inside a function, the variable is placed in a separate local namespace. Therefore, if a function named myfunction is defined in C and an argument named "x" is defined in the function, any global variable named "x" (variable outside the function) will not be affected Its impression, thereby eliminating the negative effects.

This is true in C, but not in bash. In bash, whenever create an environment variable inside a function, it will be added to the global namespace. This means that the variable will overwrite the global variable outside the function and continue to exist after the function exits:

#!/usr/bin/env bash myvar="hello" myfunc() { myvar="one two three" for x in $myvar do echo $x done } myfunc echo $myvar $x Copy code

When this script is run, it will output "one two threethree", which shows what is defined in the functionmyvar"How to affect global variables"myvar "how to affect global variables" myVar ", and the loop control variable"x"How to continue to exist after the function exits (if"x" How to continue to exist after the function exits (if " x "global variables exist, will also be affected).

In this simple example, it is easy to find the error and correct it by using other variable names. But this is not the right way. The best way to solve this problem is to prevent the possibility of affecting global variables at the beginning by using the "local" command. When you create a variable inside the function using the "local", they will be placed in the local name space, and does not affect any global variables. Here is how to implement the above code so as not to rewrite global variables:

#!/usr/bin/env bash myvar="hello" myfunc() { local x local myvar="one two three" for x in $myvar do echo $x done } myfunc echo $myvar $x Copy code

This function will output "hello" - do not rewrite global variables"myvar","myvar"," x" does not continue to exist outside of myfunc. In the first line of the function, we create the local variable x to be used later, and in the second example (local myvar="one two three In ""), we created a local variable myvar andassigned a value to itat the same time. When defining loop control variables as local variables, it is convenient to use the first form, because it is not allowed to say: "forlocal x in$myvar". This function does not affect any global variables, and you are encouraged to design all functions in this way. Only when explicitly want to modify a global variable, itdoes notshould be used "local".

Concluding remarks

We have learned the most basic bash functions, and now we have to look at how to develop an entire application based on bash. The next part is about to talk about it. Goodbye!