Bash shell programming tips to keep code clean and process clear _linux shell

Source: Internet
Author: User
Tags touch

This is my moves to write bash programs. There's nothing new here, but from my experience people love to abuse bash. They ignore computer science and create "big clay balls" from their programs: software systems that are not clearly structured.

Here I tell you the method to protect your program from obstacles and keep the code neat.

Global variables that cannot be changed

1. Use global variables as little as possible
2. Name in UPPERCASE
3. Read-only Declaration
4. Use global variables to replace obscure $0,$1, etc.

Global variables that are commonly used in my programs:

Copy Code code as follows:

ReadOnly progname=$ (basename $)
ReadOnly progdir=$ (Readlink-m $ (dirname $))
ReadOnly args= "$@"

Second, everything is part of the

All variables should be local.

Copy Code code as follows:

Change_owner_of_file () {
Local filename=$1
Local user=$2
Local group=$3

Chown $user: $group $filename
}
Change_owner_of_files () {
Local user=$1; Shift
Local group=$1; Shift
Local files=$@
Local I

For I in $files
Todo
Chown $user: $group $i
Done
}

1. Parameters for self-annotation (self documenting)
2. It is important to declare it as a local variable, usually as a cyclic variable i.
3. Local variables are not used for global domains.

Copy Code code as follows:

Kfir@goofy ~ $ local A
Bash:local:can is used in a function

Three, Main ()

1. Helps maintain the local nature of all variables
2. Intuitive functional programming
3. The only global command in the code is: main

Copy Code code as follows:

Main () {
Local files= "/tmp/a/tmp/b"
Local I

For I in $files
Todo
Change_owner_of_file Kfir Users $i
Done
}
Main

Four, everything is a function

The only code that runs globally is:
-Immutable global variable declaration
-Main () function

1. Keep the code neat
2. Process becomes clear

Copy Code code as follows:

Main () {
Local files=$ (ls/tmp | grep pid | grep-v daemon)
}

Copy Code code as follows:

Temporary_files () {
Local dir=$1

LS $dir \
| grep pid \
| Grep-v Daemon
}

Main () {
Local files=$ (TEMPORARY_FILES/TMP)
}

1. The second example is much better. The lookup file is a temporary_files () problem rather than main (). This code is also testable with unit tests of Temporary_files ().

2. If you are sure to try the first example, you will get a hodgepodge of finding temporary files and main algorithms.

Copy Code code as follows:

Test_temporary_files () {
Local dir=/tmp

Touch $dir/a-pid1232.tmp
Touch $dir/a-pid1232-daemon.tmp

Returns "$dir/a-pid1232.tmp" Temporary_files $dir

Touch $dir/b-pid1534.tmp

Returns "$dir/a-pid1232.tmp $dir/b-pid1534.tmp" Temporary_files $dir
}

As you can see, this test does not care about main ().

Five, debugging functions

Run program with-X flag:

Copy Code code as follows:

Bash-x my_prog.sh

Debugging only a small piece of code, using Set-x and Set+x, will print debugging information only for the current code that is contained by the Set-x and set +x.

Copy Code code as follows:

Temporary_files () {
Local dir=$1

Set-x
LS $dir \
| grep pid \
| Grep-v Daemon
Set +x
}

Print the name of the function and its parameters:

Copy Code code as follows:

Temporary_files () {
Echo $FUNCNAME $@
Local dir=$1

LS $dir \
| grep pid \
| Grep-v Daemon
}

Call Function:

Copy Code code as follows:

Temporary_files/tmp

will print to standard output:

Copy Code code as follows:
Temporary_files/tmp

Vi. Clarity of the code

What does this piece of code do?

Copy Code code as follows:

Main () {
Local dir=/tmp

[[z $dir]] \
&& do_something ...

[[-N $dir]] \
&& do_something ...

[[f $dir]] \
&& do_something ...

[[D-$dir]] \
&& do_something ...
}
Main

Let your code speak:

Copy Code code as follows:

Is_empty () {
Local var=$1

[[Z $var]]
}

Is_not_empty () {
Local var=$1

[[-N $var]]
}

Is_file () {
Local file=$1

[[F $file]]
}

Is_dir () {
Local dir=$1

[[D $dir]]
}

Main () {
Local dir=/tmp

Is_empty $dir \
&& do_something ...

Is_not_empty $dir \
&& do_something ...

Is_file $dir \
&& do_something ...

Is_dir $dir \
&& do_something ...
}
Main

Seven, do one thing per line

Use the backslash \ as a separator. For example:

Copy Code code as follows:

Temporary_files () {
Local dir=$1

LS $dir | grep pid | Grep-v Daemon
}

Can be written much more succinctly:

Copy Code code as follows:

Temporary_files () {
Local dir=$1

LS $dir \
| grep pid \
| Grep-v Daemon
}

Sign at the beginning of the indent

Symbol at the end of the bad example: In this example, the Temporary_files () code snippet, suspected to be affixed incorrectly. In conjunction with the context, it should be print_dir_if_not_empty ())

Copy Code code as follows:

