无法在 shell 中使用带有特殊字符的文件

无法在 shell 中使用带有特殊字符的文件

我想循环遍历一个目录,对于每个不是目录的文件,我想将名称附加在一起并用逗号分隔。

files=""
for f in $(ls ~/Downloads | grep -v ^d)
        do

                files+="$f"","

        done 
        echo "$files"

此处的下载文件夹包含如下文件

这个:01 - Agar Mujhse Mohabbat Hai - www.downloadming.com.mp3

预期输出:01 - 祝福你一切顺利 - www.downloadming.com.mp3

实际产量:01,-,Agar,Mujhse,Mohabbat,Hai,-,www.downloadming.com.mp3

欢迎所有解决方案。

答案1

不要解析ls

files=()

cd ~/Downloads
for file in *; do
    [[ -d $file ]] && continue
    files+=( "$file" )
done

printf "%s\n" "${files[@]}"

comma_separated=$(IFS=,; echo "${files[*]}")
echo "$comma_separated"

正如你所见,for f in $(ls)迭代在输出中,而不是线ls当您想让人类的眼睛看到文件时,请使用 shell 文件名模式,当您想让脚本对文件名进行某些操作时,请使用 shell 文件名模式。

答案2

更短的解决方案: find ~/Downloads -type f -not -name "d*" -printf "%f,"

答案3

使用while read而不是for

我发现迭代文件名列表的最安全的方法是通过管道传输while read

find ~/Downloads -type f | while read filename; do
  echo "found file called $filename";
done

如您所见,它将整个文件名(以行分隔而不是空格分隔)放入您提供的变量名中,此处$filename

然而,从最初的问题来看,如果真正想要的是一个以逗号分隔的文件列表,那么你甚至不需要迭代,只需tr使用tr将行分隔符转换为逗号:

find ~/Downloads -type f | tr '\n' ','

答案4

这是内部字段分隔符的问题。相反,这不是问题,它就是这样设计的。它用空格分隔输入,就像在位置参数中一样。

来自 Bash 手册页:

为了 姓名[ [[单词... ] ] ; ] 列表完毕

下面的单词列表展开,生成项目列表。变量姓名依次设置为此列表的每个元素,并且列表每次都会执行。如果 单词 被省略,为了命令执行列表对于设置的每个位置参数,执行一次。返回状态是执行的最后一个命令的退出状态。如果以下项目的扩展导致列表为空,不执行任何命令,返回状态为 0。

这意味着,它将表达式的结果扩展为一个单词列表。这就是内部字段分隔符 $IFS 的作用所在。默认情况下,$IFS 设置为空格(空格、制表符和换行符)。这意味着一个字段由直到(但不包括)这些空格字符的所有内容组成。由于文件名由空格组成,因此它将它们视为几个不同的字段并为每个字段运行一次循环。解决此问题的方法是调整 $IFS 值。

因此,您需要将 $IFS 调整为空格以外的其他内容。但是,使用当前方法,除非它们恰好都有文件扩展名,否则无法分辨一个文件名在哪里结束,另一个文件名在哪里开始。更简单的方法是使用find如下实用程序:

#Save the original IFS
oldifs=$IFS
#Set the new IFS to the ASCII null character \x00
IFS=$(echo -ne "\x00")

files="" #To prevent scoping issues

#Use the find utility to find and print just regular files (not directories),
#and not going further than ~/Downloads (remove '-maxlength 1' to make the
#search fully recursive), then print the filenames in the format
#file1\x00file2\x00file3\x00file4...

#However, since our new IFS is now \x00, the boundary upon which these filenames are
#separated now works as expected
for f in $(find ~/Downloads -maxdepth 1 -type f -print0)
do
  files="$files,$f"
done
echo "$files"

#Reset IFS
IFS=$oldifs

我还想指出,您的语句ls ~/Downloads | grep -v ^d可能没有达到您的预期。听起来您试图过滤掉目录,但实际上发生的是它过滤掉了以“d”开头的文件。这是因为您没有使用长格式lslike ls -lls -l打印输出如下:

drwxrwxrw- user group SomeDirectory size date_modified filename

而 'ls' 仅打印文件名称:

SomeDirectory
file1
file2
file3

相关内容