如何在 Bash 脚本中设置默认值

ShellBeginner
立即练习

简介

在 Bash 脚本中设置默认值是创建健壮且用户友好的 shell 脚本的关键技能。本教程将指导你通过提供默认值来处理缺失或可选的参数,确保你的脚本能够优雅地处理各种输入场景。

通过完成这个实验(Lab),你将能够编写即使在用户没有提供所有预期输入参数的情况下也能良好运行的 Bash 脚本。

理解 Bash 中的默认值

在编写 Bash 脚本时,你经常需要你的脚本即使在某些输入参数缺失的情况下也能工作。默认值通过在未显式设置变量时提供备选方案来提供解决方案。

为什么默认值很重要

Bash 脚本中的默认值有几个重要的用途:

  • 它们可以防止你的脚本在缺少预期输入时失败
  • 它们通过减少所需的参数使你的脚本更用户友好
  • 它们允许你的脚本在不同的环境中具有合理的行为
  • 它们简化了脚本开发和测试

创建一个简单的脚本

让我们创建一个简单的 Bash 脚本来演示对默认值的需求。请按照以下步骤操作:

  1. 打开 WebIDE 终端
  2. 为我们的项目创建一个新目录:
mkdir -p ~/project/bash_defaults
cd ~/project/bash_defaults
  1. 使用 WebIDE,在 bash_defaults 目录中创建一个名为 greeting.sh 的新文件,内容如下:
#!/bin/bash

## A simple greeting script
NAME=$1
echo "Hello, $NAME!"
  1. 使脚本可执行:
chmod +x greeting.sh
  1. 现在使用名称参数运行脚本:
./greeting.sh Alice

你应该看到输出:

Hello, Alice!
  1. 现在尝试在没有任何参数的情况下运行脚本:
./greeting.sh

你应该看到输出:

Hello, !

请注意,当没有提供名称时,问候语看起来不完整。这是一个非常适合使用默认值的场景,以使我们的脚本更健壮。

在下一步中,我们将修改此脚本以使用默认值,以便即使参数缺失也能正常工作。

基本的默认值技术

现在我们了解了为什么默认值很重要,让我们学习在 Bash 脚本中设置默认值的不同方法。我们将从最常见的技术开始。

参数替换运算符 (:-)

在 Bash 中设置默认值最常见的方法是使用 :- 运算符,其语法如下:

${variable:-default_value}

这意味着“如果 variable 已设置且不为空,则使用其值,否则使用 default_value”。

让我们修改我们的问候语脚本以包含一个默认值:

  1. 在 WebIDE 中打开 greeting.sh 文件,并使用以下内容更新它:
#!/bin/bash

## A greeting script with default value
NAME=${1:-"World"}
echo "Hello, $NAME!"
  1. 保存文件并使用参数运行脚本:
./greeting.sh Alice

你应该看到:

Hello, Alice!
  1. 现在在没有任何参数的情况下运行脚本:
./greeting.sh

你应该看到:

Hello, World!

该脚本现在在没有提供名称时使用“World”作为默认值,这使其更用户友好。

创建一个更复杂的脚本

让我们创建一个更复杂的脚本,它使用可自定义的消息和一天中的时间来欢迎用户。我们将对这两个参数使用默认值:

  1. bash_defaults 目录中创建一个名为 welcome.sh 的新文件,内容如下:
#!/bin/bash

## A welcome script with multiple default values
NAME=${1:-"User"}
GREETING=${2:-"Welcome"}
TIME_OF_DAY=${3:-"today"}

echo "$GREETING, $NAME! How are you doing $TIME_OF_DAY?"
  1. 使脚本可执行:
chmod +x welcome.sh
  1. 使用不同的参数组合运行脚本:
./welcome.sh

输出:

Welcome, User! How are you doing today?
./welcome.sh Bob

输出:

Welcome, Bob! How are you doing today?
./welcome.sh Bob "Good morning"

