【问题标题】:Use bash to replace substring in filename with a new substring based on pairwise (old,new) values in a .csv file使用 bash 将文件名中的子字符串替换为基于 .csv 文件中的成对(旧、新)值的新子字符串
【发布时间】:2020-05-23 01:51:34
【问题描述】:

我有一个包含数百个图像文件的目录,每个文件的名称不同,例如:

abdogf.png

abcatf.png

abhorsef.png

我创建了一个 changenames.csv 文件,其中包含两列,第一列是 oldstring,第二列是 newstring,例如:

“狗”,“汪”

"猫","喵喵"

“马”、“嘶”

这些字符串目前用引号引起来,如图所示。

我想从命令行调用 bash 命令或 .sh 脚本,用目录文件名(不在文件内容中)中的每个新字符串子字符串替换每个旧字符串子字符串,这样目录新包含的文件称为:

abwooff.png

abmiaowf.png

abneighf.png

而不是原始文件。

我尝试了各种解决方案,例如https://superuser.com/questions/508731/find-and-replace-string-in-filenames/508758#508758How to find and replace part of filenames from list,但均未成功。

例如,我尝试在包含文件的目录中调用以下内容:

#!/bin/bash
inputfile=${1}
while read line
do
    IFS=',' read -a names <<< "${line}"
    for file in `find . -maxdepth 1 -type f -name "*${names[0]}*"`; do
        rename "s/${names[0]}/${names[1]}/" *
    done
done < ${inputfile}

使用命令行命令 test.sh changenames.csv。

这不会产生错误,但不会更改文件名。

我也试过这个解决方案https://stackoverflow.com/a/55866613/10456769,它产生了一个错误,其中@echo 不是一个可识别的命令。

提前感谢您的帮助。

【问题讨论】:

  • 您尝试过的实际代码和收到的(不正确的)结果是什么?
  • #!/bin/bash inputfile=${1} while read line do IFS=',' read -a names find . -maxdepth 1 -type f -name "*${names[0]}*"; do rename "s/${names[0]}/${names[1]}/" * done done
  • 继续更新你的问题,不要把细节埋在 cmets 里
  • 然后我尝试@echo off setlocal enabledelayedexpansion for /f "tokens=1,* delims=," %%i in (myfile.csv) do ( set "search=%%i" set " replace=%%j" call :fix ) exit /b :fix for %%a in (!search!.wav) do ( set "file=%%a" set "file=!file :%search%=%replace%!!" echo ren "%%~fa" "!file!" ) 错误 ./test2.sh: line 1: @echo: command not found ./test2.sh: line 2: setlocal:找不到命令 ./test2.sh:第 3 行:意外标记附近的语法错误 "tokens=1,* delims=,"' ./test2.sh: line 3: for /f "tokens=1,* delims=," %%i in (myfile.csv) do ('
  • 添加一对echo 语句以验证您的rename 命令是否符合您的预期。尝试运行打印的重命名命令以确保它有效。调试:-)

标签: bash shell replace rename batch-rename


【解决方案1】:

首先你需要去掉双引号。代码试图找到 不会退出的文件,例如 *"cat"*
此外,您不需要执行find 命令。你不是 完全使用变量file
请您尝试以下方法:

