4

I find it much easier to use find and then grep -v in the pipe to filter files instead of developing complex regex patterns. However, when I pipe something to zmv like this:

find | grep -v TFLM | zmv "(*)" "TFLM \$1"

It just ignores the input and proceeds applying the transformation to all files. Is there a way to tell it to use pipe input? I guess I could move away the filtered out files and then use zmv, but that isn't really a solution.

0

3 Answers 3

6

zmv does not read from standard input.

I would probably use find with mv here, or zmv with a zsh filename glob, but not both, and without involving grep at all. Using grep should be done on text that is divided into lines, not filenames (which could potentially include embedded newlines).

With filename globs (will only act on the current directory, and only on non-hidden files):

zmv '^TFLM *' 'TFLM $f'

Recursively, not renaming directories, including hidden files and files in hidden directories like find would:

zmv '(**/)(^TFLM *)(#qD^/)' '${1}TFLM $2'

With find (but without the conflict handling of zmv, so adding a -i option for safety):

find . ! -type d ! -name 'TFLM *' -exec sh -c '
    for pathname do
       mv -i "$pathname" "${pathname%/*}/TFLM ${pathname##*/}"
    done' sh {} +

In bash (for the current directory only, excluding hidden files):

shopt -s extglob
for name in !(TFLM *); do
    mv -i -- "$name" "TFLM $name"
done

Or, in bash with the Perl rename utility:

shopt -s extglob
rename 's|/|/TFLM |' ./!(TFLM *)

(without the ./, some variants would fail if there were file names starting with -. Not all variants support -- to mark the end of options).

2
5

What you are doing is not how find, grep and zmv are intended to work. First of all you use find to search for files and then grep for pattern; that doesn't make any sense. The command find has built-in pattern matching, for example in GNU find starting from basic -name through -iname, -path, -regex and many many more. You can even change the syntax for regular expression if you prefer some, with -regextype. It is not only that you are doing something not fast or with too many commands involved, what is worst your command is error prone, for example if file has space inside.

Much better is pure find with -exec option followed by external command like mv. With some care this solution can be very portable across different systems.

But, since you are using zsh then it begs for using all its glory, so just add -vn option to zmv and experiment with different patterns, most probably you want

zmv -vn '(^(*TFLM*))' 'TFLM $1'

-v means verbose and -n prevents execution, just prints what would be done (that's great for testing).

4
  • Or just zmv -n '^*TFLM' 'TFLM $f'. Or recursively and not renaming directories: zmv -b '(**/)(^*TFLM*)(#q^/)' '${1}TFLM $2' Commented Aug 4, 2018 at 19:58
  • 1
    @StéphaneChazelas The pattern '^*TFLM' won't work, one needs at least one pair of parenthesis and second *, so '(^*TFLM*)' is the minimum, at least on my zsh-5.5.1. Also -b is unrecognizable option; you are probably using very new zsh or some non-standard settings.
    – jimmij
    Commented Aug 4, 2018 at 20:18
  • zmv -n '^*TFLM*' 'TFLM $f' (sorry, missed the second * above) works fine for me with 5.4.2 or 5.5.1. I can't see why you'd need parenthesis. Sorry, -b was also a typo, (b is next to n on my keyboard). Note that -n implies -v. Commented Aug 4, 2018 at 20:23
  • @StéphaneChazelas ok, with $f (but not $1) and second * its fine without parenthesis.
    – jimmij
    Commented Aug 4, 2018 at 20:28
2

I work in a similar manner mentally as you, where I prefer layering grep commands on to the end. It's hard to resist the urge but in these types of situations you have to, or mentally remember that when you want to start acting on the list coming from find | ... that you have to start pulling in xargs. When you get to this point, it's time to switch to find <regex> -exec ....

In your scenario, the pattern you might consider enlisting here is something like this:

$ find . ! -name "*TFLM*" -exec zmv "{}" "TFLM {}" \;

But this will not work, per comments from @StephaneChazelas:

Since zmv is a zsh function, it cannot be executed directly by find. Even if one made a standalone script wrapper around zmv, calling it like that as -exec zmv "{}"... wouldn't make much sense (completely defeat the purpose of zmv) and would introduce a command injection vulnerability

So you're left with the more traditional options of using one of the methods shown in this U&L Q&A titled: Batch renaming files.

Or using zmv directly to do the renaming itself. Since you're using zsh and zmv it's likely you don't even need to enlist the help of find at all.

$ zmv "(^*TFLM*)" "TFLM \$1"

NOTE: Take my advice on the zmv and zsh with a grain of salt. I don't actually use Zsh, I typically am in Bash all day.

References

2
  • @StéphaneChazelas - updated Q w/ you comment + modified.
    – slm
    Commented Aug 4, 2018 at 20:18
  • 1
    "would introduce a command injection vulnerability" idk about you, but when I rename files nobody is engineering their names, I'm simply not satisfied with their current pattern. So I don't know how that would be a relevant vulnerability. Feel free to correct me if I'm overlooking something.
    – xeruf
    Commented Aug 6, 2018 at 19:03

You must log in to answer this question.

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