输出:

Good morning, Bob! How are you doing today?
./welcome.sh Bob "Good morning" "this fine afternoon"

输出:

Good morning, Bob! How are you doing this fine afternoon?

正如你所看到的,默认值使你的脚本更加灵活,可以处理提供的任意数量的参数。

使用环境变量和默认值

在许多 Bash 脚本编写场景中,你需要使用环境变量。当你希望你的脚本即使在未设置某些环境变量的情况下也能工作时,默认值尤其有用。

环境变量的默认值

环境变量通常用于 shell 脚本中的配置。让我们创建一个使用带有默认值的环境变量的脚本:

  1. bash_defaults 目录中创建一个名为 config.sh 的新文件,内容如下:
#!/bin/bash

## Access environment variables with default values
USERNAME=${USER:-"anonymous"}
WORKING_DIR=${PWD:-"/home/labex"}
EDITOR=${VISUAL:-${EDITOR:-"nano"}}

echo "Current user: $USERNAME"
echo "Working directory: $WORKING_DIR"
echo "Preferred editor: $EDITOR"
  1. 使脚本可执行:
chmod +x config.sh
  1. 运行脚本:
./config.sh

你应该看到类似于以下的输出:

Current user: labex
Working directory: /home/labex/project/bash_defaults
Preferred editor: nano

请注意我们如何嵌套了 EDITOR 变量的默认值。此脚本首先检查是否设置了 VISUAL 环境变量,如果未设置,则检查 EDITOR 变量。如果两者均未设置,则默认为“nano”。

创建一个配置脚本

现在让我们创建一个更实用的脚本,该脚本使用带有默认值的环境变量来配置应用程序:

  1. bash_defaults 目录中创建一个名为 app_config.sh 的新文件,内容如下:
#!/bin/bash

## Application configuration script using default values
APP_NAME=${APP_NAME:-"MyApp"}
APP_VERSION=${APP_VERSION:-"1.0.0"}
APP_PORT=${APP_PORT:-8080}
APP_ENV=${APP_ENV:-"development"}
LOG_LEVEL=${LOG_LEVEL:-"info"}

echo "Starting $APP_NAME v$APP_VERSION"
echo "Configuration:"
echo "  Port: $APP_PORT"
echo "  Environment: $APP_ENV"
echo "  Log Level: $LOG_LEVEL"

## Create a config file with these settings
echo "Creating configuration file..."
cat > app.conf << EOF
APP_NAME=$APP_NAME
APP_VERSION=$APP_VERSION
APP_PORT=$APP_PORT
APP_ENV=$APP_ENV
LOG_LEVEL=$LOG_LEVEL
EOF

echo "Configuration file created at $(pwd)/app.conf"
  1. 使脚本可执行:
chmod +x app_config.sh
  1. 使用默认值运行脚本:
./app_config.sh

你应该看到关于默认配置的输出。

  1. 现在使用一些已设置的环境变量运行它:
APP_NAME="WebService" APP_PORT=3000 APP_ENV="production" ./app_config.sh

你应该看到脚本使用了你为某些设置提供的值,并使用了其他设置的默认值。

  1. 验证配置文件的内容:
cat app.conf

这个实际的例子展示了 Bash 中的默认值如何帮助创建灵活的配置脚本,这些脚本可以与用户指定的值一起使用,也可以不使用用户指定的值。

高级默认值技术

现在你了解了在 Bash 中设置默认值的基础知识,让我们探索一些高级技术,这些技术将使你的脚本更加强大和灵活。

:= 赋值运算符

:= 运算符不仅会替换默认值,还会将该值分配给变量(如果该变量未设置)。当你希望变量保留其默认值以供以后使用时,这很有用。

  1. bash_defaults 目录中创建一个名为 assign_default.sh 的新文件,内容如下:
#!/bin/bash

## Demonstrate the := assignment operator
echo "Before assignment, NAME = $NAME"

