在 Shell 脚本中使用 Bash 键值数组

ShellBeginner
立即练习

介绍

本教程将指导你理解并在 shell 脚本中使用 Bash 键值数组(也称为关联数组)。键值数组是强大的数据结构,允许你使用描述性键而不是数字索引来存储和检索数据。

通过本教程,你将能够创建、操作和使用 Bash 脚本中的键值数组,以更有效地组织数据,并解决使用传统数字数组难以解决的问题。

创建你的第一个 Bash 键值数组

Bash 键值数组(也称为关联数组)允许你将值与特定键关联起来,类似于 Python 中的字典或 JavaScript 中的对象。这使得它们非常适合存储结构化数据,其中项目通过名称而不是位置来标识。

基本语法

要在 Bash 中使用键值数组,你必须首先使用 -A 选项声明它们:

declare -A myarray

这会创建一个名为 myarray 的空键值数组。-A 标志专门告诉 Bash 这是一个关联(键值)数组。

向数组添加元素

让我们创建一个键值数组来存储关于不同水果的信息:

  1. 如果 WebIDE 中尚未打开终端,请打开一个新的终端
  2. 在 WebIDE 中点击“File > New File”创建一个新的脚本文件
  3. 将文件保存为 /home/labex/project 目录下的 fruit_array.sh
  4. 将以下代码添加到文件中:
#!/bin/bash

## Declare a key-value array
declare -A fruits

## Add elements to the array
fruits["apple"]="red"
fruits["banana"]="yellow"
fruits["grape"]="purple"
fruits["orange"]="orange"

## Print a specific element
echo "The color of an apple is ${fruits["apple"]}"

## Print all values
echo "All fruit colors: ${fruits[@]}"

## Print all keys
echo "All fruits: ${!fruits[@]}"
  1. 保存文件(Ctrl+S 或 File > Save)
  2. 使用终端中的以下命令使脚本可执行:
chmod +x fruit_array.sh
  1. 运行脚本:
./fruit_array.sh

你应该看到类似这样的输出:

The color of an apple is red
All fruit colors: red yellow purple orange
All fruits: apple banana grape orange

理解输出

  • ${fruits["apple"]}:此语法检索与键 "apple" 关联的值
  • ${fruits[@]}:这将返回数组中的所有值
  • ${!fruits[@]}:数组名称之前的 ! 符号将返回数组中的所有键

Bash 键值数组允许你直接使用有意义的名称访问数据,而不必记住数字位置,这使得你的脚本更具可读性和可维护性。

操作 Bash 键值数组

现在你已经了解了键值数组的基础知识,让我们来探索如何操作它们。在这一步中,你将学习如何修改现有值、添加新元素、删除元素以及检查键是否存在。

创建一个新脚本

  1. /home/labex/project 目录下创建一个名为 array_operations.sh 的新文件
  2. 将以下代码添加到文件中:
#!/bin/bash

## Declare a key-value array
declare -A user_info

## Add initial elements
user_info["name"]="John Doe"
user_info["email"]="john@example.com"
user_info["age"]="30"
user_info["city"]="New York"

## Display initial array content
echo "--- Initial user information ---"
for key in "${!user_info[@]}"; do
  echo "$key: ${user_info[$key]}"
done

## Modify an existing value
user_info["age"]="31"

## Add a new key-value pair
user_info["country"]="USA"

## Check if a key exists
if [[ -v user_info["email"] ]]; then
  echo -e "\nEmail exists: ${user_info["email"]}"
else
  echo -e "\nEmail does not exist"
fi

## Remove an element
unset 'user_info[city]'

## Display the modified array
echo -e "\n--- Modified user information ---"
for key in "${!user_info[@]}"; do
  echo "$key: ${user_info[$key]}"
done

## Count the number of elements
echo -e "\nNumber of elements: ${#user_info[@]}"
  1. 保存文件(Ctrl+S 或 File > Save)
  2. 使脚本可执行:
chmod +x array_operations.sh
  1. 运行脚本:
./array_operations.sh

你应该看到类似这样的输出:

--- Initial user information ---
name: John Doe
email: john@example.com
age: 30
city: New York

Email exists: john@example.com

--- Modified user information ---
name: John Doe
country: USA
email: john@example.com
age: 31

Number of elements: 4

