1

This is simple and has been asked/answered many times but not when files are in different directories. I want to do this: Renaming part of a filename, but with the files in different sub-directories.

This is what I've tried:

for f in */*run?.t; do mv -i -- "$f" "${f//subset/subset5}"; done

but it tries to rename the directory too (because subset is also in the directory name) and then errors because that directory name is not found. What am I missing?

Here is the general directory structure:

Gene1_subset/
       Gene1file_subset.run1.t
       Gene1file_subset.run2.t
Gene2_subset/
       Gene2file_subset.run1.t
       Gene2file_subset.run2.t

Here is what I'd like:

Gene1_subset/
           Gene1file_subset5.run1.t
           Gene1file_subset5.run2.t
Gene2_subset/
           Gene2file_subset5.run1.t
           Gene2file_subset5.run2.t

But it attempts to rename the directories too so you get the error:

cannot move Gene1_subset/Gene1file_subset.run1 to Gene1_subset5/Gene1file_subset5.run1 : No such file or directory
KNN
  • 459
  • 4
  • 19

3 Answers3

0

You can check if the file is a directory and, if not, rename it.

For example, [ -d FILE ] will return true if FILE exists and is a directory.

Instead of trying to move everything from the root, cycle all directories and for each one, cycle all files.

E.G.

#!/bin/bash
rename() {
  for dir in *; do
    if [ -d "${dir}" ]; then
      cd "${dir}"
      for f in *run?; do
        ! [ -d "${f}" ] && mv -i -- "${f}" "${f//subset/subset5}"
      done
      cd ..
    fi
  done
}
rename || exit $?
exit 0
ingroxd
  • 995
  • 1
  • 12
  • 28
  • Thank you for your suggestion. I tried the above and got the error: `mv: cannot stat ‘*run?.t’: No such file or directory`. Then it logs me out of the server. Though, I checked and indeed in each folder, I have files with the extensions run1.t, run2.t, etc – KNN Aug 30 '18 at 18:52
  • You are right, I forgot to change directory... I added some `cd`s to the function – ingroxd Aug 30 '18 at 18:56
  • Humm.. Something still not quite right. Copying in the above (line by line to try and catch the issue) it completes after the last `}` therefore the `rename || exit $? exit 0` is not used. But it doesn't actually change the filenames. – KNN Aug 30 '18 at 19:01
  • It is a function, after `}` no code is executed unless you call the function by name (`rename` in this case). Anyway, there was a `.t` after `*run?` that I copied from your previous code that should not have been there... I checked the function, btw, and it works now [: Cheers! – ingroxd Aug 30 '18 at 19:08
  • Forgive me since I am new to bash. The files are something.subset.run1.t, etc so that is needed. Sorry, that wasn't on the original post. Also, my function "completes" after the second `}` so it doesn't allow me to finish entering that second function: `rename || exit $? exit 0` – KNN Aug 30 '18 at 19:31
  • @NKN What I wrote is a "proper" script, you should put the code in a file and execute it... If you need something inline you can do write it as: `for dir in *; do; if [ -d "${dir}" ]; then cd "${dir}"; for f in *run?; do ! [ -d "${f}" ] && mv -i -- "${f}" "${f//subset/subset5}"; done; cd ..; fi; done` – ingroxd Aug 31 '18 at 08:20
0
$ mkdir -p Gene1_subset Gene2_subset
$ touch Gene1_subset/Gene1file_subset.run1 Gene1_subset/Gene1file_subset.run2 Gene2_subset/Gene2file_subset.run1 Gene2_subset/Gene2file_subset.run2

I think it all comes down to finding the proper regex. Here I substitute the string subset.run for subset5.run:

$ find . -name '*.run[0-9]' -exec sh -c 'mv -v "{}" "$(echo {} | sed "s/subset\.run/subset5\.run/")"' \;
renamed './Gene2_subset/Gene2file_subset.run2' -> './Gene2_subset/Gene2file_subset5.run2'
renamed './Gene2_subset/Gene2file_subset.run1' -> './Gene2_subset/Gene2file_subset5.run1'
renamed './Gene1_subset/Gene1file_subset.run2' -> './Gene1_subset/Gene1file_subset5.run2'
renamed './Gene1_subset/Gene1file_subset.run1' -> './Gene1_subset/Gene1file_subset5.run1'

But we can for example use basename and dirname to strip the path from filename and directory and run sed only on filename:

$ find . -name '*.run[0-9]' -exec sh -c "mv -v \"{}\" \"\$(dirname \"{}\")/\$(basename \"{}\" | sed 's/subset/subset5/')\"" \;
renamed './Gene2_subset/Gene2file_subset.run2' -> './Gene2_subset/Gene2file_subset5.run2'
renamed './Gene2_subset/Gene2file_subset.run1' -> './Gene2_subset/Gene2file_subset5.run1'
renamed './Gene1_subset/Gene1file_subset.run2' -> './Gene1_subset/Gene1file_subset5.run2'
renamed './Gene1_subset/Gene1file_subset.run1' -> './Gene1_subset/Gene1file_subset5.run1'

A little escaping to learn. This will probably be more readable to you:

for f in */*run?; do f2=$(basename "$f"); mv -vi -- "$f" "$(dirname "$f")/${f2//subset/subset5}"; done
renamed 'Gene1_subset/Gene1file_subset.run1' -> 'Gene1_subset/Gene1file_subset5.run1'
renamed 'Gene1_subset/Gene1file_subset.run2' -> 'Gene1_subset/Gene1file_subset5.run2'
renamed 'Gene2_subset/Gene2file_subset.run1' -> 'Gene2_subset/Gene2file_subset5.run1'
renamed 'Gene2_subset/Gene2file_subset.run2' -> 'Gene2_subset/Gene2file_subset5.run2'
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
0

Something like this should do:

#!/usr/bin/env bash

shopt -s extglob globstar nullglob
for f in **/*_subset.run+([0-9]).t; do
    tmp=${f##*_}
    mv -i -- "$f" "${f%_*}_${tmp/et/et5}"
done

If your files strictly follow the structure, you could just change your code to:

mv -i -- "$f" "${f/file_subset/file_subset5}"
PesaThe
  • 7,259
  • 1
  • 19
  • 43