## This assigns the default value to NAME if it's unset
: ${NAME:="DefaultUser"}

echo "After assignment, NAME = $NAME"

## Now use the variable in a function
greet() {
  echo "Hello, $NAME!"
}

greet
  1. 使脚本可执行:
chmod +x assign_default.sh
  1. 在未设置 NAME 的情况下运行脚本:
./assign_default.sh

你应该看到:

Before assignment, NAME =
After assignment, NAME = DefaultUser
Hello, DefaultUser!
  1. 现在在运行之前设置 NAME:
NAME="Alice" ./assign_default.sh

你应该看到:

Before assignment, NAME = Alice
After assignment, NAME = Alice
Hello, Alice!

链接默认值

有时,你可能希望在回退到硬编码的默认值之前,检查多个值来源。你可以链接默认值来实现这一点:

  1. bash_defaults 目录中创建一个名为 chain_defaults.sh 的新文件,内容如下:
#!/bin/bash

## Read config file if it exists
if [[ -f "./user.conf" ]]; then
  source "./user.conf"
fi

## Chain default values: command line arg -> config file -> environment var -> hard-coded default
USERNAME=${1:-${CONFIG_USER:-${USER:-"guest"}}}
LANGUAGE=${2:-${CONFIG_LANG:-${LANG:-"en_US"}}}

echo "Hello, $USERNAME! Your language is set to $LANGUAGE."
  1. 使脚本可执行:
chmod +x chain_defaults.sh
  1. 创建一个用于测试的配置文件:
echo "CONFIG_USER=ConfigUser" > user.conf
echo "CONFIG_LANG=es_ES" >> user.conf
  1. 使用不同的组合测试脚本:
## Use defaults from the config file
./chain_defaults.sh
## Override with command line arguments
./chain_defaults.sh CommandLineUser fr_FR
## Remove config file to test falling back to environment variables
rm user.conf
./chain_defaults.sh

条件默认值

你还可以使用条件表达式,根据条件设置不同的默认值:

  1. bash_defaults 目录中创建一个名为 conditional_defaults.sh 的新文件,内容如下:
#!/bin/bash

## Get the current hour (24-hour format)
HOUR=$(date +%H)

## Set default greeting based on time of day
if ((HOUR < 12)); then
  DEFAULT_GREETING="Good morning"
elif ((HOUR < 18)); then
  DEFAULT_GREETING="Good afternoon"
else
  DEFAULT_GREETING="Good evening"
fi

## Use the conditional default if no greeting is provided
GREETING=${1:-"$DEFAULT_GREETING"}
NAME=${2:-"User"}

echo "$GREETING, $NAME!"
echo "The current time is $(date +%H:%M)."
  1. 使脚本可执行:
chmod +x conditional_defaults.sh
  1. 运行脚本,查看它如何根据一天中的时间选择默认问候语:
./conditional_defaults.sh
  1. 覆盖默认问候语:
./conditional_defaults.sh "Hello" "World"

这些高级技术为你提供了在 Bash 中使用默认值时更多的灵活性,允许你创建更复杂和用户友好的脚本。

在实际脚本中应用默认值

现在我们已经介绍了设置默认值的各种技术,让我们将所有内容整合到一个实际的脚本中,你可以在真实场景中使用它。

我们将创建一个备份脚本,该脚本使用各种配置选项的默认值。此脚本将演示默认值如何使你的脚本更灵活且用户友好。

创建一个备份脚本

  1. bash_defaults 目录中创建一个名为 backup.sh 的新文件,内容如下:
#!/bin/bash

## Backup script with default values for all parameters

## Source directories to backup (comma-separated) - default to current directory
SRC_DIRS=${1:-$(pwd)}

## Destination directory for backups - default to ~/backups
BACKUP_DIR=${2:-"$HOME/backups"}

## Backup filename prefix - default to "backup"
PREFIX=${3:-"backup"}