while IFS=',' read -r old new; do
    old=${old//\"/}     # remove leading and trailing double-quotes
    new=${new//\"/}     # same as above
    rename "s/$old/$new/" *
done < "$1"

【讨论】:

  • 谢谢。这似乎仍然没有改变任何东西。您能否阐明我如何调用它来读取具有(旧、新)对的特定 csv?我已经在 shell test.sh 中编写了上面的代码,并在包含我要重命名的文件的目录中的 bash 命令行中使用了命令 ./test.sh changenames.csv,并且还包含 test.sh 和 changenames。 .csv 文件。谢谢!
  • 首先让我们看看rename 命令是否在命令行上有效。请确保当前目录下有文件abdogf.pngabcatf.pngabhorsef.png,执行rename "s/dog/woof/" *。如果abdogf.png 的文件名正确更改为abwooff.png,那么它可以工作。使用mv命令将其重命名回原来的名称,然后我们进入下一步。
  • 下一步让我们检查 csv 文件是否被正确读取。请修改我的脚本test.sh,在rename 行下方添加一行echo "s/$old/$new/"。然后执行./test.sh changenames.csv。如果运行良好,终端上会显示s/dog/woof/s/cat/miaow/s/horse/neigh/这三行。
  • 如果输出看起来像//dog/woof//cat/miaow//horse/neigh,您的changenames.csv 将在行尾包含一个DOS“\r”字符。如果是这样,请通过执行dos2unix changenames.csv 转换带有dos2unix 的文件。那么一切都会好起来的。
【解决方案2】:

IFS=',' read -a names &lt;&lt;&lt; "${line}" 不会从输入中删除 "。您的文件名中没有 ",因此您也必须删除它们。

不鼓励使用反引号`。不要使用它们。请改用$(....)

"for file in `" 和for file in $(cat) 一样糟糕——这是一种常见的 bash 反模式。不要使用它——你会遇到带有空格或制表符的元素的问题。使用while IFS= read -r line 来逐行阅读。

rename 有问题,rename 有两个常见的版本——GNU renameperl rename。您的脚本似乎针对 perl 版本 - 确保它是已安装的。

rename 进行重命名 - 这里不需要for file in find

如果您执行while read line,然后IFS=, read &lt;&lt;&lt;"$line" 正在复制工作,则从头开始执行while IFS=, read -a names; do

所以你可以这样做:

# split the input on ','
while IFS=',' read -r pre post; do
    # remove quotes
    pre=${pre//\"/}
    post=${post//\"/}
    # do the rename
    rename "s/${pre}/${post}/" *
done < ${inputfile}

我想我会执行以下使用sed 的脚本:

# find all files in a directory
find . -maxdepth 1 -type f |
# convert filenames into pairs (filename,new_filename) separated by newline
sed "
   # hold the line in hold space
   h
   # replace the characters as in the other file
   $(
      # generate from "abc","def" -> s"abc"def"g
      sed 's@"\([^"]*\)","\([^"]*\)"@s"\1"\2"g@' changenames.csv
   )
   # switch pattern and hold space
   x
   # append the line
   G
   # remove the line if substitute is the same
   /^\(.*\)\n\1$/d
" |
# outputs two lines per each filename:
# one line with old filename and one line with new filename
# so just pass that to mv
xargs -l2 echo mv -v

和一个班轮:

 find . -maxdepth 1 -type f | sed "h;$(sed 's@"\([^"]*\)","\([^"]*\)"@s"\1"\2"g@' changenames.csv);x;G; /^\(.*\)\n\1$/d" | xargs -l2 echo mv -v

使用以下文件结构重新创建:

touch  abdogf.png abcatf.png abhorsef.png
cat <<EOF >changenames.csv
"dog","woof"
"cat","miaow"
"horse","neigh"
EOF

脚本outputs on repl

mv -v ./abdogf.png ./abwooff.png
mv -v ./abcatf.png ./abmiaowf.png
mv -v ./abhorsef.png ./abneighf.png

【讨论】:

  • 谢谢你。我已经尝试了一个衬里,并且可以看到它正确地重写了命令输出上的新文件名,正如您在此处建议的那样:# 每个文件名输出两行:# 一行带有旧文件名,一行带有新文件名但是,原始文件仍然当我键入 ls 时,存在于没有新文件夹的文件夹中。我是否需要做其他事情才能真正将新文件名写入目录以替换原始文件?
  • 您需要删除xargs -l2 echo mv -v 中的echo 才能真正移动文件。
猜你喜欢
  • 2017-09-04
  • 2016-03-23
  • 1970-01-01
  • 2014-12-25
  • 1970-01-01
  • 2018-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多