Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

Let say that more users have a tmp directory in their home directory. I want to rename each tmp directory in each user home directory. What is the easiest way?

Something like this:

sudo mv /home/*/tmp /home/*/temp

is not ok.

And something like this:

for dir in /home/*; do
    if [ -d $dir/tmp ]; then
        mv $dir/tmp $dir/temp
    fi
done

seems too much.

share|improve this question
8  
Why is the second option "too much"? By the way, it should be mv "$dir/tmp" "$dir/temp" in case a home directory has a space in its name. Unlikely, but possible. –  Joseph R. Aug 23 '13 at 17:46
    
@JosephR. Well, looking for something shorter if possible. Something wrong if? And let's exclude the case with space (I'm not interested in). –  Radu Rădeanu Aug 23 '13 at 17:53
    
I've come up with a fancy oneliner: for dir in /home/*; do if [ -d "$dir/tmp" ]; then mv "$dir/tmp" "$dir/temp"; fi; done –  Sammitch Aug 23 '13 at 20:44
    
@Sammitch You can do shortest: for d in /home/*; do if [ -d "$d/tmp" ]; then mv "$d/tmp" "$d/temp"; fi; done :) –  Radu Rădeanu Aug 23 '13 at 20:52
3  
What should your script do if the user already has a directory named ~/temp? What if there's a file with that name? –  Michael Kjörling Aug 23 '13 at 21:42

6 Answers 6

up vote 4 down vote accepted

Perl comes with a rename(1) command that is installed on most Linux systems. On Debian-based systems it is in /usr/bin and for this case, you would use it like this:

$ rename 's/tmp$/temp/' /home/*/tmp

The first argument is a perl expression that acts on the subsequent arguments generating a new name. Each is then renamed according to the result of that expression.

If a home directory already has a file/directory called temp, you'll just get an error for that directory and rename will continue:

/home/c/tmp not renamed: /home/c/temp already exists

You can run it first with the -n flag to see what rename would do without actually doing it and make sure it all looks right. Then drop the -n and let it do its job.

share|improve this answer
    
Mind: blown. Once again, Perl to the rescue :) –  Joseph R. Aug 23 '13 at 22:35
sudo find /home -maxdepth 2 -type d -name 'tmp' -execdir mv {} temp \;

Here, maxdepth ensures that you look only upto two levels from the current directory. And, execdir specifies that the subsequent command will be executed by changing to the location where the found file is.

Source: http://stackoverflow.com/a/15007149/147021

share|improve this answer
2  
This would also rename files called tmp. You might want to add -type d. –  Joseph R. Aug 23 '13 at 18:12
    
Joseph R. Thanks, yet again! Just forgot while testing :) –  Barun Aug 23 '13 at 18:13
    
One more thing to note is that, according to the link you cited, -execdir works for GNU find only. –  Joseph R. Aug 23 '13 at 18:17
    
i'd rather check the file /etc/passwd for the real user's homedir. /home maybe the standard, but this script woudn't work for user home-dirs which are organised into subfolders or something similar. –  Bonsi Scott Aug 23 '13 at 19:41
    
@JosephR. -execdir is a relatively recent addition to GNU find (2005). It comes from BSDs (1996 in OpenBSD) –  Stéphane Chazelas Aug 23 '13 at 20:41

The only way I can think of for shortening the second option in your question is:

