【问题标题】:Bash adding a string to file nameBash将字符串添加到文件名
【发布时间】:2017-08-21 14:27:52
【问题描述】:

我需要在给定目录的扩展名之前的每个文件名末尾添加单词“hallo”。但我的代码不产生任何输出。 echo 语句没有输出,文件名也没有改变

count=""
dot="."
for file in $ls
  do
    echo $file
    count=""
    for f in $file
    do
      echo $f
      if [ $f -eq $dot ];
      then
        count=$count"hallo"
      fi
      count=$count+$f
    done
    mv $file $count
  done

【问题讨论】:

  • $ls 和 $file 定义在哪里?
  • 显然这个for file in $ls 应该是for file in "$(ls)",这是一个命令替换,因为没有定义$ls 变量。此外,您应该始终在所有变量中使用双引号。
  • 如果您期望for f in $file 将遍历变量$file 的每个字母,那么这不会发生。

标签: bash macos shell


【解决方案1】:

使用 bash 可以更简单地完成,例如:

for f in *;do
echo "$f" "${f%.*}hallo.${f##*.}"
done

示例:

$ ls -all
-rw-r--r-- 1 29847 29847    0 Aug 21 14:33 file1.txt
-rw-r--r-- 1 29847 29847    0 Aug 21 14:33 file2.txt
-rw-r--r-- 1 29847 29847    0 Aug 21 14:33 file3.txt
-rw-r--r-- 1 29847 29847    0 Aug 21 14:33 file4.txt
-rw-r--r-- 1 29847 29847    0 Aug 21 14:33 file5.txt 

$ for f in *;do mv -v "$f" "${f%.*}hallo.${f##*.}";done
'file1.txt' -> 'file1hallo.txt'
'file2.txt' -> 'file2hallo.txt'
'file3.txt' -> 'file3hallo.txt'

$ ls -all
-rw-r--r-- 1 29847 29847    0 Aug 21 14:33 file1hallo.txt
-rw-r--r-- 1 29847 29847    0 Aug 21 14:33 file2hallo.txt
-rw-r--r-- 1 29847 29847    0 Aug 21 14:33 file3hallo.txt

这是因为 ${f%.*} 返回不带扩展名的文件名 - 删除从末尾(向后)直到找到的第一个/最短的点的所有内容 (*)。

另一方面,${f##*.} 会删除从开头到找到的最长点的所有内容,只返回扩展名。

要克服 cmets 中指出的无扩展名文件问题,您可以执行以下操作:

$ for f in *;do [[ "${f%.*}" != "${f}" ]] && echo "$f" "${f%.*}hallo.${f##*.}" || echo "$f" "${f%.*}hallo"; done
file1.txt file1hallo.txt
file2.txt file2hallo.txt
file3.txt file3hallo.txt
file4 file4hallo
file5 file5hallo

如果文件没有扩展名,则返回 true "${f%.*}" == "${f}"

【讨论】:

  • 我想知道我的代码有什么问题@GeorgeVasiliou
  • @Erwin 你对 shell 语法做了几个错误的猜测,所以在不知道每条语句的实际意图的情况下,很难准确地告诉你每一行有什么问题。
  • 如果很简单,如果很难,那我真的不知道该说什么
  • 这不适用于任何没有扩展名的文件名。例如“文件名”->“文件名hallo.filename”。
  • @GeorgeVasiliou 您可以通过将测试更改为[[ "${f%.*}" != "${f}" ]] 来避免file.file 问题。
【解决方案2】:

@GeorgeVasiliou 已经展示了如何做你想做的事;我将尝试解释您的原始脚本有什么问题。在许多地方,您尝试执行的操作都使用了错误的语法。

for file in $ls

这将扩展名为ls 的shell 变量,将其内容拆分为单词,将在这些单词中找到的任何通配符扩展为匹配文件列表,然后遍历结果。但是没有定义 ls 变量,因此它扩展为空,并且循环永远不会运行。我认为您正在尝试迭代 ls 命令的输出;其语法为for file in $(ls)。但是你不应该这样做,因为文件名可以包含空格、制表符、换行符和通配符以及其他各种有趣的字符,这些字符可以使这个迭代......不是文件名列表。请参阅 BashFAQ 条目 'Why you shouldn't parse the output of ls(1)'。

您应该改用for file in *。 * 被扩展为匹配文件列表(当前目录中的所有可见文件),然后该列表不会以任何方式混淆,只是直接迭代。唯一的潜在问题是,如果没有任何匹配项,扩展将被跳过,循环运行一次,file 设置为“*”。

echo $file

当使用一个没有双引号的变量时,它将经历我上面描述的分词和通配符扩展过程。通常,什么都不会发生。有时,奇怪和意想不到的事情会发生。使用双引号,例如echo "$file"。

for f in $file

for ... in ... 语句迭代单词,而不是字符。 shell 没有特别好的方法来遍历字符串中的字符,但是您可以使用 for ((i=0; i<${#file}; i++)); do 遍历字符编号,然后使用 "${file:$i:1}" 获取单个字符。

哦,for (( )) 和 ${var:start:length} 构造是 bash 扩展,并非在所有 shell 中都可用。如果您想在脚本中使用其中一个或两个,请务必以特定于 bash 的 shebang 行(#!/bin/bash 或 #!/usr/bin/env bash)开始。

if [ $f -eq $dot ];

再次,双引号变量引用以避免在变量包含空格或通配符或为空白时造成混乱。此外,在[ ] 测试中,-eq 运算符进行数字比较而不是字符串比较;如果你给它的字符串不是有效的数字,它会给出一个错误。对于字符串比较,请改用=。此外,行尾不需要分号(好吧,除了case 的结尾等少数地方)。如果then 在同一行,则在此处使用分号,但如果在下一行,则不用。

count=$count+$f

这里的+ 将作为文字字符插入到变量值中。要附加两个变量,只需使用count="$count$f" 之类的东西。 (注意:这实际上是少数几个可以安全地去掉双引号的地方之一。但我还是建议使用它们,因为很难跟踪哪里是安全的,哪里不安全,所以更容易总是双引号你的变量。)

mv $file $count

再次,双引号。此外,当像这样运行任何类型的自动批量重命名/移动时,您应该始终使用 mv -n 或 mv -i 以防止在发生命名冲突(或错误或其他)时覆盖文件.

实际上,当我第一次运行这样的 mass-mv 脚本时,我喜欢在活动命令(例如 echo mv -n "$file" "$count")之前添加 echo,这样我就可以进行试运行并确保它是在我删除echo 并真正运行它之前,我会按照我的预期去做。

【讨论】:

  • 赞成您花时间向新人解释问题所在。我希望在我刚开始的时候能遇到更多像你这样的人。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-10-18
  • 1970-01-01
  • 1970-01-01
  • 2014-11-14
  • 2012-02-03
  • 1970-01-01
  • 2020-02-16
相关资源
最近更新 更多