9

I'm trying to move files from Terminal but only those from the results of a grep query.

ls -l | grep -i s02 | mv

What is the best way to complete the command above?

  • 1
    Where do you want to mv the files to? – nohillside Aug 2 at 15:29
8

You can accomplish this by just running

cd directory/containing/the/files
mv *[sS]02* /path/to/target/

For more complex operations there is also the option to use find to find all relevant files. The example from your question could also be written as

cd directory/containing/the/files
find . -type f -maxdepth 1 -iname '*s02*' -exec mv {} /path/to/target/directory/ \;

It's worth to have a look at man find to see which other options are available.

  • Along time ago someone told me that regular expressions match strings and globs match/generate filenames. Possibly, man 3 glob may help. – fd0 Aug 5 at 22:22
1

For example with bash. Given the files below

$ ls -1
test1
test2
test3

1) To avoid surprises let's take a look at the commands first

$ for i in test*; do echo mv $i $i.ext; done
mv test1 test1.ext
mv test2 test2.ext
mv test3 test3.ext

and execute the commands if this is what we want

$ for i in test*; do mv $i $i.ext; done
$ ls -1
test1.ext
test2.ext
test3.ext

Fit the path to your needs.

2) It is possible to use sed and xargs. Let's test it first

$ ls -1 test* | sed 'p;s/test/test_/' | xargs -n2 printf "%s %s\n"
test2 test_2
test1 test_1
test3 test_3

and execute the commands if this is what we want

$ ls -1 test* | sed 'p;s/test/test_/' | xargs -n2 mv
$ ls -1
test_1
test_2
test_3

Spaces in the filename

These scripts won't work as expected if the filenames include spaces. In Unix space is used as a separator. For example with the files

$ ls -1
test 1
test 2
test 3

it is clear that the commands below can't work as expected

$ for i in test*; do echo mv $i $i.ext; done
mv test 1 test 1.ext
mv test 2 test 2.ext
mv test 3 test 3.ext

The solution is to exclude the space from the list of separators

$ IFS="`printf '\n\t'`"

and prepend it to the commands that should process the filenames with spaces. The command below

$ IFS="`printf '\n\t'`"; for i in test*; do mv $i $i.ext; done

works as expected

$ ls -1
test 1.ext
test 2.ext
test 3.ext

Next option is the quotation of the arguments

for i in test*; do mv "$i" "$i.ext"; done

This way it's possible to use the rich variety of Unix filters and avoid the limitation to the "find ... -exec ... {}" construct. For other limitations see the notes under the line.

Notes on macOS

1) Quoting from opengroup.org - Product Standard: Commands and Utilities V4

1.4.21 sh
Question 31: Is the environment variable IFS ignored when the shell is invoked?
Response: Yes
Rationale: The specification allows the sh command ignore the setting of the IFS environment variable on invocation. The setting of this variable has been used to breach security on systems which use the shell to interpret a call to the system() and execvp() interfaces.
Reference: Technical Standard, Shell and Utilities, Issue 6, Chapter 4, Utilities, sh, ENVIRONMENT VARIABLES, IFS.

2) Quoting from developer.apple.com - Shell Input and Output

You can modify the behavior of the read command by modifying the shell variable IFS (short for internal field separators). The default behavior is to split inputs everywhere there is a space, tab, or newline. By changing this variable, ...

3) Quoting from developer.apple.com - Basic Control Statements - Standard for Loops

In the next example, the list is *.JPG ...

#!/bin/sh
for i in *.JPG ; do
    mv "$i" "$(echo $i | sed 's/\.JPG$/.x/')"
    mv "$(echo $i | sed 's/\.JPG$/.x/')" "$(echo $i | sed 's/\.JPG$/.jpg/')"
done


These scripts may not work as expected if there are special characters in the filename. Replace such characters first. See How to remove special character. There are dozens of articles how to rename files with spaces and other characters that make troubles e.g. Filenames with spaces breaking for loop, find command. It's a good idea to get rid of such characters and translate the filenames to the format you prefer (e.g. rename-file) before processing them in *nix. See Fixing Unix/Linux/POSIX Filenames.

  • This will fail with filenames like test 1 and similar. It will also not do what you expect it to do if the current directory contains files with names starting with test. – nohillside Aug 2 at 7:47
  • Renaming is not necessary, making sure to use options/tools which ensure \0-terminated strings and taking care of setting/using variables is all it takes. See mywiki.wooledge.org/BashPitfalls and mywiki.wooledge.org/BashFAQ :-) – nohillside Aug 2 at 9:09
  • 1
    Well, IMHO it's more secure, efficient and flexible to get rid of pathological filenames and don't care about the pitfalls i.e. to normalize the filenames ASAP and process them later. Security and Modularity is all it takes. – Vladimir Botka Aug 2 at 9:45
  • 1
    Thanks for the links, but maybe people should focus on learning how a system works and not how it is wrong. If shell scripts or programs break on file names containing characters which the file system (and the standard) allow those scripts/programs are simply broken and need to be fixed. – nohillside Aug 2 at 13:15
  • 2
    Sorry to say, the solution for the spaces issues is not the change the value of IFS. The solution is to not use a for loop over the the result of find. Using find ... -exec (combined with xargs and/or sh -c) is the canonical Unix way of doing these kind of things. – nohillside Aug 3 at 13:49

You must log in to answer this question.

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