finding and copying files using shell script (folder name and file name is same) - bash

I have a directory that include several folders. I want to write a shell script to find a file in another directory that has the same name as the mentioned folders.
To clarify I have a directory that include test1 and test2 folders. I have another directory that have two files with the names of test1 and test2. My goal is to go to the directory that have folders and then get the folder names. Then by using folder name find the file that has the same name and copy it to that folder that has the same name.
I wrote the following script but it could not copy the file.
for d in /home/Documents/test/*/ ; do
find /home/Documents/binaries/ -name "$d" -type f -exec cp {} /home/Documents/test/$d \;
cd "$d"
done

$d will be set to a full path name, such as /home/Documents/test/test1/, but you only need test1 as the argument for the -name primary. You can use parameter expansion to strip the leading path from the value of $d, but it will take two steps.
for d in /home/Documents/test/*/ ; do
fname=${d##*/} # Strip /home/Documents/test/
fname=${fname%/} # Strip the trailing /
# Note that d is already the full directory name you want to use
# as the target file for `cp`
find /home/Documents/binaries/ -name "$fname" -type f -exec cp {} "$d" \;
done
The cd command doesn't seem to accomplish anything useful, since you are using absolute path names throughout.
Simpler, though, would be to change the working directory to /home/Documents/test first.
cd /home/Documents/test/
for d in */; do
find /home/Documents/binaries/ -name "$d" -type f -exec cp {} /home/Documents/test/"$d" \;
done

Related

bash/command line: How to copy several files in different directories at once

I am trying to copy a file (let's call it list.txt) that is in several places and give it a new name. Some folders should be ignored (e.g. those containing _old)
Assume the following structure:
/data/folder1/subfolder/user/list.txt
/data/folder2/subfolder/user/list.txt
/data/folder2/subfolder_old/user/list.txt
/data/folder3/anothername/other/list.txt
/data/folder3/anothername/ignorethisfolder/list.txt
With following command I get the exact files listed that I expect:
ll /data/*/{subfolder,anothername}/{user,other}/*.txt 2>/dev/null
That's what I want to use for copying and the following:
cp /data/*/{subfolder,anothername}/{user,other}/*.txt /data/*/{subfolder, anothername}/{user,other}/*.txt_backup 2>/dev/null
Unfortunately, this does not deliver the desired result.
What should the command for the copying process be?
Unfortunately the cp command doesn't work that way, you need to copy each file independently:
for f in /data/*/{subfolder,anothername}/{user,other}/*.txt
do
cp "$f" "${f}_backup"
done
You could use the find command:
find /data -not -path "*/*_old/*" -name "list.txt" -type f -exec cp {} {}_backup \;
Explanation
find /data traverse your /data directory
-not -path "*/*_old/*" exclude any paths with a directory ending in _old
-name "list.txt" select files/directories with the name list.txt
-type f only select files
-exec cp {} {}_backup \; execute cp on all remaining matching paths the {} is replaced with the match path and the ; is needed to end the exec statement
note
You can add your 2>/dev/null if you still get errors on some results.

Find and rename multiple files using a bash script in Linux

As an example, in a directory /home/hel/files/ are thousends of files and hundreds of directories.
An application saves there its output files with special characters in the file names.
I want to replace these special characters with underscores in all file names. e.g. -:"<>#
I wrote a bash script which simply repeats a command to rename the files using Linux/Unix 'rename'.
Example: file name: rename.sh
#!/bin/bash
rename "s/\'/_/g" *
rename 's/[-:"<>#\,&\s\(\)\[\]?!–~%„“;│\´\’\+#]/_/g' *
rename 'y/A-Z/a-z/' *
rename 's/\.(?=[^.]*\.)/_/g' *
rename 's/[_]{2,}/_/g' *
I execute the following find command:
find /home/hel/files/ -maxdepth 1 -type f -execdir /home/hel/scripts/rename.sh {} \+
Now the issue:
This works fine, except the fact, that it renames subdirectories too, if they have the searched characters in their name.
The find command searches just for files and not for directories.
I tried some other find variations like:
find /home/hel/files/ -maxdepth 1 -type f -execdir sh /home/hel/scripts/rename.sh {} \+
find /home/hel/files/ -maxdepth 1 -type f -execdir sh /home/hel/scripts/rename.sh {} +
find /home/hel/files/ -maxdepth 1 -type f -execdir sh /home/hel/scripts/rename.sh {} \;
They are all working, but with the same result.
What is not working:
find /home/hel/files/ -maxdepth 1 -type f -exec sh /home/hel/scripts/rename.sh {} \+
This one is dangerous, because it renames the directories and files in the current directory, where you call the find command too.
Maybe one has an idea, why this happens or has a better solution.
The script rename.sh did not use its command line arguments at all, but instead searched files and directories (!) on its own using the glob *.
Change your script to the following.
#!/bin/bash
rename -d s/\''/_/g;
s/[-:"<>#\,&\s\(\)\[\]?!–~%„“;│\´\’\+#]/_/g;
y/A-Z/a-z/;
s/\.(?=[^.]*\.)/_/g;
s/[_]{2,}/_/g' "$#"
Then use find ... -maxdepth 1 -type f -exec sh .../rename.sh {} +.
Changes Made
Use "$#" instead of * to process the files given as arguments rather than everything in the current directory.
Execute rename only once as a 2nd rename wouldn't find the files specified with "$#" after they were renamed by the 1st rename.
Use the -d option such that only the basenames are modified. find always puts a path in front of the files, at the very least ./. Without this option rename would change ./filename to mangledPath/newFilename and therefore move the file to another directory.
Note that man rename is a bit misleading
--path, --fullpath
Rename full path: including any directory component. DEFAULT
-d, --filename, --nopath, --nofullpath
Do not rename directory: only rename filename component of path.
For a given path rename -d 's...' some/path/basename just processes the basename and ignores the leading components some/path/. If basename is a directory it will still be renamed despite the -d option.

Making a directory and moving files into that directory that match a pattern

I can pattern match files and move them into a directory using the line below. But I need to make the directory first.
(must make testdir directory first)
find . -type f -name '*-bak*' -exec mv '{}' ./testdir ';'
What I'm trying to do now is have the line of code also create the directory and move the files that match that pattern into that directory using the same line of code.
mkdir -p testdir && find . -type f -name '*-bak*' -exec mv {} testdir/ ';'
Be careful though, if you get two backups with the same name in different folders, you'll only be left with a single copy and all other copies overwritten!
EDIT: use mv -i to get prompted in that case instead of overwriting the files

Bash: Loop through each file in each subfolder and rename

I'm in a directory with 3 subdirectories: sub1, sub2, and sub3. Each subdirectory has files in it. I would like to rename each file by prepending sample_ to it.
Here's what I have:
for d in */; do
for f in "$d"; do
mv "$f" "sample_$f"
done
done
This prepends to the folder name, which isn't what I want. What am I doing incorrectly?
Thanks!
You can easily accomplish this with find and brace expansion (part of shell expansion):
find . -type f -execdir mv {,sample_}{} \;
This should recursively find only files (-type f) within each subdirectory then move them (renaming them) using the -execdir option (see below), prepending sample_ to each filename. The remaining mv {,_sample}{} is the Cartesian product way of doing mv {} sample_{}.
-execdir command {} + Like -exec, but the specified command is run from the subdirectory
containing the matched file, which is not normally the directory in
which you started find. This a much more secure method for invoking
commands, as it avoids race conditions during resolution of the paths
to the matched files. As with the -exec option, the '+' form of
-execdir will build a command line to process more than one matched file, but any given invocation of command will only list files that
exist in the same subdirectory. If you use this option, you must
ensure that your $PATH environment variable does not reference the
current directory; otherwise, an attacker can run any commands they
like by leaving an appropriately-named file in a directory in which
you will run -execdir.
↳ GNU : Brace / Shell Expansions
you need to use dirname and basename to split your file name.
for d in */; do
for f in $d/*; do
mv "$f" "$d/sample_$(basename $f)"
done
done

How to move files en-masse while skipping a few files and directories

I'm trying to write a shell script that moves all files except for the ones that end with .sh and .py. I also don't want to move directories.
This is what I've got so far:
cd FILES/user/folder
shopt -s extglob
mv !(*.sh|*.py) MoveFolder/ 2>/dev/null
shopt -u extglob
This moves all files except the ones that contain .sh or .py, but all directories are moved into MoveFolder as well.
I guess I could rename the folders, but other scripts already have those folders assigned for their work, so renaming might give me more trouble. I also could add the folder names but whenever someone else creates a folder, I would have to add its name to the script or it will be moved as well.
How can I improve this script to skip all folders?
Use find for this:
find -maxdepth 1 \! -type d \! -name "*.py" \! -name "*.sh" -exec mv -t MoveFolder {} +
What it does:
find: find things...
-maxdepth 1: that are in the current directory...
\! -type d: and that are not a directory...
\! -name "*.py: and whose name does not end with .py...
\! -name "*.sh: and whose name does not end with .sh...
-exec mv -t MoveFolder {} +: and move them to directory MoveFolder
The -exec flag is special: contrary to the the prior flags which were conditions, this one is an action. For each match, the + that ends the following command directs find to aggregate the file name at the end of the command, at the place marked with {}. When all the files are found, find executes the resulting command (i.e. mv -t MoveFolder file1 file2 ... fileN).
You'll have to check every element to see if it is a directory or not, as well as its extension:
for f in FILES/user/folder/*
do
extension="${f##*.}"
if [ ! -d "$f" ] && [[ ! "$extension" =~ ^(sh|py)$ ]]; then
mv "$f" MoveFolder
fi
done
Otherwise, you can also use find -type f and do some stuff with maxdepth and a regexp.
Regexp for the file name based on Check if a string matches a regex in Bash script, extension extracted through the solution to Extract filename and extension in Bash.

Resources