3

I want to change all filenames they have the exact length of 16 characters (digits and lower case letters). I tried [0-9a-z]{16} and [0-9a-z]\{16\} for the placeholder regX in the following snippet, but it doesn't work.

for file in <regX>
do
  mv "$file" "${file}.txt"
done
5
  • 2
    which shell are you using? en.wikipedia.org/wiki/Glob_(programming) is different from regex
    – Sundeep
    Apr 9 '17 at 2:58
  • 2
    if you are using bash and has extglob , you can iterate over for file in +([0-9a-z]) and then rename only if length of ${#file} is 16
    – Sundeep
    Apr 9 '17 at 3:02
  • @Sundeep, my default shell $ echo $0 is bash. And it's not possible to specify the number of repetitions in this case?
    – Hölderlin
    Apr 9 '17 at 3:13
  • 1
    You may find success with ???????????????? in place of <regX>, though this doesn't let you care what those sixteen characters are
    – Fox
    Apr 9 '17 at 3:31
  • What about using find instead?
    – cylgalad
    Apr 9 '17 at 8:39
5

Shell globbing patterns are not regular expressions. They may look similar, but they work quite differently in some respects.

The regular expression [0-9a-z]{16} matches a string that contains a run of 16 characters of the mentioned character range (the string containing the match may be longer).

The equivalent shell globbing pattern would be something like

*[0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z]*

This is because

  1. Globbing patterns do not support modifiers that says "repeat the previous N times".
  2. Globbing patterns are by default anchored to the beginning and end. This is why I inserted * at the start and end of the pattern. A * matches any number of any character. Remove these if you want to match exactly 16 characters.

You may create a string of 16 [0-9a-z] using Perl:

$ pattern="$( perl -e 'print "[0-9a-z]" x 16' )"

With bash, you may choose to iterate over all names in the current directory, and then test whether the names match your regular expression:

for name in *; do
    if [[ -f "$name" ]] && [[ "$name" =~ ^[0-9a-z]{16}$ ]]; then
        echo mv "$name" "$name.txt"
    fi
done

Remove the echo once you know that it's doing the right thing.

Notice that I've anchored the pattern so that longer names won't match. I'm also making sure, with the -f test, that the names that we get are actually those of regular files only.

3

With extglob

shopt -s extglob
for file in +([0-9a-z])
do
    [[ ${#file} == 16 ]] && echo mv "$file" "${file}.txt"
done
  • +([0-9a-z]) means one or more of [0-9a-z] characters
  • ${#file} gives the length of filename
  • echo is for dry run, remove once things are fine
3

If you have perl-rename (called rename on Debian-based systems, perl-rename on others), you can do:

perl-rename -n 's/^[0-9a-z]{16}$/$&.txt/' *

If that does what you want, remove the -n to make it actually rename the files. The syntax of perl-rename is a perl command. Here, we're using the substitution operator (s/from/to/) which will replace from with to. The from in this case is your regex and the to is the special variable $& which means "whatever was matched", plus the extension .txt.


To do this with a shell glob (use @Kusalananda's or @Sundeep's approach, this is just for the sake of completion) :

for f in [0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z]; do
    mv -- "$f" "$f".txt
done

With GNU find:

find . -regextype posix-extended -regex '.*/[0-9a-z]{16}' -exec mv {} {}".txt" \;
0
1
L16=$(csh -c 'repeat 16 echo -n "?"')
find . -name "$L16" ! -name '*[!0-9a-z]*' -exec sh -c '
   mv "$1" "$1.txt"
' {} {} \;
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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