Bash

最后更新: 2024-12-23 18:04:45 +0800 +0800
tags: Linux | Bash | Shell
原文以英文撰写。您正在阅读AI翻译版本。

Bash 既是交互式命令语言,也是脚本运行时。可靠的 shell code treats words, exit statuses, and file descriptors as distinct data flows. Start scripts with an explicit interpreter when Bash features are required:

bash
#!/usr/bin/env bash

bash -c 'commands' name arg1 assigns name to $0 and arg1 to $1. That detail matters when a command string consumes positional parameters.

Shell 语法与执行 #

A list joins pipelines with ;, &, &&, or ||. && and || are short-circuit operators; & starts an asynchronous list. A pipeline joins commands with |, or with |& when the left command’s standard error is also sent to the pipe.

bash
build && test && deploy
generate_report 2>&1 | tee build.log

Parentheses create a subshell, so assignments, directory changes, and options inside them do not affect the calling shell. Braces execute in the current shell and require spaces and a terminating semicolon or newline.

bash
( cd build && make )
{ cd build && make; }

Commands in a pipeline execute in separate environments by default. Therefore, do not expect a variable assigned by a while loop on the right of a pipeline to remain set afterward. Feed the loop with redirection or process substitution instead:

bash
count=0
while IFS= read -r line; do
  ((count++))
done < <(printf '%s\n' one two)
printf '%s\n' "$count"

shopt -s lastpipe changes this for the final pipeline command under specific non-interactive, job-control-disabled conditions; code should not depend on that optional execution model.

条件、循环与函数 #

Shell conditions are command statuses: zero succeeds and nonzero fails. if, while, and until accept a list, not a Boolean expression.

bash
if [[ -d $directory && -r $directory/config ]]; then
  printf 'configuration is readable\n'
elif [[ -e $directory ]]; then
  printf 'configuration is unavailable\n' >&2
else
  printf 'directory does not exist\n' >&2
fi

Use [[ ... ]] for Bash conditionals because it avoids word splitting and pathname expansion on unquoted operands. Use (( ... )) for arithmetic and case for glob-pattern dispatch. Quote variable expansions in commands even when a surrounding conditional does not require it.

bash
for file in "$@"; do
  [[ -f $file ]] || continue
  process "$file"
done

retry=0
until fetch; do
  ((++retry >= 3)) && exit 1
done

Functions run in the current shell. local limits a variable to the current function; return supplies its status. Arguments are the function’s positional parameters, so forward them with "$@".

bash
run_checked() {
  local output
  output=$("$@") || return
  printf '%s\n' "$output"
}

展开与引用 #

Before a command executes, Bash performs brace expansion; tilde expansion; parameter, arithmetic, command, and process substitution; word splitting; pathname expansion; and quote removal. Parameter, arithmetic, command, and process substitutions occur left to right. Brace expansion occurs before parameter expansion, so it cannot use a variable to create a range.

Quoting changes this process:

  • Single quotes preserve every character until the next single quote.
  • Double quotes preserve one word while allowing parameter, arithmetic, and command substitution.
  • Unquoted expansions undergo word splitting and pathname expansion.
  • "$@" expands to one word per positional parameter; "$*" expands to one word joined with the first character of IFS.
