`find` only in certain subdirectories - linux

I have this directory structure
$ tree test
test
├── test1
│   ├── test11
│   │   └── file.txt
│   ├── test12
│   │   └── file.txt
│   └── test13
│   └── file.txt
├── test2
│   └── file.txt
└── test3
└── file.txt
6 directories, 5 files
and I want a find command that will return
$ find test -constrain_to_paths test/test{13} -name file.txt
test/test3/file.txt
test/test1/test11/file.txt
test/test1/test12/file.txt
test/test1/test13/file.txt
So basically search for file.txt only in directories test/test1 and test/test3. To get this going I tried this
$ find test \( -path './test3/*' -o -path './test2/*' \) -name file.txt
but it returns nothing.

Seems this answer could help you https://unix.stackexchange.com/a/60850
For example: find test/test1 test/test3 -name file.txt

Related

Using bash command to copy files from a subfolder to another

I have the following structure:
.
├── dag_1
│   ├── dag
│   │   ├── current
│   │   └── deprecated
│   └── sparkjobs
│   ├── current
│      | └── spark_3.py
│   └── deprecated
│      └── spark_1.py
│      └── spark_2.py
├── dag_2
│   ├── dag
│   │   ├── current
│   │   └── deprecated
│   └── sparkjobs
│   ├── current
│      | └── spark_3.py
│   └── deprecated
│      └── spark_1.py
│      └── spark_2.py
I want to create a new folder getting only current spark jobs, my expected output folder is:
.
├── dag_1
| └── spark_3.py
├── dag_2
└── spark_3.py
I've tried to use
find /mnt/c/Users/User/Test/ -type f -wholename "sparkjob/current" | xargs -i cp {} /mnt/c/Users/User/Test/output/
Although my script is not writing the files and returns me no error. How can I solve this?
Use this, install command take the input file and copy it to another dir structure, creating the whole tree of dirs if necessary as mkdir -p transparently:
(you need to add wildcard * in -wholename to effectively find files)
find . -type f -wholename "*/sparkjob/current/*" -exec bash -c '
dir=${1#./} dir=${dir%%/*} file=${1##*/}
install -D "$1" "./$dir/$file"
' bash {} \;
Exemple of what is done:
install -D ./dag_2/sparkjob/current/spark_3.py ./dag_2/spark_3.py
install -D ./dag_1/sparkjob/current/spark_3.py ./dag_1/spark_3.py
The source path is an example, if longer, no issue.
First you should check what find returns by removing everything after |. You'll see find doesn't find any files. The reasons:
as the name implies, -wholename matches the whole name, so you need */sparkjob/current/*
according to your tree output, the folder is not named sparkjob but sparkjobs.
I'd start with something like this:
find /mnt/c/Users/User/Test/ -type f -wholename "*/sparkjobs/current/*" -print0 | while IFS= read -r -d '' file; do
echo mv "$file" "$(realpath "$(dirname "$file")"/../..)"
done
I added an echo so you can check all paths and commands are correct.
You may want to trade simplicity for performance. See https://mywiki.wooledge.org/BashFAQ/001 if performance is important (many files or frequent runs).
You'll want to do:
mkdir ../new_folder
find . -type f \
-path '*/sparkjobs/current/*' \
-exec sh -c 'f=$1
new=${f/sparkjobs\/current\//}
dest="../new_folder/$(dirname "$new")"
mkdir -p "$dest"
cp -v "$f" "$dest"' sh '{}' \;
‘./dag_1/sparkjobs/current/spark_3.py’ -> ‘../new_folder/./dag_1/spark_3.py’
‘./dag_2/sparkjobs/current/spark_3.py’ -> ‘../new_folder/./dag_2/spark_3.py’
This looks pretty straightforward.
for d in $old_loc/dag_*
do mkdir -p "$new_loc/${d##*/}"
cp "$d"/sparkjobs/current/spark_*.py "${d##*/}"
done

How to change all hidden folders/files to visible in a multiple sub directories

