Bash递归中数组里出现幽灵项的错误?

编程语言 2026-07-12

我的脚本用于批量下载一组LibreText的开放教材,它会准备一个链接列表:

#!/bin/bash

function recurse_directories {

S="$1"
echo "S=$S"

r="$(echo "$S" | perl -pe 's/\//\\\//g' | perl -pe 's/\./\\./g')\/[^\"]+"
echo "r=$r"

ls=($(curl -s "$S" | perl -lne "print for /$r/g" | sort | uniq))
# echo '${ls[@]}='"${ls[@]}"
printf '\n=====\nlist of array items\n=====\n'
echo "arry index limit: $((${#ls[*]}-1))"
for i in $(seq 0 $((${#ls[*]}-1)) ); do
echo "$i : ${ls[$i]}"
done
printf '=====\n'

for i in $(seq 0 $((${#ls[*]}-1)) ); do

echo '${ls['"$i"']}=''"'"${ls[$i]}"'"'

cd="math-$(curl -s "${ls[$i]}" | perl -lne 'print for /data-page-id="(.+?)"/')"
ln="https://batch.libretexts.org/print/Letter/Finished/$cd/Full.pdf"

if ! [ -z "${ls[i]}" ]; then #ch/x
if curl --head --silent -o /dev/null --fail "$ln" 2> /dev/null; then
printf "\n=====\nTRUE: $ln\n=====\n"
else
printf "\n=====\n=====\nFALSE: $ln\n=====\n=====\n"
recurse_directories "${ls[i]}"
fi
else
echo "No items left. Exiting..."
exit
fi
done

}


recurse_directories 'https://math.libretexts.org/Bookshelves'

它会一直运行,直到出现 ${ls[9]}="" 这一行为止(这很神秘,因为前一行 echo "arry index limit: $((${#ls[*]}-1))" 显示数组会从0 递增到8):在达到最大索引后,竟然又从1 循环到9,虽然没有任何代码会这样做。这是一个错误,还是我哪里写错了?

数组索引和数组项在不同层级之间混淆,这怎么可能?每个函数都是独立隔离执行的,除了它所接收的初始参数。

在一次 recurse_directories "${ls[i]}" 调用中,ls 数组甚至被替换为不同的内容。看起来唯一的解决办法是使用两个不同的变量,在它的项用完时再重用其中一个。即便如此,存放临时数据所需的变量数量会增加,并且取决于递归的深度。我需要为每次函数调用生成一个唯一的变量,但怎么实现?也许面向对象的语言会按作用域来隔离变量,但Bash里当然也有替代方案,至少可以实现递归函数。

解决方案

已修复。习惯了像Java这样的高级编程语言,在某种意义上会为你处理变量作用域,我忘了使用 local 关键字:

#!/bin/bash

function recurse_directories {



local S="$1"
echo "S=$S"

local r="$(echo "$S" | perl -pe 's/\//\\\//g' | perl -pe 's/\./\\./g')\/[^\"]+"
echo "r=$r"

local ls=($(curl -s "$S" | perl -lne "print for /$r/g" | sort | uniq))
# echo '${ls[@]}='"${ls[@]}"
printf '\n=====\nlist of array items\n=====\n'
echo "arry index limit: $((${#ls[*]}-1))"
for i in $(seq 0 $((${#ls[*]}-1)) ); do
echo "$i : ${ls[$i]}"
done
printf '=====\n'

for i in $(seq 0 $((${#ls[*]}-1)) ); do

echo '${ls['"$i"']}=''"'"${ls[$i]}"'"'

local cd="math-$(curl -s "${ls[$i]}" | perl -lne 'print for /data-page-id="(.+?)"/')"
local ln="https://batch.libretexts.org/print/Letter/Finished/$cd/Full.pdf"

# if ! [ -z "${ls[i]}" ]; then #ch/x
if [ -n "${ls[i]}" ]; then
if curl --head --silent -o /dev/null --fail "$ln" 2> /dev/null; then
printf "\n=====\nTRUE: $ln\n=====\n"
else
printf "\n=====\n=====\nFALSE: $ln\n=====\n=====\n"
recurse_directories "${ls[i]}"
fi
else
echo "No items left. Exiting..."
exit
fi
done

}


recurse_directories 'https://math.libretexts.org/Bookshelves'

为更清晰地展示 [@markp-fuso的注释]:

function recurse_directories {
    local S="$1"
    echo "S=$S"

    local r="$(echo "$S" | perl -pe 's/\//\\\//g' | perl -pe 's/\./\\./g')\/[^\"]+"
    echo "r=$r"

    local ls=($(curl -s "$S" | perl -lne "print for /$r/g" | sort | uniq))
    # echo '${ls[@]}='"${ls[@]}"
    printf '\n=====\nlist of array items\n=====\n'
    echo "arry index limit: $((${#ls[*]}-1))"
    for i in $(seq 0 $((${#ls[*]}-1)) ); do
        echo "$i : ${ls[$i]}"
    done
    printf '=====\n'

    for i in $(seq 0 $((${#ls[*]}-1)) ); do

        echo '${ls['"$i"']}=''"'"${ls[$i]}"'"'

        local cd="math-$(curl -s "${ls[$i]}" | perl -lne 'print for /data-page-id="(.+?)"/')"
        local ln="https://batch.libretexts.org/print/Letter/Finished/$cd/Full.pdf"

        # if ! [ -z "${ls[i]}" ]; then #ch/x
        if [ -n "${ls[i]}" ]; then
            if curl --head --silent -o /dev/null --fail "$ln" 2> /dev/null; then
                printf "\n=====\nTRUE: $ln\n=====\n"
            else
                printf "\n=====\n=====\nFALSE: $ln\n=====\n=====\n"
                recurse_directories "${ls[i]}"
            fi
        else
            echo "No items left. Exiting..."
            exit
        fi
    done
}

recurse_directories 'https://math.libretexts.org/Bookshelves'
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章