1

My application X organizes projects in folders with a specific tree. The first level of the tree is defined (hard constraint), while all the contents are freely organized.

My goal is to iterate over all folders of the project tree, parse folder names to get targets based on conditions, then copy the selected targets to a new project folder, keeping the tree structure, so that application X is not going to complain.

So far, I've come up with this:

cp -pr `find . -type d | grep -i targetstring` ../newProjectFolder/

This has issues: 1) it doesn't preserve folder tree; 2) throws a lot of errors cp: cannot stat (see also this). It isn't just working as intended.

Moreover, parsing the output of find is generally a bad idea.

I was able to overcome first problem with --parents as suggested here. However, I'm copying way less than what I need due to point 2, so not a solution after all.

I am looking for a one-liner solution to the problem. Script based solution is available here. EDIT: as this was marked as duplicated of the link provided, I'll rephrase a little: I want a solution where I don't need to explicitly while and if-else my way to it. Piping find and grep provides all the locations to be copied, but cp is complaining. How do we feed them to cp, without it complaining?

I was looking for something using -exec as suggersted here, and came up with the following, which is not working (nothing gets copied) (only partial tree structure is copied, and files inside that structure are only copied if they match the regex as well):

find . -regex ".*targetstring.*" -exec cp -rp --parents \{\} ../newProjectFolder \;
Daemon Painter
  • 3,208
  • 3
  • 29
  • 44
  • Duplicate of: [Shell - copying directories recursively with RegEx matching preserving tree structure](https://stackoverflow.com/questions/50513734/shell-copying-directories-recursively-with-regex-matching-preserving-tree-stru). *I am looking for a one-liner solution to the problem.* remove linebreaks and boom, it's a one liner – oguz ismail Apr 08 '20 at 15:56
  • Yeah, ok, everything is a one-liner that way, even a script where I use nested loops over loops. Is there a way to achieve this without looping and if-elsing my way, calling simple, made-to-purpose software pieces? – Daemon Painter Apr 08 '20 at 16:00
  • Not that I know of. Try your chance at [softwarerecs.se] – oguz ismail Apr 08 '20 at 16:33

1 Answers1

2

"one-liner" but sed will not work for newlines $'\n' in filename

assuming you cd into project folder and run find . always with .
it will match targetstring (on any level) and will copy the whole (containing) tree from first level

example tree
searchProjectFolder/some/parent/folders/matched/targetstring/foo/bar/

a search for *targetstring* inside searchProjectFolder/ will copy whole ./some folder (including all files and subfolders)

while -regex or -ipath will print all files, -iname will only print the matching folder targetstring/ itself. you can limit search to specific level(s) with -mindepth 5 and -maxdepth 5, and specify the target type to match folders only with -type d (but let us take away this options for now)

create newProjectFolder and run find on example tree

mkdir newProjectFolder
cd searchProjectFolder
find . -iname "*targetstring*"

result on example tree is

./some/parent/folders/matched/targetstring

now find result is piped to grep so we can cut the string to first level only

find . -iname "*targetstring*" | grep -o ^\./[^/]*

result on example tree is

./some

instead of grep we can use sed to quote the "folder name" (may have spaces)

find . -iname "*targetstring*" | sed -n 's,^\(\./[^/]*\).*,"\1",p'

result on example tree is

"./some"

finally let's cp all folders with eval because "folder names" are quoted. this is the "one-liner":

eval cp -a $(find . -iname "*targetstring*" | sed -n 's,^\(\./[^/]*\).*,"\1",p') ../newProjectFolder

result on example tree is

cp -a "./some" ../newProjectFolder

for better understanding i will explain the sed

sed -n 's,^\(\./[^/]*\).*,"\1",p'
sed -n 's, ^  \(  \.  /  [^/]*  \)  .* , "\1" ,p'

-n = do not print input
p in ,p' = print only matching output

\1 = print the first capture group
\( = begin of capture group
\) = end of capture group

^ = match begin of string only
\. = single dot (.)
/ = single slash (path delimiter)
[^/] = any single byte, but not a slash
[^/]* = any string not containing slashes
. = single byte
.* = any string

so the (first) capture group is (begin of line)
./example string

the capture group is quoted ("\1")
"./example string"


edit:

another "real" one-liner if you only want to copy folder targetstring/ (with parent tree)

mkdir newProjectFolder
cd searchProjectFolder
find . -iname "*targetstring*" -exec cp -a --parents {} ../newProjectFolder \;

result on example tree is

cp -a --parents "./some/parent/folders/matched/targetstring" ../newProjectFolder
alecxs
  • 701
  • 8
  • 17
  • which folder do you want to copy? `./some/parent/folders/matched/targetstring/foo/bar/` – alecxs Apr 09 '20 at 17:25
  • `find . -iname "*targetstring*" -exec cp -a --parents {} ../newProjectFolder \;` does only copy folder *"targetstring"* (with tree) - i guess you want copy whole folder *"some"*? – alecxs Apr 09 '20 at 17:26
  • I actually like this very much! For once, it has no while loops with if conditions, and uses piece of software as they are intended to. I want to copy only `targetstring/` and all its content, keeping the tree rooting at `some/`. Say some has also a folder `parent2/` and none of the children of `parent2/` match, then `parent2/` shall not be copied. Rinse and repeat at all levels. – Daemon Painter Apr 09 '20 at 17:38
  • then you already have had the solution yourself, just replace `-regex` with `-iname` (see my last comment) – alecxs Apr 09 '20 at 17:41