Score:4

Batch renaming files, moving two specific words from filename to beginning and removing last part

in flag

I have a bunch of videos in a folder that all end with a random code between square brackets i.e. [_qj3ZkAR2tE], and right before this part, there's two words I want to move to the beginning of the filename. Example of what I want to accomplish:

The Ultimate Fallacy Lesson 1 [code*123].mp4
How to Structure Concrete Lesson 2 [code_412].mp4

Would become

Lesson 1 The Ultimate Fallacy.mp4
Lesson 2 How to Structure Concrete.mp4

And so on. How can I accomplish that using rename, qmv, a shell script or anything that'd do the trick? I'd be fine with replacing the spaces with symbols or something if needed.

Score:7
hr flag

Using the Perl-based rename command (with -n for a dry-run) and assuming your "words" are sequences of non-whitespace characters separated by exactly one space:

$ rename -n 's/(.*) (\S* \S*) \[.*?\]/$2 $1/' -- *.mp4
rename(How to Structure Concrete Lesson 2 [code_412].mp4, Lesson 2 How to Structure Concrete.mp4)
rename(The Ultimate Fallacy Lesson 1 [code*123].mp4, Lesson 1 The Ultimate Fallacy.mp4)
Ignis Incendio avatar
sk flag
Noob question: why is the question mark needed for the asterisk? Isn’t the asterisk already for 0 or more characters?
hr flag
@IgnisIncendio in this context, the `?` makes the preceding `.*` match *non-greedily*. So for example if the string was `[code*123][code_412]` it would match only up to the *first* closing `]`. I'm not sure quite why I wrote it that way - there's not enough information in the question to determine whether a greedy or non-greedy match is more appropriate here.
SkySpiral7 avatar
au flag
Seems like it should be 's/([^[]+?) (Lesson \d+) \[[^]]+\]/$2 $1/' for several unnecessary nitpicks and assuming some things. I feel like this would be a more appropriate laziness and it prevents catastrophic backtracking. Although since this likely isn't production code these changes don't matter.
Score:4
my flag
for filename in *.mp4; do
    # Get extention (element after last dot)
    ext="${filename##*.}"
    # Remove token and all characters after ' [' token 
    fname_wo_brackets="${filename%% [*}"
    # Get last word
    last_word_1="${fname_wo_brackets##* }"
    # Remove last word
    fname_wo_brackets="${fname_wo_brackets% *}"
    # Get last word
    last_word_2="${fname_wo_brackets##* }"
    # Remove last word
    fname_wo_brackets="${fname_wo_brackets% *}"
    # Create new filename
    new_filename="$last_word_2 $last_word_1 $fname_wo_brackets.$ext"
    # Print current and future filnames
    echo "rename '$filename' to '$new_filename'"
    # Rename (without hash)
    # mv "$filename" "$new_filename"
done

If you are agree with proposed filenames, remove hash before mv command.

Score:4
jp flag

In the Bourne-Again Shell(bash) ... utilizing arrays and parameter expansion:

for f in *.mp4
  do
    IFS='. ' read -r -a a <<< "$f"
    f1="${a[-4]} ${a[-3]} ${a[@]::${#a[@]}-4}.${a[-1]}"
    echo mv -nv -- "$f" "$f1"
    done

Notice: echo is added for a safe dry-run ... Remove it when satisfied with the output for the actual renaming to happen.


Demonstration:

$ for f in *.mp4
  do
    IFS='. ' read -r -a a <<< "$f"
    f1="${a[-4]} ${a[-3]} ${a[@]::${#a[@]}-4}.${a[-1]}"
    mv -nv -- "$f" "$f1"
    done
renamed 'How to Structure Concrete Lesson 2 [code_412].mp4' -> 'Lesson 2 How to Structure Concrete.mp4'
renamed 'The Ultimate Fallacy Lesson 1 [code*123].mp4' -> 'Lesson 1 The Ultimate Fallacy.mp4'

How it works:

