10

In the directory /home/username/data I have both files and directories. Some of these filenames end in .txt (to which I'll refer as text files), others don't. The same happens in the subdirectories.

One of the subdirectories is called other_files (its full path is /home/username/data/other_files/).

I'd like to move all the files not ending with .txt in the root of /home/username/data to other_files.


I could possibly do it with a loop, but that's not what I want. I want to use commands and piping. I believe this is easy, I'm just not seeing it. A combination of mv, find, grep and xargs should do it, I'm just not sure how.

So I'm stuck in trying to match the text files (to then think of way to match everything except them). In the following, assume my current directory is /home/username/data.
First I went for find . | grep -E "*\.txt", but this matches all text files, including the ones in the subdirectories.
So I tried find . | grep -E "\./*\.txt" just to see if I would get the same matches to then work my way towards my goal, but this doesn't match anything and this is where I'm stuck.


How do I go about doing what I described at the beginning of the question?

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
  • so you would like to move all files which are not ending with .txt from currunt directory /home/username/data to its sub-directory /home/username/data/other_files'.... am i right? – Siva Jan 02 '19 at 10:40
  • find DIR \! -name '*.txt' might help. Also can you add an example of source and target structure? Right now it's not clear whether the other directories beneath /home/username/data need to be recreated beneath /home/username/data/other_files/. – nohillside Jan 02 '19 at 10:41
  • @msp9011 You're correct. – Moving Man Jan 02 '19 at 10:59
  • @nohillside They don't need to be recreated within other_files because I only want to move files that are directly on the root of home/username/data. – Moving Man Jan 02 '19 at 11:00
  • some greps have a -v flag that negates the results of the regex – JGNI Jan 02 '19 at 15:18
  • when doing find -exec xyz to do an action on a lot of files, change it to find -exec echo xyz so you can see what commands are being generated. You can pipe this to a file to inspect by eye, or to more, and when happy you can run said file. – Ben Jan 02 '19 at 17:40

4 Answers4

13

The simple shell loop variant (in bash):

shopt -s extglob dotglob nullglob

for pathname in ~username/data/!(*.txt); do
    ! test -d "$pathname" && mv "$pathname" ~username/data/other_files
done

The shell options set on the first line will make the bash shell enable extended globbing patterns (!(*.txt) to match all names not ending with .txt), it enables glob patterns to match hidden names, and it makes the pattern expand to nothing at all if nothing matches.

The body of the loop will skip anything that is a directory (or symbolic link to a directory) and will move everything else to the given directory.

The equivalent thing with find and GNU mv (will move symbolic links to directories if there are any, and will invoke mv for as many files as possible at a time, but those are the only differences):

find ~username/data -maxdepth 1 ! -type d ! -name '*.txt' \
    -exec mv -t ~username/data/other_files {} +

Related:

Kusalananda
  • 333,661
7
find /home/username/data -maxdepth 1 -type f ! -name '*.txt' -exec mv {} /home/username/data/other_files/ \;
  • maxdepth limits to the top directors
  • type ensures that only files are found, not directories
nohillside
  • 3,251
  • 2
    You'd better use -type f or it will try to move other_files, and any other subdirectory of data that we don't know about. – Kusalananda Jan 02 '19 at 11:20
  • @Kusalananda is correct, I'm going to need that. – Moving Man Jan 02 '19 at 11:21
  • I'm trying to figure out the behavior of -exec mv {} /home/username/data/other_files/ \;. How is it that this moves from the current directory to other_files? The syntax for move, usually, is mv source directory. – Moving Man Jan 02 '19 at 11:23
  • @MovingMan The {} will replaced by the current pathname that find is looking at. – Kusalananda Jan 02 '19 at 11:28
  • @Kusalananda Oh! That was my first guess, but then what is the meaning of \;? That's what led me to discard that very plausible interpretation. – Git Gud Jan 02 '19 at 11:30
  • 1
    @MovingMan -exec takes a utility and arguments. To know where that command line ends, find looks for ;. The ; needs to be escaped to protect it from the shell. – Kusalananda Jan 02 '19 at 11:33
  • just realized that the bash solution doesn’t work... – nohillside Jan 02 '19 at 11:35
  • @nohillside No, it also copies the other_files directory. – Kusalananda Jan 02 '19 at 11:36
2

This code should move all files not ending in ".txt" to your target folder, however if you happen to have files with the same name in different paths it will throw an error.

find /home/username/data ! -name "*.txt" -type f -maxdepth 1 -exec mv {} /home/username/data/other_files/ \;
1

The following line finds all files and hidden files in the current directory that are not *.txt and not a path and move them into newpath:

ls -1p | grep -v "^.*\.txt$" | grep -v ".*/$" | xargs mv -vt newpath

The following is the same but moves also hidden files:

ls -1ap | grep -v "^.*\.txt$" | grep -v ".*/$" | xargs mv -vt newpath

Both command lines don't scan directories recursively and don't move directories

If you have filenames that contains spaces you may use:

ls -1ap | grep -v "^.*\.txt$" | grep -v ".*/$" | xargs -d'\n' printf "\"%s\"\n" | xargs mv -vt newpath

  • 1
    This might have a problem with files whose names contain a space. – nohillside Jan 02 '19 at 12:23
  • Is true. But also using find we've the same issue. Have you an indication to solve this issue without using scripts?. – Sir Jo Black Jan 02 '19 at 12:29
  • 1
    This isn't an issue for find (use -exec ... {} or -print0 | xargs -0 ...). The problem can't be solved for text/pipe based shell constructs though (at least not with reasonable effort and complexity) – nohillside Jan 02 '19 at 12:32
  • I mean the issue in my command line. – Sir Jo Black Jan 02 '19 at 12:34
  • The problem can't be solved for text/pipe based shell constructs like yours (at least not with reasonable effort and complexity) – nohillside Jan 02 '19 at 12:38
  • 1
    I think the edit I've added to my answer may solve this issue. – Sir Jo Black Jan 02 '19 at 12:49
  • 1
    Nice. Did you try with file names containing \n as well? Sorry to be a nuisance here, but there are so many special cases to cover that it's usually just not worth the effort. It might be an interesting intellectual challenge though :-) – nohillside Jan 02 '19 at 12:53