· Zen HuiFer · Learn  · 11 min read

Shell Encoding Specification

Learn the best practices for Shell script coding standards focusing on readability and maintenance. Key points include proper commenting, indentation, line length, and loop conventions.

Learn the best practices for Shell script coding standards focusing on readability and maintenance. Key points include proper commenting, indentation, line length, and loop conventions.

Shell Encoding Specification

File header or function header:

1. The beginning of each file is a description of its file content.

Top level annotation

#!/bin/bash
#
# Perform hot backups of Oracle databases.

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

Others can learn how to use your program or library functions without reading code by reading comments (and help information, if available).

*All function comments should include:

*Function description

*The use and modification of global variables

*Explanation of Parameters Used

*Return value instead of the default exit state after the previous command is executed

Function header annotation

#!/bin/bash
#
# Perform hot backups of Oracle databases.
export PATH='/usr/xpg4/bin:/usr/bin:/opt/csw/bin:/opt/goog/bin'
#######################################
# Cleanup files from the backup dir
# Globals:
#   BACKUP_DIR
#   ORACLE_SID
# Arguments:
#   None
# Returns:
#   None
#######################################
cleanup() {
  ...
}

Indent:

Shell indentation is two spaces without tabs. (Do not use tabs)

Please use blank lines between code blocks to improve readability. Indent to Two spaces . Whatever you do, please do not use tabs. For existing files, maintain the existing indentation format.

Line length and long string:

By @ Ye Weijie Reason: Easy to read code

Principle: The length of each line should not be too long. If it is too long to fit one line, use ’\’ to change the numbering, and the next line should be indented by two spaces from the beginning

The Conduit:

If one line cannot accommodate the entire pipeline operation, please divide the entire pipeline operation into each line and segment.

If one line can accommodate the entire pipeline operation, please write the entire pipeline operation on the same line.

,,2。’|’’||’’&&’。

Processing line breaks for excessively long shells

# All fits on one line
command1 | command2
# Long commands
command1 \
  | command2 \
  | command3 \
  | command4

Cycle:

Please loop relevant; Do,; then and while, for, if are placed on the same line.

By @ Ye Weijie: Pay attention to indentation, variable naming conventions, double brackets for judgment, etc., which will be further explained later

For example:

Examples of loops and judgment statements

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

*Indent with 2 spaces as an option.

*After the right parenthesis and the closing symbol of the selectable pattern on the same line;; Previously, each required a space.

*Long options or multi command options should be split into multiple lines, including mode, operation, and terminator;; In different rows.

For example:

Case Example 1

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 combined with patterns and;; Write on the same line. This usually applies to the handling of single letter options. When a single line cannot accommodate an operation, please place the mode on a separate line, followed by the operation, and finally the terminator;; Also on a separate line. When the operation is on the same line, the right parenthesis and terminator of the pattern;; Please use a space to separate before.

Case Example 2

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 consistent with what you have discovered; Quoting your variables; Recommend using varinsteadof{var} instead ofvar. Please explain in detail as follows.

These are just guidelines, as they seem controversial as mandatory regulations.

Listed below in order of priority.

1. Keep consistent with what you have found in the existing code.

2. Reference variables refer to the following section, References.

3. Unless absolutely necessary or to avoid deep confusion, do not enclose single character shell special variables or positional variables in curly braces. Recommend enclosing all other variables in curly braces.

quote

   * Unless you need to be careful with extensions without references, always reference strings that contain variables, command placeholders, spaces, or shell metacharacters.   
   * The recommended reference is a string of words (not command options or path names).   
   * Never reference integers.   
   * Pay attention to the reference rules for pattern matching in [].   
   * Please use $@ unless you have special reasons to use $*.   

Command replacement

Use $(command) instead of quotation marks.

By @ Ye Weijie Reason: Easy to read, obviously, we can easily see $(), but these two reverse quotes can cause confusion with ‘and are not intuitive enough to read the code.

Nested quotation marks require escaping the internal quotation marks with a back slash. The nested form of $(command) does not need to be changed and is easier to read.

For example:

Command reference result

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

Judgment related:

