BASH中带空格的文件名

15

我正在尝试编写脚本,将大型照片裁剪并调整大小为高清壁纸。

#! /bin/bash


for i in `ls *.jpg`
do
    width=`identify -format '%w' $i`
    height=`identify -format '%h' $i`

    if [ `echo "$width/$height > 16/9" | bc -l` ]
    then
        exec `convert $i -resize 1920 -gravity Center -crop '1920x1080+0+0' +repage temp`
    else
        exec `convert $i -resize x1080 -gravity Center -crop 1920x1080+0+0 +repage temp`
    fi

    rm $i
    mv temp $i
done

但是看起来脚本在处理带有空格的文件名时出现了问题(例如Tumble Weed.jpg)。我该如何解决这个问题?


3
下面已经给出了答案,但我只想补充一句话,即 shell 脚本对处理文件名中的空格非常不利,因为许多列表都是以空格为分隔符的 - 特别是命令参数。例如尝试通过 scp 复制带有空格的文件!几乎不可能不手动转义。 - Timmmm
5
你应该完整地阅读 http://mywiki.wooledge.org/BashPitfalls,这能大幅提升你的Bash技能。 - Benoit
GNU coreutils于2022年6月11日添加了ls --zero选项(即将每个输出行以NUL而不是换行符"\n"结尾)。请参阅ls源代码 - user2514157
6个回答

29

首先,您不需要使用ls命令。通过在反引号内使用ls,您会隐式地让bash将字符串解析为列表,并以空格分割。相反,让bash生成列表并将其分离,而无需进行此类奇怪的操作:

此外,您需要将所有$i用法括起来,以使bash将其作为整体替换,而不是作为分隔单词的字符串。

以下是演示这两个想法的脚本:

for i in *.jpg ; do 
  echo "$i";
done

2
这并不总是有效,因此会引导人偏离正确的理解。而这则代码,则总是有效的。SAVEIFS=$ IFS; IFS = $(echo -en“\ n \ b”); for f in $(ls -lah); do echo“$ f”done; IFS = $ SAVEIFS - user657127
谢谢,那确实有帮助,但我认为缺少了一个分号。这个对我有效:SAVEIFS=$IFS;IFS=$(echo -en "\n\b");for f in $(ls -lah);do echo "$f"; done;IFS=$SAVEIFS - Kai Carver

19

使用read方法解决空格问题。尽管这种写法看起来有点不自然,但它的工作效果更好:

find . -type f -iname "*.jpg" | while read i
do
    # your original code inside the loop using "$i" instead of $i
done

使用-iname,您还可以获取具有不同大小写扩展名(如.JPG)的jpg文件。 "i"表示忽略大小写。


1
就目前而言,这个程序在包含文字反斜杠、包含文字换行符以及以空格结尾的名称中会失败。 - Charles Duffy
1
修复它,将 -print0 添加到 find 中,并使 shell 结束 while IFS= read -r -d '' i-r 修复反斜杠,-d''-print0 修复字面换行,而 IFS= 修复对以空格结尾的名称的支持。 - Charles Duffy

5
我建议将for循环写成这样:
for i in *.jpg

将变量$i用双引号括起来:"$i"


如果您坚持使用

`ls *.jpg`

如果您从较复杂的命令中获取文件名,可以尝试将 IFS 设置为 \n

样式,(例如通过更复杂的命令获取文件名)您可以尝试将 IFS 设置为 \n

IFS='\n'

比较以下两个执行结果:

$ for f in `ls *`; do echo $f; done
hello
world
test

$ IFS='\n'; for f in `ls *`; do echo $f; done
hello world
test

1
在bash中,使用字符串替换与find

${string//substring/replacement}
$replacement替换所有匹配的$substring

因此,这个命令可以运行:
find . -type f -name '*.jpg' | while read i ; do /bin/mv -f "$i" ${i// /_}; done

0

在文件名周围使用双引号。像这样:

width=`identify -format '%w' "$i"`

请注意在"$i"周围的双引号。

0
 #! /bin/bash 
 mkfifo ./lsOutput
 ls -1 *.jpg > ./lsOutput
    while read line  
    do 

    width=`identify -format '%w' "$line"` 
    height=`identify -format '%h' "$line"` 

    if [ `echo "$width/$height > 16/9" | bc -l` ] 
    then 
        exec `convert "$line" -resize 1920 -gravity Center -crop '1920x1080+0+0' +repage temp` 
    else 
        exec `convert "$line" -resize x1080 -gravity Center -crop 1920x1080+0+0 +repage temp` 
    fi 

    rm "$line"
    mv temp "$line"
 done<./lsOutput

可能需要一些关于流和FIFO的解释。 我喜欢read命令只查找'\n'来获取字符串。 - Chris Reid

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