#!/usr/bin/env bash
# 日志函数(可自定义或集成前面提供的日志库)
log_info() { echo -e "\033[32m[INFO] $(date '+%Y-%m-%d %H:%M:%S') $*\033[0m" >&2; }
log_warn() { echo -e "\033[33m[WARN] $(date '+%Y-%m-%d %H:%M:%S') $*\033[0m" >&2; }
log_error() { echo -e "\033[31m[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $*\033[0m" >&2; }
# 通用命令执行函数
# 用法: run_cmd "命令字符串" [超时时间秒] [重试次数]
run_cmd() {
local cmd="$1"
local timeout_sec="${2:-0}" # 0=不超时
local retry="${3:-1}"
local stdout stderr exit_code i
for ((i=1; i<=retry; i++)); do
log_info "执行命令: $cmd (第 $i 次尝试)"
if (( timeout_sec > 0 )); then
# 使用 timeout 命令
stdout=$(mktemp)
stderr=$(mktemp)
timeout "$timeout_sec" bash -c "$cmd" >"$stdout" 2>"$stderr"
exit_code=$?
else
stdout=$(mktemp)
stderr=$(mktemp)
bash -c "$cmd" >"$stdout" 2>"$stderr"
exit_code=$?
fi
# 读取输出内容
local out_content err_content
out_content=$(<"$stdout")
err_content=$(<"$stderr")
rm -f "$stdout" "$stderr"
if [[ $exit_code -eq 0 ]]; then
log_info "命令成功: $cmd"
[[ -n "$out_content" ]] && log_info "标准输出:\n$out_content"
return 0
else
if [[ $exit_code -eq 124 ]]; then
log_warn "命令超时($timeout_sec 秒): $cmd"
else
log_error "命令失败: $cmd"
fi
[[ -n "$err_content" ]] && log_error "错误输出:\n$err_content"
[[ $i -lt $retry ]] && log_warn "准备重试..."
fi
done
log_error "命令执行失败(重试 $retry 次后): $cmd"
return $exit_code
}
#!/bin/bash
# 日志级别定义
LOG_LEVELS=("DEBUG" "INFO" "WARN" "ERROR")
LOG_LEVEL="INFO" # 默认日志级别,可通过环境变量覆盖
# 日志颜色
declare -A LOG_COLORS=(
["DEBUG"]="\033[36m" # 青色
["INFO"]="\033[32m" # 绿色
["WARN"]="\033[33m" # 黄色
["ERROR"]="\033[31m" # 红色
)
LOG_RESET="\033[0m"
# 获取日志级别对应的数字
get_log_level_num() {
local level="$1"
for i in "${!LOG_LEVELS[@]}"; do
if [[ "${LOG_LEVELS[$i]}" == "$level" ]]; then
echo "$i"
return
fi
done
echo "1" # 默认INFO
}
# 日志输出函数
log_msg() {
local level="$1"
shift
local msg="$*"
local level_num
local current_level_num
level_num=$(get_log_level_num "$level")
current_level_num=$(get_log_level_num "$LOG_LEVEL")
# 只打印大于等于当前日志级别的日志
if (( level_num >= current_level_num )); then
local color="${LOG_COLORS[$level]}"
local timestamp
timestamp=$(date "+%Y-%m-%d %H:%M:%S")
echo -e "${color}[$timestamp][$level]${LOG_RESET} $msg" >&2
fi
}
# 各级别快捷函数
log_debug() { log_msg "DEBUG" "$@"; }
log_info() { log_msg "INFO" "$@"; }
log_warn() { log_msg "WARN" "$@"; }
log_error() { log_msg "ERROR" "$@"; }
# 命令执行并输出日志
run_cmd() {
local cmd="$*"
log_info "执行命令: $cmd"
if output=$(eval "$cmd" 2>&1); then
log_debug "命令输出: $output"
return 0
else
log_error "命令失败: $cmd"
log_error "错误输出: $output"
return 1
fi
}
readonly LOG_FILE="/var/log/docker_install_$(date +%Y%m%d_%H%M%S).log"
# 日志函数
function log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}
function log_info() {
log "INFO" "$1"
}
function log_error() {
log "ERROR" "$1" >&2
}
function log_success() {
log "SUCCESS" "$1"
}
getopts 和 getopt 是两种在 Shell 脚本中处理命令行参数的方法。它们各有特点,适用于不同的场景。下面分别介绍这两种方法的基本用法和区别。
getopts
getopts 是一个内置于 Shell 的命令,用于解析短选项(即以单个破折号 - 开头的选项)。它的使用相对简单,但功能较为基础,主要用于处理短选项。
基本用法
#!/bin/bash
while getopts ":a:b:" opt; do
case $opt in
a) var_a="$OPTARG"
;;
b) var_b="$OPTARG"
;;
\?) echo "无效的选项: -$OPTARG" >&2
;;
esac
done
echo "var_a: $var_a"
echo "var_b: $var_b"
getopts ":a:b:" opt:这里的 :a:b: 定义了两个可选参数 a 和 b,并且这两个参数都需要后跟一个值(由 : 指示)。
OPTARG:保存了当前选项的参数值。
?:处理无效选项的情况。
限制
getopts 主要处理短选项,不直接支持长选项(如 --option)。
对于复杂的选项解析需求,getopts 的灵活性和功能可能不够。
getopt
getopt 是一个独立的命令行工具,能够处理更复杂的选项解析需求,包括长选项。它通常需要安装在系统上(在大多数 Linux 发行版上默认安装)。
基本用法
#!/bin/bash
options=$(getopt -o "a:b:" -l "option-a:,option-b:" --name "$(basename "$0")" -- "$@")
if [ $? -ne 0 ]; then
echo "使用方法: $(basename "$0") [-a <arg1>] [-b <arg2>]"
exit 1
fi
eval set -- "$options"
while true; do
case "$1" in
-a | --option-a ) var_a="$2"; shift 2;;
-b | --option-b ) var_b="$2"; shift 2;;
-- ) shift; break;;
* ) break;;
esac
done
echo "var_a: $var_a"
echo "var_b: $var_b"
-o "a:b:":定义短选项,其中 a 和 b 后跟 : 表示这些选项需要参数。
-l "option-a:,option-b:":定义长选项,同样地,后跟 : 表示这些选项需要参数。
--name "0")":指定程序的名称,用于错误消息。
-- "@" 表示所有参数,-- 用于分隔 getopt 的选项和脚本的参数。
eval set -- "$options":将 getopt 解析后的参数重新设置为脚本的参数,使得后续的 while 循环可以正确处理。
优势
支持长选项和短选项。
更灵活的选项解析,适合复杂的命令行参数处理需求。
总结
getopts 适用于处理简单的短选项需求,使用简单,但功能有限。
getopt 提供了更强大的选项解析功能,支持长选项,适合处理复杂的命令行参数。
选择哪种方法取决于你的具体需求。如果你的脚本只需要处理简单的短选项,getopts 可能已经足够。如果需要处理长选项或更复杂的选项解析逻辑,getopt 会是更好的选择。
在 Shell 脚本中传递参数是非常常见的需求,特别是当你需要根据用户输入或外部条件动态调整脚本行为时。Shell 脚本可以接收命令行参数,这些参数可以在脚本中通过 2 等变量访问。下面是一些关于如何在 Shell 脚本中传递和使用参数的基本知识和示例。
基本参数访问
在 Shell 脚本中,2 等变量用于访问传递给脚本的参数。
# 表示传递给脚本的参数数量,
* 则表示所有参数的列表。
示例脚本
下面是一个简单的 Shell 脚本示例,展示了如何传递和使用参数:
#!/bin/bash
# 显示脚本名称
echo "脚本名称: $0"
# 显示传递给脚本的参数数量
echo "参数数量: $#"
# 显示所有参数
echo "所有参数: $@"
# 显示第一个参数
echo "第一个参数: $1"
# 显示第二个参数
echo "第二个参数: $2"
# 使用 shift 命令处理所有参数
echo "处理所有参数:"
while [ $# -gt 0 ]; do
echo "参数: $1"
shift
done
- 运行脚本
保存上述脚本为 example.sh,赋予执行权限,然后运行它并传递一些参数:
chmod +x example.sh
./example.sh 参数1 参数2 参数3
- 高级参数处理
对于更复杂的参数处理,如处理带有选项的参数,可以使用 getopts 或 case 语句来解析参数。
使用 getopts 处理选项
#!/bin/bash
while getopts ":f:d:" opt; do
case $opt in
f)
FILE="$OPTARG"
;;
d)
DIRECTORY="$OPTARG"
;;
\?)
echo "无效的选项: -$OPTARG"
exit 1
;;
:)
echo "选项 -$OPTARG 需要一个参数。"
exit 1
;;
esac
done
echo "文件: $FILE"
echo "目录: $DIRECTORY"
运行这个脚本时,可以这样传递参数:
./example.sh -f 文件名 -d 目录名
使用 case 语句处理参数
#!/bin/bash
while [ $# -gt 0 ]; do
case "$1" in
-f|--file)
FILE="$2"
shift 2
;;
-d|--directory)
DIRECTORY="$2"
shift 2
;;
*)
echo "未知参数: $1"
exit 1
;;
esac
done
echo "文件: $FILE"
echo "目录: $DIRECTORY"
运行这个脚本时,可以这样传递参数:
./example.sh -f 文件名 -d 目录名
参数验证
在处理参数时,验证参数的有效性是很重要的,以确保脚本的健壮性。例如,检查文件是否存在,目录是否可写等。使用 shift 命令
shift 命令用于在处理完一个参数后,将所有参数向左移动一个位置,这对于处理多个参数非常有用。参数数量检查
在脚本开始时检查参数数量,确保脚本接收到了足够的参数。
if [ $# -lt 2 ]; then
echo "使用方法: $0 -f <文件> -d <目录>"
exit 1
fi
通过上述方法,你可以灵活地在 Shell 脚本中处理和使用传递的参数,满足不同的需求。掌握这些技巧可以帮助你编写更加强大和灵活的 Shell 脚本。