ChatGPT解决这个技术问题 Extra ChatGPT

如何在shell脚本中获取目录中的文件列表?

我正在尝试使用 shell 脚本获取目录的内容。

我的脚本是:

for entry in `ls $search_dir`; do
    echo $entry
done

其中 $search_dir 是相对路径。但是,$search_dir 包含许多名称中带有空格的文件。在这种情况下,此脚本不会按预期运行。

我知道我可以使用 for entry in *,但这仅适用于我当前的目录。

我知道我可以更改到该目录,使用 for entry in * 然后更改回来,但我的特殊情况阻止我这样做。

我有两个相对路径 $search_dir$work_dir,我必须同时处理这两个路径,读取它们在其中创建/删除文件等。

那我现在该怎么办?

PS:我使用bash。


K
KansaiRobot
search_dir=/the/path/to/base/dir/
for entry in "$search_dir"/*
do
  echo "$entry"
done

您能解释一下为什么 for entry in "$search_dir/*" 不起作用吗?为什么我们需要将 /* 放在引号之外?
@mrgloom:因为您需要让shell glob 通配符。
该解决方案提供了完整的路径。如果我只想列出当前目录中的内容怎么办?
@mrgloom 如果您想这样做,您可以使用 for entry in "${search_dir}/*" 来实现
如果文件夹为空或某些文件以句点开头,则这不起作用(在 bash 中使用默认设置)。
r
rrr

这是一种让我更容易理解语法的方法:

yourfilenames=`ls ./*.txt`
for eachfile in $yourfilenames
do
   echo $eachfile
done

./ 是当前工作目录,但可以替换为任何路径
*.txt 返回anything.txt
您可以通过直接在终端中输入 ls 命令轻松查看将列出的内容。

基本上,您创建一个变量 yourfilenames,其中包含 list 命令作为单独元素返回的所有内容,然后循环遍历它。循环创建了一个临时变量 eachfile,其中包含它正在循环的变量的单个元素,在本例中为文件名。这不一定比其他答案更好,但我觉得它很直观,因为我已经熟悉 ls 命令和 for 循环语法。


这适用于快速、非正式的脚本或单行脚本,但如果文件名包含换行符,它将中断,这与基于 glob 的解决方案不同。
@SorenBjornstad 感谢您的建议!我不知道文件名中允许换行 - 什么样的文件可能有它们?比如,这是经常发生的事情吗?
由于这个原因,文件名中的换行符是邪恶的,据我所知,没有正当理由使用它们。我自己从未在野外见过一只。也就是说,完全有可能通过换行符恶意构造文件名,从而利用这一点。 (例如,想象一个包含文件 ABC 的目录。您创建名为 B\nCD 的文件,然后选择删除它们。不处理此权限的软件可能会结束即使您没有这样做的权限,也要删除预先存在的文件 B 和 C。)
mywiki.wooledge.org/ParsingLs 解释了这种方法的大量缺陷。您基本上不应该在脚本中使用 ls。无论如何,这很愚蠢;在 ls 运行时,shell 已经扩展了通配符。
t
tegan

这里的其他答案很好,可以回答你的问题,但这是“bash 获取目录中的文件列表”的最高谷歌结果,(我正在寻找保存文件列表)所以我想我会发布一个回答这个问题:

ls $search_path > filename.txt

如果您只想要某种类型(例如任何 .txt 文件):

ls $search_path | grep *.txt > filename.txt

注意 $search_path 是可选的; ls > filename.txt 将执行当前目录。


无需使用 grep 仅获取 .txt 文件:`ls $search_path/*.txt > filename.txt'。但更重要的是,不应使用 ls 命令的输出来解析文件名。
@VictorZamanian,你能详细说明为什么我们不应该使用 ls 的输出来解析文件名吗?以前没听说过这个。
@samurai_jane 关于这个主题有很多链接可以提供,但这里是第一个搜索结果:mywiki.wooledge.org/ParsingLs。我什至在这里看到一个关于 SO 的问题,声称不解析 ls 输出的原因是 BS 并且对此非常详尽。但是回复/答案仍然声称这是一个坏主意。看看:unix.stackexchange.com/questions/128985/…
l
l0b0
for entry in "$search_dir"/* "$work_dir"/*
do
  if [ -f "$entry" ];then
    echo "$entry"
  fi
done

V
Victoria Stuart
$ pwd; ls -l
/home/victoria/test
total 12
-rw-r--r-- 1 victoria victoria    0 Apr 23 11:31  a
-rw-r--r-- 1 victoria victoria    0 Apr 23 11:31  b
-rw-r--r-- 1 victoria victoria    0 Apr 23 11:31  c
-rw-r--r-- 1 victoria victoria    0 Apr 23 11:32 'c d'
-rw-r--r-- 1 victoria victoria    0 Apr 23 11:31  d
drwxr-xr-x 2 victoria victoria 4096 Apr 23 11:32  dir_a
drwxr-xr-x 2 victoria victoria 4096 Apr 23 11:32  dir_b
-rw-r--r-- 1 victoria victoria    0 Apr 23 11:32 'e; f'

$ find . -type f
./c
./b
./a
./d
./c d
./e; f

$ find . -type f | sed 's/^\.\///g' | sort
a
b
c
c d
d
e; f

$ find . -type f | sed 's/^\.\///g' | sort > tmp

$ cat tmp
a
b
c
c d
d
e; f

变化

$ pwd
/home/victoria

$ find $(pwd) -maxdepth 1 -type f -not -path '*/\.*' | sort
/home/victoria/new
/home/victoria/new1
/home/victoria/new2
/home/victoria/new3
/home/victoria/new3.md
/home/victoria/new.md
/home/victoria/package.json
/home/victoria/Untitled Document 1
/home/victoria/Untitled Document 2