The for f in *.mp4 ... will assign each one of the filenames(none-hidden i.e. don't start with a dot.) with the extension .mp4 in the current working directory to the variable f one filename at a time in each loop run.

Then, that filename in the variable f is split into parts(words) around the dot . and the space characters by setting the Internal Field Separator to those characters with IFS='. ' while reading it as a Here String into an indexed array elements withe read -r -a.

Taking the filename The Ultimate Fallacy Lesson 1 [code*123].mp4 as an example, after:

IFS='. ' read -r -a a <<< "$f"

the array a will look like this:

$ declare -p a
declare -a a=([0]="The" [1]="Ultimate" [2]="Fallacy" [3]="Lesson" [4]="1" [5]="[code*123]" [6]="mp4")

... and its elements can be called by index from left to right(first to last) like:

$ echo "${a[0]} ${a[1]}"
The Ultimate

... or from right to left(last to first) like:

$ echo "${a[-1]} ${a[-2]}"
mp4 [code*123]

... or by range with ${a[@]:start_index:length}(if start_index is omitted, then 0 is assumed) like:

$ echo "${a[@]:0:2}"
The Ultimate
$
$ echo "${a[@]::2}"
The Ultimate

... ranges relative to the length of the array ${#a[@]} can also be called with e.g.:

$ echo "${a[@]::${#a[@]}-4}"
The Ultimate Fallacy

... Therefor the variable assignment to f1:

f1="${a[-4]} ${a[-3]} ${a[@]::${#a[@]}-4}.${a[-1]}"

... will look like this:

$ declare -p f1
declare -- f1="Lesson 1 The Ultimate Fallacy.mp4"

Notice for the Z Shell(zsh) users:

You can do the same utilizing extended glob patterns with the builtin shell function zmv with something like this:

% autoload zmv
%
% zmv -n -- '(* *) (* * )(\[*\])(.mp4)' '$2$1$4'
mv -- 'How to Structure Concrete Lesson 2 [code_412].mp4' 'Lesson 2 How to Structure Concrete.mp4'
mv -- 'The Ultimate Fallacy Lesson 1 [code*123].mp4' 'Lesson 1 The Ultimate Fallacy.mp4'

The -n option is for a safe dry-run ... When satisfied with the output, substitute it with -v(for verbosity) so the actual renaming will happen.

Score:1
in flag

You mentioned qmv from the renameutils package in your post, and from my perspective this is one of the easiest options you can use.

For those who don’t know, qmv prepares a temporary file from the list of files you pass it, with each line consisting of one file name repeated twice, with the two copies of the filename separated by whitespace, then opens that temporary file in whatever your preferred terminal text editor is. You update the file names in the second column, save the file, and exit, and then the files get renamed based on your updates. This lets you leverage the complex bulk search/replace functionality found in most good editors to do large-scale batch renames.

Assuming Vim or Neovim as the editor and that all the files have an mp4 extension, the following somewhat monstrous looking single search and replace command will do what was requested:

:%s/\.mp4\(\s+\)\(.*\) \(.*\) \(.*\)\[.*\]\.mp4$/.mp4\1\3 \4 \2.mp4/

This works by anchoring on the first ,mp4 extension and the whitespace separating the two columns, then matching on the four functional groups of characters being manipulated (the first part of the filename, the second to last word, the last word, and the tag at the end), then further anchoring on the final extension at the end of the line. Vim regexes are greedy by default, so the second capture group will match as much as possible, forcing the third and fourth to only match the last two words in the file name. The substitution then simply shuffles the order of capture groups 2, 3, and 4 to match the desired ordering and drops the tag at the end.

It should be doable to construct a similar search and replace operation for EMACS or Nano, though I do not have enough experience with either to provide examples for them.

David Z avatar
es flag
nano may not have that functionality - at least, not as far as I know, although I'm not intimately familiar with its search and replace function.
Score:0
ru flag
Vi.

If you know how to use Vim text editor, then

vidir *.mp4

can help you mass-rename files interactively by changing a text file with file names in the text editor. Saving and exiting vidir would trigger the rename based on new lines.

This requires knowing advanced, bulk text editing operations.

It is not a fully batch solution like in other answers, but may be simpler in some cases and can offer a preview of how the list of files would look before the actual renaming.

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.