Search code examples
bashgrepmacos-sierra

Rename file based on content


In macOS Sierra (v. 10.12.6), I have a directory containing many flat-text (.txt) files. Each file has an unhelpful name such as "untitled text 252.txt". I would like to know if it's possible to programmatically rename every file based on a code located in the first line of every file.

Every file begins with a section symbol (§), a space, a code that always contains a period (.), and a space. The code is usually just numeric, but occasionally there is also a trailing hyphen (-) followed by a letter. For example: '§ 177.30 ', or '§ 60.10-a '.

I would like to rename every file based on its code, but reformat the code first. In short, prefix a P, strip out the period from the code, and add a trailing .txt. Using the examples above, the file names would be: P17730.txt and P6010-a.txt.

At a command prompt, I have figured out how to grep the code from each file:

grep -o '^§ [A-Za-z0-9]*\.[A-Za-z0-9\-]* ' *.txt

This returns everything from the section symbol to the trailing whitespace (e.g. '§ 130.65-a ').

So, the remaining questions are:

  1. How do I reformat the grep result to the filename I want (e.g. P13065-a.txt); and,
  2. How do I combine a file rename operation with the grep?

Solution

  • You can use sed to extract only digits from the first line (by deleting all non-digits):

    $ sed -n '1 {s/[^0-9]//g; p;}' <<<"§ 177.30 "
    17730
    

    and then find all *.txt files, extract digits, and rename to P<digits>.txt. Instead of in-place rename, it's safer to copy the files, renaming on fly, to a target dir:

    $ mkdir -p /path/to/targetdir
    $ find . -name '*.txt' -exec bash -c 'digits=$(sed -n "1{s/[^0-9]//g;p;}" "$1"); cp "$1" "/path/to/targetdir/$newname"' _ {} \;
    

    by executing a short bash script:

    #!/bin/bash
    digits=$(sed -n "1{s/[^0-9]//g;p;}" "$1")
    cp "$1" "/path/to/targetdir/$newname"
    

    for each file found by find (and given as the first positional parameter, $1).