$ find . -maxdepth 1 -type f -not -path '*/\.*' | sed 's/^\.\///g' | sort
new
new1
new2
new3
new3.md
new.md
package.json
Untitled Document 1
Untitled Document 2

笔记:

. : 当前文件夹

删除 -maxdepth 1 以递归搜索

-type f : 查找文件,而不是目录 (d)

-not -path '*/\.*' :不返回 .hidden_files

sed 's/^\.\///g' : 从结果列表中删除前面的 ./


N
Noel Yap
find "${search_dir}" "${work_dir}" -mindepth 1 -maxdepth 1 -type f -print0 | xargs -0 -I {} echo "{}"

我知道这已经很老了,但我似乎无法获得最后一个 xargs -0 -i echo "{}" 命令,愿意解释一下吗?特别是 -i echo "{}" 部分是做什么的?此外,我从 man 页面读到 -i 现在已弃用,我们应该使用 -I insted。
-i{} 替换为 arg。
谢谢!这很有用,对于像我这样思维迟钝的人来说,我认为 {} 是由 find 命令替换为匹配项的字符串。
你为什么使用xargs?默认情况下,find 打印它找到的内容...您可以从 -print0 中删除所有内容。
这样做不会很好地处理带有空格的文件条目。
T
TrevTheDev

接受的答案不会返回带有 .为此使用

