Copy files of specific type but prevent overwriting files - bash

I need to copy all files of a specific type to a folder, which I'm doing like this:
find ./ -name '*.gql' -exec cp -prv '{}' '/path/to/dir/' ';'
But if there are two files with a identical name, although located in different subfolders, some files would be overwritten.
Is it possible to keep all files, which are copied? Maybe renaming the copied file or is it possible to keep the folder structure in the target directory?

c.f. https://ss64.com/osx/cp.html : Historic versions of the cp utility had a -r option...its use is strongly discouraged, as it does not correctly copy special files, symbolic links, or fifo's. You can use -n to prevent overwrites, but more complex logic will likely require custom code.
dest=/path/to/dir
while IFS= read -r -d '' filename # read null-delimited list from find
do basename="${filename##*/}"
if [[ -e "$dest/$basename" ]]
then ctr=0
while [[ -e "$dest/$basename.$((++ctr))" ]]; do : ; done # find available name
mv "$dest/$basename" "$dest/$basename.$ctr"
fi
cp -pv "$filename" "$dest/$basename"
done < <( find ./ -name '*.gql' -type f -print0 ) # null-delimit

Related

Move files with whitespaces in their names to folders named like the files' modification dates

I created a bunch of folders from modification dates, like these:
..
2012-11-29
2012-11-20
..
Now I want to move files into these folders, in case the have a modification date that equals the folders name. The files contain whitespace in their names.
If I run this I get a list that looks like the folder names:
find . -iname "*.pdf" -print0 | while read -d $'\0' file; do stat -c "%.10y" "$file"; done
How do I take this output and use it in a script that moves these files like (pseudocode):
find . -iname "*.pdf" -print0 | while read -d $'\0' file; do mv $<FILEWITHWHITESPACEINNAME> <FOLDERNAMEDLIKE $file stat -c "%.10y" "$file" > ; done
find . -iname "*.pdf" -print0 |
while IFS= read -r -d '' file; do
folder=$(stat -c "%.10y" -- "$file") || continue # store date in variable
[[ $file = ./$folder/$file ]] && continue # skip if already there
mkdir -p -- "$folder" || continue # ensure directory exists
mv -- "$file" "$folder/" # actually do the move
done
To read a NUL-delimited name correctly, use IFS= to avoid losing leading or trailing spaces, and -r to avoid losing backslashes in the filename. See BashFAQ #1.
Inside your shell loop, you have shell variables -- so you can use one of those to store the output of your stat command. See How do I set a variable to the output of a command in bash?
Using -- signifies end-of-arguments, so even if your find were replaced with something that could emit a path starting with a - instead of a ./, we wouldn't try to treat values in file as anything but positional arguments (filenames, in the context of stat, mkdir and mv).
Using || continue inside the loop means that if any step fails, we don't do the subsequent ones -- so we don't try to do the mkdir if the stat fails, and we don't try to do the mv if the mkdir fails.

Can one automatically append file names with increasing numerical value when copying many files from one directory to another on Mac terminal?

I am trying to use a single line command in terminal to find and copy all the files of a certain type in one directory of my computer to another directory. I can do this right now using the below command:
find ./ -name '*.fileType' -exec cp -prv '{}' '/destination_directory/' ';'
The problem I'm having is that if a file that is being copied has the same name as a file that was previously copied, it will replace the previously copied file.
To remedy this, I would like to edit my command such that the files are numbered as they are copied to the new directory.
so the output should look something like this:
Original Files
cat.txt
dog.txt
dog.txt
Copied Files
cat1.txt
dog2.txt
dog3.txt
Edit:
The list of commands I can work with are linked here: https://ss64.com/osx/
Specifically for the cp command: https://ss64.com/osx/cp.html
-Note: --backup and -b are not available (it seems) for this version of cp
You are looking for the --backup option of the cp command. E.g.:
find ./ -name '*.fileType' -exec cp --backup=t -prv '{}' '/destination_directory/' ';'
Edit: If you are stuck with MacOS's cp you can emulate --backup's behaviour in a script:
#!/bin/bash
set -e
# First parameter: source directory
srcdir=$1
# Second parameter: destination directory
destdir=$2
# Print all filenames separated by '\0' in case you have strange
# characters in the names
find "$srcdir" -type f -print0 |
# Split the input using '\0' as separator and put the current line
# into the $file variable
while read -d $'\0' file; do
# filename = just the name of the file, without dirs
filename=$(basename "$file")
# if destdir does not have a file named filename
if [ \! -f "$destdir/$filename" ]; then
cp -pv "$file" "$destdir/$filename";
continue;
fi
# Otherwise
suffix=1
# Find the first suffix number that is free
while [ -f "$destdir/$filename.$suffix" ]; do
suffix=$(($suffix + 1))
done
cp -pv "$file" "$destdir/$filename.$suffix"
done

