{"id":3021,"date":"2022-04-16T17:31:55","date_gmt":"2022-04-16T21:31:55","guid":{"rendered":"https:\/\/blog.jonesling.us\/?p=3021"},"modified":"2022-04-16T17:43:09","modified_gmt":"2022-04-16T21:43:09","slug":"recursively-delete-directories-unless-specific-file-is-present","status":"publish","type":"post","link":"https:\/\/blog.jonesling.us\/?p=3021","title":{"rendered":"Recursively delete directories unless a specific file is present"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">There are several ways to do this, but my Google-fu may be weak because it took me much too long to figure this out.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I want to recursively delete directories with a specific name (or names) within directory structure, UNLESS the matched directory contains a sentinel file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In my case I want to make a C# directory structure &#8220;cleaner-than-clean&#8221; by removing all &#8216;bin&#8217; and &#8216;obj&#8217; directories, leaving just the user-generated files behind.\u00a0 This is pretty easy to achieve:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n#!\/bin\/env bash\n\ndir=\/path\/to\/project\n\nfind $dir -type d \\\n    \\( -name &#039;bin&#039; -o -name &#039;obj&#039; \\)\n    -print\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">This says &#8220;find things under <code>$dir<\/code> that are directories (<code>-type d<\/code>) and are named either &#8216;bin&#8217; (<code>-name 'bin'<\/code>) or (<code>-o<\/code>) named &#8216;obj&#8217; (<code>-name 'obj'<\/code>).\u00a0 The parentheses force the two <code>-name<\/code> statements to be considered as a single condition, so the effect is to return true if either item matches.  If the final result is true then print the path.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Notice that I&#8217;ve escaped (<code>\\<\/code>) the parentheses because I&#8217;m using bash.  Most UNIX shells do require these to be escaped, but yours may not.  I&#8217;ve also terminated each line by escaping it.  A single-line command may be spread over several lines this way, making it easier to read.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&#8216;bin&#8217; is also the conventional name for a directory of non-build executables, like helper scripts.\u00a0 I do have some, including this cleaner-than-clean cleaning script that I&#8217;m working out, and don&#8217;t want to delete those by accident.  The above command would find them, if they were in the directory tree.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>find<\/code> allows you to prune (<code>-prune<\/code>) the search tree, ignoring selective directories, according to certain criteria but it doesn&#8217;t support the concept of peeking into sub-directories.  Bummer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You may, however, execute independent commands (<code>-exec<\/code>) and use the results of those commands to affect <code>find<\/code>&#8216;s parameters, including <code>-prune<\/code>.  We can exec the test command, which can tell us if our sentinel file exists.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n#!\/bin\/env bash\n\ndir=\/path\/to\/project\nsentinel=.keep\n\nfind &quot;$basedir&quot; \\\n    -type d \\\n    \\( -name bin -o -name obj \\) \\\n    ! -exec test -e &quot;{}\/$sentinel&quot; &#039;;&#039; \\\n    -print\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">The new line executes <code>test<\/code> to see if the current path (<code>{}<\/code>) contains a file called <code>$sentinel<\/code> (I&#8217;ve defined $sentinel to be <code>.keep<\/code> but any filename will do), which returns true if it exists.  The line is negated (<code>!<\/code>) so if the sentinel is found further actions are skipped.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The final step is to actually delete the directory.  We call <code>rm -Rf<\/code> (<code>-R<\/code> = recursive, <code>-f<\/code> = force) because we just want the whole thing gone, no questions asked.  The trailing plus (<code>+<\/code>) tells <code>find<\/code> that <code>rm<\/code> can accept multiple paths in a single call, rather than calling rm once for each path.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n#!\/bin\/env bash\n\ndir=\/path\/to\/project\nsentinel=.keep\n\nfind &quot;$basedir&quot; \\\n    -type d \\\n    \\( -name bin -o -name obj \\) \\\n    ! -exec test -e &quot;{}\/$sentinel&quot; &#039;;&#039; \\\n    -print \\\n    -exec rm -Rf &#039;{}&#039; \\+\n<\/pre><\/div>","protected":false},"excerpt":{"rendered":"<p>There are several ways to do this, but my Google-fu may be weak because it took me much too long to figure this out. I want to recursively delete directories with a specific name (or names) within directory structure, UNLESS the matched directory contains a sentinel file. In my case I want to make a &hellip; <a href=\"https:\/\/blog.jonesling.us\/?p=3021\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Recursively delete directories unless a specific file is present&#8221;<\/span><\/a><\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_crdt_document":"","wprm-recipe-roundup-name":"","wprm-recipe-roundup-description":"","advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[9],"tags":[84,749,151,750,83,190],"class_list":["post-3021","post","type-post","status-publish","format-standard","hentry","category-linux","tag-bash","tag-c","tag-dad-needs-to-stop-bringing-work-home","tag-dotnet","tag-linux","tag-unix"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p4o3FW-MJ","jetpack-related-posts":[],"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=\/wp\/v2\/posts\/3021","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3021"}],"version-history":[{"count":8,"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=\/wp\/v2\/posts\/3021\/revisions"}],"predecessor-version":[{"id":3029,"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=\/wp\/v2\/posts\/3021\/revisions\/3029"}],"wp:attachment":[{"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3021"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3021"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3021"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}