I have hundreds of sub directories in a directory that all have hidden files in them that I need to remove the period at the beginning of them to make them visible. I found a command to go into each directory and change them to make them visible but I need to know how to make this command work from one directory up.
rename 's/\.//;' .*
I have tried about an hour to modify this to work one level up but don't understand the Perl string enough to do it. If someone could help out I am sure it's simple and I just can't land on the right answer.
This requires a find that supports the + (can use \; instead, which will call rename multiple times), but even POSIX find specifies it:
find -mindepth 1 -depth -exec rename -n 's{/\.([^\/]*$)}{/$1}' {} +
The -depth option prevents directories from being renamed before all the files in them are renamed
-mindepth 1 prevents find from trying to rename the current directory, ..
-n is to just print what would be renamed instead of actually renaming (has to be removed to do the renaming).
The regular expression removes the last period after which there are no forward slashes, if it is preceded by a forward slash.
rename doesn't overwrite existing files, unless the -f ("force") option is used.
For a test directory structure like this:
.
├── .dir1
│   ├── .dir2
│   │   ├── .dir3
│   │   │   └── .file2
│   │   └── .file1
│   ├── file3
│   └── .file6
├── dir5
│   └── .file5
├── .file4
├── test1.bar
└── test1.foo
the output is
rename(./dir5/.file5, ./dir5/file5)
rename(./.file4, ./file4)
rename(./.dir1/.file6, ./.dir1/file6)
rename(./.dir1/.dir2/.file1, ./.dir1/.dir2/file1)
rename(./.dir1/.dir2/.dir3/.file2, ./.dir1/.dir2/.dir3/file2)
rename(./.dir1/.dir2/.dir3, ./.dir1/.dir2/dir3)
rename(./.dir1/.dir2, ./.dir1/dir2)
rename(./.dir1, ./dir1)
and the result after removing -n is
.
├── dir1
│   ├── dir2
│   │   ├── dir3
│   │   │   └── file2
│   │   └── file1
│   ├── file3
│   └── file6
├── dir5
│   └── file5
├── file4
├── test1.bar
└── test1.foo
safely_unhide:
#!/usr/bin/perl
use strict;
use warnings;
use File::Basename qw( fileparse );
for (#ARGV) {
my $o = $_;
my ($fn, $dir_qfn) = fileparse($_);
$fn =~ s/^\.//
or next;
my $n = "$dir_qfn/$fn";
if (stat($n)) {
warn("Skipping \"$o\": \"$n\" already exists\n");
next;
}
elsif (!$!{ENOENT}) {
warn("Skipping \"$o\": Can't stat \"$n\": $!\n");
next;
}
rename($n, $o)
or warn("Skipping \"$o\": Can't rename to \"$n\": $!\n");
}
Usage:
find -type f -exec safely_unhide {} + # Supports all file names. Requires GNU find
find -type f | xargs safely_unhide # Doesn't support newlines in file names.
find -type f -print0 | xargs -0 safely_unhide # Supports all file names.
Drop -type f and add -depth if you want to rename hidden dirs too.

bash script to rename following a pattern in subdirectories and make a copy

