Bash 脚本基本语法

1) What is a Bash script

A Bash script is a plain text file which contains a series of commands. These commands are a mixture of commands we would normally type ouselves on the command line (such as ls or cp for example) and commands we could type on the command line but generally wouldn’t (you’ll discover these over the next few pages). An important point to remember though is:

Anything you can run normally on the command line can be put into a script and it will do exactly the same thing. Similarly, anything you can put into a script can also be run normally on the command line and it will do exactly the same thing.

You don’t need to change anything. Just type the commands as you would normally and they will behave as they would normally. It’s just that instead of typing them at the command line we are now entering them into a plain text file. In this sense, if you know how to do stuff at the command line then you already know a fair bit in terms of Bash scripting.

2) Variables

2-1) Special variables

  • $0 - The name of the Bash script
  • $1-9 - The first 9 arguments to the Bash script
  • $# - How many arguments were passed to the Bash script
  • $@ - All the arguments supplied to the Bash script
  • $? - The exit status of the most recently run process
  • $USER - The username of the user running the script
  • $HOSTNAME - The hostname of the machine the script is running on
  • $SECONDS - The number of seconds since the script was started
  • $RANDOM - Returns a different random number each time is it referred to
  • $LINENO - Returns the current line number in the Bash script

2-2) Setting our own variables

#!/bin/bash
# A simple variable example
myvariable=Hello
anothervar=Fred
echo $myvariable $anothervar
echo
sampledir=/etc
ls $sampledir

When we enclose our content in quotes we are indicating to Bash that the contents should be considered as a single item. You may use single quotes(‘) or double quotes(“”)
* Single quotes will treat every character literally
* Double quotes will allow you to do substitution (that is include variables within the settting of the value)

myvar='Hello World'
echo $myvar
Hello World
newvar="More $myvar"
echo $newvar
More Hello World
newvar='More $myvar'
echo $newvar
More $myvar

2-3) Command substitution

Command substitution allows us to take the output of a command or program(what would normally be printed to the screen) and save it as the value of a variable. To do this we place it within brackets, preceded by a $ sign.

myvar=$(ls /etc | wc -l)
echo There are $myvar entries in the directory /etc

Commmand substitution is nice and simple if the output of the command is a single word or line. if the output goes serveral lines then the newlines are simply removed and all the output ends up on a single line.

ls
bin Documents Desktop ...
Downloads public_html ...

myvar=$( ls )
echo $myvar
bin Documents Desktop Downloads public_html ...

2-4) Exporting varibales

myvar=helloworld
export myvar
...

When we export a variable is that we are telling Bash that every time a new process is created (to run another script or such) the make a copy of the variable and hand it over to the new process. So although the variables will have the same name they exist in separate process and so are unrelated to each other.

Exporting variables is a one way process. The original process may pass variables over to the new process but anything that process does with the copy of the variables has no impact on the original variables.

3) Input

If we would like to ask the user for input then we use a command called read. This command takes the input and will save it into a variable.

Let’s look at a simple example:

#!/bin/bash
# Ask the user for their name
echo Hello, who am I talking to?
read varname
echo It\'s nice to meet you $varname

3-1) More with read

You are able to alter the behaviour of read with a variety of command line options.(See the man page for read to see all of them.) Two commonly used options however are -p which allows you to specifiy a prompt and -s which makes the input silent. This can make it easy to ask for a username and password combination like the example below:

#!/bin/bash
# Ask the user for login details
read -p 'Username: ' uservar
read -sp 'Password: ' passvar
echo
echo Thankyou $uservar we now have your login details

3-2) More variables

So for we’ve looked at a single word as input. We can do more than that however.


#!/bin/bash
# Demonstrate how read actually works
echo What cars do you like?
read car1 car2 car3
echo Your first car was: $car1
echo Your second car was: $car2
echo Your third car was: $car3

output:

What cars do you like?
Jaguar Maserati Bentley
Your first car was: Jaguar
Your second car was: Maserati
Your third car was: Bentley

What cars do you like?
Jaguar Maserati Bentley Lotus
Your first car was: Jaguar
Your second car was: Maserati
Your third car was: Bentley Lotus

The general mechanism is that you can supply several variable name to read. Read will then take your input and split it on whitespace.
The first item will then be assigned to the first variable name, the second item to the second variable name and so on. if there are more items than variable names then the remaining items will all be added to the last variable name. If there are less items than variable naems then the remaining variable names will be set to blank or null.

3-3) Reading from STDIN

It’s common in Linux to pipe a series of simple, single purpose commands together to create a larger solution tailored to our exact needs.

Bash accommodates piping and redirection by way of special files. Each process gets it’s own set of files(one for STDIN, STDOUT and STDERR respectively) and they are linked when piping or redirection is invoked.
Each process gets the following files:
* STDIN - /proc//fd/0
* STDOUT - /proc//fd/1
* STDERR - /proc//fd/2

To make life more convenient the system creates some shortcuts for us:
* STDIN - /dev/stdin or /proc/self/fd/0
* STDOUT - /dev/stdout or /proc/self/fd/1
* STDERR - /dev/stderr or /proc/self/fd/2
fd in the paths above stands for file descriptor.

So if we would like to make our script able to process the data that is piped to it all we need to do is read the relevant file. All of the files mentioned above behave like normal file:

#!/bin/bash
# A basic summary of my sales report
echo Here is a summary of the sales data:
echo ====================================
echo
cat /dev/stdin | cut -d' ' -f 2,3 | sort

Outputs:

cat salesdata.txt
Fred apples 20 November 4
Susy oranges 5 November 7
Mark watermelons 12 November 10
Terry peaches 7 November 15
cat salesdata.txt | ./summary
Here is a summary of the sales data:
====================================
apples 20
oranges 5
peaches 7
watermelons 12

So we now have 3 methods for getting input from the user:
* Command line arguments
* Read input during script execution
* Accept data input has been redirection into the bash script vis STDIN

4) Arithmetic

4-1) Let

let is a builtin function of Bash that allows us to do simple arithmetic.

let <arithmetic expression>

#!/bin/bash
# Basic arithmetic using let
let a=5+4
echo $a # 9
let "a = 5 + 4"
echo $a # 9
let a++
echo $a # 10
let "a = 4 * 5"
echo $a # 20
let "a = $1 + 30"
echo $a # 30 + first command line argument

4-2) Expr

Expr is similar to let except instead of saving the result to a variable it instead prints the answer. Unlike let you don’t need to enclose the expression in quotes. You also must have spaces between the items of the expression. It is also common to use expr within command substitution to save the output to a variable.

#!/bin/bash
# Basic arithmetic using expr
expr 5 + 4
expr "5 + 4"
expr 5+4
expr 5 \* $1
expr 11 % 2
a=$( expr 10 - 3 )
echo $a # 7

Outputs:

9
5 + 4
5+4
60
1
7

Notes:
* If we do put quotes around the expression then the expression will not be evaluated but printed instead.
* If we do not put spaces between the items of the expression then the expression will not be evaluated but printed instead.
* Some characters have a special meaning to Bash so we have to escape them (put a backslash in front of ) to remove their special meaning.
* This time we’re using the expr within command substitution in order to save the result to the variable a.

4-3) Double Parentheses

In the section on Variables we saw that we can save the output of a command easily to a variable. It turns out that this mechanism is also able to do basic arithmetic for us if we tweak the syntax a little. We do so by using double brackets like so:

$((expression))

Here’s an example to illustrate:

#!/bin/bash
# Basic arithmetic using double parentheses
a=$(( 4 + 5 ))
echo $a # 9
a=$((3+5))
echo $a # 8
b=$(( a + 3 ))
echo $b # 11
b=$(( $a + 4 ))
echo $b # 12
(( b++ ))
echo $b # 13
(( b += 3 ))
echo $b # 16
a=$(( 4 * 5 ))
echo $a # 20

Outputs:
9
8
11
12
13
16
20

So as you can see double parentheses is quite flexible in how you format it’s expression. This is part of why we prefer this method. As double parentheses is builtin to Bash it also runs slightly efficiently.

4-4) Length of a Variable

This isn’t really arithmetic but it can be quite useful. If you want to find out the length of a variable (how many characters) you can do the following:

