0

I have a directoryOne folder which has several files:

solution/directoryOne/

  • 100test.txt
  • 101test.txt
  • 102test.txt
  • 103test.txt
  • 104test.txt

solution/directoryTwo/

I'm trying to use a shell script to copy all the files except 104test.text from directoryOne to directoryTwo.

#!/bin/bash
cp directoryOne/!(104*) solution/directoryTwo/

It didn't work out. Is there any way we can use some kind of expression to achieve this?

1 Answers1

1

Try this:

find solution/directoryOne -maxdepth 1 -type f | grep -v 104 | xargs -I _ cp _ solution/directoryTwo

Explanation:

  • find lists full paths of files (with options only normal files and don’t descend directories)
  • grep -v returns only lines matching the regex 104, but -v negates the match, like “except”
  • xargs runs a command tacking the input on the end, but -I _ sets the placeholder for the input
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • It works but need a * after solution/directoryOne. find solution/directoryOne/* | grep -v 104 | xargs -I _ cp _ solution/directoryTwo – Francis Zhu Aug 08 '18 at 00:02
  • Can you explain it a little bit more? what do -v and xargs mean? or is there any page I can have a look? – Francis Zhu Aug 08 '18 at 00:03
  • @FrancisZhu OK, updated. (And have a command line so I just thumbed it in on my phone while on the train) – Bohemian Aug 08 '18 at 00:04
  • Explanation added – Bohemian Aug 08 '18 at 00:57
  • This would also skip files named, say, 1104test.txt and 104saveme.txt. – user1934428 Aug 08 '18 at 07:40
  • @user1934428 then tune the regex accordingly. We’re not here to spoon feed, just steer in the right direction – Bohemian Aug 08 '18 at 08:24
  • Agreed ... on second thought, I think what is really not correct, is the `find` command. Shouldn't it be `find solution/directoryOne -maxdepth 1 -type f`, or do I miss something here? – user1934428 Aug 08 '18 at 12:55
  • @user1934428 I did think of adding those params to find originally but kept it simple. I’ve added them now. – Bohemian Aug 08 '18 at 16:17
  • 1
    Please, no. This is **much** buggier than `find solution/directoryOne -maxdepth 1 -type f '(' '!' -name '*104*' ')' -exec cp -- '{}' solution/directoryTwo ';'`, and for no good reason -- the bugs you get from using `xargs` here (around unusual filenames, f/e) add no compensatory value. – Charles Duffy Aug 08 '18 at 16:39
  • See the warnings about `xargs` use in http://mywiki.wooledge.org/UsingFind#Actions_in_bulk:_xargs.2C_-print0_and_-exec_.2B-, or the section "The Separator Problem" in https://en.wikipedia.org/wiki/Xargs#The_separator_problem -- keeping in mind that files are permitted to have literal newlines within their names. – Charles Duffy Aug 08 '18 at 16:40
  • ...BTW, if you had GNU `cp`, I would suggest `find solution/directoryOne -maxdepth 1 -type f '(' '!' -name '*104*' ')' -exec cp -t solution/directoryTwo -- {} +`, to run the smallest possible number of `cp` instances necessary to process all the files. – Charles Duffy Aug 08 '18 at 16:44
  • @Bohemian, what does -maxdepth 1 mean? only find 1 file? – Francis Zhu Aug 09 '18 at 01:02
  • @CharlesDuffy, the suggestion you provide is complaining **-t** is an invalid option – Francis Zhu Aug 09 '18 at 01:22
  • @FrancisZhu, that's why I said it would only work if you had the GNU version of `cp` (and not the MacOS version or the Busybox version or any other). `-t` is a GNU extension that lets you put the destination first and the source filenames after. If you need portability, use the other version I gave upthread -- the important thing from a correctness perspective is **not** to use `xargs` when handling uncontrolled filenames (except when they're in a NUL-delimited list, allowing use of `-0`). – Charles Duffy Aug 09 '18 at 01:23
  • @FrancisZhu, ...as for `-maxdepth 1`, that tells it not to recurse into subdirectories. – Charles Duffy Aug 09 '18 at 01:24
  • @Bohemian, would you accept an edit in line with my comments (removing `grep` in favor of `find` arguments, and either removing `xargs` outright in favor of the `-exec` action, or using the `-print0` action with `xargs -0`)? – Charles Duffy Aug 09 '18 at 01:28
  • @Charles no, that would change the answer. Also, I like using a regex tool to handle regex, because I can use it for filtering any kind of output so it's good to familiarize oneself with grep, instead of using some reinvented-wheel swiss army knife implementation within `find` (no thanks). However, I encourage you to post your idea as a new answer - it's good to have options for future users to choose from. – Bohemian Aug 09 '18 at 03:35
  • @Bohemian, the question is properly closed as a duplicate, and not presently open to new/additional answers. If this were posted on the instance we're duplicate of, I'd absolutely agree that the right thing would be to compete in the marketplace of ideas by adding my own competing answer. :) – Charles Duffy Aug 09 '18 at 03:36
  • ...as for "using a regex tool to handle regex", I can see that as a justifiable position when you aren't trading against correctness for it -- but to handle streams of arbitrary filenames safely you need NUL-delimited records, and that means GNU `grep` with the `-z` argument... and as observed above, our OP here has non-GNU tools. – Charles Duffy Aug 09 '18 at 03:37
  • As a concrete example of what I was referring to around safety, consider `f=$'\n/etc/passwd\n'; mkdir -p -- "$f" && touch "$f/foo'` inserting `/etc/passwd` on its own line into the output of a `find` command such as this, if it didn't have `-maxdepth 1`. – Charles Duffy Aug 09 '18 at 03:39
  • (well -- there *are* correct options other than `grep -z`; `find solution/directoryOne -maxdepth 1 -exec bash -c 'for arg; do [[ $arg = *104* ]] && continue; cp "$arg" solution/directoryTwo; done' _ {} +` would be another... but still the point holds that `find ... -print | grep | xargs` compromises correctness for simplicity, and I'm not comfortable with that tradeoff in a teaching resource without making the sacrifice an explicit, documented tradeoff readers are making an informed decision to accept). – Charles Duffy Aug 09 '18 at 03:44
  • ...as another example, `touch $'foo\n..\nbar'` inserts `..` into your file stream even with the `-maxdepth 1`. If one is deleting the content that's found, that can be... unfortunate. – Charles Duffy Aug 09 '18 at 13:50