Link to home
Start Free TrialLog in
Avatar of Jawanza
Jawanza

asked on

Need to search "Directory" for common character/string and replace with supplied string

I am new to this system... as well as Linux, so forgive the expected nievity of the question.  

I need to search a list of directory names (no sub directory search needed) for a common string.  Once detected, I would like to replace the found string with a given string.

 Example (directory names): n113, n21, N342
change to:  Nordic113, Nordic21,Nordic342

Please note the lack of case sensitivity.  If I have to run two commands (one for upper case, and another for lower) then that is not a problem.  Also, I am not seeking to change any of the included filenames that reside within the directories.

Any help would be appreciated.
Avatar of brettmjohnson
brettmjohnson
Flag of United States of America image

cd /path/to/parent/directory
for f in `find . -name "[nN][0-9]*" -a -type d -a -maxdepth 1` ; do mv $f ${f/^[nN]/Nordic} ; done

Avatar of Jawanza
Jawanza

ASKER

Did not seem to work... keeps giving me the following error message.

[vhs@s2 vhs]$ for f in 'find . -name "[nN][0-9]*" -a -type d -a -maxdepth 1'; do mv $f${f/^[nN]/Novice};done
mv: invalid option -- n
Try `mv --help' for more information.
The single quotes (') around the find command should be back quotes (`).
There also seems to be a space missing in the mv command.  I think there is some
copy-paste character translation foo going on here...  unless you mis-typed it rather
than copy-paste.




Seems like a job for awk - not necessarily the most succinct way to do it, but probably the easiest to understand and reuse:

find path -type d | awk '/[nN][0-9]+$/{original=$1; sub (/\/[nN][0-9]+$/,""); root= $1; $1=original; sub (/.*\//,"/Nordic"); print "mv " original " " root $1;}' | /bin/bash

Best I explain what is going on here! This is broken down into nice easy steps so don't panic!


first:

find path -type d

This finds all directories within the given path.

This output is piped into awk for it to act upon. First the matching - /[nN][0-9]+$/
This means that the directory name must match the letter n or N followed by the number 0-9 any number of times (the +), and the $ indicates that this match must be at the end of the directory.

Now the script:
First make a copy of the directory name
original=$1

Substitute all of the end of the directory name (the nN[0-9]+$) bit with nothing - i.e, strip this off, and then call this bit root (root=$1).

Then restore $1 from teh original and strip off everything up to the n or N and replace it with Nordic.

Finally print all the bits together. This should print out the commands that you want to run!

find path -type d | awk '/[nN][0-9]+$/{original=$1; sub (/\/[nN][0-9]+$/,""); root= $1; $1=original; sub (/.*\/[nN]/,"/Nordic"); print "mv " original " " root $1;}'

If it doesn't then it will need a little tweaking? Once the commands output are exactly what you want to run, then AND ONLY THEN pipe then through to /bin/bash as detailed in the original line:)

HTH:)
find path -type d | awk '/[nN][0-9]+$/{a=$1; sub (/\/[nN][0-9]+$/,""); b= $1; $1=a; sub (/.*\//,"/Nordic"); print "mv " a " " b $1;}' | /bin/bash

looks a little shorter, but is probably less easy to read?
> looks a little shorter, but is probably less easy to read?

But it is not as short as the following (which uses two fewer processes):

for f in `find . -name "[nN][0-9]*" -a -type d -a -maxdepth 1` ; do mv $f ${f/^[nN]/Nordic} ; done

Yep - I'll go with that. It is shorter. I also had a little grief trying to find a sensible way to deal with substituting a string within awk, and guaranteeing that it only swapped the name correctly.

You've used fewer processes, because you don't check for all alternatives. As a result yours needs altering a bit:

for f in `find . -name "[nN][0-9]*" -a -type d -a -maxdepth 1` ; do mv $f ${f/^[nN]/Nordic} ; done

will change any directory name containing an n or N!.(* is 0 or more occurances)........and I probably need to think a little more about your mv statement for folders such as ./nNnn89n. If there are only files of the form n123 etc, then you shouldn't have a problem. But what if additional folders are added after the conversion?

Also, it doesn't have the convenience of being able to view the output first in a very simple way before committing yourself to it. I originally considered a similar approach to yours, but I also got in a mess with the substitution. I'm sure that it should be possible, but I spend a couple of hours playing with it and couldn't get anything that I was really happy with.

As long as the provisos above apply then your script is fine...I've just tried to tie things down a little more. I think mine copes with most wierd variations...but I couldn't guarantee it......hence the view before running!

> will change any directory name containing an n or N!.(* is 0 or more occurances)...

Actually that is not true in filename globbing (not quite true regular expressions).  
[nN][0-9]* matches any filename whose first character is n or N, second character
is a digit, and any number of characters after that.  
So it matches, n123, n1, N567JAZZ, but does not match nx123, number, .n234, or ñ123


>  Also, it doesn't have the convenience of being able to view the output first in a very
> simple way before committing yourself to it.

I simply add  'echo' between 'do' and 'mv'.  This prints out the move commands it would execute otherwise:
for f in `find . -name "[nN][0-9]*" -a -type d -a -maxdepth 1` ; do echo mv $f ${f/^[nN]/Nordic} ; done
That is how I tested the command as I refined it.   Occasionally, when answering these questions,
I forget to remove the 'echo' before pasting the command into the comment.

You can also run the find command on its own to see which directory names it collects.
That is how I build up these compound one-line programs - one segment at a time.








That's the great delight of shell scripting. Loads of different ways to do it:)

Sorry if I got the matching slightly wrong for your setup, but it does still leave n5jazz, with all the other endings. Late night, and I'd been fighting to get a suitably short regex for about half an hour!  ..and yep the echo approach is nice.


Avatar of Jawanza

ASKER

Thanks for the assistance thus far... though even using copy/paste, and I still getting the folowoing response.  Please see below:

[vhs@s2 Test]$ for f in `find . -name "[nN][0-9]*" -a -type d -a -maxdepth 1` ; do echo mv $f ${f/^[nN]/Nordic} ; done
find: invalid expression
[vhs@s2 Test]$


Any idea as to why the command is not working for me?  I even removed the "echo" and received the same error.  

All help is appreciated!
> Any idea as to why the command is not working for me?  I even removed the "echo" and received the same error.

First you need to learn to understand the commands used.  

1)  for f in [list] ; do [command] ; done
For each item f in the list, perform the command.

2) `find . -name "[nN][0-9]*" -a -type d -a -maxdepth 1`
This is the [list].  By enclosing the command in backquotes, the shell executes
the find command and populates [list] with the output.  The find command
is looking for files that satisfy 3 criteria (ANDed together with -a):
  a) filename starts with n or N, followed by a digit, followed by anything else ( -name "[nN][0-9]*" )
  b) file is a directory ( -type d )
  c) file is in current directory, not a subdirectory ( . and -maxdepth 1 )

3) mv $f ${f/^[nN]/Nordic}
This is the [command] from 1).  The first parameter is one if the items from [list].
The second parameter is the result of bash string substitution (substitute the n or N
at the beginning of f with Nordic.  When you prepend 'echo' to the command, echo
becomes the new command, with its 3 parameters:
  a) the string 'mv'
  b) the source filename
  c) the destination filename
And of course the echo command simply prints the three parameters out to the console.


So now you can see why changing the command in 3) has absolutely no impact on the execution
of `find ...` to build the list in 2)

I am using Mac OS X, which implements the FreeBSD version of the find command.
Linux uses the GNU fileutils version of the find command which might have a slightly
different syntax.  Check the man pages looking for differences in the syntax of the
three criteria used (2a, 2b, 2c, and '-a'):
  man 1 find
Avatar of Jawanza

ASKER

Actually, thanks for the response and helping me with the syntax.  Although I understood that portion, it was definitely nice to have it spelled out in such a manner.

I believe the actual issue that I am having is with the version of the find command.  I am not on a Mac OS... so I believe there may lie my issue.

Where can I find these "man pages" which you referenced?  Please advise.

Thanks,
The man pages are usually installed in a path (like the executables they document),
typically /usr/man, /usr/local/man, etc. They are accessable by the 'man' commnad.

Like I mentioned in the previous post, run 'man 1 find' to search for the documentation
for 'find' in section 1 (shell commands) of the man pages.

For more information on man and man pages, run the command 'man man'.

Not only is using 'find' a bit overblown, but I suspect 'for f in [list]' fries if no files are found.   I would check it, but I only
get to play with µSoft at work.
ASKER CERTIFIED SOLUTION
Avatar of Computer101
Computer101
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial