Shell syntax specification

catalog

background

Blog: https://www.cnblogs.com/Rohn

Which Shell to use

The executable file must start with a ×! / bin/bash and a minimum number of flags. Please use set to set the options of the shell so that you can use < script_ Name > calls your script without breaking its function.

Recommended use:

#!/usr/bin/env bash

env is usually fixed in the / usr/bin directory, while the rest of the interpreter installation location is relatively less fixed.

Limiting all executable shell scripts to bash makes the shell language we install on all computers consistent.

Whatever you code for, the only exception to this is when you are forced not to. One example is the Solaris SVR4 package, which requires a pure Bourne shell to write any script.

[root@test ~]# echo $SHELL
/bin/bash

When to use Shell

Some guidelines for using shells:

  • If you are mainly calling other tools and doing relatively small amount of data operations, using Shell to complete the task is an acceptable choice.
  • If you care about performance, choose a different tool than Shell.
  • If you find that you need to use data instead of variable assignments (such as ${PHPESTATUS}), you should use Python scripts.
  • If you're going to write more than 100 lines of script, you might want to write it in Python instead of Shell.

Remember, when the number of lines increases, rewrite your script in another language as early as possible to avoid spending more time later.

notes

Blog: https://www.cnblogs.com/Rohn

Bash only supports single line comments, and the ones that start with ා are used as comment statements.

Top level notes

Each file must contain a top-level comment that provides a brief overview of its contents. Copyright notice and author information are optional.
For example:

#!/usr/bin/env bash
# Author: Rohn
# Version: 1.0
# Created Time: 2020/06/06
# Perform hot backups of MySQL databases.
  • Line 1, specify the interpreter, using bash

#! called“ Shebang "Or" Sha Bang "(in UNIX terms, the" ා "sign is usually called sharp, hash or mesh, while the"! Sign is often called bang), indicating the interpreter that executes the script file. Of course, if you use bash test.sh Such a command to execute the script, then the line will be ignored.

  • Lines 2-5 are author, version number, creation time and function description.

Function notes

Any function that is not both obvious and short must be annotated. Any library function, regardless of its length and complexity, must be annotated.

Other people can learn how to use your program or library functions without reading the code by reading the comments (and help information, if any).

All function comments should include:

  • Function description
  • Use and modification of global variables
  • Parameter description used
  • Return value instead of the default exit status after the previous command was run

For example:

#!/usr/bin/env bash
# Author: Rohn
# Version: 1.0
# Created Time: 2020/06/06
# Perform hot backups of Oracle databases.

export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin'

#######################################
# Cleanup files from the backup dir
# Globals:
#   BACKUP_DIR
#   ORACLE_SID
# Arguments:
#   None
# Returns:
#   None
#######################################
cleanup() {
  ...
}

TODO comments

TODOs should contain the uppercase string TODO followed by your user name in parentheses. Colons are optional. It's best to add the bug or ticket sequence number after the TODO entry.

For example:

# TODO(mrmonkey): Handle the unlikely edge cases (bug ####)

format

Blog: https://www.cnblogs.com/Rohn

indent

Indents two spaces without tabs. For example:

if [ a > 1 ];then
  echo '${a} > 1'
fi

Line length and long string

The maximum length of a line is 80 characters. For example:

# DO use 'here document's
cat <<END;
I am an exceptionally long
string.
END

# Embedded newlines are ok too
long_string="I am an exceptionally
  long string."

The Conduit

If one line does not hold the entire pipe operation, split the entire pipe operation into one pipe segment per line.

The whole pipe operation should be divided into one pipe segment per line, and the next part of the pipe operation should place the pipe character on the new line and indent 2 spaces. This applies to merge command chains using the pipe character|, as well as logical operation chains using|, and &.

For example:

# All fits on one line
command1 | command2

# Long commands
command1 \
  | command2 \
  | command3 \
  | command4

loop

If else statement

If and; then are placed on the same line, followed by a space, else is on a separate line, fi is on a separate line, and aligned vertically with if. Namely:

if condition; then
  statement(s)
else
  statement(s)
