12 Replies Latest reply: Mar 25, 2008 9:16 AM by Bill Scott
DSA1 Level 1 Level 1 (95 points)
I had a group of files that had double extensions in the name and I wanted to strip the second extension:

myfile.r01.1
myfile.r02.1

so that the new names were

myfile.r01
myfile.r02

In DOS this would be accomplished easily by using the command line:

rename myfile.r??.1 myfile.r??

In OS X Terminal/Bash shell, though I couldn't find a command that has similar function that allows the use of wild cards in the file names.

I tried both the 'mv' abd 'cp' commands along the lines of:

mv myfile.r??.1 myfile.r??

but nothing worked, even using the * for the wildcard.

I did manage to use the Automator to accomplish the task by using some of its Finder options, but really, a simple command line would have been simpler and easier than building an Automator workflow for this.

Can anyone point me to a unix command that would have done what I am looking for, and the proper syntax for it?

Thanks.

Mac Pro 4gb, dual 23" Cinema Display, Mac OS X (10.4.9), MacBook Pro 15" 60gb Video iPod 8gb iPhone
  • Bill Scott Level 6 Level 6 (11,445 points)
    You'll get a bunch of answers on how to do this, using foreach loops and various perl scripts.

    In zsh, which should be the OS X default shell, this is very easy.

    To start zsh, just type "zsh"

    To make it your default shell, just issue "chsh -s /bin/zsh"

    Once you are in zsh, you need do only two things (which you can put in your ~/.zshrc file for safe-keeping and automation):


    autoload -U zmv
    alias mmv="noglob zmv -W"


    Now you have a simple command to do as you want:


    mmv myfile.*.1 myfile.*


    I suspect


    mmv myfile.r??.1 myfile.r??


    does the same thing. The ? is a wildcard for a single space.

    You can call "mmv" anything you want, like "rename".
  • Cole Tierney Level 4 Level 4 (1,375 points)
    I like to use awk to build a list of commands. When it looks ok, I feed it to bash:
    <pre style="padding-left: .75ex; padding-top: .25em; padding-bottom: .25em; margin-top: .5em; margin-bottom: .5em; margin-left: 1ex; max-width: 80ex; overflow: auto; font-size: 9px; font-family: Monaco, 'Courier New', Courier, monospace; color: #222; background: #ddd; line-height: normal">ls | awk '{org=$0; sub(/\.[^\.]*$/,""); printf("mv %s %s\n", org, $0);}'
    mv myfile.r01.1 myfile.r01
    mv myfile.r02.1 myfile.r02
    </pre>

    If you like that, pipe it sh:
    <pre style="padding-left: .75ex; padding-top: .25em; padding-bottom: .25em; margin-top: .5em; margin-bottom: .5em; margin-left: 1ex; max-width: 80ex; overflow: auto; font-size: 9px; font-family: Monaco, 'Courier New', Courier, monospace; color: #222; background: #ddd; line-height: normal">ls | awk '{org=$0; sub(/\.[^\.]*$/,""); printf("mv %s %s\n", org, $0);}' | sh
    </pre>
    --
    Cole
  • etresoft Level 7 Level 7 (27,125 points)
    Bill Scott wrote:
    You'll get a bunch of answers on how to do this, using foreach loops and various perl scripts.


    Actually, I wouldn't even bother that much. I would just get a directory listing, put it into a text file, do global search and replace to construct "mv" statements, then "sh" my text file.

    In zsh, which should be the OS X default shell, this is very easy.

    To start zsh, just type "zsh"

    To make it your default shell, just issue "chsh -s /bin/zsh"


    To be fair to other shells, you don't have to make zsh your default shell to use it.


    autoload -U zmv
    alias mmv="noglob zmv -W"


    Bill,
    Would you be kind enough to explain what all that does?

    Cole,
    You don't need to bother. Awk is way over my head.
  • Gnarlodious Level 4 Level 4 (3,225 points)
    Here is a command I use to change uppercase JPGs to lowercase:

    find . -type f -name \*JPG -exec sh -c 'mv "$1" "${1//JPG/jpg}"' -- {} \;


    Maybe there is something in there you can adapt.
  • Hawaiian Scuba Dude Level 1 Level 1 (5 points)
    If you want the solution is one command the zsh solution may be the easiest.

    The best answer is a matter of preference.
    Bill's right though about the thousands of 3-4 command possibilities with loops depending on which shell is used. The fact is regular expressions in UNIX are so powerful that many prefer that to a simple replace. Others may not want to get that technical.

    Here is a solution using a for loop in sh, ksh, or bash

    for file in myfile.r??.1
    do
    base=`basename $file .1`
    mv $file $base
    done

    The for or foreach loop depending on shell will cycle through each string matching the regular expression.

    basename is a command that removes the directory path and the specified regular expression suffix from any string. Each shell has additional similar constructs for manipulating any string in any way in one word.
  • Bill Scott Level 6 Level 6 (11,445 points)
    To be fair to other shells, you don't have to make zsh your default shell to use it.


    To be fair, I wrote just that.

    Would you be kind enough to explain what all that does?


    zsh comes with a bunch of functions that live in /usr/share/zsh/4.3.4/functions

    The first line loads the function /usr/share/zsh/4.3.4/functions/zmv

    The second line creates an alias to make that function (when invoked via the alias) behave in a way that is consistent with the "rename" command he was asking about.

    Read the top of the zmv function file (it is a shell script) if you want the details.
  • Cole Tierney Level 4 Level 4 (1,375 points)
    Gnarlodious wrote:
    find . -type f -name \*JPG -exec sh -c 'mv "$1" "${1//JPG/jpg}"' -- {} \;


    Interesting construct. Thanks for sharing!

    --
    Cole
  • Bill Scott Level 6 Level 6 (11,445 points)
    From this page: http://www.faqs.org/faqs/unix-faq/faq/part2/section-6.html


    How do I rename "*.foo" to "*.bar", or change file names to lowercase?

    Why doesn't "mv *.foo *.bar" work? Think about how the shell
    expands wildcards. "*.foo" and "*.bar" are expanded before the
    mv command ever sees the arguments. Depending on your shell,
    this can fail in a couple of ways. CSH prints "No match."
    because it can't match "*.bar". SH executes "mv a.foo b.foo
    c.foo *.bar", which will only succeed if you happen to have a
    single directory named "*.bar", which is very unlikely and almost
    certainly not what you had in mind.

    Depending on your shell, you can do it with a loop to "mv" each
    file individually. If your system has "basename", you can use:

    C Shell:
    foreach f ( *.foo )
    set base=`basename $f .foo`
    mv $f $base.bar
    end

    Bourne Shell:
    for f in *.foo; do
    base=`basename $f .foo`
    mv $f $base.bar
    done

    Some shells have their own variable substitution features, so
    instead of using "basename", you can use simpler loops like:

    C Shell:

    foreach f ( *.foo )
    mv $f $f:r.bar
    end

    Korn Shell:

    for f in *.foo; do
    mv $f ${f%foo}bar
    done

    If you don't have "basename" or want to do something like
    renaming foo.* to bar.*, you can use something like "sed" to
    strip apart the original file name in other ways, but the general
    looping idea is the same. You can also convert file names into
    "mv" commands with 'sed', and hand the commands off to "sh" for
    execution. Try

    ls -d *.foo | sed -e 's/.*/mv & &/' -e 's/foo$/bar/' | sh

    A program by Vladimir Lanin called "mmv" that does this job
    nicely was posted to comp.sources.unix (Volume 21, issues 87 and
    88) in April 1990. It lets you use

    mmv '*.foo' '=1.bar'

    Shell loops like the above can also be used to translate file
    names from upper to lower case or vice versa. You could use
    something like this to rename uppercase files to lowercase:

    C Shell:
    foreach f ( * )
    mv $f `echo $f | tr '[A-Z]' '[a-z]'`
    end
    Bourne Shell:
    for f in *; do
    mv $f `echo $f | tr '[A-Z]' '[a-z]'`
    done
    Korn Shell:
    typeset -l l
    for f in *; do
    l="$f"
    mv $f $l
    done

    If you wanted to be really thorough and handle files with `funny'
    names (embedded blanks or whatever) you'd need to use

    Bourne Shell:

    for f in *; do
    g=`expr "xxx$f" : 'xxx(.*)' | tr '[A-Z]' '[a-z]'`
    mv "$f" "$g"
    done

    The `expr' command will always print the filename, even if it
    equals `-n' or if it contains a System V escape sequence like `c'.

    Some versions of "tr" require the [ and ], some don't. It
    happens to be harmless to include them in this particular
    example; versions of tr that don't want the [] will conveniently
    think they are supposed to translate '[' to '[' and ']' to ']'.

    If you have the "perl" language installed, you may find this
    rename script by Larry Wall very useful. It can be used to
    accomplish a wide variety of filename changes.

    #!/usr/bin/perl
    #
    # rename script examples from lwall:
    # rename 's/.orig$//' *.orig
    # rename 'y/A-Z/a-z/ unless /^Make/' *
    # rename '$_ .= ".bad"' *.f
    # rename 'print "$_: "; s/foo/bar/ if <stdin> =~ /^y/i' *

    $op = shift;
    for (@ARGV) {
    $was = $_;
    eval $op;
    die $@ if $@;
    rename($was,$_) unless $was eq $_;
    }

  • DSA1 Level 1 Level 1 (95 points)
    All-

    Several comprehensive answers. As I said in my original post, I did accomplish it with Automator replace text command, but I will try each of these as a learning experience. I am surprised that there's nothing as simple as the DOS solution to this type of task.

    Thanks again.
  • Bill Scott Level 6 Level 6 (11,445 points)
    I don't have access to dos, but am willing to bet what they call rename is that perl script Larry Wall wrote (syntax changed for dos).
  • DSA1 Level 1 Level 1 (95 points)
    Bill Scott wrote:
    I don't have access to dos, but am willing to bet what they call rename is that perl script Larry Wall wrote (syntax changed for dos).


    RENAME (REN) is an internal command of DOS's terminal. I don't know what it does under the hood, and DOS pre-dates perl by a few years, but I don't recall if the REN command was there from the beginning or not. Knowing microsoft's reputation for stealing other's work, wouldn't be surprised either way if their internal command came from them or someone else.
  • Bill Scott Level 6 Level 6 (11,445 points)
    All you need to do is put that "rename" perl script into a file, make it executable, and put it somewhere in your path, and the shell treats it just the same as if it were a compiled binary executable. So I guess the real question is why Apple doesn't do that.