Copy group of files (Filename*) to backup (Filename*.bak)
Background
In Linux you can:
- List a group of files with
ls Filename*
- Remove a group of files with
rm Filename*
- Move a group of files with
mv Filename* /New/Directory
- But you cannot copy a group of files with:
cp Filename* *.bak
Change Linux cp
command to copy group of files
I have a group of files I'd like to copy without entering the names one by one and using the cp
command:
$ ls gmail-meta3*
gmail-meta3 gmail-meta3-REC-1558392194-26467821
gmail-meta3-LAB-1558392194-26467821 gmail-meta3-YAD-1558392194-26467821
How can I use something like the old DOS command copy gmail-meta3* *.bak
?
I don't want to type similar command four times:
cp gmail-meta3-LAB-1558392194-26467821 gmail-meta3-LAB-1558392194-26467821.bak
I'm looking for a script/function/app that accepts parameters for old and new filename group and not something with hard-coded filenames. For example, a user can type:
copy gmail-meta3* *.bak
or they might type:
copy gmail-meta3* save-*
Solution 1:
You can use find
:
find . -max-depth 1 -name 'gmail-meta3*' -exec cp "{}" "{}.bak" \;
That will find in the current directory .
all files with a name matching the glob pattern (mind the single quotes around the pattern to prevent shell globbing). For each file found, it will exec cp
from name to name.bak. The \; at the end ensures it will do each file individually instead of passing all of them at once. The max depth as 1 only searches the cuurent directory instead of recursing down.
Solution 2:
Here is an example of one atypical usage of sed
, that is applicable for this task:
sed -i.bak '' file-prefix*
In this way, actually, sed
will not change the files, because we didn't provided any commands ''
, but due to the option -i[suffix]
it will create a backup copy of each file. I found this approach when I was searching Is there any way to create backup copy of a file, without type its name twice?
Solution 3:
You can use a for
loop with bash
. Normally, I would just type it as a one-liner because this isn't a task I perform often:
for f in test* ; do cp -a "$f" "prefix-${f}.ext" ; done
However, if you need it as a script:
cps() {
[ $# -lt 2 ] && echo "Usage: cps REGEXP FILES..." && return 1
PATTERN="$1" ; shift
for file in "$@" ; do
file_dirname=`dirname "$file"`
file_name=`basename "$file"`
file_newname=`echo "$file_name" | sed "$PATTERN"`
if [[ -f "$file" ]] && [[ ! -e "${file_dirname}/${file_newname}" ]] ; then
cp -a "$file" "${file_dirname}/${file_newname}"
else
echo "Error: $file -> ${file_dirname}/${file_newname}"
fi
done
}
Usage is similar to rename
. To test:
pushd /tmp
mkdir tmp2
touch tmp2/test{001..100} # create test files
ls tmp2
cps 's@^@prefix-@ ; s@[email protected]@' tmp2/test* # create backups
cps 's@[email protected]@' tmp2/test* # more backups ... will display errors
ls tmp2
\rm -r tmp2 # cleanup
popd
Solution 4:
The closest you will likely get to the DOS paradigm is mcp
(from the mmv
package):
mcp 'gmail-meta3*' 'gmail-meta3#1.bak'
If zsh
is available, its contributed zmv
module is perhaps a little closer:
autoload -U zmv
zmv -C '(gmail-meta3*)' '$1.bak'
I'd avoid ls
regardless - a variant on your own answer that's safe for whitespace (including newlines) would be
printf '%s\0' gmail-meta3* | while IFS= read -r -d '' f; do cp -a -- "$f" "$f.bak"; done
or perhaps
printf '%s\0' gmail-meta3* | xargs -0 -I{} cp -a -- {} {}.bak