如何正确使用换行符作为分隔符将输入保存到数组中

how to correctly use newline as delimiter for saving input into array

我正在编写一个 bash 脚本,该脚本应该备份用户可以指定的文件夹。脚本纯属娱乐,进入bash。我不会将它用于真正的备份目的。

这个想法是用户可以输入他们想要更新的特定文件夹。每次输入后,他们会点击 ENTER,然后在控制台中输入另一个目录。完成后,他们会按 CTRL-D。然后输入将保存到数组 USER_DIR 中。然后将检查该数组以查看目录是否存在。如果它们确实存在,条目将保持不变。如果它们不存在,条目将被覆盖为默认值 /home/$USER.

我在 AskUbuntu 上发现了一个有趣的想法(我正在使用 Ubuntu):

while read line
do
    my_array=("${my_array[@]}" $line)
done
echo ${my_array[@]}

我用过它并尝试输入多个目录,但只有当输入恰好是一个目录时它才有效。每次我使用多个目录作为输入时,它默认为/home/$USER。我已将 echo ${my_array[@]} 放在脚本的不同位置,看起来问题出在分隔符上,因为输入似乎与空格连接在一起。

我试图寻找一种方法来遍历数组并在保存输入后更改分隔符。我在 Whosebug:

上发现了一个有趣的想法
SAVEIFS=$IFS   # Save current IFS
IFS=$'\n'      # Change IFS to new line
names=($names) # split to array $names
IFS=$SAVEIFS   # Restore IFS

我试过了,但它也只接受单数输入。我的脚本目前看起来像这样:

echo "Please enter absolute path of directory for backup. Default is ""/home/$USER"
echo "You can choose multiple directories. Press CTRL-D to start backup."

# save user input into array
while read line
do
    USER_DIR=("${USER_DIR[@]}" "$line")
done

SAVEIFS=$IFS   # Save current IFS
IFS=$'\n'      # Change IFS to new line
USER_DIR=($USER_DIR) # split to array $names
IFS=$SAVEIFS   # Restore IFS

