替换文件名 bash 中的部分字符串
我有一个正在从文件夹 test
中读取的文本文件列表,如下所示:
file_list="$(ls ~/Desktop/test |
while read path; do basename "$path"; done)"
这将生成这些文件的列表:
test_1.txt
test_2.txt
我想更改名称中的特定字符串,特别是将 test
更改为 this
,这样列表中就会包含如下文件:
this_1.txt
this_2.txt
我想直接在 file_list
中执行此操作,但不想对计算机上文件夹中的实际文件执行此操作。
逐一循环是最有效的方法吗?
I have a list of text files I am reading in like this from a folder test
like this:
file_list="$(ls ~/Desktop/test |
while read path; do basename "$path"; done)"
This will produce a list of these files:
test_1.txt
test_2.txt
I want to change particular strings in the name, specifically test
to this
so the list would then have files like this:
this_1.txt
this_2.txt
I would like to do this directly in file_list
I don't want to do it on the actual files in the folder on the computer.
Is looping through one by one the most efficient way to do this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您不需要循环或外部命令(例如
basename
、find
和sed
)。试试这个 Shellcheck - 干净的代码:shopt -s nullglob
使 glob 扩展为空当没有文件与模式匹配时。如果没有它,当没有任何匹配时,全局变量会扩展为(相当于)垃圾。files=( ~/Desktop/test/* )
使用~/Desktop/ 中所有文件(和目录)的路径填充名为
目录(files
的数组test(~/Desktop/test/test_1.txt ...)
)。请注意,名称以点 (.
) 开头的文件将被排除。可以通过在程序早期运行shopt -s dotglob
来包含它们。bases=( "${files[@]##*/}" )
使用files
中文件的基本名称填充bases
数组> 数组 (( test_1.txt ... )
)。有关## 的信息,请参阅 参数扩展 [Bash Hackers Wiki]正在做。
.txt
扩展名,您可以向该过程添加一个额外的阶段:stems=( "${bases[@]%. txt}")
。在 Bash 中不可能同时执行多个字符串操作(例如##
和%
)。this_list="${bases[*]//test/this}"
使用bases
中出现的所有条目填充this_list
字符串每个中的test
替换为this
("this_1.txt ..."
)。再次,请参阅参数扩展 [Bash Hackers Wiki] 了解其工作原理的详细信息。列表中的条目由空格分隔。问题列表中的条目由换行符分隔。您可以通过在执行this_list=...
赋值之前设置IFS=$'\n'
来对this_list
执行此操作。请参阅在构建和数组时修改 bash 中的 IFS、IFS=$'\n'的确切含义是什么?和这是“备份”$IFS变量的合理方法吗?。当使用"${arrayname[*]}"
将数组转换为字符串时,IFS
值中的第一个字符用于分隔数组元素。declare -p this_list
以明确的方式显示this_list
的内容。一些一般要点:
find
和sed
的组合应该能够处理更多数量的文件,但 Bash 可能难以处理生成的巨大字符串(或数组)。You don't need either loops or external commands (like
basename
,find
, andsed
). Try this Shellcheck-clean code:shopt -s nullglob
makes globs expand to nothing when no files match a pattern. Without it globs expand to (what amounts to) garbage when nothing matches.files=( ~/Desktop/test/* )
populates an array calledfiles
with the paths to all the files (and directories) in the~/Desktop/test
directory ((~/Desktop/test/test_1.txt ...)
). Note that files whose names begin with a dot (.
) are excluded. They can be included by runningshopt -s dotglob
earlier in the program.bases=( "${files[@]##*/}" )
populates thebases
array with the basenames of the files in thefiles
array (( test_1.txt ... )
). See Parameter expansion [Bash Hackers Wiki] for information about what the##
is doing..txt
extensions, as suggested in one of the comments, you could add an extra stage to the process:stems=( "${bases[@]%.txt}" )
. It's not possible to do multiple string operations (e.g.##
and%
) at once in Bash.this_list="${bases[*]//test/this}"
populates thethis_list
string with all the entries inbases
with all occurrences oftest
in each of them replaced bythis
("this_1.txt ..."
). Again, see Parameter expansion [Bash Hackers Wiki] for details of how this works. The entries in the list are separated by spaces. The entries in the list in the question were separated by newlines. You can do that forthis_list
by settingIFS=$'\n'
before doing thethis_list=...
assignment. See Modify IFS in bash while building and array, What is the exact meaning of IFS=$'\n'?, and Is it a sane approach to "back up" the $IFS variable?. The first character in the value ofIFS
is used to separate array elements when converting an array to a string with"${arrayname[*]}"
.declare -p this_list
shows the contents ofthis_list
in an unambiguous way.A few general points:
ls
in programs. It's for interactive use only. You might get away with using it in programs sometimes, but it will eventually bite you hard. See Why you shouldn't parse the output of ls(1) and Why not parse 'ls' (and what do to instead)?.find
andsed
should be able to handle much larger numbers of files, but Bash might struggle to handle the resulting huge strings (or arrays) anyway.不,它也不是提取基本名称的最有效方法。就此而言,解析 ls 的输出也不明智,尽管这是一个相对良性的情况。如果您想处理文件名列表,那么通过一个
sed
或awk
进程传递整个列表是一种更好的方法。例如:find
命令输出指定目录中非点文件的路径,每行一个,就像ls
所做的那样。sed
然后尝试对每个替换进行两次替换:第一个删除所有内容,直到并包括最后一个/
字符 alabasename
,第二个替换this
用于test
,其中后者出现在该行剩余内容的开头。另请注意,这种方法与您原来的方法一样,会出现包含换行符的文件名问题。它不存在包含其他空格的文件名的固有问题,但如果任何文件名包含空格,您将无法正确解释结果。
No, nor is it the most efficient way to to extract the base names. Nor, for that matter, is it wise to parse the output of
ls
, though this is a relatively benign case. If you want to massage a list of filenames then passing the whole list through onesed
orawk
process is a better approach. For example:That
find
command outputs paths to the non-dotfiles in the specified directory, one per line, much asls
would do.sed
then attempts two substitutions on each one: the first removes everything up to and including the last/
character, alabasename
, and the second substitutesthis
fortest
where the latter appears at the beginning of what's left of the line.Note also that this approach, like your original one, will have issues with filenames containing newlines. It doesn't have an inherent issue with file names containing other whitespace, but you will have trouble interpreting the results correctly if any of the file names contain whitespace.
在这里解决:
https://unix.stackexchange.com/questions/36795/find-sed-search- and-replace
您可以使用 find 和 -exec 以及多个由
;
分隔的 sed 命令在一行中执行此操作:第一个 sed 命令最多可达
s/\([^\.]*\)\..*/\1/g
删除第一个.
之后的所有内容。第二个 sed 命令
s?users/ uname?gs://uname?g
进行替换解析
ls
输出是不好的做法。Solved here:
https://unix.stackexchange.com/questions/36795/find-sed-search-and-replace
You can do it one line, using find with -exec and multiple sed commands separated by
;
:First sed command up to
s/\([^\.]*\)\..*/\1/g
removes everyting after first.
Second sed command
s?users/uname?gs://uname?g
does substitutionParsing
ls
output is bad practice.