fi

For do and while do statements

while/for and; do are placed on the same line, and do is vertically aligned with while/for, that is:

# while structure
while condition; do
  statement(s)
done

# for structure
for condition; do
  statement(s)
done

For example:

for dir in ${dirs_to_cleanup}; do
  if [[ -d "${dir}/${ORACLE_SID}" ]]; then
    log_date "Cleaning up old files in ${dir}/${ORACLE_SID}"
    rm "${dir}/${ORACLE_SID}/"*
    if [[ "$?" -ne 0 ]]; then
      error_message
    fi
  else
    mkdir -p "${dir}/${ORACLE_SID}"
    if [[ "$?" -ne 0 ]]; then
      error_message
    fi
  fi
done

case statement

  • Optional with 2 spaces indented.
  • A space is required after and after the closing parenthesis of the optional pattern on the same line; before.
  • Long or multiple command options should be split into multiple lines, with modes, operations, and terminators; on different lines.

Matching expressions are indented one level more than case and esac. Multiple line operations need to be indented one more level. In general, you do not need to refer to a matching expression. Opening parentheses should not precede pattern expressions. Avoid; & and;; & symbols. Namely:

# case structure
case in expression in
  pattern1)
    statement1
    ;;
  pattern2)
    statement2
    ;;
  ...
  *)
    statementn
    ;;
esac

For example:

case "${expression}" in
  a)
    variable="..."
    some_command "${variable}" "${other_expr}" ...
    ;;
  absolute)
    actions="relative"
    another_command "${actions}" "${other_expr}" ...
    ;;
  *)
    error "Unexpected expression '${expression}'"
    ;;
esac

As long as the entire expression is readable, simple commands can be written on the same line as patterns and. This is usually true for single letter options. When a single line cannot contain operations, please put the mode in a separate line, then the operation, and finally the terminator; also a separate line. When the operation is on the same line, the closing parenthesis of the pattern is followed by the closing character;; use a space to separate before.

verbose='false'
aflag=''
bflag=''
files=''
while getopts 'abf:v' flag; do
  case "${flag}" in
    a) aflag='true' ;;
    b) bflag='true' ;;
    f) files="${OPTARG}" ;;
    v) verbose='true' ;;
    *) error "Unexpected option ${flag}" ;;
  esac
done

Variable extension

In order of priority: keep up with what you find; reference your variables; recommend ${var} instead of $var.

for example

# Section of recommended cases.

# Preferred style for 'special' variables:
echo "Positional: $1" "$5" "$3"
echo "Specials: !=$!, -=$-, _=$_. ?=$?, #=$# *=$* @=$@ \$=$$ ..."

# Braces necessary:
echo "many parameters: ${10}"

# Braces avoiding confusion:
# Output is "a0b0c0"
set -- a b c
echo "${1}0${2}0${3}0"

# Preferred style for other variables:
echo "PATH=${PATH}, PWD=${PWD}, mine=${some_var}"
while read f; do
  echo "file=${f}"
done < <(ls -l /tmp)

# Section of discouraged cases

# Unquoted vars, unbraced vars, brace-quoted single letter
# shell specials.
echo a=$avar "b=$bvar" "PID=${$}" "${1}"

# Confusing use: this is expanded as "${1}0${2}0${3}0",
# not "${10}${20}${30}
set -- a b c
echo "$10$20$30"

characteristic

Blog: https://www.cnblogs.com/Rohn

Command substitution

Use $(command) instead of quotes.

Nested backquotes require the inner backquotes to be escaped with a backslash. The $(command) form doesn't need to be changed when nested, and it's easier to read.

For example:

# This is preferred:
var="$(command "$(command1)")"

# This is not:
var="`command \`command1\``"

Wildcard extension for file names

Use an explicit path when extending the filename with wildcard characters.

Because filenames can start with -, it's much safer to use the extended wildcard. / * than *.

# Here's the contents of the directory:
# -f  -r  somedir  somefile

# This deletes almost everything in the directory by force
psa@bilby$ rm -v *
removed directory: `somedir'
removed `somefile'