I am trying to do an iterative renaming of certain files in all directories.
homefolder/folder1/ouput/XXXXX_ab.png
homefolder/folder1/ouput/XXXXX_abcdefg.png
homefolder/folder2/ouput/XXXXX_ab.png
homefolder/folder2/ouput/XXXXX_abcdefg.png
homefolder/folder3/ouput/XXXXX_ab.png
homefolder/folder3/ouput/XXXXX_abcdefg.png
...
homefolder/folder500/ouput/XXXXX_ab.png
homefolder/folder500/ouput/XXXXX_abcdefg.png
I want to get the folder name (ex. folder1, folder2, ... folder500) and pass it to the two png files as a prefix and remove those five Xs at the beginning of each file.
The pattern of those png files are:
XXXXX_ab.png
XXXXX_abcdrfg.png
so only the first five characters are different in each subdirectory, which will be replaced by the name of its parent directory, those folder names.
the results will be:
homefolder/folder1/ouput/folder1_ab.png
homefolder/folder1/ouput/folder1_abcdefg.png
homefolder/folder2/ouput/folder2_ab.png
homefolder/folder2/ouput/folder2_abcdefg.png
homefolder/folder3/ouput/folder3_ab.png
homefolder/folder3/ouput/folder3_abcdefg.png
...
homefolder/folder500/ouput/folder500_ab.png
homefolder/folder500/ouput/folder500_abcdefg.png
at the end of renaming, create a copy of these two newly renamed files inside another folder in the homefolder, for example all_png_folder.
find . -iname "*_ab.png" -exec rename _ab.png folder1_ab.png '{}' \;
find . -name "*_ab.png" -exec cp {} ./all_png_folder \;
Here is a start, the copying at the end should be a trivial addition.
#!/usr/bin/env bash
files=$(find . -type f -name "*_ab.png" -or -name "*_abcdefg.png")
for file in $files; do
foldername=$(cut -d '/' -f 2 <<< $file)
# The name of the png-file minus the leading xxxxxx
pngfile=$(basename "$file" | cut -d '_' -f 2)
destinationdir=$(dirname "$file")
mv $file "$destinationdir/$foldername"'_'"$pngfile"
done
Demo
$ tree
.
├── folder1
│   └── ouput
│   ├── foo_bar.png
│   ├── xxxxx_abcdefg.png
│   └── xxxxx_ab.png
├── folder2
│   └── ouput
│   ├── xxxxx_abcdefg.png
│   └── xxxxx_ab.png
└── rename.sh
4 directories, 6 files
$ ./rename.sh
$ tree
.
├── folder1
│   └── ouput
│   ├── folder1_abcdefg.png
│   ├── folder1_ab.png
│   └── foo_bar.png
├── folder2
│   └── ouput
│   ├── folder2_abcdefg.png
│   └── folder2_ab.png
└── rename.sh

Linux/shell - Remove all (sub)subfolders from a directory except one

I've inherited a structure like the below, a result of years of spaghetti code...
gallery
├── 1
│   ├── deleteme1
│   ├── deleteme2
│   ├── deleteme3
│   └── full
│   ├── file1
│   ├── file2
│   └── file3
├── 2
│   ├── deleteme1
│   ├── deleteme2
│   ├── deleteme3
│   └── full
│   ├── file1
│   ├── file2
│   └── file3
└── 3
├── deleteme1
├── deleteme2
├── deleteme3
└── full
├── file1
├── file2
└── file3
In reality, this folder is thousands of subfolders large. I only need to keep ./gallery/{number}/full/* (i.e. the full folder and all files within, from each numbered directory within gallery), with everything else no longer required and needs to be deleted.
Is it possible to construct a one-liner to handle this? I've experimented with find/maxdepth/prune could not find an arragement which met my needs.
(Update: To clarify, all folders contain files - none are empty)
Using PaddyD answer you can first clean unwanted directories and then delete them:
find . -type f -not -path "./gallery/*/full/*" -exec rm {} + && find . -type d -empty -delete
This can easily be done with bash extglobs, which allow matching all files that don't match a pattern:
shopt -s extglob
rm -ri ./gallery/*/!(full)
How about:
find . -type d -empty -delete

Recursively remove directories inside folder on same level Linux

My structure is as follows:
├── Proj 1
│   ├── .git
│   ├── LICENSE
│   ├── README.md
│   └── example.cpp
├── Proj 2
│   ├── .git
│   ├── root_folder
│   └── README.md
├── Proj 3
│   ├── .git
│   ├── root_folder
│   └── README.md
...
Why is it when I do a rm -ri \.git it says:
rm: cannot remove `.git': No such file or directory
you could try
rm -ri */.git
(not sure that's what you want)
The semantics of rm's recursive search are not right for finding and deleting directories below the current one. The -ri flag will probably show each file beneath the .git folder right?
Happily if you are using bash, a one-liner with find will do what you need:
find . -name .git -type d -exec bash -c 'read -p "$0: Delete? " -n 1 -r && echo "" && case $REPLY in y) rm -rf "$0" ;; esac' {} \; -prune

Resources