bash
files=("$directory"/*.log)
printf '%s\n' "${files[@]}"

name=${path##*/}             # Remove the longest matching prefix.
extension=${name##*.}
printf '%s\n' "${name%.*}"   # Remove the shortest matching suffix.
printf '%s\n' "$((retries + 1))"

An unmatched unquoted glob remains literal by default. Set nullglob only in a tightly scoped context when an empty result is required; failglob turns an unmatched pattern into an error. Never parse arbitrary file names with for file in $(command) because splitting and globbing corrupt names.

数组、算术与正则表达式 #

Indexed and associative arrays preserve element boundaries only when expanded as "${array[@]}". Declare an associative array before assigning string keys.

bash
declare -a files=(one 'two words')
declare -A status=([build]=pending)
files+=("three words")
for file in "${files[@]}"; do
  printf '<%s>\n' "$file"
done

Arithmetic accepts shell variables and returns status zero when the evaluated value is nonzero. Prefix a decimal number with 10# if input may contain leading zeroes; otherwise Bash can interpret a leading zero as octal.

bash
number=008
(( value = 10#$number + 1 ))

[[ value =~ regex ]] uses POSIX extended regular expressions. Keep a nonliteral pattern in a variable and leave that expansion unquoted; quotation changes matching semantics. After a successful match, BASH_REMATCH[0] contains the full match and later elements contain capturing groups.

bash
version='release-1.2.3'
regex='^release-([0-9]+)\.([0-9]+)\.([0-9]+)$'
if [[ $version =~ $regex ]]; then
  printf 'major=%s\n' "${BASH_REMATCH[1]}"
fi

重定向与输入 #

Redirections apply left to right after the pipeline connection is established. 2>&1 duplicates the current destination of standard output, so its position changes the result.

bash
command >output.log 2>&1     # Both standard streams go to the file.
command 2>&1 >output.log     # Standard error retains its prior destination.
command |& consumer           # Equivalent to command 2>&1 | consumer.

Open a dedicated descriptor when a loop needs input while a command uses standard input:

bash
while IFS= read -r line <&3; do
  printf '%s\n' "$line"
done 3< input.txt

read -r prevents backslash interpretation. With no variable, read assigns to REPLY; it removes the input delimiter. mapfile (also named readarray) reads lines into an indexed array and retains delimiters unless -t is used.

bash
mapfile -t lines < input.txt
printf '%s\n' "${lines[@]}"

An unquoted here-document delimiter permits parameter, command, and arithmetic expansion. A quoted delimiter makes the body literal. <<- removes leading tab characters, not spaces.

bash
cat <<'EOF'
literal $HOME and $(command)
EOF

启动文件、提示符与参数 #

Interactive login Bash reads /etc/profile and then the first readable file of ~/.bash_profile, ~/.bash_login, and ~/.profile. Interactive non-login Bash reads ~/.bashrc. Distribution files can add system-wide startup files, so use the local documentation rather than assuming a path. Non-interactive Bash reads the file named by BASH_ENV when that variable is set.

Place interactive aliases, completion, and prompt setup in ~/.bashrc. Login profiles commonly source it when an interactive login shell should share that setup. Export environment variables in a login startup file only when child processes need them.

PS1 controls the primary interactive prompt. Wrap nonprinting terminal sequences in \[ and \]; otherwise readline calculates the visible prompt width incorrectly.

bash
PS1='\[\e[32m\]\u@\h \W\[\e[0m\]\$ '

$0 names the shell or script, $# is the argument count, and $1 through ${10} select arguments. Preserve argument boundaries with "$@"; use shift only after consuming the current argument deliberately.

失败语义与选项 #

The status of a pipeline is the final command’s status unless set -o pipefail is active, in which case a nonzero status is reported if any command fails. PIPESTATUS holds every command’s status immediately after a pipeline.

bash
set -o pipefail
producer | consumer
printf 'statuses: %s\n' "${PIPESTATUS[*]}"

set -e does not mean “exit on every error.” Bash suppresses its effect in condition tests, all but the final commands of && and || lists, commands whose status is inverted with !, and several compound-command contexts. Thus write error handling where it matters:

bash
if ! deploy "$environment"; then
  printf 'deployment failed\n' >&2
  exit 1
fi

set -u detects expansions of unset variables but requires explicit defaults such as ${optional:-} for optional inputs. set -x exposes expanded commands and may reveal secrets, so enable it only in controlled diagnostics. Use set -euo pipefail only when the script understands each option’s semantics; it supplements, rather than replaces, explicit validation and cleanup.

评论

在 GitHub 上阅读或评论(发表评论需要登录)