1

I have a bunch of folders I need to rename on a Linux disk.

The folders are named as below

VT_GH

I would like to rename every single folder called VT_GH to VT GH

I only want to remand folders with that exact name. Essentially replacing the underscore with a space in that folder name recursively on the system. There are about 50,000 of these folders.

Its on RHEL7 and this can be run as root.

JandP
  • 31
  • 2
  • What if `VT GH` exists along with `VT_GH` in some directory? Should we assume it cannot happen? If not, should the two directories be merged under the name `VT GH`? What if their content collides? – Kamil Maciorowski Jan 13 '21 at 13:39
  • Thanks for your reply. If VT GH exists it should ignore that folder. I've searched the system and cannot find a single VT GH folder. – JandP Jan 13 '21 at 13:46
  • @JandP You can indeed [accept an answer](https://i.imgur.com/OZho1tT.png) by checking the green check mark to the upper left side of the answer you accept. There is an up arrow and a down arrow for voting which is restricted, but the grey check mark in that same area can be clicked to turn it green to accept the answer regardless of your rep for a question you ask – Vomit IT - Chunky Mess Style Jan 16 '21 at 15:34

2 Answers2

0

Looks a bit complicated, but that should do the trick:

find / -depth -type d -name VT_GH -print | awk '{ print "mv -n -T \"" $0 "\" \"`dirname '"'"'" $0 "'"'"'`/VT GH\"" }' | bash

Let's break it into pieces. The first command:

find / -depth -type d -name VT_GH -print

outputs a list of pathnames of all folders called VT_GH on your system. The -depth parameter is important here, as it causes the paths to be listed in order from deepest ones. This will be important later when we rename them, because if we have a path like /some/dir/VT_GH/VT_GH, then the lower-level VT_GH must be renamed before the upper-level one, otherwise the path to the lower-level one will cease to exist.

The second command:

awk '{ print "mv -n -T \"" $0 "\" \"`dirname '"'"'" $0 "'"'"'`/VT GH\"" }'

transforms this list into a list of mv commands, where each command has the form:

mv -n -T "/a path/to/some/random dir/VT_GH" "`dirname '/a path/to/some/random dir/VT_GH'`/VT GH"

The first argument of each mv command is the path to source directory, enclosed in double quotes to protect from possible spaces in directory names (as in "a path" and "random dir").

The second argument consists of two parts. The first part is output of the dirname command called on the same path as above (this time the path is enclosed in single quotes, as we need the double quotes to quote the entire second argument). So for /a path/to/some/random dir/VT_GH the dirname command will output just /a path/to/some/random dir. Then we add /VT GH to it - the new name we want - and enclose everything in double quotes, again to protect from spaces.

The -n -T parameters to mv command tell it to not do anything if there is already folder called VT GH along with VT_GH in the same directory. In that case VT_GH will remain unchanged.

This list of mv commands is just provided as input to bash, which is a final part of this one-liner :)

raj
  • 1,961
  • 8
  • 14
  • **Warning: code injection vulnerability.** What if there is a literal path `/a path/to/" "whatever"; rm /important/file; "some/random dir/VT_GH`? This is a valid path in Unix/Linux. – Kamil Maciorowski Jan 13 '21 at 15:43
  • Simpler example: `/a path/to$(malicious code)/VT_GH`. Upon further analysis it seems what you're doing with `awk` is roughly equivalent to [embedding `{}` in shell code](https://unix.stackexchange.com/a/156010/108618). Sometimes your code is worse: it doesn't support newlines in pathnames. At the same time it's *potentially* better: probably you can fix the vulnerability by escaping every single character of `$0` with a backslash (this requires some single-quotes to be replaced by double-quotes in the resulting shell code). – Kamil Maciorowski Jan 13 '21 at 16:51
0

If your find supports -execdir (GNU find does) then this is quite simple:

find . -depth -type d -name VT_GH ! -execdir test -e 'VT GH' \; -execdir mv -i {} 'VT GH' \; -print

If your find does not support -execdir then you need it to run a shell (or shells) to manipulate paths. A POSIX solution may be:

find . -depth -type d -name VT_GH -exec sh -c '
   for f do
      ( cd "${f%/*}" && ! test -e "VT GH" && mv -i VT_GH "VT GH" && printf "%s\n" "$f" )
   done
' find-sh {} +

Notes:

  • The starting point is . for testing (I mean you can create a test directory and test inside it). You want a solution that works "recursively on the system", so eventually use / instead of . in the code. Still it's good to exclude at least /proc, /dev and /sys. You can explicitly exclude them or give find starting points that are more specific than / (-xdev may be useful).
  • -depth is needed to prevent find from entering a path that doesn't exist anymore (compare this answer; rm or mv, the same issue).
  • The code tests if there is no VT GH in the way. Still this is prone to TOCTOU. If VT GH appears between test and mv then the code will misbehave. If the new VT GH is a directory or a symlink to a directory then mv will move VT_GH to the inside of VT GH (where another VT_GH may already exist, therefore mv -i). If the new VT GH is not a directory then it will not be overwritten (this is true even if you agree to overwrite when mv -i prompts you; you will get cannot overwrite non-directory with directory from mv). Consider mv -n -T (if supported, the options are not portable).
  • In general tests and actions performed by find are prone to TOCTOU. E.g. it's possible a directory VT_GH passes -type d but when mv is executed a regular file VT_GH is there instead.
  • -print (or printf in the shell) is only there to inform you what directories were altered.
  • find-sh is explained here: What is the second sh in sh -c 'some shell code' sh?
  • If test and printf are builtins in your sh then the second form may actually be faster. The first form does not spawn sh but it spawns one test process per VT_GH directory. The second form spawns possibly multiple sh processes but each sh process handles many directories and saves you many test processes.
Kamil Maciorowski
  • 69,815
  • 22
  • 136
  • 202
  • `mv -n -T` will ignore the rename if there is already `VT GH` in the same directory. So the test for `VT GH` can be skipped using that. – raj Jan 13 '21 at 15:26
  • @raj Thank you. These options are not portable. I know the asker uses RHEL7, but from the start my answer is designed to supply more portable code. Maybe I should rebuild it a little though. – Kamil Maciorowski Jan 13 '21 at 15:29
  • The find command with exec worked perfectly. Thank you. – JandP Jan 13 '21 at 17:48
  • @JandP Are you aware [you can accept answers](https://superuser.com/help/accepted-answer)? *You may or may not accept, no pressure.* I'm asking because there is an answer under another question of yours where you also commented "works perfectly, thank you" but haven't accepted (yet); so *maybe* you don't know. – Kamil Maciorowski Jan 13 '21 at 20:53
  • Thanks for the comment. I'm under 15 rep I can't accept anything at the moment it looks like. As soon as I can get past 15 I'll mark answered. – JandP Jan 14 '21 at 20:53
  • @JandP I believe below 15 you cannot vote up. I'm pretty sure you can accept answers. Some of my recent answers have been accepted by users with reputation below 15. – Kamil Maciorowski Jan 14 '21 at 21:31