for entry in "$search_dir"/* "$search_dir"/.[!.]* "$search_dir"/..?*
do
  echo "$entry"
done

G
Gabriel Staples

如何在shell脚本中获取目录中的文件列表?

除了 most-upvoted answer by @Ignacio Vazquez-Abrams 之外,请考虑以下解决方案,它们也都有效,具体取决于您要执行的操作。请注意,您可以将 "path/to/some/dir" 替换为 . 以便在当前目录中搜索

1. 使用 find 和 ls 列出不同类型的文件

参考:

对于查找,请参阅此答案。另请参阅我的评论。对于 ls,请参阅 linuxhandbook.com:如何在 Linux 中仅列出目录

提示:对于下面的任何 find 示例,如果您希望对其进行排序,可以将输出通过管道传输到 sort -V

例子:

find . -maxdepth 1 -type f | sort -V

仅列出常规文件 (-type f) 1 级深度:

# General form
find "path/to/some/dir" -maxdepth 1 -type f

# In current directory
find . -maxdepth 1 -type f

仅列出符号链接 (-type l) 1 级深度:

# General form
find "path/to/some/dir" -maxdepth 1 -type l

# In current directory
find . -maxdepth 1 -type l

仅列出目录 (-type d) 1 级:

请注意,对于此处的 find 示例,我们还添加了 -mindepth 1 以排除当前目录 .,否则它将在目录列表的顶部打印为 .。见这里:How to exclude this / current / dot folder from find "type d"

# General form
find "path/to/some/dir" -mindepth 1 -maxdepth 1 -type d

# In current directory
find . -mindepth 1 -maxdepth 1 -type d

# OR, using `ls`:
ls -d

结合以上部分:仅列出常规文件符号链接 (-type f,l) 1 级深度:

使用逗号 (,) 分隔 -type 的参数:

# General form
find "path/to/some/dir" -maxdepth 1 -type f,l

# In current directory
find . -maxdepth 1 -type f,l

2. 将任何命令的输出捕获到 bash 索引数组中,其中元素由换行符 (\n) 分隔

但是,$search_dir 包含许多名称中带有空格的文件。在这种情况下,此脚本不会按预期运行。

这可以通过告诉 bash 根据换行符 \n 而不是空格字符来分隔字符串中的元素——这是 bash 使用的默认 IFS(内部字段分隔符——参见 The Meaning of IFS in Bash Scripting)变量来解决。为此,我建议使用 mapfile 命令。

名为 shellscript 的 bash 脚本静态代码分析器工具建议您在要将字符串读入 bash 数组时使用 mapfileread -r,根据换行符 (\n) 分隔元素。请参阅:https://github.com/koalaman/shellcheck/wiki/SC2206

更新:要查看如何使用 mapfileread -r 执行此操作的示例,请在此处查看我的答案:How to read a multi-line string into a regular bash "indexed" array我现在更喜欢使用 read -r 而不是 mapfile,因为 mapfile 将保留任何空行作为数组中的元素(如果存在),这是我不想要的,而 read -r [再次,我的现在的偏好] 不会将空行保留为数组中的元素。

(回到我原来的答案:)

以下是如何使用 mapfile 命令将换行符分隔的字符串转换为常规 bash“索引”数组

# Capture the output of `ls -1` into a regular bash "indexed" array.
# - includes both files AND directories!
mapfile -t allfilenames_array <<< "$(ls -1)"
# Capture the output of `find` into a regular bash "indexed" array
# - includes directories ONLY!
# Note: for other `-type` options, see `man find`.
mapfile -t dirnames_array \
    <<< "$(find . -mindepth 1 -maxdepth 1 -type d | sort -V)"

笔记:

我们使用 ls -1 (这是一个“破折号 numeric_one”)将每个文件名放在自己的行上,从而将它们全部用换行符 \n 字符分隔。如果你想用谷歌搜索它,<<< 在 bash 中被称为“这里的字符串”。请参阅 mapfile --help 或帮助 mapfile 以获取帮助。

完整代码示例:

从我的 eRCaGuy_hello_world 存储库中的文件 array_list_all_files_and_directories.sh

echo "Output of 'ls -1'"
echo "-----------------"
ls -1
echo ""

# Capture the output of `ls -1` into a regular bash "indexed" array.
# - includes both files AND directories!
mapfile -t allfilenames_array <<< "$(ls -1)"
# Capture the output of `find` into a regular bash "indexed" array
# - includes directories ONLY!
# Note: for other `-type` options, see `man find` and see my answer here:
# https://stackoverflow.com/a/71345102/4561887
mapfile -t dirnames_array \
    <<< "$(find . -mindepth 1 -maxdepth 1 -type d | sort -V)"

# Get the number of elements in each array
allfilenames_array_len="${#allfilenames_array[@]}"
dirnames_array_len="${#dirnames_array[@]}"

# 1. Now manually print all elements in each array

echo "All filenames (files AND dirs) (count = $allfilenames_array_len):"
for filename in "${allfilenames_array[@]}"; do
    echo "    $filename"
done
echo "Dirnames ONLY (count = $dirnames_array_len):"
for dirname in "${dirnames_array[@]}"; do
    # remove the `./` from the beginning of each dirname
    dirname="$(basename "$dirname")"
    echo "    $dirname"
done
echo ""

# OR, 2. manually print the index number followed by all elements in the array

echo "All filenames (files AND dirs) (count = $allfilenames_array_len):"
for i in "${!allfilenames_array[@]}"; do
    printf "  %3i: %s\n" "$i" "${allfilenames_array["$i"]}"
done
echo "Dirnames ONLY (count = $dirnames_array_len):"
for i in "${!dirnames_array[@]}"; do
    # remove the `./` from the beginning of each dirname
    dirname="$(basename "${dirnames_array["$i"]}")"
    printf "  %3i: %s\n" "$i" "$dirname"
done
echo ""

以下是在我的 eRCaGuy_hello_world 存储库的 eRCaGuy_hello_world/python 目录中运行的代码块的示例输出:

eRCaGuy_hello_world/python$ ../bash/array_list_all_files_and_directories.sh
Output of 'ls -1'
-----------------
autogenerate_c_or_cpp_code.py
autogenerated
auto_white_balance_img.py
enum_practice.py
raw_bytes_practice.py
slots_practice
socket_talk_to_ethernet_device.py
textwrap_practice_1.py
yaml_import

All filenames (files AND dirs) (count = 9):
    autogenerate_c_or_cpp_code.py
    autogenerated
    auto_white_balance_img.py
    enum_practice.py
    raw_bytes_practice.py
    slots_practice
    socket_talk_to_ethernet_device.py
    textwrap_practice_1.py
    yaml_import
Dirnames ONLY (count = 3):
    autogenerated
    slots_practice
    yaml_import

All filenames (files AND dirs) (count = 9):
    0: autogenerate_c_or_cpp_code.py
    1: autogenerated
    2: auto_white_balance_img.py
    3: enum_practice.py
    4: raw_bytes_practice.py
    5: slots_practice
    6: socket_talk_to_ethernet_device.py
    7: textwrap_practice_1.py
    8: yaml_import
Dirnames ONLY (count = 3):
    0: autogenerated
    1: slots_practice
    2: yaml_import


S
SnoopDogg

这是在目录中列出文件的另一种方式(使用不同的工具,效率不如其他一些答案)。

cd "search_dir"
for [ z in `echo *` ]; do
    echo "$z"
done

echo * 输出当前目录的所有文件。 for 循环遍历每个文件名并打印到标准输出。

此外,如果在目录中查找目录,则将其放在 for 循环中:

if [ test -d $z ]; then
    echo "$z is a directory"
fi

test -d 检查文件是否为目录。


for [ z 是语法错误。 if [ test 是愚蠢和错误的。不引用 "$z" 是引用错误。
请解释。当我写下答案时,我成功地运行了代码。如果您要进行 necropost 至少是有用的。
要解释什么?如果您当时对此进行了测试,那么您肯定没有使用标准外壳。我已经指出了错误,甚至从这里的其他一些答案中查找正确的语法应该不难。但请查看此演示:ideone.com/ERcu2c 并按您的意愿进行分叉。
如果您认为这无关紧要,因为它已经过时了,那么您就不了解 Stack Overflow 是如何工作的。这经常 - 可能每天 - 链接为发布此常见问题重复的新用户的规范答案,但也可能从 Google 搜索中获得 100 倍的流量。
K
Kolyan1

类似于 Accepted answer - 但仅列出文件名而不是完整路径:

这似乎已经回答了一段时间,但我想我也想提供一个答案,只列出所需目录中的文件,而不是完整路径。

    #search_dir=/the/path/to/base/dir/
    IFS=$'\n' #for in $() splits based on IFS
    search_dir="$(pwd)"
    for entry in $(ls $search_dir)
    do
        echo $entry
    done

如果您还想过滤特定文件,您可以添加 grep -q 语句。

    #search_dir=/the/path/to/base/dir/
    IFS=$'\n' #for in $() splits based on IFS
    search_dir="$(pwd)"
    for entry in $(ls $search_dir)
    do
        if grep -q "File should contain this entire string" <<< $entry; then
        echo "$entry"
        fi
    done

参考:

有关 IFS 的更多信息,请参见here

有关在 shell 中查找子字符串的更多信息,请参阅 here


S
Stark Programmer
ls $search_path ./* |grep ".txt"|
while IFS= read -r line
do 
   echo "$line"
done

正如目前所写的那样,您的答案尚不清楚。请edit添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。您可以找到有关如何写出好答案的更多信息in the help center
这个问题有八个现有的答案,包括一个获得最高票数、超过三百票的被接受的答案。你确定你的解决方案还没有给出吗?如果不是,您为什么认为您的方法改进了已通过社区验证的现有提案?在 Stack Overflow 上提供解释总是有用的,但在问题得到解决且让 OP 和社区都满意的情况下,这一点尤其重要。通过解释您的答案的不同之处以及何时可能更受欢迎,来帮助读者。