## Maximum number of backups to keep - default to 5
MAX_BACKUPS=${4:-5}

## Create destination directory if it doesn't exist
mkdir -p "$BACKUP_DIR"

## Generate timestamp for the backup filename
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/${PREFIX}_${TIMESTAMP}.tar.gz"

echo "Starting backup process..."
echo "Source: $SRC_DIRS"
echo "Destination: $BACKUP_FILE"

## Create the backup archive (we'll simulate this for the lab)
echo "Creating backup archive..."
echo "tar -czf $BACKUP_FILE $SRC_DIRS" > "$BACKUP_DIR/last_backup_command.txt"
touch "$BACKUP_FILE" ## Just create an empty file for demonstration

## Clean up old backups if we have more than MAX_BACKUPS
echo "Checking for old backups to remove..."
NUM_BACKUPS=$(ls -1 "$BACKUP_DIR"/${PREFIX}_*.tar.gz 2> /dev/null | wc -l)
if ((NUM_BACKUPS > MAX_BACKUPS)); then
  NUM_TO_DELETE=$((NUM_BACKUPS - MAX_BACKUPS))
  echo "Removing $NUM_TO_DELETE old backup(s)..."
  ls -1t "$BACKUP_DIR"/${PREFIX}_*.tar.gz | tail -n "$NUM_TO_DELETE" | while read file; do
    echo "Would delete: $file (simulation)"
  done
else
  echo "No old backups need to be removed (keeping $NUM_BACKUPS of $MAX_BACKUPS maximum)"
fi

echo "Backup process completed!"
echo "Backup saved to: $BACKUP_FILE"
  1. 使脚本可执行:
chmod +x backup.sh
  1. 创建测试所需的目录:
mkdir -p ~/backups
mkdir -p ~/test_data
echo "Test file 1" > ~/test_data/file1.txt
echo "Test file 2" > ~/test_data/file2.txt

测试备份脚本

现在让我们使用不同的参数组合来测试我们的备份脚本,看看默认值在实践中如何工作:

  1. 运行带有所有默认值的脚本:
./backup.sh

这将把当前目录备份到 ~/backups,前缀为“backup”。

  1. 运行脚本,指定源目录:
./backup.sh ~/test_data

这将使用其他默认值备份 test_data 目录。

  1. 运行脚本,使用自定义源和目标:
./backup.sh ~/test_data ~/project/bash_defaults/my_backups
  1. 运行脚本,指定所有参数:
./backup.sh ~/test_data ~/project/bash_defaults/my_backups project_backup 3

此示例显示了默认值如何使你的脚本具有灵活性——用户可以根据需要指定任意数量的参数,并且你的脚本仍然可以正常工作。

检查结果

使用不同的参数运行备份脚本后,你可以检查结果:

## Check the default backup location
ls -la ~/backups

## Check the custom backup location (if you used it)
ls -la ~/project/bash_defaults/my_backups

通过在脚本中始终如一地使用默认值,你可以创建更用户友好且适应不同环境和用例的工具。

总结

在这个实验中,你已经学习了如何在 Bash 脚本中设置默认值,这是创建健壮、用户友好脚本的基本技能。在整个实验过程中,你已经:

  • 使用 :- 运算符创建了使用基本默认值技术的简单脚本
  • 使用了环境变量和默认值
  • 探索了高级默认值技术,包括 := 赋值运算符和条件默认值
  • 开发了一个实用的备份脚本,该脚本演示了默认值如何使你的脚本更灵活

这些技术使你的脚本即使在用户没有提供所有预期参数的情况下也能很好地工作,从而使它们更具弹性和易于使用。默认值还通过减少对全面输入验证的需求来简化脚本开发和测试。

当你继续编写 Bash 脚本时,请记住自由地使用默认值来改善用户体验和脚本的健壮性。这种做法将使你的脚本在各种环境和场景中更专业、更有用。