2

To rename directories containing left & right squares you can use:

shopt -s globstar 
rename -n 's/\(|\[|\]|\)//g' **

This will rename everything with [ & ] from the directory you're in and recursive..

but I need to execute this as a nemo.action script, and can't get it to work. (Basically a normal bash script)

My current script is:

#!/bin/bash
shopt -s globstar

rename "$@" 's/(|[|]|)//g' **

where $@ is /media/sf_Mediaserver3/Untitled Folder/[ hdjue] [kskk]

where the [ hdjue] [kskk] (just random letters) is the starting folder that I want to rename and the following files/folders...

This doesn't work of course, and now I have to go to the parent directory and run the first lines, but that would process ALL directories in the parent directory, not only the [ hdjue] [kskk] anybody got any idea how to solve this?

it almost seems like the rename function can't handle a start directory?

And if possible, remove all spaces (replace with . (dot)), and make the filename/directory lowercase, to simplify the compatibility with external/internal requests..

JoBe
  • 397
  • 5
  • 17
  • Sure not, why should it, but can't you simply cd to it? cd "$@" && rename 's/...//' ? – pLumo Aug 14 '20 at 08:24
  • @pLumo by using cd I'm in the directory I want to rename, that don't work.. – JoBe Aug 14 '20 at 08:30
  • The script should accept an argument that's a directory, and then rename not only that directory but also all its files and subdirectories (recursively)? And the rename operation is simply to get rid of [ and ] characters from the file names? – Chris Davies Aug 14 '20 at 08:41
  • I think rename "$@" '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:47
  • In my Ubuntu (Larry Wall's) rename can rename the current directory. But its name must be supplied with characters you want to match. This means rename … ./ is futile while rename … "$(pwd -P)" is often the right way. Does this information advance your research? – Kamil Maciorowski Aug 14 '20 at 08:58
  • @Kamil I forgot to mention that I'm a newbie, $(pwd -P)?? the path last didn't work, but Roaima's solution worked great – JoBe Aug 14 '20 at 09:20

1 Answers1

3

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
Chris Davies
  • 116,213
  • 16
  • 160
  • 287
  • wow, that worked, not that I fully understand how it exactly works, it was took me a full day to find why my other script that removes spaces and puts . instead, and that was because the filenames had [ ] in them.. if I understand this correctly, it only runs the renaming code if the directory or filename has a [ or a ] in it? – JoBe Aug 14 '20 at 09:18
  • 1
    @JoBe I remember your previous question. The code there might misbehave for filenames with [ and/or ] because you didn't quote the variable properly. This leads me to a hypothesis: you keep "fixing" filenames by removing troublesome characters because you want some flawed scripts/commands to work without errors. I may be wrong. But if I'm right then you should rather concentrate on fixing the scripts/commands. – Kamil Maciorowski Aug 14 '20 at 09:34
  • @Kamil actually in trying to write a massive script that processes movies, subtitles, auto renaming and making them compatible with my media system, my next work after solving this is to make it check if the subtitles are duplicates.. oh, I'm also using gio to set the folder icons with country flags depending on what subtitles it has (works good), but as I have written in my profile, I'm dyslexic, and unfortunately beginning to show signs of dementia, so sometimes I can write a code, and the next day don't understand how it works (do a lot of comments nowadays) - a programmers hell.. – JoBe Aug 14 '20 at 09:43
  • @roaima well, file//[[]]/} for example, I suspect it's regex? don't fully understand ${item##*/} , the ## (if you see my answer to Kamil you might understand why I'm having issues) – JoBe Aug 14 '20 at 09:46
  • @JoBe It's OK. I can imagine a media system that doesn't fully support some characters in filenames (and you cannot fix it easily). I just wanted to make sure your need for renaming is not solely because of flawed scripts. – Kamil Maciorowski Aug 14 '20 at 09:54
  • @Kamil nope, just trying my best with my knowledge to get it to work, appreciating the help – JoBe Aug 14 '20 at 10:04
  • 1
    @roaima Thanks for the extra information, now I understand better! – JoBe Aug 14 '20 at 10:19
  • @roaima I actually got a follow-up question, I assume it always renames the first directory last (otherwise it would crash), but I need to put the first directory's new name to a variable (if I want to implant this script into another), and in the last loop, the $name contains the new name, but it seems to clear the variables when existing the loop, so $name is empty, is there anyway to keep that variable intact? – JoBe Aug 14 '20 at 12:30
  • You can't do that, unfortunately, but I can't see a reason why you shouldn't be able to regenerate the modified initial filenames from the values in "$@" – Chris Davies Aug 14 '20 at 12:52
  • @roaima Ok, I did a quick-solution, and just saved the variable to a temporary file and then I just read it back to a variable outside the loop.. – JoBe Aug 14 '20 at 13:00
  • @roaima hm, I'm having issues, it renames the directory but not the files inside it? – JoBe Aug 17 '20 at 15:13
  • Example please, @JoBe – Chris Davies Aug 17 '20 at 16:10
  • @roaima the script you wrote, are having issues renaming files with more than one [ ] – JoBe Aug 17 '20 at 17:34
  • .. but it does work. but sometimes it doesn't.. I've got to investigate a little more.. – JoBe Aug 17 '20 at 18:35
  • @JoBe file='ab[cd]ef[gh'; echo "${file//[[\]]/}" works exactly as expected for me - all three instances of a square bracket get removed – Chris Davies Aug 17 '20 at 18:36
  • @roaima yes, don't really know what's the issue, it works most of the time (unless there's a ' in the filename) – JoBe Aug 17 '20 at 19:05
  • My code doesn't care about single quote characters in filenames – Chris Davies Aug 17 '20 at 19:18
  • oops, my bad, I added if [[ $jdir =~ [']['] ]] as a pre-check to see if it was necessary to run your rename code, but I missed that it don't detects if any files have ][ , it only checked the first directory.. as I wrote earlier I'm not so good at this, so when I'm removing the ][, removing the space and adding . between the words, and when I'm renaming it all to lowercase, is 3 different function, since I can't make it in one.. so it was my mistake, I messed it up.. as usual.. – JoBe Aug 17 '20 at 21:29
  • @JoBe if you wanted to remove square brackets, replace spaces with dots, and lowercase the result why didn't you say so? Answer amended – Chris Davies Aug 17 '20 at 22:23
  • @roaima oh, gee, thanks a lot. I didn't mention it because I already had "solved" that issue, not effective, but manageable, and I didn't want to ask for to much, since I've seen that if you're asking for a complete solution, people normally don't help, since they think you're "lazy", and at least need to try... But now when I see your solution, I understand how it works, and should be able to use it and develope it as I see fit.. hopefully.. and again, I really appreciate the help.. (I'll update the question to include the last update from you) – JoBe Aug 18 '20 at 03:51
  • @roaima BUT! I just realised that that code only runs it the filename/directory has ] [ in the name, can that be changed to run on all filenames/directories? – JoBe Aug 18 '20 at 04:41
  • Remove the name criterion from find so that the loop runs for everything – Chris Davies Aug 18 '20 at 06:47