Recommend using [[…]] instead of [, test, and/etc/bin/[.

Because there will be no path name extension or word segmentation between [[] and [], using [[…]] can reduce errors. And […] allows regular expression matching, while […] does not.

Test string

Use references as much as possible instead of filtering strings.

Bash is capable of handling empty strings during testing. So, please use an empty (non empty) string test (i.e. use - z to determine) instead of filtering characters to make the code easier to read.

String judgment

# Do this:
if [[ "${my_var}" = "some_string" ]]; then
  do_something
fi
# -z (string length is zero) and -n (string length is not zero) are
# preferred over testing for an empty string
if [[ -z "${my_var}" ]]; then
  do_something
fi
# This is OK (ensure quotes on the empty side), but not preferred:
if [[ "${my_var}" = "" ]]; then
  do_something
fi
# Not this:
if [[ "${my_var}X" = "some_stringX" ]]; then
  do_something
fi

To avoid confusion about the purpose of your testing, please specify the use of - z or - n

Explanation: - z determines whether a string is empty (z for zero, string length is 0); -Determine whether the string is not empty (n for not zero)

String judgment uses null

# Use this
if [[ -n "${my_var}" ]]; then
  do_something
fi
# Instead of this as errors can occur if ${my_var} expands to a test
# flag
if [[ "${my_var}" ]]; then
  do_something
fi

Wildcard extension for file names

When performing wildcard extensions on file names, please use a clear path.

Because file names may start with a -, extended wildcard characters are used/ It’s much safer than before.

By @ Ye Weijie: This match has more operational thinking and is more accurate. I didn’t have this understanding before, as follows. If there is a file called - f, it will make the situation very bad.

For example:

# 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 function name

Use lowercase letters and separate words with underscores. Use double colons:: to separate libraries. There must be parentheses after the function name. The keyword ‘function’ is optional, but must remain consistent within a project.

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

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

When there is a () after the function name, the keyword ‘function’ is redundant. But it promotes the rapid identification of functions.

By @ Ye Weijie: It is recommended to include a function, so that when we read the code, we can know that the thing you named is a function. Like below

function function_name() {
  echo ****
}

Variable Name

Please name the function with lowercase letters and separate words with underscores

By @ Ye Weijie: Variable naming should be meaningful. The meaning of this sentence is: If you clearly know that your variable represents an IP; So use IP instead of I for variable names; If you clearly know that this variable is a file, name it with ‘file’ instead of using simple ‘a, b, c’.

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

Constant and environment variable names

All capitalized, separated by underscores, declared at the top of the file.

By @ Ye Weijie: As seen below, the use of readonly and declare - r indicates that the variable name is read-only and cannot be modified; The meaning of a constant is that once defined, it cannot be modified again; And it is generally a global variable. In the following example, exporting a variable has global significance.

# Constant
readonly PATH_TO_FILES='/some/path'
# Both constant and environment
declare -xr ORACLE_SID='PROD'

Some of them become constants during the first setup (for example, through getopts). Therefore, constants can be set in getopts or based on conditions, but they should be immediately set to read-only afterwards. It is worth noting that declare does not operate on global variables in the function. So it is recommended to use readonly and export instead.

VERBOSE='false'
while getopts 'v' flag; do
  case "${flag}" in
    v) VERBOSE='true' ;;
  esac
done
readonly VERBOSE

Source file name

Lowercase, use underline to separate words if necessary.

This is to maintain consistency with other code styles in Google: maketemplate or make_template, rather than make template.

read-only variable

Use readonly or declare - r to ensure that variables are read-only.

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

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 local to declare specific functions The variables. The declaration and assignment should be on different lines.

Use ‘local’ to declare local variables to ensure that they are only visible within the function and its subfunctions. This avoids contaminating the global namespace and inadvertently setting variables that may have importance outside of the function.

When the value assigned is replaced by a command, the declaration and assignment must be separated. Because the built-in local does not pass exit codes from command replacements.

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
  ...
}

Function position

Put all the functions in the file together under the constant. Do not hide executable code between functions.

If you have functions, please put them together at the beginning of the file. Only includes, set declarations, and constant settings may be completed before the function declaration. Do not hide executable code between functions. If we do that, it will make it difficult to trace the code during debugging and result in unexpected annoying outcomes.

Main function

For a sufficiently long script that contains at least one other function, it needs to be called a main function.

To facilitate the search for the start of the program, place the main program into a function called ‘main’ as the bottom function. This keeps it consistent with the rest of the code repository, while allowing you to define more variables as local variables (which cannot be done if the main code is not a function). The last non commented line in the file should be a call to the main function.

Obviously, for short scripts that are only linear streams, main is overcorrected and therefore unnecessary.

Call command

Check the return value

Always check the return value and provide the information return value.

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

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

conclusion

Use common sense and maintain consistency.

1. Shell indentation, using two spaces

2. Reasonable comments, simple explanations of usage at the beginning of code and functions.

3. Naming conventions, do not use simple names such as i/a/b/c; The method of using lowercase and underline

4. Please use [[] to make a judgment

5. For in the loop; do while ; do if ; Then put them on the same line. And; Leave a blank space behind

6. Command reference: $(), no need“

7. The reference to a variable should have an additional pair of curly braces, ${}

8. When using regularization, consider more rigorous matching, but do not add meaningless strings

more …

Back to Blog

Related Posts

View All Posts »
New package in Go 1.23: unique

New package in Go 1.23: unique

Go 1.23 introduces unique package for value normalization, enhancing memory efficiency and equality checks. Learn how "interning" works with unique and its benefits for Go developers.

How to cache well in Go

How to cache well in Go

Optimize Go app performance with caching strategies. Learn local vs distributed cache, memory management, and eviction policies. Enhance efficiency with Go's new unique package.

The noCopy strategy you should know in Golang

The noCopy strategy you should know in Golang

Discover the importance of noCopy in Golang development. Learn how it prevents accidental copying of critical structures like sync primitives. Enhance your Go code safety and efficiency.