${#vaiable}

Here’s an example:

#!/bin/bash
# Show the length of a variable.
a='Hello World'
echo ${#a} # 11
b=4953
echo ${#b} # 4

Outputs:
11
4

4-4) Summary

  • let - Make a variable equals to an expression
  • expr - print out the result of the expression
  • $(()) - Return the result of the expression
  • ${#var} - Return the length of the variable var

Important Concepts:
* Arithmetic - There are several ways in which to do arithmetic in Bash scripts. Double parentheses is the preferred method.
* Formatting - When doing arithmetic, the presence or absence of space (and quotes) is often important.

5) If Statements

Bash if statements are very useful. In this section of our Bash script tutorial you will learn the ways you may use if statements in your Bash script to help automate tasks.

5-1) Basic If Statements

A basic if statement effectively say, if a particular test is true, then perform a given set of actions. if it is not true then don’t perform those actions. If follows the format below:

if [ <some test> ]
then
<commands>
fi

Here’s an example:

#!/bin/bash
# Basic if statement
if [ $1 -gt 100 ]
then
echo Hey that\'s a large number.
pwd
fi
date

Tips: It is always good practice to test your script with input that cover the different scenarios that are possible.

5-2) Test

The square brackets ([]) in the if statement above are actually a reference to the command test. This means that all of the operators that test allows may be used in here as well. Look up the man page for test to see all of the possible operators, but some of the more common ones are list below:

OperatorDescription
! EXPRESSIONThe EXPRESSION is false.
-n STRINGThe length of STRING is greater than zero.
-z STRINGThe lengh of STRING is zero (ie it is empty).
STRING1 = STRING2STRING1 is equal to STRING2
STRING1 != STRING2STRING1 is not equal to STRING2
INTEGER1 -eq INTEGER2INTEGER1 is numerically equal to INTEGER2
INTEGER1 -gt INTEGER2INTEGER1 is numerically greater than INTEGER2
INTEGER1 -lt INTEGER2INTEGER1 is numerically less than INTEGER2
-d FILEFILE exists and is a directory.
-e FILEFILE exists.
-r FILEFILE exists and the read permission is granted.
-s FILEFILE exists and it’s size is greater than zero (ie. it is not empty).
-w FILEFILE exists and the write permission is granted.
-x FILEFILE exists and the execute permission is granted.

A few points to note:
* = is slightly different to -eq. [ 001 = 1 ] will return false as = does a string comparison(ie. character for character the same) whereas -eq does a numerical comparison meaning [ 001 -eq 1] will return true.
* When we refer to FILE above we are actually meaning path. Remember that a path may be absolute or relative and may refer to a file or a directory
* Because [] is just a reference to the command test we may experiment and trouble shoot with test on the command line to make sure our understanding of its behaviour is correct

test 001 = 1
echo $?
1
test 001 -eq 1
echo $?
0
touch myfile
test -s myfile
echo $?
1
ls /etc > myfile
test -s myfile
echo $?
0
  • Perform a string based comparison. Test doesn’t print the result so instead we check it’s exit status which is what we will do on the next line
  • The variable $? holds the exit status of the previously run command. 0 means true (or success). 1 = false (or failure).

5-3) Nested If Statements

Here is an example:

#!/bin/bash
# Nested if statements
if [ $1 -gt 100 ]
then
    echo Hey that is a large number.
    if (( $1 % 2 == 0 ))
    then
        echo And is also an even number.
    fi
fi

Let’s break it down:
* if (( $1 % 2 == 0 )) This is a ligth variation on the if statement. If we would like to check an expression then we may use the double brackets just like we did for variables.

Tips: You can nest as many if statement as you like but as a general rule of thumb if you need to nest more than 3 levels deep then you should probably have a think about reorganising your logic.

5-4) If Else

Sometimes we want to perform a certain set of actions if a statement is true, and another set of actions if it is false. We can accommodate this with the else mechanism

if [ <some test> ]
then
    <commands>
else
    <other commands>
fi

Example:

#!/bin/bash
# else example
if [ $# -eq 1 ]
then
    nl $1
else
    nl /dev/stdin
fi

5-5) If Elif Else

