Bootstrap

编程练习——shell脚本实现

1、趣玩小游戏

游戏1:猜数字

下面是一个简单的猜数字游戏的Shell脚本实现。这个脚本将生成一个1到100之间的随机数,然后让用户尝试猜测这个数字。

#!/bin/bash  
  
# 生成一个1到100之间的随机数  
TARGET=$(($RANDOM % 100 + 1))  
  
# 初始化用户的猜测  
GUESS=  
  
# 欢迎用户  
echo "欢迎来到猜数字游戏!"  
  
# 游戏主循环  
while [[ $GUESS -ne $TARGET ]]  
do  
  # 读取用户的猜测  
  read -p "请输入你的猜测(1-100):" GUESS  
  
  # 检查猜测是否合法  
  if [[ ! $GUESS =~ ^[0-9]+$ ]]  
  then  
    echo "无效的输入!请输入一个数字。"  
  elif (( GUESS < 1 )) || (( GUESS > 100 ))  
  then  
    echo "无效的输入!请输入一个在1到100之间的数字。"  
  elif (( GUESS < TARGET ))  
  then  
    echo "太小了!"  
  elif (( GUESS > TARGET ))  
  then  
    echo "太大了!"  
  fi  
done  
  
echo "恭喜你,猜对了!目标数字就是 $TARGET。"

这个脚本首先生成一个1到100之间的随机数作为目标数字,然后进入一个循环,直到用户猜到这个数字为止。在每次循环中,它都会提示用户输入一个猜测,然后根据用户的猜测给出相应的反馈。如果用户猜到了目标数字,脚本就会结束并显示祝贺信息。

游戏2:石头剪刀布

以下是一个简单的 Bash shell 脚本实现石头剪刀布游戏:

#!/bin/bash  
  
echo "欢迎来到石头剪刀布游戏!"  
echo "请做出你的选择: "  
echo "1. 石头"  
echo "2. 剪刀"  
echo "3. 布"  
  
read -p "请输入你的选择[1-3]: " choice  
computer_choice=$((1 + RANDOM % 3))  
  
# 根据玩家和计算机的选择,判断胜负  
if [ $choice -eq $computer_choice ]  
then  
  echo "平局!"  
elif [ $choice -eq 1 ]  
then  
  if [ $computer_choice -eq 2 ]  
  then  
    echo "你赢了!"  
  else  
    echo "你输了!"  
  fi  
elif [ $choice -eq 2 ]  
then  
  if [ $computer_choice -eq 3 ]  
  then  
    echo "你赢了!"  
  else  
    echo "你输了!"  
  fi  
elif [ $choice -eq 3 ]  
then  
  if [ $computer_choice -eq 1 ]  
  then  
    echo "你赢了!"  
  else  
    echo "你输了!"  
  fi  
fi

这个脚本会生成一个菜单,让用户输入他们的选择(1、2或3),然后计算机随机选择。根据玩家和计算机的选择,脚本会判断谁赢了。玩家可以选择石头(1)、剪刀(2)或布(3)。计算机的选择是随机的。

游戏3:跑马灯

在shell中实现跑马灯效果,可以利用终端的清屏和字符复制功能。下面是一个简单的shell脚本示例:

#!/bin/bash  
  
# 跑马灯文字  
text="跑马灯效果"  
  
# 初始位置  
x=0  
  
# 无限循环  
while true  
do  
    # 清空屏幕  
    clear  
  
    # 打印出一行跑马灯文字  
    for (( i=0; i<${#text}; i++ ))  
    do  
        echo -ne "\r${text:$i:1}"  
        # 模拟跑马灯效果,将光标向左移动一格  
        x=$((x+1))  
        if [ $x -gt ${#text} ]; then  
            x=0  
        fi  
        sleep 0.1  
    done  
done

上述脚本会不断在终端的同一行上循环打印出"跑马灯效果"这个字符串,产生跑马灯效果。其中\r是回车符,可以使光标回到行首,实现跑马灯的滚动效果。sleep 0.1用来使每次滚动之间有0.1秒的延迟,以使效果更易观察。

2、算法练习

1)冒泡排序

冒泡排序是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

以下是一个使用 Bash shell 实现冒泡排序的例子:

#!/bin/bash  
  
# 定义一个数组  
array=(34 17 23 35 17 45 9 1)  
  
# 获取数组长度  
length=${#array[@]}  
  
# 冒泡排序  
for ((i = 0; i < $length ; i++))  
do  
    for ((j = 0; j < $length-i-1 ; j++))  
    do  
        if ((array[j] > array[j+1]))  
        then  
            # 交换元素  
            temp=${array[j]}  
            array[j]=${array[j+1]}  
            array[j+1]=$temp  
        fi  
    done  
done  
  
# 打印排序后的数组  
echo "排序后的数组:"  
for ((i=0; i<${#array[@]}; i++))  
do  
    echo ${array[$i]}  
done

这个脚本首先定义了一个数组,然后使用两个嵌套的for循环进行冒泡排序。在每一次内部循环中,都会比较当前元素和下一个元素的大小,如果当前元素大于下一个元素,就交换他们的位置。这样,每一次外部循环结束时,最大的元素都会"冒泡"到数组的最后面。最后,脚本打印出排序后的数组。

2)快速排序

下面是使用 Bash shell 实现快速排序的示例代码:

#!/bin/bash  
  
# 定义快速排序函数  
quick_sort() {  
  local arr=("$@")  
  local left=()  
  local right=()  
  local pivot=${arr[0]}  
  
  # 如果数组只有一个元素,则直接返回该数组  
  if [ ${#arr[@]} -le 1 ]; then  
    echo "${arr[@]}"  
    return  
  fi  
  
  # 将比基准元素小的元素放入左数组,比基准元素大的元素放入右数组  
  for ((i=1; i<${#arr[@]}; i++)); do  
    if [ ${arr[i]} -lt $pivot ]; then  
      left+=(${arr[i]})  
    elif [ ${arr[i]} -gt $pivot ]; then  
      right+=(${arr[i]})  
    fi  
  done  
  
  # 递归排序左数组和右数组,并将结果合并  
  echo "$(quick_sort ${left[@]}) $pivot $(quick_sort ${right[@]})"  
}  
  
# 测试快速排序函数  
echo "测试快速排序:"  
echo "输入: [3, 6, 4, 1, 8, 2, 7]"  
echo "输出: $(quick_sort 3 6 4 1 8 2 7)"

这个脚本定义了一个名为quick_sort的函数,它采用一个数组作为参数,并返回一个排序后的新数组。该函数使用pivot(基准元素)将数组分成两个子数组:比pivot小的元素和比pivot大的元素。然后,递归地对子数组进行排序,最后将左、右子数组和pivot合并成一个有序数组。测试代码用于验证快速排序函数的正确性,将一个无序数组作为输入,并输出排好序的新数组。

3)插入排序

以下是使用 Bash shell 实现插入排序的示例代码:

#!/bin/bash  
  
# 定义插入排序函数  
insertion_sort() {  
  local arr=("$@")  
  local n=${#arr[@]}  
  local j=0  
  local temp  
  
  # 遍历数组,从第二个元素开始  
  for ((i=1; i<$n; i++)); do  
    # 将当前元素暂存到temp变量中  
    temp=${arr[i]}  
  
    # 将该元素前面所有元素与temp进行比较,如果前面的元素比temp大,则将前面的元素后移一位  
    for ((j=i-1; j>=0 && temp<${arr[j]}; j--)); do  
      arr[j+1]=${arr[j]}  
    done  
  
    # 将temp插入到正确的位置上  
    arr[j+1]=$temp  
  done  
  
  # 输出排好序的数组  
  echo "${arr[@]}"  
}  
  
# 测试插入排序函数  
echo "测试插入排序:"  
echo "输入: [3, 6, 4, 1, 8, 2, 7]"  
echo "输出: $(insertion_sort 3 6 4 1 8 2 7)"

这个脚本定义了一个名为insertion_sort的函数,它采用一个数组作为参数,并返回一个排好序的新数组。该函数使用两个嵌套循环来实现插入排序。外层循环从第二个元素开始遍历整个数组,内层循环将当前元素与前面的元素进行比较,并将前面的元素后移一位,以便将当前元素插入到正确的位置上。最后,输出排好序的数组。

4)计数排序

计数排序是一种非基于比较的排序算法,适用于正整数数组。它的基本思想是将输入数据值转化为键存储在额外开辟的数组空间中,然后输出排序后的键序列。这种排序方式对于确定的数据范围非常有效。

以下是在 Bash shell 环境中实现计数排序的示例代码:

#!/bin/bash  
  
# 定义计数排序函数  
counting_sort() {  
  local arr=("$@")  
  local max=${arr[0]}  
  local count=()  
  local output=()  
  local i  
  
  # 找到数组中的最大值  
  for ((i=1; i<${#arr[@]}; i++)); do  
    if [ ${arr[i]} -gt $max ]; then  
      max=${arr[i]}  
    fi  
  done  
  
  # 初始化计数数组  
  for ((i=0; i<=max; i++)); do  
    count[$i]=0  
  done  
  
  # 计算每个元素的计数  
  for ((i=1; i<${#arr[@]}; i++)); do  
    count[${arr[i]}]=$((count[${arr[i]}] + 1))  
  done  
  
  # 生成输出数组  
  for ((i=1; i<${#arr[@]}; i++)); do  
    output[$((count[${arr[i]}]-1)]=${arr[i]}  
    count[${arr[i]}]=$((count[${arr[i]}] - 1))  
  done  
  
  # 输出排序后的数组  
  for ((i=0; i<${#arr[@]}; i++)); do  
    echo ${output[$i]}  
  done  
}  
  
# 测试计数排序函数  
echo "测试计数排序:"  
echo "输入: [3, 2, 3, 1, 2, 4, 3]"  
echo "输出: $(counting_sort 3 2 3 1 2 4 3)"

这个脚本定义了一个名为counting_sort的函数,它采用一个整数数组作为参数,并输出一个排好序的新数组。首先,该函数找到数组中的最大值,并初始化一个计数数组。然后,它计算每个元素的计数,并生成一个输出数组。最后,它输出排好序的数组。

5)归并排序

归并排序是一种经典的排序算法,它的主要思想是将待排序的序列递归地划分为两个子序列,然后对这两个子序列分别进行排序,最后将两个有序子序列合并成一个有序序列。

以下是使用 Shell 实现归并排序的代码:

#!/bin/bash  
  
# 归并排序函数  
merge_sort() {  
    local -n arr=$1  
    local start=$2  
    local end=$3  
  
    # 递归终止条件  
    if (( $start >= $end )); then  
        return  
    fi  
  
    # 分割数组  
    local mid=$(( ($start + $end) / 2 ))  
    merge_sort arr $start $mid  
    merge_sort arr $(( mid + 1 )) $end  
  
    # 合并有序数组  
    local left=$start  
    local right=$(( mid + 1 ))  
    local i=0  
    local temp=()  
    while (( $left <= $mid && $right <= $end )); do  
        if (( ${arr[$left]} < ${arr[$right]} )); then  
            temp[$i]=${arr[$left]}  
            (( left++ ))  
        else  
            temp[$i]=${arr[$right]}  
            (( right++ ))  
        fi  
        (( i++ ))  
    done  
    while (( $left <= $mid )); do  
        temp[$i]=${arr[$left]}  
        (( left++, i++ ))  
    done  
    while (( $right <= $end )); do  
        temp[$i]=${arr[$right]}  
        (( right++, i++ ))  
    done  
  
    # 将合并后的数组赋值回原数组  
    for (( i=0; i<${#temp[@]}; i++ )); do  
        arr[$(( start + i ))]=${temp[$i]}  
    done  
}  
  
# 测试代码  
arr=(4 3 2 1 5)  
merge_sort arr 0 $(( ${#arr[@]} - 1 ))  
echo "${arr[@]}"

在上面的代码中,我们定义了一个名为 merge_sort 的函数来实现归并排序。该函数接受三个参数,分别是待排序的数组、排序区段的起始下标和结束下标。在函数中,我们首先判断是否满足递归终止条件,如果满足则直接返回。否则,我们将数组递归地划分为两个子数组,然后对这两个子数组分别进行排序。最后,我们将两个有序子数组合并成一个有序数组,并将合并后的数组赋值回原数组。

在测试代码中,我们定义了一个包含五个元素的数组,然后调用 merge_sort 函数对其进行排序,并使用 echo 命令输出排序后的数组。

6)堆排序

堆排序是一种基于比较的排序算法,通过构建一个最大堆或者最小堆,然后将根节点与最后一个节点交换,最后通过不断调整堆结构达到排序的目的。以下是使用Shell实现堆排序的示例代码:

#!/bin/bash  
  
# 堆排序函数  
heap_sort() {  
  local -n arr=$1  
  local n=${#arr[@]}  
  
  # 构建最大堆  
  for ((i=n/2-1; i>=0; i--)); do  
    heap_adjust arr $n $i  
  done  
  
  # 逐个将元素交换到堆顶,并调整堆结构  
  for ((i=n-1; i>=0; i--)); do  
    # 交换根节点与最后一个节点  
    local temp=${arr[0]}  
    arr[0]=${arr[i]}  
    arr[i]=$temp  
  
    # 调整堆结构  
    heap_adjust arr $i 0  
  done  
}  
  
# 调整堆结构函数  
heap_adjust() {  
  local -n arr=$1  
  local n=$2  
  local i=$3  
  
  # 如果当前节点不是叶子节点,则不需要交换元素  
  if (( i*2+1 < n )); then  
    local left=$((i*2+1))  
    local right=$((i*2+2))  
    local largest=$i  
  
    # 找出最大元素的位置  
    if (( arr[left] > arr[largest] )); then  
      largest=$left  
    fi  
    if (( arr[right] > arr[largest] )); then  
      largest=$right  
    fi  
  
    # 如果最大元素不是当前节点,则交换元素并继续调整堆结构  
    if (( largest != i )); then  
      local temp=${arr[i]}  
      arr[i]=${arr[largest]}  
      arr[largest]=$temp  
      heap_adjust arr $n $largest  
    fi  
  fi  
}  
  
# 测试代码  
arr=(4 3 2 1 5)  
heap_sort arr[@]  
echo "${arr[@]}"

在这个示例代码中,heap_sort函数用于实现堆排序,接受一个数组作为参数。首先通过构建最大堆来将数组元素按照从大到小的顺序排列,然后逐个将元素交换到堆顶,并调整堆结构,最终得到一个有序的数组。heap_adjust函数用于调整堆结构,接受一个数组、数组长度和当前节点的下标作为参数。该函数会根据最大堆的性质将当前节点的父节点与子节点比较,找出最大元素的位置,如果最大元素不是当前节点,则交换元素并继续调整堆结构。

7)希尔排序

希尔排序(Shell Sort)是插入排序的一种更高效的改进版本,也称为“缩小增量排序”,它的基本思想是:先将整个待排序的记录序列分割成为若干子序列,分别进行直接插入排序。然后依次缩减增量再进行排序,待整个序列中的记录"基本有序"时,再对全体记录进行一次直接插入排序。

以下是一个使用 Bash shell script 实现的希尔排序:

#!/bin/bash  
  
# 希尔排序函数  
shell_sort() {  
  local -n arr=$1  
  local n=${#arr[@]}  
  
  # 初始增量设为数组长度的一半  
  local gap=$((n/2))  
  
  # 不断缩小增量直到为1  
  while ((gap > 0)); do  
    # 对每个子序列进行直接插入排序  
    for ((i=gap; i<n; i++)); do  
      local temp=${arr[i]}  
      local j=$((i-gap))  
      while ((j>=0 && arr[j]>temp)); do  
        arr[j+gap]=$arr[j]  
        j=$((j-1))  
      done  
      arr[j+gap]=$temp  
    done  
    gap=$((gap/2))  
  done  
}  
  
# 测试代码  
arr=(4 3 2 1 5)  
echo "Before sorting: ${arr[@]}"  
shell_sort arr[@]  
echo "After sorting: ${arr[@]}"

这个脚本中的 shell_sort 函数接收一个数组作为参数。在开始的时候,将增量设为数组长度的一半,然后不断地缩小增量,对每个子序列进行直接插入排序。直到增量减小到1,此时对整个序列进行一次直接插入排序,得到完全有序的数组。测试代码部分创建了一个包含随机数的数组,然后调用 shell_sort 函数进行排序,最后打印出排序前后的数组。

8)桶排序

桶排序是一种线性排序算法,其基本思想是将待排序的数据分配到有限数量的桶中,然后对每个桶中的数据进行排序,最后按照桶的顺序将数据重新合并。下面是一个使用Shell脚本实现桶排序的示例代码:

#!/bin/bash  
  
# 待排序数据  
input=(4 13 2 1 16 9 10 14 3 7)  
  
# 桶的数量  
num_buckets=5  
  
# 获取输入数据的长度  
length=${#input[@]}  
  
# 计算桶的大小  
bucket_size=$(( (${#input[@]} + num_buckets - 1) / num_buckets ))  
  
# 创建桶数组  
declare -a buckets  
  
# 将数据分配到桶中  
for (( i=0; i<num_buckets; i++ )) do  
  buckets[$i]=()  
  for (( j=0; j<bucket_size && i*bucket_size+j<length; j++ )) do  
    buckets[$i][]=${input[i*bucket_size+j]}  
  done  
done  
  
# 对每个桶中的数据进行排序  
for (( i=0; i<num_buckets; i++ )) do  
  # 使用冒泡排序对当前桶中的数据进行排序  
  for (( j=0; j<${#buckets[$i]}; j++ )) do  
    for (( k=0; k<${#buckets[$i]}-j-1; k++ )) do  
      if [[ ${buckets[$i][k]} -gt ${buckets[$i][k+1]} ]]; then  
        # 交换元素位置  
        temp=${buckets[$i][k]}  
        buckets[$i][k]=${buckets[$i][k+1]}  
        buckets[$i][k+1]=$temp  
      fi  
    done  
  done  
done  
  
# 将桶中的数据按照顺序合并到一个新数组中  
output=()  
for (( i=0; i<num_buckets; i++ )) do  
  for (( j=0; j<${#buckets[$i]}; j++ )) do  
    output+=(${buckets[$i][j]})  
  done  
done  
  
# 输出排序后的结果  
echo "${input[@]}"  
echo "${output[@]}"

上述代码中,首先定义了待排序的数据input和桶的数量num_buckets。接下来,计算了每个桶的大小bucket_size,并创建了一个名为buckets的数组来存储每个桶中的数据。然后,将数据分配到桶中,使用冒泡排序对每个桶中的数据进行排序,最后将桶中的数据按照顺序合并到一个新数组output中。最后,输出了排序前和排序后的数据。

9)基数排序

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。下面是一个用shell实现基数排序的简单示例:

#!/bin/bash  
  
# 实现基数排序  
radix_sort() {  
  # 最大数字长度  
  max_len=0  
  for num in "$@"; do  
    len=${#num}  
    if ((len > max_len)); then  
      max_len=len  
    fi  
  done  
  
  # 用于保存每个数字的位数和对应的计数  
  declare -A count  
  declare -A max_count  
  
  # 从最低位开始(最右边)计算每个数字的频次  
  digit=1  
  while ((digit <= max_len)); do  
    for num in "$@"; do  
      temp=${num: -digit} # 取当前位数字  
      ((count[temp]++))  
      if ((count[temp] > max_count[temp])); then  
        max_count[$temp]=count[temp]  
      fi  
    done  
    # 打印结果  
    echo -n "$digit: "  
    for ((i=0; i<=digit; i++)); do  
      echo -n " ${count[$i]} $i"  
    done  
    echo  
    # 清除计数器,准备下一轮计算  
    count=()  
    ((digit++))  
  done  
}  
  
# 测试基数排序函数  
echo "请输入一些整数:"  
read -a numbers  
radix_sort "${numbers[@]}"

在上述脚本中,我们首先找到输入数字中的最大长度。然后,从最低位开始(最右边),我们逐个对每个数字的每一位进行计数。我们使用两个数组countmax_count来保存每个数字出现的次数以及该数字在某个位数上出现的最大次数。最后,我们打印出每轮的结果。

3、提升练习

1)点单系统

要使用Shell实现点单系统,您需要考虑使用基本命令和脚本编程技巧。以下是一个简单的示例,展示如何使用Shell脚本实现基本点单系统。

创建一个点单目录,并在其中创建一个包含菜品清单的文本文件,例如 menu.txt

echo "1. 宫保鸡丁"      >> menu.txt  
echo "2. 鱼香肉丝"      >> menu.txt  
echo "3. 回锅肉"        >> menu.txt  
echo "4. 麻婆豆腐"      >> menu.txt  
echo "5. 四川火锅"      >> menu.txt

创建一个Shell脚本文件 order.sh,用于处理点单逻辑。

#!/bin/bash  
  
# 菜品清单文件路径  
menu_file="menu.txt"  
  
# 判断文件是否存在  
if [ ! -f "$menu_file" ]; then  
    echo "菜品清单文件不存在!"  
    exit 1  
fi  
  
# 显示菜品清单  
echo "欢迎光临,以下是我们的菜品清单:"  
cat "$menu_file"  
echo "请输入您要点的菜品的编号,输入 q 结束点单:"  
  
# 点单循环  
while true; do  
    # 读取用户输入  
    read -p "> " choice  
  
    # 判断用户是否选择退出  
    if [ "$choice" = "q" ]; then  
        echo "谢谢光临!"  
        break  
    fi  
  
    # 检查用户输入是否有效  
    grep -q "^$choice[0-9]\{1,2\}$" "$menu_file"  
    if [ $? -eq 0 ]; then  
        echo "您点了:$choice"  
    else  
        echo "无效的选择,请重新输入!"  
    fi  
done

以上示例是一个简单的点单系统,可以根据您的需求进行扩展和优化。您可以添加更多的逻辑,例如库存管理、计价等。还可以将脚本与其他编程语言或框架集成,以构建更复杂的点单系统。

;