# As opposed to:
psa@bilby$ rm -v ./*
removed `./-f'
removed `./-r'
rm: cannot remove `./somedir': Is a directory
removed `./somefile'

Naming convention

Blog: https://www.cnblogs.com/Rohn

Function name

Use lowercase letters and separate words with underscores. Use double colons:: to separate libraries. The function name must be followed by parentheses. The keyword function is optional, but must be consistent within a project.

If you are writing a single function, name it in lowercase and separate the words with underscores. If you are writing a package, use double colons:: to separate the package names. Braces must be on the same line as the function name (as in other Google languages), and there is no space between the function name and the parentheses.

# Single function
my_func() {
  ...
}

# Part of a package
mypackage::my_func() {
  ...
}

When () exists after the function name, the keyword function is redundant. But it promotes the fast identification of functions.

Variable name

Using lowercase letters, the loop's variable name should be the same as any of the loop's variables. For example:

for zone in ${zones}; do
  something_with "${zone}"
done

Constants and environment variable names

All uppercase letters, separated by underscores, are declared at the top of the file. For example:

# Constant
readonly PATH_TO_FILES='/some/path'

# Both constant and environment
declare -xr ORACLE_SID='PROD'

Source filename

Use lowercase letters and use underscores to separate words if necessary. For example: make template or make_template, not make template.

read-only variable

Use lowercase letters, readonly or declare -r to make sure variables are read-only.

Because global variables are widely used in shells, it is important to catch errors when using them. When you declare a variable and want it to be read-only, make it clear.

zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)"
if [[ -z "${zip_version}" ]]; then
  error_message
else
  readonly zip_version
fi

Use local variables

Use lowercase letters and local to declare variables for specific functions. Declaration and assignment should be on different lines.

Use local to declare local variables to make sure that they are visible only inside the function and in subfunctions. This avoids polluting the global namespace and inadvertently setting variables that may be of importance outside of the function.

When the value of an assignment is provided by a command substitution, the declaration and assignment must be separated. Because the built-in local does not pass the exit code from the command substitution.

my_func2() {
  local name="$1"

  # Separate lines for declaration and assignment:
  local my_var
  my_var="$(my_func)" || return

  # DO NOT do this: $? contains the exit code of 'local', not my_func
  local my_var="$(my_func)"
  [[ $? -eq 0 ]] || return

  ...
}

Call command

Blog: https://www.cnblogs.com/Rohn

Check return value

For non pipeline commands, use $? Or check directly through an if statement to keep it concise. For example:

if ! mv "${file_list}" "${dest_dir}/" ; then
  echo "Unable to move ${file_list} to ${dest_dir}" >&2
  exit "${E_BAD_MOVE}"
fi

# Or
mv "${file_list}" "${dest_dir}/"
if [[ "$?" -ne 0 ]]; then
  echo "Unable to move ${file_list} to ${dest_dir}" >&2
  exit "${E_BAD_MOVE}"
fi

Bash also has the PIPESTATUS variable, which allows you to check the code returned from all parts of the pipeline. If you only need to check whether the whole pipeline is successful or failed, the following methods are acceptable:

tar -cf - ./* | ( cd "${dir}" && tar -xf - )
if [[ "${PIPESTATUS[0]}" -ne 0 || "${PIPESTATUS[1]}" -ne 0 ]]; then
  echo "Unable to tar files to ${dir}" >&2
fi

However, as long as you run any other command, PIPESTATUS will be overridden. If you need to perform different operations based on errors in the pipeline, you need to assign PIPESTATUS to another variable immediately after running the command (don't forget [is a command that erases PIPESTATUS).

tar -cf - ./* | ( cd "${DIR}" && tar -xf - )
return_codes=(${PIPESTATUS[*]})
if [[ "${return_codes[0]}" -ne 0 ]]; then
  do_something
fi
if [[ "${return_codes[1]}" -ne 0 ]]; then
  do_something_else
fi

Tags: Linux shell Google Python Programming

Posted on Sat, 13 Jun 2020 21:45:00 -0400 by pjc2003