# check if user input in array is valid (directory exists)
for ((i=0; i<${#USER_DIR[@]}; i++))
do
    if [[ -d "${USER_DIR[$i]}" ]]; then
        # directory exists
        USER_DIR=("${USER_DIR[@]}")
        echo "${USER_DIR[@]}" # for debugging
    else
        # directory does not exist
        USER_DIR=("/home/$USER")
        echo "${USER_DIR[@]}" # for debugging
    fi
done

# show content of array
echo "Backups of the following directories will be done:"
for i in "${USER_DIR[@]}"; do echo "$i"; done

如有任何帮助,我们将不胜感激。感谢您的时间。

您不需要此代码段。

SAVEIFS=$IFS   # Save current IFS
IFS=$'\n'      # Change IFS to new line
USER_DIR=($USER_DIR) # split to array $names
IFS=$SAVEIFS   # Restore IFS

默认读取命令使用\n作为输入分隔符。

演示:

$./test.ksh 
Please enter absolute path of directory for backup. Default is /home/renegade
You can choose multiple directories. Press CTRL-D to start backup.
abc
123
def
Backups of the following directories will be done:
abc
123
def
$cat test.ksh 
echo "Please enter absolute path of directory for backup. Default is ""/home/$USER"
echo "You can choose multiple directories. Press CTRL-D to start backup."

# save user input into array
while read line
do
    USER_DIR=( "${USER_DIR[@]}" "$line" )
done

#SAVEIFS=$IFS   # Save current IFS
#IFS=$'\n'      # Change IFS to new line
#USER_DIR=($USER_DIR) # split to array $names
#IFS=$SAVEIFS   # Restore IFS

# show content of array
echo "Backups of the following directories will be done:"
for i in "${USER_DIR[@]}"; do echo "$i"; done
$

这里有几个严重的问题,我还有一些小的建议。第一个(可能)是次要的:你的脚本没有以 shebang 行开头来告诉系统它是用什么脚本语言编写的。你正在使用 bash 功能(不仅仅是基本的 POSIX shell) 所以你需要从一个 shebang 开始,它说 运行 bash,而不是一些通用的 shell。所以第一行应该是 #!/bin/bash#!/usr/bin/env bash.

接下来,让我们看一下 while read 循环:

while read line
do
    USER_DIR=("${USER_DIR[@]}" "$line")
done

这个可以正常工作。它用用户的输入填充数组,每个条目一个元素。听起来您使用 echo ${USER_DIR[@]} 检查结果数组,并感到困惑,以为出了什么问题;但是循环工作正常,是 echo ${USER_DIR[@]} 具有误导性。正如我在评论中所说,请改用 declare -p USER_DIR,bash 将打印一条语句(如果您要 运行 它)将重新创建数组的当前内容。事实证明,这是一种很好的、​​通常明确的显示数组内容的方法。

我确实有一些小建议:使用小写或混合大小写的变量名称(例如 user_diruserDir 或类似名称)而不是全部大写的名称。有一堆具有特殊含义的全大写名称,如果您使用全大写命名,很容易不小心使用其中一个并造成混乱。此外,我会在使用 user_dir=() 循环之前将数组初始化为空,以确保它开始时为空。正如 Jetchisel 在评论中所说, user_dir+=("$line") 将是向数组添加新元素的更简单方法。只是不要省略括号,否则它将附加到第一个元素而不是添加新元素。

好的,下一节:

SAVEIFS=$IFS   # Save current IFS
IFS=$'\n'      # Change IFS to new line
USER_DIR=($USER_DIR) # split to array $names
IFS=$SAVEIFS   # Restore IFS

这似乎是在尝试解决一个不存在的问题,但最终会在此过程中产生一个新问题。正如我所说,USER_DIR 已经作为(正确填充的)数组存在,因此 $USER_DIR(没有 [@] 或任何东西)只是获取它的第一个元素。这意味着 USER_DIR=($USER_DIR) 只获取第一个元素,尝试在换行符上拆分它(因为那是 IFS 设置的内容),但是没有任何元素,所以什么也没有发生。然后它将 USER_DIR 设置为由 组成的数组,只是第一个元素 .

本质上,它是从数组中删除除第一个元素以外的所有元素。只需删除整个部分即可。

好的,下一个:

for ((i=0; i<${#USER_DIR[@]}; i++))

这在 bash 中适用于具有默认索引的数组(这是您拥有的),但我个人的偏好是改用它:

for i in "${!USER_DIR[@]}"

看到里面的 ! 了吗?这使得它获得数组中 索引值 的列表。在 bash 中工作,在 zsh 中工作(它使用不同的编号约定),我不必试图记住哪个使用哪个约定。它甚至适用于关联数组!但这也没什么大不了的,只是我的喜好。

现在,在循环内:

    if [[ -d "${USER_DIR[$i]}" ]]; then
        # directory exists
        USER_DIR=("${USER_DIR[@]}")

这扩展了数组的内容(正确引用和所有内容,这样它就不会弄乱),并将它重新赋值给数组。它绝对没有任何作用!请注意,这里没有什么是正确的,但是有更简单的方法可以什么都不做。我会完全删除它,只使用 if [[ ! -d "${USER_DIR[$i]}" ]]; then 后跟 "what do do if it doesn't exist" 部分。说到这里:

    else
        # directory does not exist
        USER_DIR=("/home/$USER")

这是第二个严重的问题。它将 整个数组 替换为指向您的主目录的单个条目。您只想替换 当前条目并保留数组的其余部分,因此请使用 USER_DIR[$i]="/home/$USER"。请注意,数组引用周围没有 ${ } —— 那些是当您 从数组中获取 一个值时使用的,而不是当您 设置时使用的一个。

好的,还有一个建议:与其将所有值读入一个数组,然后返回遍历它们以修复不存在的值,不如在读取目录路径时进行存在性测试,然后首先将正确的东西添加到数组中?您甚至可以在用户输入名称时给予反馈,如下所示:

#!/bin/bash

echo "Please enter absolute path of directory for backup. Default is ""/home/$USER"
echo "You can choose multiple directories. Press CTRL-D to start backup."

# save user input into array
user_dir=()
while read line
do
    if [[ -d "$line" ]]; then
        user_dir+=("$line")
    else
        echo "$line isn't an existing directory; we'll back up /home/$USER instead"
        user_dir+=("/home/$USER")
    fi
done

# show content of array
echo "Backups of the following directories will be done:"
for i in "${user_dir[@]}"; do echo "$i"; done