1

I am trying to rename all files in a directory, for example, ~/folder/ contains(from newest to oldest): apple.foo, pie.foo, cookie.foo, melon.foo

renaming them with a fixed prefix and a number of 001 being the oldest and increase up to 999:

melon.foo >> object001.foo

cookie.foo >> object002.foo

pie.foo >> object003.foo

apple.foo >> object004.foo

Anyone know how to achieve this?

2
  • Hint: Use ls -rt to list the files, and use a loop with an incrementing counter? Watch out for "weird" file names (e.g. containing spaces). Commented Jan 28, 2020 at 4:49
  • @NickD ... or newlines. Commented Jan 28, 2020 at 9:08

3 Answers 3

2

Using the zsh shell:

counter=0

for name in $HOME/folder/*(.NDOm); do
    counter=$(( counter + 1 ))

    suffix=$name:e
    printf -v newname '%s/object%.3d%s' $name:h $counter ${suffix:+.$suffix}

    mv $name $newname
done

This iterates over all names of regular files in the ~/folder directory, from the oldest to the newes by last-modified timestamp. For each name, a new name is constructed using the directory part of the current name and a counter. The files are renamed into these new names (without confirmation and without checking for name collisions).

The printf -v newname call will "print" its output into the variable newname. The %.3d format string will output a zero-filled integer in three positions (e.g. 001, 023, 109 etc.) The $name:h parameter expansion will expand to the head/directory portion of the $name pathname (it's the same as dirname "$name").

The $name:e will expand to the "extension" of the original filename (e.g. foo if the current file is apple.foo), and we store this in suffix. With ${suffix:+.$suffix} we prepend a dot to the filename extension if there is one.

The . in (.NDOm) at the end of the filename globbing pattern is a glob qualifier that makes the preceding globbing pattern match only regular files. The N and D makes the pattern act as if nullglob and dotglob in the bash shell had been set (expand to nothing if there is no match, and include hidden names in the result). The Om orders the matching names so that the oldest (least recently modified) file is sorted first.

If zsh is not your login shell, then this could be run as a script instead.

$ zsh ./this-script
1

Here's a version written in bash with GNU utilities that doesn't require parsing ls, and will handle any legally named file (embedded spaces, punctuation, newlines, etc.)

#!/bin/bash
k=0                                               # start at the beginning
find *.foo -type f -printf "%T@ %p\0" |           # list all the files with modification time
    sort -z |                                     # sort by increasing modification time
    cut -z -d' ' -f2- |                           # discard the time value leaving just the name
    while IFS= read -r -d '' file                 # now loop for each file...
    do
        ((++k))                                   # next count
        seq=$(printf "%03d" $k)                   # format the three-digit counter
        name="${file%.*}" ext="${file##*.}"       # split filename into name and extension
        echo will mv "$file" "$name.$seq.$ext"    # show what would happen without doing it
    done

Remove the echo will when you are ready for the mv command to execute.

Example

# preparation
for f in {melon,cookie,pie,apple}.foo; do touch "$f"; sleep 1; done

# list in order of modification time (oldest to newest)
ls -tr
melon.foo  cookie.foo  pie.foo  apple.foo

# run the script and observe the output
k=0; find *.foo -type f -printf "%T@ %p\0" | sort -z | cut -z -d' ' -f2- | while IFS= read -r -d '' file; do ((++k)); seq=$(printf "%03d" $k); name="${file%.*}" ext="${file##*.}"; echo mv "$file" "$name.$seq.$ext"; done

will mv melon.foo melon.001.foo
will mv cookie.foo cookie.002.foo
will mv pie.foo pie.003.foo
will mv apple.foo apple.004.foo
0

Assuming that the filenames do not contain spaces, tabs or newlines, using one-liner and bash and awk would be simple:

ls -rt | awk -F "." '{print  $0 " object" sprintf("%03d",NR)  "." $NF }' | xargs -L 1 mv

The xargs -l command execute mv for each line. Before executing command execute the following command and check that target and source file names are correct:

ls -rt | awk -F "." '{print  $0 " object" sprintf("%03d",NR)  "." $NF }'

should output something like:

melon.foo object001.foo
cookie.foo object002.foo
pie.foo object003.foo
apple.foo object004.foo
3
  • Hello, the second line to check the target and source worked, but the first line didn't work, with error: xargs: illegal option -- l usage: xargs [-0opt] [-E eofstr] [-I replstr [-R replacements]] [-J replstr] [-L number] [-n number [-x]] [-P maxprocs] [-s size] [utility [argument ...]] Commented Feb 6, 2020 at 3:18
  • @kira, please use xargs -L 1 mv forget about the posix standard. Updated the answer. Commented Feb 6, 2020 at 7:41
  • Thanks! It worked mostly, although none of the files has empty space in their name, but some of my files seemed to have illegal characters, the ones that failed are those that starts with a minus sign - ,and I received the following errors: mv: illegal option -- 2 usage: mv [-f | -i | -n] [-v] source target mv [-f | -i | -n] [-v] source ... directory mv: illegal option -- w usage: mv [-f | -i | -n] [-v] source target mv [-f | -i | -n] [-v] source ... directory Commented Feb 6, 2020 at 17:53

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.