for dir in /home/*;do
    [ -d $dir/tmp ] && mv -n $dir/t{,e}mp
done

but this does away with readability somewhat. Note that t{,e}mp is expanded by the shell into the two words tmp and temp. So the command line would be expanded to mv <contents of $dir>/tmp <contents of $dir>/temp. In general, the shell expands prefix_{a,b,c}_suffix to prefix_a_suffix prefix_b_suffix prefix_c_suffix. An empty position such as the first one in {,e} is expanded into an empty string.

You can also do something similar with perl:

perl -e '
    @a=grep {-d} glob "/home/*/tmp";
    map { rename $_ => $_=~ s/tmp\Z/temp" } @a
'

but I still maintain that your original version was good enough.

Edit

Added the -n switch to mv to guarantee it won't clobber $dir/temp if it exists as noted in Michael Kjörling's comment on the question. The Perl version will clobber temp if it exists.

share|improve this answer
    
Ok, it's shorter, I'll give you +1. But can you explain what means t{,e}mp? –  Radu Rădeanu Aug 23 '13 at 18:13
    
@RaduRădeanu Answer updated. –  Joseph R. Aug 23 '13 at 18:16

If your system has a getent command and a mv that accepts a -T option, you could do:

getent passwd |
  while IFS=: read -r x x uid x x home x; do
    [ "$home" = / ] ||
    [ "$home" = /var ] ||
    [ "$uid" -lt 500 ] ||
    [ ! -d "$home/tmp" ] ||
    find "$home/tmp" -prune -user "$uid" -exec sh -xc '
      mv -T -- "$1" "${1%/*}/temp"' sh {} \;
done

That is, we list the user database to retrieve uid and home directory. We rule out those users that have / or /var as their home directories (as we don't want to rename /tmp and /var/tmp) and users whose uid is below 500 (as on most systems, those are system users).

Then we only consider $home/tmp if its owner is the user in question (again to avoid moving the wrong file/dir).

The -T option as found in GNU mv is in case $home/temp already existed, to avoid moving tmp inside it.

Note that any failure of mv will generate an error message, but the failure exit status will be lost.

share|improve this answer
1  
Not to criticize your (characteristic) juggernaut, there, but I believe the OP's intent was to shorten their (already very short) loop. :) –  Joseph R. Aug 23 '13 at 21:41
1  
@JosephR. - yes we always learn something new every time he posts a solution 8-) –  slm Aug 23 '13 at 22:06
    
@slm Agreed. The above snippet is definitely "ready for prime-time" and I did learn at least 3 new things from it. I just think it's probably overkill in this particular case. –  Joseph R. Aug 23 '13 at 22:11

Produce the move statements and evaluate them in one shot:

ls -d /home/*/tmp | sed 's/.*t/mv &mp &e/' | sh -xv
share|improve this answer
    
I think you meant mv &mp &emp inside your sed command. Also don't parse the output of ls. –  Joseph R. Aug 23 '13 at 22:32
    
No, mv &mp &e works just fine. Try it. –  sunaku Aug 26 '13 at 21:55

If you're just looking for a more compact version of your for loop you can do it slightly more compact like so:

$ for dir in /home/*/tmp; do [ -d $dir ] && mv $dir ${dir/%tmp/temp/};done

This will use bashes globbing facility to find all the directories and files that match the regular expression /home/*/tmp. It then tests to see whether it's a directory or not, if it's a directory, then move it. The last bit ${dir/%tmp/temp/} does a search and replace on the contents of variable $dir and replaces tmp with temp. This search & replace is done only once from right to left, thus protecting anything beyond the tmp directory.

Example

Say I have this fake directory structure:

$ tree -p
.
`-- [drwxrwxr-x]  home
    |-- [drwxrwxr-x]  sam1
    |   `-- [drwxrwxr-x]  tmp
    |-- [drwxrwxr-x]  sam2
    |   `-- [drwxrwxr-x]  tmp
    |-- [drwxrwxr-x]  sam3
    |   `-- [drwxrwxr-x]  tmp
    `-- [drwxrwxr-x]  sam4
        `-- [-rw-rw-r--]  tmp

Now if we use a modified version of our command from above which simply echoes out the mv command rather than run it we can see what it's going to do:

$ for dir in home/*/tmp; do [ -d $dir ] && echo "mv $dir ${dir/%tmp/temp/}";done

mv home/sam1/tmp home/sam1/temp/
mv home/sam2/tmp home/sam2/temp/
mv home/sam3/tmp home/sam3/temp/

NOTE: The beginning / is intentionally not there so that I could run this test from a sample directory I setup.

rename instead of mv

You could also enlist the help of the command rename instead of mv. This changes the example slightly:

 $ for dir in /home/*/tmp; do [ -d $dir ] && rename $dir ${dir/%tmp/temp/};done
share|improve this answer
    
What if one of the user's home directories had the letters tmp in it? –  Joseph R. Aug 23 '13 at 21:37
    
@JosephR. - hmm...I can change the regex so that it replaces /tmp/ instead. Thanks! –  slm Aug 23 '13 at 21:39
    
A question: is there a specific reason you prefer [ ! -d $dir ] || mv to [ -d $dir ] && mv? –  Joseph R. Aug 23 '13 at 21:43
1  
@JosephR. - no, just the way I was thinking about it. If not a dir. then skip it. I tend to think that way for some reason. I generally like to code to the thing I'm excluding rather than the thing I'm looking for. Not sure if that's a bad habit or what, just something I've noticed that I do. I think you've made me more attentive that I'm doing it 8-). It might be because when you're doing shell work you're often times trying to remove cruft from one pipe to the next. –  slm Aug 23 '13 at 21:47
1  
@JosephR. - That's a compliant any time you do something the same as him 8-). BTW I liked your use of the t{,e}mp. I can never remember to use that abbreviation. –  slm Aug 23 '13 at 22:01

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.