Recursively copy/backup all .php files to .php.bak files and keep them in their current paths

I am not sure how to word this question to find the solution easily online, so after much searching I thought I would ask here.
I access my website's files using bitvise ssh client and I use command lines for various grep and sed functions that I've been recently taught, but I can't seem to find a simple way to do this:
What is the command line to make a backup copy (.bak) of EVERY file that ends in .php? I am looking for the command to instantly make a backup of every php file at once, so when I go into my files I see things like...
index.php
index.php.bak
For every php file.
Also, what is the command line to do this for EVERY file at once, regardless of extension?
Would be awesome to see a solution that uses xargs or find's -exec.
But here is how can do this with a shell loop and find:
Note, this recursively backs up files in sub directories.
For .php files:
find . -iname '*.php' -type f -print0 | while read -d $'\0' file; do cp "$file" "$file.bak"; done
For all files:
find . -type f -print0 | while read -d $'\0' file; do cp "$file" "$file.bak"; done
For all files that have an extension:
find . -iname '*.*' -type f -print0 | while read -d $'\0' file; do cp "$file" "$file.bak"; done
You can just use the cp command
enter the dir that you have all the .php files then type
cp *.php temp/ where temp is a directory in the current directory. The * means all
if you just want to copy the whole folder you could
cp -R foldername destinationArea

Bash rename extension recursive

I know there are a lot of things like this around, but either they don't work recursively or they are huge.
This is what I got:
find . -name "*.so" -exec mv {} `echo {} | sed s/.so/.dylib/` \;
When I just run the find part it gives me a list of files. When I run the sed part it replaces any .so with .dylib. When I run them together they don't work.
I replaced mv with echo to see what happened:
./AI/Interfaces/C/0.1/libAIInterface.so ./AI/Interfaces/C/0.1/libAIInterface.so
Nothing is replaced at all!
What is wrong?
This will do everything correctly:
find -L . -type f -name "*.so" -print0 | while IFS= read -r -d '' FNAME; do
mv -- "$FNAME" "${FNAME%.so}.dylib"
done
By correctly, we mean:
1) It will rename just the file extension (due to use of ${FNAME%.so}.dylib). All the other solutions using ${X/.so/.dylib} are incorrect as they wrongly rename the first occurrence of .so in the filename (e.g. x.so.so is renamed to x.dylib.so, or worse, ./libraries/libTemp.so-1.9.3/libTemp.so is renamed to ./libraries/libTemp.dylib-1.9.3/libTemp.so - an error).
2) It will handle spaces and any other special characters in filenames (except double quotes).
3) It will not change directories or other special files.
4) It will follow symbolic links into subdirectories and links to target files and rename the target file, not the link itself (the default behaviour of find is to process the symbolic link itself, not the file pointed to by the link).
for X in `find . -name "*.so"`
do
mv $X ${X/.so/.dylib}
done
A bash script to rename file extensions generally
#/bin/bash
find -L . -type f -name '*.'$1 -print0 | while IFS= read -r -d '' file; do
echo "renaming $file to $(basename ${file%.$1}.$2)";
mv -- "$file" "${file%.$1}.$2";
done
Credits to aps2012.
Usage
Create a file e.g. called ext-rename (no extension, so you can run it like a command) in e.g. /usr/bin (make sure /usr/bin is added to your $PATH)
run ext-rename [ext1] [ext2] anywhere in terminal, where [ext1] is renaming from and [ext2] is renaming to. An example use would be: ext-rename so dylib, which will rename any file with extension .so to same name but with extension .dylib.
What is wrong is that
echo {} | sed s/.so/.dylib/
is only executed once, before the find is launched, sed is given {} on its input, which doesn't match /.so/ and is left unchanged, so your resulting command line is
find . -name "*.so" -exec mv {} {}
if you have Bash 4
#!/bin/bash
shopt -s globstar
shopt -s nullglob
for file in /path/**/*.so
do
echo mv "$file" "${file/%.so}.dylib"
done
He needs recursion:
#!/bin/bash
function walk_tree {
local directory="$1"
local i
for i in "$directory"/*;
do
if [ "$i" = . -o "$i" = .. ]; then
continue
elif [ -d "$i" ]; then
walk_tree "$i"
elif [ "${i##*.}" = "so" ]; then
echo mv $i ${i%.*}.dylib
else
continue
fi
done
}
walk_tree "."

How do I copy directory structure containing placeholders

I have the situation, where a template directory - containing files and links (!) - needs to be copied recursively to a destination directory, preserving all attributes. The template directory contains any number of placeholders (__NOTATION__), that need to be renamed to certain values.
For example template looks like this:
./template/__PLACEHOLDER__/name/__PLACEHOLDER__/prog/prefix___FILENAME___blah.txt
Destination becomes like this:
./destination/project1/name/project1/prog/prefix_customer_blah.txt
What I tried so far is this:
# first create dest directory structure
while read line; do
dest="$(echo "$line" | sed -e 's#__PLACEHOLDER__#project1#g' -e 's#__FILENAME__#customer#g' -e 's#template#destination#')"
if ! [ -d "$dest" ]; then
mkdir -p "$dest"
fi
done < <(find ./template -type d)
# now copy files
while read line; do
dest="$(echo "$line" | sed -e 's#__PLACEHOLDER__#project1#g' -e 's#__FILENAME__#customer#g' -e 's#template#destination#')"
cp -a "$line" "$dest"
done < <(find ./template -type f)
However, I realized that if I want to take care about permissions and links, this is going to be endless and very complicated. Is there a better way to replace __PLACEHOLDER__ with "value", maybe using cp, find or rsync?
I suspect that your script will already do what you want, if only you replace
find ./template -type f
with
find ./template ! -type d
Otherwise, the obvious solution is to use cp -a to make an "archive" copy of the template, complete with all links, permissions, etc, and then rename the placeholders in the copy.
cp -a ./template ./destination
while read path; do
dir=`dirname "$path"`
file=`basename "$path"`
mv -v "$path" "$dir/${file//__PLACEHOLDER__/project1}"
done < <(`find ./destination -depth -name '*__PLACEHOLDER__*'`)
Note that you'll want to use -depth or else renaming files inside renamed directories will break.
If it's very important to you that the directory tree is created with the names already changed (i.e. you must never see placeholders in the destination), then I'd recommend simply using an intermediate location.
First copy with rsync, preserving all the properties and links etc.
Then change the placeholder strings in the destination filenames:
#!/bin/bash
TEMPL="$PWD/template" # somewhere else
DEST="$PWD/dest" # wherever it is
mkdir "$DEST"
(cd "$TEMPL"; rsync -Hra . "$DEST") #
MyRen=$(mktemp)
trap "rm -f $MyRen" 0 1 2 3 13 15
cat >$MyRen <<'EOF'
#!/bin/bash
fn="$1"
newfn="$(echo "$fn" | sed -e 's#__PLACEHOLDER__#project1#g' -e s#__FILENAME__#customer#g' -e 's#template#destination#')"
test "$fn" != "$newfn" && mv "$fn" "$newfn"
EOF
chmod +x $MyRen
find "$DEST" -depth -execdir $MyRen {} \;

Resources