9

System: Ubuntu MATE 18.04.

I have a collection of files (in multiple directories in a directory structure) and I want to move many of them to another directory. I can tell which files I want to move by their names (they are recipe titles), but there is no other way to distinguish between files I do want to move and files I don't want to move; I need to make a decision for every file by examining the name myself.

So, I am running a command once for each file I want to move, like

mv Snacks/OkaraCrackers NeedsTesting/
mv Snacks/SunflowerBrittle NeedsTesting/
mv Treats/ChocolateChilliFudge NeedsTesting/
mv Treats/PecanBlondies NeedsTesting/

Even with tab completion this is toooo much typing. What I really want to do is something like

shopt -s globstar
mv --prompt-me-for-every-file ** NeedsTesting/

So I can just press y or n for each file.

How can I move a group of files and be prompted for confirmation for every file?

0

2 Answers 2

8

Here is a small script (mv-confirm.sh):

#!/bin/bash

# Just make sure there are at least one source and one destination
if [ "$#" -lt 2 ]; then
    echo "Illegal number of parameters"
fi

DESTINATION=${*:$#}             # last argument
SOURCES=( ${@:1:$#-1} )         # all but last arguments

for i in "${SOURCES[@]}"
do
    read -p "move '$i' to '$DESTINATION' ? [y/n] " -n 1 -r
    echo
    if [[ $REPLY =~ ^[Yy]$ ]]
    then
        echo mv "$i" "$DESTINATION"
    fi
done

Sample use :

./mv-confirm.sh ** somewhere
move 'a' to 'somewhere' ? [y/n] n
move 'b' to 'somewhere' ? [y/n] y
mv b d
...

don't forget to make the script executable (chmod +x mv-confirm.sh).

NB

Since I don't want to mess with your files, I added echo before the actual mv command. Feel free to remove it for actually moving the files.

6

Way 1: In your graphical file browser, using a tree view

I assume your actual goal is to make a choice for each file, and not strictly speaking to be prompted. However, if you prefer to be prompted about each file in a terminal, see Way 2 below.

Most graphical file browsers support a tree view where you can view multiple levels of a directory hierarchy together. You can copy or move files out of this view, either individually or by selecting them and then operating on the whole selection. This works even with files from multiple folders.

In some file browsers, one must go into a Preferences menu to enable the tree view. In Caja, the MATE file browser, it's always enabled in the list view. Click View → List (or press Ctrl+2) to switch to the list view. Each directory has a small rightward-facing triangle to the left of it, indicating it can be expanded. To expand a folder, click that triangle, or press the (right arrow) key when the folder is selected. In this example, I've selected them all one by one and am dragging them all at once their destination:

multiple files selected in a tree structure in the Caja file browser, being dragged into another folder but not yet moved

Assuming the source and destination are on the same drive, dragging moves by default. If you wanted to copy instead, you'd hold down the Ctrl key.

the situation after the drag-move has completed, in which the files that were selected are now all together in the destination folder instead of their original separate locations

In theory, it's easy to fix mistakes when using this method, because most file browsers (including Caja) support Ctrl+Z to undo recent actions, including batch actions. However, I've had mixed luck with that. Sometimes files go back into the wrong folder.

The good news is that you appear to be working with mostly text documents, which tend to be small, so you can make an extra backup with tar first without much hassle. I suggest doing that regardless of what method you use.

Way 2: The find command with the -ok action

It's common use find to find files and pass their paths to an arbitrary external command with the -exec and -execdir actions. But find also has -ok and -okdir that prompt before each time they run a command. If you really want to be prompted in a terminal about each file, I think this is the way to go.

When done to produce the same effects as shown above, that looks like this:

ek@Gnar:~$ find Texts -xtype f -ok mv -t 'Glorious Destination Folder' {} \;
< mv ... Texts/Robert Frost/New Hampshire.pdf > ? y
< mv ... Texts/Robert Frost/In The Clearing.pdf > ? n
< mv ... Texts/Helen Keller/The Story of My Life.pdf > ? y
< mv ... Texts/Saki/The Short Stories of Saki.pdf > ? y
< mv ... Texts/Jane Austen/Sense and Sensibility.pdf > ? n
< mv ... Texts/Jane Austen/Pride and Prejudice.pdf > ? y
< mv ... Texts/Richard Connell/The Stolen Crime.pdf > ? n
< mv ... Texts/Richard Connell/The Most Dangerous Game.txt > ? y
< mv ... Texts/Mary Wollstonecraft Shelley/Mathilda.epub > ? n
< mv ... Texts/Mary Wollstonecraft Shelley/Frankenstein: Or, The Modern Prometheus.pdf > ? y
< mv ... Texts/W. E. B. Du Bois/The Souls of Black Folk.pdf > ? n
< mv ... Texts/W. E. B. Du Bois/Darkwater: Voices from within the Veil.htm > ? y

Replace Texts with the source directory (which could just be . if you're cd'd to it) and Glorious Destination Folder with the actual destination directory.

This runs one command per file; unlike -exec and -execdir, -ok and -okdir don't support + in place of \; to pass multiple found paths in the same invocation of a command. Consequently, you don't need to use the -t dest form of mv. I suggest doing so anyway, though, both in general with find and particularly in this situation so the prompts look more like the commands they're prompting about.

As that command is written, if find encounters a symbolic link that ultimately points to a regular file, it (the link, not the target) is moved rather than ignored. If you really want to operate just one regular files, use -type f instead of -xtype f.

You may want to do this in a script session so you can figure out what happened if you make a mistake. But again, no reason not to have a backup.

Way 3: Generate a manifest and edit it

At least so long as the files don't have newlines in their names, you can start by using a simple find command to generate a manifest--a file that lists the names of the files you're (in this case potentially) interested in:

find Texts -xtype f >manifest

Replace manifest with whatever name you like for the file that lists the files under consideration (and adjust the remaining directions accordingly).

As above, replace Texts with your actual source directory, which could be . if you've cd'd to it. However, if Texts is a relative path (including if it's .) then you should make sure to be in the same location later when you actually perform the moves.

After running that find command, open manifest in a text editor. This could be nano, vim, emacs, gedit, pluma, etc. In the editor, comment out the lines that name files you don't want to move by placing a # at the beginning of them. Some editors will highlight those lines differently even when it doesn't recognize your file to be of any format it knows has comments. Most won't, though, and the real reason I suggest to do it this way is that you, as a human, are likely to have experience commenting lines out in configuration files (and perhaps in source code). I think this is more intuitive, and less error prone, than it would be to mark the lines you do want to move.

Of course, you could instead just delete the lines for files you don't want to move. The advantage of commenting them out is that it will be clear later what you did.

In my example, this is what the file's contents looked like after I edited it:

Texts/Robert Frost/New Hampshire.pdf
# Texts/Robert Frost/In The Clearing.pdf
Texts/Helen Keller/The Story of My Life.pdf
Texts/Saki/The Short Stories of Saki.pdf
# Texts/Jane Austen/Sense and Sensibility.pdf
Texts/Jane Austen/Pride and Prejudice.pdf
# Texts/Richard Connell/The Stolen Crime.pdf
Texts/Richard Connell/The Most Dangerous Game.txt
# Texts/Mary Wollstonecraft Shelley/Mathilda.epub
Texts/Mary Wollstonecraft Shelley/Frankenstein: Or, The Modern Prometheus.pdf
# Texts/W. E. B. Du Bois/The Souls of Black Folk.pdf
Texts/W. E. B. Du Bois/Darkwater: Voices from within the Veil.htm

Then you can filter out the commented lines with grep and pipe the result to xargs to pass them to mv:

grep -Pv '^\s*#' manifest | xargs -d '\n' mv -t 'Glorious Destination Folder'

As above, replace Glorious Destination Folder with the name of the actual destination. That's it; your files are moved. You can of course write echo before mv to see the commands that will be run first, if you like.

The way that command works is:

  1. The grep command outputs lines from manifest that don't (-v) start (^) with optional (*) whitespace (\s) followed by a literal # character. The -P flag select PCRE as the dialect of regular expressions (which enables \s which I consider more readable than the traditional [[:space:]]).
  2. The xargs command divides its input (which is the output of grep) into newline-terminated fields (-d '\n'), then pastes each field (without the terminating newline) as a separate argument at the end of the command mv -t 'Glorious Destination Folder', which it runs.
1
  • 1
    -ok IMHO the best answer
    – pim
    Aug 8, 2019 at 12:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .