2

I have a 10+ TB NFS /home for 100+ users plus public folders. I need to change the username and UID of 10 of those users. At first, I was thinking of running the following:

 find /home -uid 812 -exec chown NEWUSER {} \;

Now, the issue on this is that it will go through all my 10TB once and change whatever file it finds with uid 812 to NEWUSER, which is what I want. But it will take a pretty long time, and will do it just for that user; then I will have to run the command again for each of the other 9 users, turning it from a pretty long time to a pretty long time * 9.

Besides the fact that I don't like scripting, I guess a script would be a friend here, but I don't know where to start. I want to use the find command and check all the files from /home. then:

IF FILEOWNER IS 813 then NEWOWNER IS NEWUSER1
IF FILEOWNER IS 814 then NEWOWNER IS NEWUSER2

... and so on.

Do you think there is a way to do this so I don't have to scan 10 GB of data 10 times -- to just scan once?

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255

2 Answers2

7

Do the whole thing using find.

find /home ! -type l \( \
     -uid 812 -exec chown NEWUSER {} + \
  -o -uid 813 -exec chown ANOTHER {} + \
  -o -uid 814 -exec chown SOMEONE {} + \
  -o -uid 815 -exec chown SOMEGUY {} + \)

One directory structure traversal. All chowns complete.

We exclude symlinks as otherwise the chown would apply to the target of the symlink. On some systems, you can change the owner of a symlink with chown -h. On those, you could add the -h and remove the ! -type l.

Note that if any of the files are setuid, this will screw that up. You can handle that also using find.

find's business is evaluating expressions — not locating files. Yes, find certainly locates files; but that's really just a side effect.

— Unix Power Tools

Wildcard
  • 36,499
0

Firstly you don't need to actually scan the full 10TB, just the directory structure so doing the basic find is not a bad as it sound.

Second if you do the basic and start all ten at the same time you will probably get better performance because of read caching.

Or you could do find /home -uid 812 -o uid 813 -o ... -printf '%U:%p\0'| sed -zre 's!["$]!\\&!g' -e 's/^812:(.*)$/chown NEWUSER1 "\1"/'e -e 's/^813 ...

I think I spotted the major gotchas but sed eval is always risky. If you are not using GNU sed you can pipe the output to a shell.

Here is a cleaner version with better quote handling using gnu tools.

find /home -uid 812 -o uid 813 -o ` ... ` -printf '%U:' -print0| \
sed -zre "s/'/'\''/g" \
   -e 's/^812:(.*)$/chown NEWUSER1 '"'\1'/e" \
   -e 's/^813:(.*)$/chown NEWUSER2 '"'\1'/e" \
   ...

The way these work:

The first part of the find is a bunch of ORed uid checks, the meat is the printf (or printf and print0 in the second version) which prints the numeric uid, a colon, the file name with c escapes (without escapes in the second version) and a null for each file. Then sed using the null separator and extended regex quotes dollar signs and double quotes then with one expression per user matches the uid and colon separates the filename creates a shell command line to do a chown and evaluates it with a shell. I probably should have spent a couple minutes more verifying the list of escapes on the printf, but enough to start with if you test without the e flag on the s command. In the second version the only character that is escaped it the single quote. The reason for not escaping is in that allowing all characters except null to pass through without special meaning after the strictly defined header we do not have to remove escapes.

hildred
  • 5,829
  • 3
  • 31
  • 43
  • I'm pretty versed in Bash scripting, but I have trouble following your command. Perhaps add some explanation? – Wildcard Jul 15 '17 at 02:49
  • the first part of the find is a bunch of ORed uid checks, the meat is the printf which prints the numeric uid, a colon, the file name with c escapes and a null for each file. Then sed using the null separator and extended regex quotes dollar signs and double quotes then with one expression per user matches the uid and colon separates the filename creates a shell command line to do a chown and evaluates it with a shell. I probablly should have spent a couple minutes more verifying the list of escapes on the printf, but enough to start with if you test without the e flag on the s command. – hildred Jul 15 '17 at 03:24
  • 1
    I see; thanks. So essentially, you're using find ... -printf ... | sed ... | sh as a (scary) alternative to find ... -exec ...? :) – Wildcard Jul 15 '17 at 03:30
  • kinda, except It is not quit as scary as it sounds specifically the null separator when combined with s//""/e only needs special handling of backslashes double quotes and dollar-signs however since find backslashes the backslashes (along with some other problem characters) and we want to undo that the shell will since it is double quoted. I prefer single quotes but the don't reverse escaping, . . . – hildred Jul 15 '17 at 03:43
  • I just got an idea, see the edit for a version that only needs to worry about single quotes. – hildred Jul 15 '17 at 03:44
  • 1
    Would you run this command on a 10 TB drive with over 100 users' data on it? Be honest, now. For a quick hack on a personal machine this might work; for anything else you are begging for trouble. – Wildcard Jul 15 '17 at 03:47
  • The single quoted version? sure -print0 is your friend. the double quoted version after some testing, but with only ten users I would probably just run the finds in parallel to allow the cache to do it's thing. – hildred Jul 15 '17 at 03:56
  • You'd need to fix the locale to C or otherwise it may fail to change the uid of files whose name contain invalid characters in the user's locale. – Stéphane Chazelas Aug 01 '17 at 08:50