英文:
rename multiple folders under different conditions by matching regex patterns in a bash script?
问题
I understand your request. Here is the translated content:
作为一个例子,假设我有一个包含以下文件夹的文件夹:
Universal 2023 02 15 Some Name
Universal 2023 02 15 Some Name and Words After
Sony Some Name 2023 02 15
Sony Some Name 2023 02 15 and Words After
期望输出
Some Name - 2023 02 15 - Universal
Some Name - 2023 02 15 - And Words After - Universal
Some Name - 2023 02 15 - Sony
Some Name - 2023 02 15 - and Words After – Sony
我为每种命名结构编写了一个命令。
Universal 2023 02 15 Some Name
将被重命名为:
Some Name - 2023 02 15 - Universal
使用此命令:
rename -v 's/([\s\S]+)\s((\d{4})\s(\d{2})\s(\d{2}))\s(([\s\S]+)\s([\s\S]+))/$6 - $2 - $1/g' *
Universal 2023 02 15 Some Name and Words After
将被重命名为:
Some Name - 2023 02 15 - And Words After - Universal
使用此命令:
rename -v 's/([\s\S]+)\s((\d{4})\s(\d{2})(\s)(\d{2}))\s((\w+)\s(\w+))\s([\s\S]+)/$7 - $2 - $10 - $1/g' *
Sony Some Name 2023 02 15
将被重命名为:
Some Name - 2023 02 15 - Sony
使用此命令:
rename -v 's/([\s\S]+)\s((\w+)\s(\w+))\s((\d{4})\s(\d{2})\s(\d{2}))/$2 - $5 - $1/g' *
- 最后,
Sony Some Name 2023 02 15 and Words After
将被重命名为:
Some Name - 2023 02 15 - and Words After - Sony
使用此命令:
rename -v 's/([\s\S]+)\s((\d{4})\s(\d{2})\s(\d{2}))\s(([\w]+)\s([\w]+))\s([\s\S]+)/$6 - $2 - $9 - $1/g' *
当我想要重命名这些文件夹时,我必须将它们放入单独的文件夹中并运行相应的命令,然后在完成后将它们全部放回到同一个文件夹中。这非常麻烦。因此,我想在bash中编写一个脚本,避免将它们分开文件并在主文件夹中完成所有操作。在VS Code中,一切似乎都很顺利,除了重命名命令。这一行呈橙色... 这意味着某些东西丢失了,但我不知道是什么:
's/([\s\S]+)\s((\d{4})\s(\d{2})\s(\d{2}))\s(([\w]+)\s([\w]+))\s([\s\S]+)/$6 - $2 - $9 - $1/g'
请查看此链接以查看在VS Code中的脚本颜色:VS Code中的脚本颜色
我的脚本:
for i in $*/; do
# 对于 Universal 2023 02 15 Some Name
if [[ "$i" =~ ([\s\S]+)\s((\d{4})\s(\d{2})\s(\d{2}))\s(([\s\S]+)\s([\s\S]+)) ]];
then
rename -v 's/([\s\S]+)\s((\d{4})\s(\d{2})\s(\d{2}))\s(([\s\S]+)\s([\s\S]+))/$6 - $2 - $1/g' *
# 对于 Universal 2023 02 15 Some Name and Words After
elif [[ "$i" =~ ([\s\S]+)\s((\d{4})\s(\d{2})(\s)(\d{2}))\s((\w+)\s(\w+))\s([\s\S]+) * ]];
then
rename -v 's/([\s\S]+)\s((\d{4})\s(\d{2})(\s)(\d{2}))\s((\w+)\s(\w+))\s([\s\S]+)/$7 - $2 - $10 - $1/g' *
# 对于 Sony Some Name 2023 02 15
elif [[ "$i" =~ ([\s\S]+)\s((\w+)\s(\w+))\s((\d{4})\s(\d{2})\s(\d{2})) ]];
then
rename -v 's/([\s\S]+)\s((\w+)\s(\w+))\s((\d{4})\s(\d{2})\s(\d{2}))/$2 - $5 - $1/g' *
# 对于 Sony Some Name 2023 02 15 and Words After
else [[ "$i" =~ ([\s\S]+)\s((\w+)\s(\w+))\s((\d{4})\s(\d{2})\s(\d{2})) ]];
then
rename -v 's/([\s\S]+)\s((\d{4})\s(\d{2})\s(\d{2}))\s(([\w]+)\s([\w]+))\s([\s\S]+)/$6 - $2 - $9 - $1/g' *
fi
done
请帮忙看看,谢谢!
Martin
英文:
As an example, let's say I have a folder containing these folders:
Universal 2023 02 15 Some Name
Universal 2023 02 15 Some Name and Words After
Sony Some Name 2023 02 15
Sony Some Name 2023 02 15 and Words After
Desired output
Some Name - 2023 02 15 - Universal
Some Name - 2023 02 15 - And Words After - Universal
Some Name - 2023 02 15 - Sony
Some Name - 2023 02 15 - and Words After – Sony
I wrote a command for every name structure.
1.
« Universal 2023 02 15 Some Name » will be renamed:
« Some Name - 2023 02 15 - Universal »
With this command:
rename -v 's/([\s\S]+)\s((\d{4})\s(\d{2})\s(\d{2}))\s(([\s\S]+)\s([\s\S]+))/$6 - $2 - $1/g' *
2.
« Universal 2023 02 15 Some Name and Words After » will be renamed:
« Some Name - 2023 02 15 - And Words After - Universal »
With this command:
rename -v 's/([\s\S]+)\s((\d{4})\s(\d{2})(\s)(\d{2}))\s((\w+)\s(\w+))\s([\s\S]+)/$7 - $2 - $10 - $1/g' *
3.
« Sony Some Name 2023 02 15 » will be renamed :
« Some Name - 2023 02 15 - Sony »
With this command :
rename -v 's/([\s\S]+)\s((\w+)\s(\w+))\s((\d{4})\s(\d{2})\s(\d{2}))/$2 - $5 - $1/g' *
- Finally,
« Sony Some Name 2023 02 15 and Words After » will be renamed :
« Some Name - 2023 02 15 - and Words After - Sony »
With this command :
rename -v 's/([\s\S]+)\s((\d{4})\s(\d{2})\s(\d{2}))\s(([\w]+)\s([\w]+))\s([\s\S]+)/$6 - $2 - $9 - $1/g' *
When I want to rename these folders, I have to put them in separate folders and run the corresponding command, then put them all back in the same folder when I'm done. This is very annoying. So I thought of writing a script in bash to avoid having to file them separately and have everything done in the main folder. In the VS code, everything seems to work fine except for the renaming commands. This line is colored orange... Which means that something is missing but I don't know what it is:
's/([\s\S]+)\s((\d{4})\s(\d{2})\s(\d{2}))\s(([\w]+)\s([\w]+))\s([\s\S]+)/$6 - $2 - $9 - $1/g'
See this link to view the scipt in VS code colors:
https://i.stack.imgur.com/tosSv.png
My script :
for i in $*/; do
# for Universal 2023 02 15 Some Name
if [[ "$i" =~ ([\s\S]+)\s((\d{4})\s(\d{2})\s(\d{2}))\s(([\s\S]+)\s([\s\S]+)) ]];
then
rename -v 's/([\s\S]+)\s((\d{4})\s(\d{2})\s(\d{2}))\s(([\s\S]+)\s([\s\S]+))/$6 - $2 - $1/g' *
# for Universal 2023 02 15 Some Name and Words After
elif [[ "$i" =~ ([\s\S]+)\s((\d{4})\s(\d{2})(\s)(\d{2}))\s((\w+)\s(\w+))\s([\s\S]+) * ]];
then
rename -v 's/([\s\S]+)\s((\d{4})\s(\d{2})(\s)(\d{2}))\s((\w+)\s(\w+))\s([\s\S]+)/$7 - $2 - $10 - $1/g' *
# for Sony Some Name 2023 02 15
elif [[ "$i" =~ ([\s\S]+)\s((\w+)\s(\w+))\s((\d{4})\s(\d{2})\s(\d{2})) ]];
then
rename -v 's/([\s\S]+)\s((\w+)\s(\w+))\s((\d{4})\s(\d{2})\s(\d{2}))/$2 - $5 - $1/g' *
# for Sony Some Name 2023 02 15 and Words After
else [[ "$i" =~ ([\s\S]+)\s((\w+)\s(\w+))\s((\d{4})\s(\d{2})\s(\d{2})) ]];
then
rename -v 's/([\s\S]+)\s((\d{4})\s(\d{2})\s(\d{2}))\s(([\w]+)\s([\w]+))\s([\s\S]+)/$6 - $2 - $9 - $1/g' *
fi
done
the script in color for VS code. My commands are all orange...
Anyone can help me please!!!!!!!!
Many Thanks!
Martin
答案1
得分: 3
此脚本可以重命名所有四个目录(除了And Words After
中的大写字母):
rename -n 's/(\w+) (.* )?(\d{4} \d{2} \d{2})( \w+ \w+)?(.*)/
($2 ? $2 : (substr($4,1). " ")) .
"- " .
$3 .
" -" .
($2 ? ($4 ? $4 : "") . $5 : $5) . ($5 ? " - " : " ") . $1
/e' *
一旦您对结果满意,删除-n
。
英文:
This script can rename all four directories (apart from the capitalization in And Words After
):
rename -n 's/(\w+) (.* )?(\d{4} \d{2} \d{2})( \w+ \w+)?(.*)/
($2 ? $2 : (substr($4,1). " ")) .
"- " .
$3 .
" -" .
($2 ? ($4 ? $4 : "") . $5 : $5) . ($5 ? " - " : " ") . $1
/e' *
Remove -n
once you are satisfied of the result.
答案2
得分: 2
以下是翻译好的代码部分:
# 尝试这段经过[Shellcheck](https://www.shellcheck.net/)清理的代码:
#! /bin/bash -p
sep_rx='[[:space:]]+'
part_rx='[^[:space:]]+'
company_rx=$part_rx
name_rx="${part_rx}${sep_rx}${part_rx}"
date_rx="[[:digit:]]{4}${sep_rx}[[:digit:]]{2}${sep_rx}[[:digit:]]{2}"
after_rx="${part_rx}(${sep_rx}${part_rx})*"
cdn_rx="^($company_rx)$sep_rx($date_rx)$sep_rx($name_rx)"
cdna_rx="^($company_rx)$sep_rx($date_rx)$sep_rx($name_rx)$sep_rx($after_rx)"
cnd_rx="^($company_rx)$sep_rx($name_rx)$sep_rx($date_rx)"
cnda_rx="^($company_rx)$sep_rx($name_rx)$sep_rx($date_rx)$sep_rx($after_rx)"
for d in */; do
dir=${d%/}
if [[ $dir =~ $cdn_rx ]]; then
company=${BASH_REMATCH[1]}
date=${BASH_REMATCH[2]}
name=${BASH_REMATCH[3]}
newdir="$name - $date - $company"
elif [[ $dir =~ $cdna_rx ]]; then
company=${BASH_REMATCH[1]}
date=${BASH_REMATCH[2]}
name=${BASH_REMATCH[3]}
words_after=${BASH_REMATCH[4]}
newdir="$name - $date - $words_after - $company"
elif [[ $dir =~ $cnd_rx ]]; then
company=${BASH_REMATCH[1]}
name=${BASH_REMATCH[2]}
date=${BASH_REMATCH[3]}
newdir="$name - $date - $company"
elif [[ $dir =~ $cnda_rx ]]; then
company=${BASH_REMATCH[1]}
name=${BASH_REMATCH[2]}
date=${BASH_REMATCH[3]}
words_after=${BASH_REMATCH[4]}
newdir="$name - $date - $words_after - $company"
else
printf 'ERROR: Failed to match: %s\n' "$dir" >&2
exit 1
fi
mv -v -- "$dir" "$newdir"
done
请注意,这是您提供的代码的翻译部分,不包括其他内容。
英文:
Try this Shellcheck-clean code:
#! /bin/bash -p
sep_rx='[[:space:]]+'
part_rx='[^[:space:]]+'
company_rx=$part_rx
name_rx="${part_rx}${sep_rx}${part_rx}"
date_rx="[[:digit:]]{4}${sep_rx}[[:digit:]]{2}${sep_rx}[[:digit:]]{2}"
after_rx="${part_rx}(${sep_rx}${part_rx})*"
cdn_rx="^($company_rx)$sep_rx($date_rx)$sep_rx($name_rx)\$"
cdna_rx="^($company_rx)$sep_rx($date_rx)$sep_rx($name_rx)$sep_rx($after_rx)\$"
cnd_rx="^($company_rx)$sep_rx($name_rx)$sep_rx($date_rx)\$"
cnda_rx="^($company_rx)$sep_rx($name_rx)$sep_rx($date_rx)$sep_rx($after_rx)\$"
for d in */; do
dir=${d%/}
if [[ $dir =~ $cdn_rx ]]; then
company=${BASH_REMATCH[1]}
date=${BASH_REMATCH[2]}
name=${BASH_REMATCH[3]}
newdir="$name - $date - $company"
elif [[ $dir =~ $cdna_rx ]]; then
company=${BASH_REMATCH[1]}
date=${BASH_REMATCH[2]}
name=${BASH_REMATCH[3]}
words_after=${BASH_REMATCH[4]}
newdir="$name - $date - $words_after - $company"
elif [[ $dir =~ $cnd_rx ]]; then
company=${BASH_REMATCH[1]}
name=${BASH_REMATCH[2]}
date=${BASH_REMATCH[3]}
newdir="$name - $date - $company"
elif [[ $dir =~ $cnda_rx ]]; then
company=${BASH_REMATCH[1]}
name=${BASH_REMATCH[2]}
date=${BASH_REMATCH[3]}
words_after=${BASH_REMATCH[4]}
newdir="$name - $date - $words_after - $company"
else
printf 'ERROR: Failed to match: %s\n' "$dir" >&2
exit 1
fi
mv -v -- "$dir" "$newdir"
done
- The long, and duplicated, regular expressions in the original code are very difficult to read, so I've tried to break them down into named parts.
- Regular expression extensions such as
\s
,\S
, and\d
don't work consistently with=~
in Bash, so I've used portable character classes instead (e.g.[^[:space:]]
for\S
). - See mkelement0's excellent answer to How do I use a regex in a shell script? to learn more about using regular expressions in Bash code.
- See Bash Pitfalls #35 (if [[ $foo =~ 'some RE' ]]) for an explanation of why I put all the regular expressions in variables.
- The
rename
utility isn't available on all systems, and there are at least two very different versions of it in circulation, so I've used the standardmv
utility instead. See Why is the rename utility on Debian/Ubuntu different than the one on other distributions, like CentOS?. - The code works on the given examples, but it may well fail on other directory names. You'll need to check the regular expressions and modify them as necessary.
- The
-p
in the#! /bin/bash -p
shebang prevents Bash from reading configuration files and environment variables that could change how it behaves (e.g. by defining functions that override standard utilities, or by defining environment variables that make standard utilities behave in non-standard ways). It makes Bash programs more reliable, and reduces the "it works on my machine" effect. It may also avoid some security issues (see Shell Script Security - Apple Developer). - The parentheses in regular expression strings like
"^($company_rx)$sep_rx($date_rx)$sep_rx($name_rx)\$"
delimit "capture groups". Matches for regular expression parts between parentheses are copied into the BASH_REMATCH array. For instance, the second set of parentheses in the given string surround the date pattern, so the matched date is copied into index 2 inBASH_REMATCH
(${BASH_REMATCH[2]}
). The\$
at the end of the reqular expression is a backslash-escaped literal dollar character, which is a regular expression metacharacter matching the end of the string being matched. See POSIX Extended Regular Expressions for a full description of the Bash regular expressions. (Though some implementations, inconsistently, support extensions like\s
etc.) The backslash in\$
is to prevent the dollar causing an expansion (which it normally does within double quotes). - The
*/
infor d in */
expands to the list of slash-terminated names (excluding names beginning with the dot character (.
)) of directories under the current directory. See glob - Greg's Wiki. dir=${d%/}
causesdir
to get the value ofd
with a trailing slash removed. See Removing part of a string (BashFAQ/100 (How do I do string manipulation in bash?)).printf 'ERROR: Failed to match: %s\n' "$dir"
prints the stringERROR: Failed to match: %s
with a trailing newline and with%s
replaced by the value ofdir
. It is a safer version ofecho "ERROR: Failed to match: $dir"
, which doesn't work in general. See the accepted, and excellent, answer to Why is printf better than echo? for more information. See the POSIX printf page for detailed information about theprintf
utility.- The
>&2
at the end ofprintf 'ERROR: ...' "$dir" >&2
causes the output to go to the "standard error" stream ("stderr") instead of the "standard output" stream ("stdout"). One practical consequence of this is that the error message will be visible even if the (standard) output of the program is redirected. It is normal to do that for error messages, and other diagnostic messages (warning, debugging, ...). See BashGuide/InputAndOutput - Greg's Wiki (wooledge.org). - The
--
inmv -v -- "$dir" "$newdir"
is to ensure that there will not be a problem if the code is ever used with names that begin with hyphen/dash (-
), even if the code is copied into a different program. Without the--
leading hyphens would cause the directory names to be interpreted as strings of options tomv
. See Bash Pitfalls #2 (cp $file $target) and Bash Pitfalls #3 (Filenames with leading dashes).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论