Print_dir_if_not_empty () {
Local dir=$1

Is_empty $dir &&
echo "dir is empty" | | \
echo "Dir= $dir"
}

Good example: we can see clearly the connection between the line and the join symbol.

Copy Code code as follows:

Print_dir_if_not_empty () {
Local dir=$1

Is_empty $dir \
&& echo "dir is empty" \
|| echo "Dir= $dir"
}

Eight, print usage

Don't do this:

Copy Code code as follows:

echo "This prog does: ..."
echo "Flags:"
echo "-H print Help"

It should be a function:

Copy Code code as follows:

Usage () {
echo "This prog does: ..."
echo "Flags:"
echo "-H print Help"
}

Echo repeats on each line. So we got this document:

Copy Code code as follows:

Usage () {
Cat <<-EOF
Usage: $PROGNAME Options

program deletes files from filesystems to release spaces.
It gets config file that define Fileystem paths to work on, and whitelist the rules to
Keep certain files.

OPTIONS:
-C--config configuration file containing the rules. Use--help-config to the syntax.
-N--pretend do not really delete, just i what you are to do.
-T--test run unit test to check the program
-V--verbose verbose. Can specify more then one-v to have more verbose
-X--debug Debug
-H--help Show this Help
--help-config Configuration Help


Examples:
Run All tests:
$PROGNAME--test All

Run Specific test:
$PROGNAME--test test_string.sh

Run:
$PROGNAME--config/path/to/config/$PROGNAME. conf

Just show what your are going to do:
$PROGNAME-vn-c/path/to/config/$PROGNAME. conf
Eof
}

Note that there should be a real tab ' t ' at the beginning of each line.

In Vim, if your tab is 4 spaces, you can use this to replace the command:

Copy Code code as follows:

: s/^/\t/

Ix. command-line parameters

Here is an example that completes the use of the above usage function. I got this code from http://kirk.webfinish.com/2009/10/bash-shell-script-to-use-getopts-with-gnu-style-long-positional-parameters/.

Copy Code code as follows:

CmdLine () {
# Got here:
# http://kirk.webfinish.com/2009/10/bash-shell-script-to-use-getopts-with-gnu-style-long-positional-parameters/
Local arg=
For ARG
Todo
Local delim= ""
Case "$arg" in
#translate--gnu-long-options to-g (short options)
--config) args= "${args}-c";;
--pretend) args= "${args}-n";;
--test) args= "${args}-t";;
--help-config) Usage_config && exit 0;;
--HELP) args= "${args}-h";;
--verbose) args= "${args}-v";;
--debug) args= "${args}-x";;
#pass through anything else
*) [[[' ${arg:0:1} ' = = ']] | | | Delim= "\" "
args= "${args}${delim}${arg}${delim}";;
Esac
Done

#Reset the positional parameters to the short options
Eval Set--$args

While getopts "NVHXT:C:" OPTION
Todo
Case $OPTION in
V
ReadOnly verbose=1
;;
H
Usage
Exit 0
;;
X
ReadOnly debug= '-X '
Set-x
;;
T
run_tests= $OPTARG
Verbose vinfo "Running tests"
;;
C
ReadOnly config_file= $OPTARG
;;
N
ReadOnly pretend=1
;;
Esac
Done

if [[$recursive _testing | |-Z $RUN _tests]]; Then
[[!-f $CONFIG _file]] \
&& Eexit "You must provide--config file"
Fi
return 0
}

You like this, using the immutable args variable we defined on the head:

Copy Code code as follows:

Main () {
CmdLine $ARGS
}
Main

Ten, Unit test

1. It is important in a higher level of language.
2. Use Shunit2 to do unit test

Copy Code code as follows:

Test_config_line_paths () {
Local s= ' partition Cpm-all, 80-90, '

Returns "/A" "Config_line_paths ' $s/A, '"
Returns "/A/B/C" "config_line_paths ' $s/a:/b/c, '"
Returns "/a/b/C" "Config_line_paths ' $s/A:/b:/C, '"
}

Config_line_paths () {
Local partition_line= "$@"

echo $partition _line \
| Csv_column 3 \
| Delete_spaces \
| Column 1 \
| Colons_to_spaces
}

Source/usr/bin/shunit2

Here is another example of using the DF command:

Copy Code code as follows:

Df=df

Mock_df_with_eols () {
Cat <<-EOF
FileSystem 1k-blocks Used Available use% mounted on
/very/long/device/path
124628916 23063572 100299192 19%/
Eof
}

Test_disk_size () {
Returns 1000 "DISK_SIZE/DEV/SDA1"

Df=mock_df_with_eols
Returns 124628916 "Disk_size/very/long/device/path"
}

Df_column () {
Local disk_device=$1
Local column=$2

$DF $disk _device \
| Grep-v ' use% '
| Tr ' \ n ' \
| awk "{print \$ $column}"
}

Disk_size () {
Local disk_device=$1

Df_column $disk _device 2
}

I have an exception here, for the sake of testing, I declared DF to be read-only in the global domain. This is because Shunit2 does not allow changes to global domain functions.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.