0

For example if I were to move a group of files with the filename of chapter1_sectionX to a specific folder, say to book/chapter1 and chapter2_sectionX to book/chapter2 and so on. I am currently using this method which I find it to be inefficient:

touch chapter{1,2}_section{1..4}.odf
mkdir -p book/chapter{1,2}
mv chapter1_section* book/chapter1
mv chapter2_section* book/chapter2

Is there a shorter way of doing this without using a for loop?

  • You talk about files containing the word, but you move files based on filename. Please clarify this is what you want. – Panki Jul 01 '21 at 13:46
  • 1
    Also, what is your issue with for loops? This is the perfect application for one. – Panki Jul 01 '21 at 13:47
  • I would like to move files based on filename. I'm sry for the confusion caused. –  Jul 01 '21 at 13:49
  • I'm currently still a noob self learning and haven't read up on for loops yet. –  Jul 01 '21 at 13:50
  • In that case, it's about time. Hint: Start with for x in $(seq 1 10); do echo $x; done and work your way up :) – Panki Jul 01 '21 at 13:52
  • Call your chapters chapter01 or chapter001 (in case your memoirs are very long)... – Jeremy Boden Jul 01 '21 at 13:53
  • ic tks for the help –  Jul 01 '21 at 13:53
  • 2
    You are aware that specifying the mv target with a wildcard can create problems? Imagine you have directories book/chapter1 and book/chapter11 - your shell would expand your command to mv chapter1<whatever files> book/chapter1 book/chapter11 and all chapter1 files plus the entire folder book/chapter1 would end up in book/chapter11. – AdminBee Jul 01 '21 at 14:10
  • 1
    Tks for the concern, but I do understand the implications of the wildcard character. The format of my filename is chapterX_sectionY.odf. I was simply too lazy to type the rest out and worded my question in a confusing way. –  Jul 01 '21 at 14:24

3 Answers3

1

Without using a for loop? You can use the perl rename utility (also known as prename, or file-rename or perl-rename on some distros) to create the subdirectories and rename the files into them. For example:

$ rename -v 'BEGIN{mkdir "book"};
             if (m/chapter(\d+)/) {
               my $d="book/chapter$1";
               mkdir $d;
               $_ = "$d/$_"
             }' *
chapter1.txt renamed as book/chapter1/chapter1.txt
chapter2.txt renamed as book/chapter2/chapter2.txt
chapter3.txt renamed as book/chapter3/chapter3.txt
chapter4.txt renamed as book/chapter4/chapter4.txt
chapter5.txt renamed as book/chapter5/chapter5.txt

English summary of the script:

If the current filename ($_) matches the regex chapter(\d+) then extract the chapter number from the filename (i.e. $1, which is the first and only capture group in the regex, the (\d+)), create a directory for the chapter, and then rename the current file into the directory.

Non-matching filenames are ignored.

perl rename only attempts to rename a file if the rename script changes the value of $_. It also refuses to overwrite an existing file unless you force it to with the -f option.

perl rename allows you to use any perl code within the rename script (but note that it the use strict pragma is in force so you need to declare your variables). If the code changes $_, the file will be renamed. If not, it won't.

Note that it's good practice to do a dry-run first with the -n option to make sure that rename is going to do what you want it to (recovering from a bad bulk rename can be a major PITA). -n doesn't rename any files, it just shows what it would do. Replace the -n with -v (as I have above) to get verbose output, or just remove the -n for silent operation.

cas
  • 78,579
  • Appreciate the response but I was hoping that there was some method of implementing my code above using the built in utilities. I had this weird idea after learning brace expansion and was fascinated by its combining and nesting capabilites. Guess I was asking for the impossible –  Jul 01 '21 at 14:33
  • that's OK. I cheated. With perl rename, there's an implicit loop over all the filenames, with $_ being set to the current filename for each iteration of the loop. But unless you rename each filename manually, the only way to do this is with a loop of some kind. – cas Jul 01 '21 at 14:36
1

I find the mmv utility handy for stuff like this:

$ mmv -v 'chapter*_section*.odf' 'book/chapter#1/'
chapter1_section1.odf -> book/chapter1/chapter1_section1.odf : done
chapter1_section2.odf -> book/chapter1/chapter1_section2.odf : done
chapter1_section3.odf -> book/chapter1/chapter1_section3.odf : done
chapter1_section4.odf -> book/chapter1/chapter1_section4.odf : done
chapter2_section1.odf -> book/chapter2/chapter2_section1.odf : done
chapter2_section2.odf -> book/chapter2/chapter2_section2.odf : done
chapter2_section3.odf -> book/chapter2/chapter2_section3.odf : done
chapter2_section4.odf -> book/chapter2/chapter2_section4.odf : done

giving

$ tree book
book
├── chapter1
│   ├── chapter1_section1.odf
│   ├── chapter1_section2.odf
│   ├── chapter1_section3.odf
│   └── chapter1_section4.odf
└── chapter2
    ├── chapter2_section1.odf
    ├── chapter2_section2.odf
    ├── chapter2_section3.odf
    └── chapter2_section4.odf

2 directories, 8 files

steeldriver
  • 81,074
0

For the sake of completeness, and owing to the simple structure of your file and target directory names, the following find command would iterate over all chapter files and move them to a target directory whose name is derived from the filename:

find . -maxdepth 1 -type f -name 'chapter*_section*.odf' -exec bash -c 'f={}; d="book/${f%_*}"; mv "$f" "$d"' \;

This will (implicitly) loop over all files matching chapter*_section*.odf, and execute a shell command that derives the target directory name by removing the last _* component from the filename, effectively leaving only the chapterN part. It will then move the file currently processed to the corresponding subdirectory under book/.

Note

  • The -maxdepth 1 option is relevant to ensure the operation is only performed on the files in the current directory, and not recursively on those already moved to their target directories once the current directory is processed.
  • Embedding the {} in a shell command called via -exec is discouraged but will work if you don't assume malicious code injection attempts.
AdminBee
  • 22,803