Sometimes we may have a series of conditions that may lead to different paths

if [ <some test> ]
then
    <commands>
elif [ <some test> ] 
then
    <different commands>
else
    <other commands>
fi

Example:

#!/bin/bash
# elif statements
if [ $1 -ge 18 ]
then
    echo You may go to the party.
elif [ $2 == 'yes' ]
then
    echo You may go to the party but be back before midnight.
else
    echo You may not go to the party.
fi

5-6) Boolean Operations

Sometimes we only want to do something if multiple conditions are met. Other times we would like to perform the action if one of several condition is met. We can accommodate these with boolean operators.

and - &&
or - ||

Examples:

One:
#!/bin/bash
# and example
if [ -r $1 ] && [ -s $1 ]
then
    echo This file is useful.
fi

Two:
#!/bin/bash
# or example
if [ $USER == 'bob' ] || [ $USER == 'andy' ]
then
    ls -alh
else
    ls
fi

5-7) Case Statements

Sometimes we may wish to take different paths based upon a variable matching a series of patterns.

case <variable> in
<pattern 1>)
    <commands>
;;
<pattern 2>)
    <other commands>
;;
esac

Example:

#!/bin/bash
# case example
case $1 in
    start)
        echo starting
        ;;
    stop)
        echo stoping
        ;;
    restart)
        echo restarting
        ;;
    *)
        echo do not know
        ;;
esac

Now let’s look at a slightly more complex example where patterns are used a bit more.

disk_useage.sh

#!/bin/bash
# Print a message about disk useage.
space_free=$( df -h | awk '{ print $5 }' | sort -n | tail -n 1 | sed 's/%//' )

case $space_free in
    [1-5]*)
        echo Plenty of disk space available
        ;;
    [6-7]*)
        echo There could be a problem in the near future
        ;;
    8*)
        echo Maybe we should look at clearing out old files
        ;;
    9*)
        echo We could have a serious problem on our hands soon
        ;;
    *)
        echo Something is not quite right here
        ;;
esac

6) Loops

Bash loops are very useful. In this section of our Bash script tutorial we’ll look at the different loop formats available to us as well as discuss when and why you may want to use each of them

There are 3 basic loop structures in Bash scripting which we’ll look at below. There are also a few statements which we can use to control the loops operation.

6-1) While Loops

One of the easiest loops to work with is while loops.

while [ <some test> ]
do
<commands>
done

Example:

#!/bin/bash
# Basic while loop
counter=1
while [ $counter -le 10 ]
do
    echo $counter
    // Using the double brackets 
    // we can increase the value of counter by 1.
    ((counter++)) 

done
echo All done

Outputs:
1
2
3
4
5
6
7
8
9
10
All done

6-2) Until Loops

The until loop is quite similar to the while loop. The difference is that it will execute the commands within it until the test becomes true.

until [ <some test> ]
do
    <commands>
done

Example:

#!/bin/bash
# Basic until loop
counter=1
until [ $counter -gt 10 ]
do
    echo $counter
    ((counter++))
done
echo All done

6-3) For Loops

The for loop is a little bit different from the previous two loops.
It has the following syntax:

for var in <list>
do
    <commands>
done

The for loop will take each item in the list(in order, one after the other). assign that item as the value of the variable var, execute the commands between do and done then go back to the top, grab the next item in the list and then repeat over.
The list is defined as a series of strings, separated by spaces.
Example:

#!/bin/bash
# Basic for loop
names='Stan Kyle Cartman'
for name in $names
do
    echo $name
done
echo All done

6-4) Ranges

We can also process a series of numbers.

#!/bin/bash
# Basic range in for loop
// It's important when specifying a range like this that there 
// are no spaces present between the curly brackets. If there 
// are then it not be seen as a range but as a list of items.
for value in {1..5}
do
    echo $value
done
echo All done

Output:
1
2
3
4
5

One of the more useful applications of for loop is in the processing of a set of files. To do this we may use wildcards. Let’s say we want to convert a series of .html files over to .php files.

