列出文件夹中的所有子目录,将它们写入数组以在菜单中使用的Bash命令

3
我正在编写一段Bash脚本,这个脚本针对一个特定目录下的多个子文件夹进行操作。我想要列出所有子文件夹的名称,并将结果读入一个数组中,但要忽略名为“cmmdm”的单个文件夹。在将名称读入数组后,我想生成一个菜单,在该菜单中每个子菜单名称都是一个选项,用户可以根据选择的选项对给定的子文件夹执行不同的函数。
编辑: 抱歉,我应该先附上我的初始代码:
#!/bin/bash
# - create array
declare -a CDARRAY

# - set 0 to exit in prep for menu
CDARRAY[0]=exit

# - create a counter to use in while loop
count=1

# - while loop to itterate through folder and add each folder except cmmdm into array
ls -d /home/nginx/domains/* | {
    while read CMMDOMAIN ; do

            if [ $CMMDOMAIN != "/home/nginx/domains/cmmdm" ]
            then
            $CDARRAY[$count]=$CMMDOMAIN
            echo $CDARRAY[$count]
            count=$[count + 1]
            fi

    done
}

这确实会遍历文件夹并忽略“cmmdm”,但是我添加变量CMMDOMAIN到数组的代码是错误的。我以前从未编写过bash脚本,所以我认为可能语法有误或者缺少括号或其他东西。


足够简单。你为什么不试试?你卡在哪里了吗? - SMA
在Bash中,使用扩展通配符shopt -s extglob nullglob; array=( dir/!(cmmdm)/ ) - gniourf_gniourf
抱歉,我已经添加了我的初始代码以查看我卡在哪里了。 - mike atkinson
使用shell调试标志set -x查看每行执行时变量使用的值,这永远不会有害。祝好运。 - shellter
2个回答

7

您的代码存在很多问题,这里不方便一一讨论(无意冒犯)。

以下是一个完整的示例,可以按您的要求显示菜单,并进行一些常见的检查:

#!/bin/bash

shopt -s extglob nullglob

basedir=/home/nginx/domains

# You may omit the following subdirectories
# the syntax is that of extended globs, e.g.,
# omitdir="cmmdm|not_this_+([[:digit:]])|keep_away*"
# If you don't want to omit any subdirectories, leave empty: omitdir=
omitdir=cmmdm

# Create array
if [[ -z $omitdir ]]; then
   cdarray=( "$basedir"/*/ )
else
   cdarray=( "$basedir"/!($omitdir)/ )
fi
# remove leading basedir:
cdarray=( "${cdarray[@]#"$basedir/"}" )
# remove trailing backslash and insert Exit choice
cdarray=( Exit "${cdarray[@]%/}" )

# At this point you have a nice array cdarray, indexed from 0 (for Exit)
# that contains Exit and all the subdirectories of $basedir
# (except the omitted ones)
# You should check that you have at least one directory in there:
if ((${#cdarray[@]}<=1)); then
    printf 'No subdirectories found. Exiting.\n'
    exit 0
fi

# Display the menu:
printf 'Please choose from the following. Enter 0 to exit.\n'
for i in "${!cdarray[@]}"; do
    printf '   %d %s\n' "$i" "${cdarray[i]}"
done
printf '\n'

# Now wait for user input
while true; do
    read -e -r -p 'Your choice: ' choice
    # Check that user's choice is a valid number
    if [[ $choice = +([[:digit:]]) ]]; then
        # Force the number to be interpreted in radix 10
        ((choice=10#$choice))
        # Check that choice is a valid choice
        ((choice<${#cdarray[@]})) && break
    fi
    printf 'Invalid choice, please start again.\n'
done

# At this point, you're sure the variable choice contains
# a valid choice.
if ((choice==0)); then
    printf 'Good bye.\n'
    exit 0
fi

# Now you can work with subdirectory:
printf "You chose subdirectory \`%s'. It's a good choice.\n" "${cdarray[choice]}"

评论应该很清楚地解释了正在发生的事情。用于构建数组的技术,也是你问题的目的,是扩展通配符。例如:

shopt -s extglob nullglob
cdarray=( /home/nginx/domains/!(cmmdm)/ )

将使用所有子目录填充cdarray,这些子目录位于/home/nginx/domains/下,但不匹配cmmdm(精确、完全匹配)。如果要获取所有不以ab结尾的子目录:

shopt -s extglob nullglob
cdarray=( /home/nginx/domains/!(*[ab])/ )

不会有任何冒犯,周末之前我对bash的了解完全为零,所以我并没有一个好的起点。你的实现完美地运行,并且注释得非常好,这样我就可以轻松地将其扩展到我的脚本中,谢谢:] - mike atkinson

4

你是否考虑过使用find命令?

DIRS=$(find ${PWD} -maxdepth 1 -type d)
echo ${DIRS}

或者使用for循环(这将需要你自己创建数组)?
for DIR in $(find ${PWD} -maxdepth 1); do if $(test -d ${DIR}); then echo $(basename ${DIR}); fi; done

有人告诉我,使用以下所示的"内部字段分隔符"(IFS)是更安全的解决方案(可以保护免受字段/目录名称中奇怪字符的影响,例如空格)

while IFS= read -r DIR; do echo "${DIR}"; done < <(find . -maxdepth 1 -type d -printf "%P\n")

嗨,感谢回复。抱歉,我忘记添加我的初始代码以查看我卡住的地方了。我使用了ls,但也许find会更好,无论如何,我现在卡在将结果添加到数组中。 - mike atkinson
1
将“ls”解析为参数可能是一个不好的主意(如果名称中有空格,则可能会灾难性)[ParsingLs]。我从来没有自己构建过数组(我通常找到了绕过它的方法),你看过这个讨论吗?我现在没有测试的条件,但我会的,因为这对我未来也是一个潜在的情况。 - Sigve Karolius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接