The requirement here, as I understand it, is to remove all instances of the [
and ]
characters from files and directories at and under the specified paths.
The difficulty is to avoid renaming the directory while it's still being traversed. The globstar **
lists items from the root down rather than depth first, so given an example such as a
a/b
a/b/c
, renaming a
will mean that the paths to a/b
and a/b/c
are no longer valid. The find
option can handle this with its -depth
, but that requires a different approach
#!/bin/bash
#
find "$@" -depth -name '*[[\]]*' -print0 |
while IFS= read -d '' -r item
do
path="${item%/*}" file="${item##*/}" # Separate path and file name parts
name="${file//[[\]]/}" # Generate new file name
name="${name// /.}" # Comment: space becomes dot
name="${name,,}" # Comment: lowercase name
echo mv -f "$item" "$path/$name" # Rename
done
Remove echo
(or supplement it) when you are happy the script will do what you expect.
I've used -print0
in conjunction with the -d ''
option for read
to handle files with really strange filenames (including those with newlines and non-printing characters). You can remove both if you want to see what is going on - or if your implementations don't support them - but the script then becomes less robust
The modifiers when assigning the variables path
, file
, and name
match using globs (not regular expressions). A single modifier (%
, #
, /
) means a shortest or single match; a double modifier (%%
, ##
, //
) means a longest match or multiple matches. It is all documented in the bash
man page but here is my explanation with context:
${item%/*}
The %
indicates that the shortest match to the glob pattern should be removed from the end of the value of $item
. So for a/b/c
we would remove text matching /*
leaving a/b
${item##*/}
The ##
indicates that the longest match to the glob pattern should be removed from the beginning of the value of $item
. So for a/b/c
we would remove text matching */
leaving c
${file//[[\]]/}
The //
indicates that multiple replacements of the glob should be replaced with the text following the next /
, i.e. nothing. The glob is a square-bracketed collection of the two characters [
and ]
, meaning "either [
or ]
". So for ab[cd]ef[gh
we would end up with abcdefgh
cd
to it?cd "$@" && rename 's/...//'
? – pLumo Aug 14 '20 at 08:24[
and]
characters from the file names? – Chris Davies Aug 14 '20 at 08:41rename "$@" 's/\(|\[|\]|\)//g' **
where"$@"
expands to filename(s)/pathname(s) makes no sense. Pathnames must be at the end. – Kamil Maciorowski Aug 14 '20 at 08:47rename
can rename the current directory. But its name must be supplied with characters you want to match. This meansrename … ./
is futile whilerename … "$(pwd -P)"
is often the right way. Does this information advance your research? – Kamil Maciorowski Aug 14 '20 at 08:58