关键操作说明

  • 修改值:只需为现有键分配一个新值:user_info["age"]="31"
  • 添加新元素:使用与初始赋值相同的语法:user_info["country"]="USA"
  • 检查键是否存在:使用 -v 测试运算符:[[ -v user_info["email"] ]]
  • 删除元素:使用 unset 命令:unset 'user_info[city]'
  • 计算元素数量:使用 ${#array[@]} 获取元素的数量
  • 遍历元素:使用带有 ${!array[@]} 的 for 循环获取所有键

请注意,在修改后的输出中,city 键值对被删除,并且添加了一个新的 country 键值对。此外,age 值已从 30 更新为 31。

这些操作在你的脚本中使用动态数据时至关重要,允许你根据需要更新信息。

创建一个实用的配置管理器

现在你已经了解了如何创建和操作键值数组,让我们构建一些实用的东西。在这一步中,你将创建一个简单的配置管理器,该管理器从配置文件加载设置,将它们存储在键值数组中,并允许你访问它们。

步骤 1:创建配置文件

首先,让我们创建一个包含一些示例设置的配置文件:

  1. /home/labex/project 目录下创建一个名为 app_config.txt 的新文件
  2. 将以下内容添加到文件中:
database_host=localhost
database_port=3306
database_user=admin
database_password=secret123
app_name=MyAwesomeApp
debug_mode=true
max_connections=100
  1. 保存文件

步骤 2:创建配置管理器脚本

现在,让我们创建一个脚本,该脚本将读取此配置文件并将其内容存储在键值数组中:

  1. /home/labex/project 目录下创建一个名为 config_manager.sh 的新文件
  2. 将以下代码添加到文件中:
#!/bin/bash

## Define the path to the configuration file
CONFIG_FILE="/home/labex/project/app_config.txt"

## Declare a key-value array for the configuration
declare -A config

## Function to load configuration from file
load_config() {
  local file=$1

  ## Check if the file exists
  if [[ ! -f "$file" ]]; then
    echo "Error: Configuration file '$file' not found."
    return 1
  fi

  ## Read the file line by line
  while IFS='=' read -r key value; do
    ## Skip empty lines and comments
    if [[ -z "$key" || "$key" == \#* ]]; then
      continue
    fi

    ## Store the key-value pair in the array
    config["$key"]="$value"
  done < "$file"

  echo "Configuration loaded successfully from '$file'"
  return 0
}

## Function to get a configuration value
get_config() {
  local key=$1
  local default=$2

  ## Check if the key exists
  if [[ -v config["$key"] ]]; then
    echo "${config["$key"]}"
  else
    echo "$default"
  fi
}

## Function to display all configuration
display_config() {
  echo "--- Configuration Settings ---"
  for key in "${!config[@]}"; do
    echo "$key = ${config[$key]}"
  done
}

## Load the configuration
load_config "$CONFIG_FILE"

## Display all configuration settings
display_config

## Example of getting specific configuration values
echo -e "\n--- Example Usage ---"
db_host=$(get_config "database_host" "127.0.0.1")
db_port=$(get_config "database_port" "5432")
db_user=$(get_config "database_user" "root")
db_pass=$(get_config "database_password" "")
app_name=$(get_config "app_name" "DefaultApp")
debug_mode=$(get_config "debug_mode" "false")

echo "Database connection: $db_user@$db_host:$db_port"
echo "Application name: $app_name"
echo "Debug mode: $debug_mode"

## Example of getting a non-existent value (will use default)
timeout=$(get_config "connection_timeout" "30")
echo "Connection timeout: $timeout (default value used)"
  1. 保存文件(Ctrl+S 或 File > Save)
  2. 使脚本可执行:
chmod +x config_manager.sh
  1. 运行脚本:
./config_manager.sh

你应该看到类似这样的输出:

Configuration loaded successfully from '/home/labex/project/app_config.txt'
--- Configuration Settings ---
database_host = localhost
database_port = 3306
database_user = admin
database_password = secret123
app_name = MyAwesomeApp
debug_mode = true
max_connections = 100

--- Example Usage ---
Database connection: admin@localhost:3306
Application name: MyAwesomeApp
Debug mode: true
Connection timeout: 30 (default value used)

理解配置管理器

你创建的脚本做了几件重要的事情:

  1. 它从配置文件中读取键值对
  2. 它将这些对存储在键值数组中
  3. 它提供了以下功能:
    • 从文件加载配置
    • 使用默认回退获取特定的配置值
    • 显示所有配置设置

这种模式通常用于实际应用中来管理配置设置。主要优点是:

  • 集中式配置:所有设置都在一个地方
  • 默认值:如果缺少设置,可以使用默认值
  • 易于访问:可以通过名称访问设置
  • 灵活性:可以在不更改脚本的情况下更新配置

此示例演示了如何使用键值数组来解决 shell 脚本中的实际问题。你可以通过添加将更改保存回配置文件或验证设置的功能来扩展它。

使用键值数组构建菜单驱动的脚本

在最后一步中,我们将创建一个实用的菜单驱动的脚本,该脚本使用键值数组来存储和处理用户的选择。此示例演示了如何使用键值数组来创建更具交互性和用户友好的命令行应用程序。

创建菜单系统

  1. /home/labex/project 目录下创建一个名为 menu_system.sh 的新文件
  2. 将以下代码添加到文件中:
#!/bin/bash

## Declare a key-value array for menu options
declare -A menu_options
menu_options["1"]="Display system information"
menu_options["2"]="Check disk usage"
menu_options["3"]="List running processes"
menu_options["4"]="View network connections"
menu_options["q"]="Quit"

## Declare a key-value array for commands to execute
declare -A menu_commands
menu_commands["1"]="display_system_info"
menu_commands["2"]="check_disk_usage"
menu_commands["3"]="list_processes"
menu_commands["4"]="view_network"

## Function to display system information
display_system_info() {
  echo -e "\n--- System Information ---"
  echo "Hostname: $(hostname)"
  echo "Kernel version: $(uname -r)"
  echo "System uptime: $(uptime -p)"
  echo "CPU information: $(grep 'model name' /proc/cpuinfo | head -1 | cut -d ':' -f 2 | xargs)"
  echo "Total memory: $(free -h | grep Mem | awk '{print $2}')"
}

## Function to check disk usage
check_disk_usage() {
  echo -e "\n--- Disk Usage ---"
  df -h | grep -v tmp
}

## Function to list running processes
list_processes() {
  echo -e "\n--- Top 10 Processes by Memory Usage ---"
  ps aux --sort=-%mem | head -11
}

## Function to view network connections
view_network() {
  echo -e "\n--- Active Network Connections ---"
  netstat -tuln | grep LISTEN
}

## Function to display the menu
display_menu() {
  echo -e "\n=== System Utility Menu ==="
  for key in 1 2 3 4 q; do
    echo "$key. ${menu_options[$key]}"
  done
  echo -e "\nPlease enter your choice:"
}

## Main loop
while true; do
  ## Display the menu
  display_menu

  ## Read user choice
  read -r choice

  ## Check if choice is valid
  if [[ -v menu_options["$choice"] ]]; then
    ## Exit if user chose quit
    if [[ "$choice" == "q" ]]; then
      echo "Exiting. Goodbye!"
      exit 0
    fi

    ## Execute the corresponding command
    if [[ -v menu_commands["$choice"] ]]; then
      ## Call the function associated with the choice
      ${menu_commands["$choice"]}
    fi
  else
    echo "Invalid option. Please try again."
  fi

  ## Pause for user to read output
  echo -e "\nPress Enter to continue..."
  read -r
done
  1. 保存文件(Ctrl+S 或 File > Save)
  2. 使脚本可执行:
chmod +x menu_system.sh
  1. 运行脚本:
./menu_system.sh
  1. 通过输入相应的数字来尝试不同的菜单选项,然后在提示时按 Enter 返回菜单。完成后输入 q 退出程序。

理解菜单系统

此脚本演示了键值数组的几个高级用法:

  1. 菜单选项menu_options 数组将选项编号映射到描述性文本
  2. 命令映射menu_commands 数组将选项编号映射到函数名称
  3. 动态执行:脚本使用一个数组中的值来确定要调用的函数

在此菜单系统中使用键值数组的好处包括:

  • 可维护性:添加或修改菜单选项只需要更新数组
  • 可读性:代码清楚地显示了哪个命令对应于哪个菜单选项
  • 灵活性:你可以轻松地重新排序菜单项,而无需更改函数调用

这种模式对于创建易于维护和扩展的交互式命令行工具非常有用。

关键要点

在本教程中,你已经学习了:

  1. 如何在 Bash 中创建和初始化键值数组
  2. 如何访问、修改和删除键值数组中的元素
  3. 如何遍历键值数组中的键和值
  4. 如何使用键值数组存储配置数据
  5. 如何使用键值数组创建交互式菜单系统

这些技术可以应用于各种 shell 脚本任务,从简单的数据存储到复杂的交互式应用程序。

总结

在本教程中,你探索了 Bash 键值数组的强大功能和多功能性。从基础知识开始,你学习了如何创建和访问键值数组,然后逐步掌握了更高级的操作,例如修改、添加和删除元素。

你将这些知识应用于创建实际示例,包括一个从文件读取设置的配置管理器,以及一个菜单驱动的实用程序脚本,该脚本演示了如何使用键值数组来创建交互式命令行应用程序。

键值数组提供了一种在 Bash 脚本中组织和访问数据的强大方法。使用描述性键而不是数字索引的能力使你的脚本更具可读性和可维护性,尤其是在处理复杂数据结构时。

现在你已经对 Bash 中的键值数组有了扎实的理解,你可以使用它们来增强你的 shell 脚本,并更有效地解决各种编程挑战。