#!/bin/bash
# Make a php copy of any html files
for value in $1/*.html
do
    cp $value $1/$( basename -s .html $value ).php
done

6-5) Controlling Loops: Break and Continue

Break :
The break statement tells Bash to leave the loop straight away.

#!/bin/bash
# Make a backup set of files
for value in $1/*
do
    used=$( df $1 | tail -1 | awk '{ print $5 }' | sed 's/%//' )
    if [ $used -gt 90 ]
    then
        echo Low disk space 1>&2
    break
    fi
    cp $value $1/backup/
done

Continue:
The continue statement tells Bash to stop running through this iteration of the loop and begin the next iteration.

#!/bin/bash
# Make a backup set of files
for value in $1/*
do
    if [ ! -r $value ]
    then
        echo $value not readable 1>&2
        continue
    fi
    cp $value $1/backup/
done

6-6) Select

The select mechanism allows you to create a menu system. It has the following format:

select var in <list>
do
    <commands>
done

When invoked it will take all the items in list (similar to other loops this is a spaces separated set of items) and present them on the screen with a number before each item. A prompt will be printed after this allowing user to select a number. When they select a number and hit enter the corresponding the value will be assigned to the variable var and the commands between the do and done will run. Once finished a prompt will be displayed again so the user may select another option.

A few points to note:
* No error checking is done. If the user enters something other than a number or a number not corresponding to an item then var becomes null(empty).
* If the user hits enter without entering any data then the list of options will be displayed again.
* The loop will be ended when an EOF signal is entered or the break statement is issued.
* You may change the system variable PS3 to change the prompt that is displayed.

Here is a simple example to illustrate it’s usage:

#!/bin/bash
# A simple menu system
names='Kyle Cartman Stan Quit'
// Change the value of the system variable PS3 so that the 
// prompt is set to something a little more descriptive.
PS3='Select character: '
select name in $names
do
    if [ $name == 'Quit' ]
    then
    break
    fi
    echo Hello $name
done
echo Bye

7) Functions

Functions in Bash Scripting are a great way to reuse code. In this section of our Bash scripting tutorial you’ll learn how they work and what you can do with them.

7-1) Basic Concept

Creating a function is fairly easy. They may be written in two different formats:

function_name () {
    <commands>
}

or

function function_name {
    <commands>
}

A few points to note:

  • Either of the above methods of specifying a function is valid. Both operate the same and there is no advantage or disadvantage to one over the other. It’s really just personal preference.
  • In other programming languages it is common to have arguments passed to the function listed inside the brackets(). In Bash they are there only for decoration and you never put anything inside them.
  • The function definition(the actual function itself) must appear in the script before any calls to the function.

Let’s look at a simple example:

#!/bin/bash
# Basic function
print_something () {
    echo Hello I am a function
}
print_something
print_something

\
Outputs:
./function_example.sh
Hello I am a function
Hello I am a function

7-2) Passing Arguments

It is often case that we would like the function to process some data for us. We may send data to the function in a similar way to passing command line arguments to a script. We supply the arguments directly after the function name. Within the function they are accessible as 1, 2, etc.

#!/bin/bash
# Passing arguments to a function
print_something () {
echo Hello $1
}
print_something Mars
print_something Jupiter

Outputs:
Hello Mars
Hello Jupiter

7-3) Return Values

Most other programming language have the concept of a return value for function, a means for the function to send data back to the original calling location. Bash function does’t allow us to do this. They do however allow us to set a return status. Similar to how a program or command exits with an exit status which indicates whether it succeeded or not. We use the keyword return to indicate a return status.


#!/bin/bash
# Setting a return status for a function
print_something () {
echo Hello $1
return 5
}
print_something Mars
print_something Jupiter
echo The previous function has a return value of $?

Outputs:
./return_status_example.sh
Hello Mars
Hello Jupiter
The previous function has a return value of 5

Let’s break it down:

  • The return status doesn’t have to be hardcoded. It may be a variable.
  • Remember that the variable $? contains the return status of the previously run command or function.

Notes:

  • Typically a return status of 0 indicates that everything went successfully. A non zero value indicates an error occurred.
  • If all you want to do is return a number then you can consider using the return status to achieve this. It is not it’s intended purpose but it will work.

One way to get around this is to use command substitution and have the function print the result (and only the result).

#!/bin/bash
# Setting a return value to a function
lines_in_file () {
cat $1 | wc -l
}
num_lines=$( lines_in_file $1 )
echo The file $1 has $num_lines lines in it.

Outputs:


cat myfile.txt
Tomato
Lettuce
Capsicum
./return_hack.sh myfile.txt
The file myfile.txt has 3 lines in it.

Let’s break it down:

  • This command will print the number of line in the file referred to by $1
  • We use the command substitution to take what would normally be printed to the screen and assign it to the variable num_lines.

7-4) Variable Scope

Scope refers to which part of a script can see which variables. By default a variable is global. This means that it is visible everywhere in the script. We may also create a variable as a local variable. When we create a local variable within a function. It is only visible within that function. To do that we use the keyword “local” in front of the variable the first time we set it’s value.

local var_name=<var_value>

It is generally considered good practice to use the local variables within functions so as to keep everything within the function contained. This way variables are safer from being inadvertently modified by another part of the script which happens to have a variable with the same name.

#!/bin/bash
# Experimenting with variable scope
var_change () {
local var1='local 1'
echo Inside function: var1 is $var1 : var2 is $var2
var1='changed again'
var2='2 changed again'
}
var1='global 1'
var2='global 2'
echo Before function call: var1 is $var1 : var2 is $var2
var_change
echo After function call: var1 is $var1 : var2 is $var2

Outputs:

./local_variables.sh
Before function call: var1 is global 1 : var2 is global 2
Inside function: var1 is local 1 : var2 is global 2
After function call: var1 is global 1 : var2 is 2 changed again

Tips:
Always use local variables within functions. Use global variables as a last resort and consider if there is a better way to do it before using them.

7-5) Overriding Commands

It is possible to name a function as the same name as the command you would normally use on the command line. This allows us to create a wrapper. eg. Maybe every time we call the ls command in our script, what we actually want is ls -lh. We could do the following:

#!/bin/bash
# Create a wrapper around the command ls
ls () {
command ls -lh
}
ls

7-6) Import functions from another file

Sometimes you maybe want to use functions that defined in another script file. To solve this issue, you can use “.” or source keyword to import a file which the functions are defined in

Here is an example:

#!/bin/bash
. /home/username/yourscript.sh # The file that you want to import

or
source /home/username/yourscript.sh # The file that you want to import

# do something ...
...

8) User Interface

This is the final section in the tutorial and I’d like to use it to discuss a very important topic (which is often neglected) the user interface.

When most people think about the user interface they think about the bits the end user see and how they interact with the tool. For Bash scripts I like to think about the layout and structure of the commends inside the script as well. Bash scripts are often small tools used to automate tedious and repeated tasks. They are always readable by the user and often modified to suit changing requirements. Therefore the ease with which the user may modify and extend the script is also very important.

8-1) TPut

TPut is a command which allows you to control the cursor on the terminal and the format of content that is printed. It is quite a powerful and complex tool so I’ll introduce some of the basics here but leave it up to you to do further research.

Here is an example printing a message in the middle of the screen.

#!/bin/bash
# Print message in center of terminal

# tput cols will tell us how many columns the terminal has
cols=$( tput cols )

# tput lines will tell us how many lines the terminal has.
rows=$( tput lines )

# Take all the command line arguments and assign them to a single variable message
message=$@

# Find out how many characters in the string message.
input_length=${#message}
half_input_length=$(( $input_length / 2 ))
middle_row=$(( $rows / 2 ))
middle_col=$(( ($cols / 2) - $half_input_length ))

# tput clear will clear the terminal
tput clear

# tput cup will place the cursor at the given row and column.
tput cup $middle_row $middle_col

# tput bold will make everything printed to the screen bold.
tput bold
echo $@

# tput sgr0 will turn bold off (and any other changes we may have made)
tput sgr0

# Place the prompt at the bottom of the screen.
tput cup $( tput lines ) 0

Outputs:

user@bash: ./center_message.sh Hello there

                             Hello there

user@bash:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值