Copy files from list while keeping subdirectory structure - bash

I have a text file which specifies files that need to be copied:
...
b/bamboo/forest/00000456.jpg
b/bamboo/forest/00000483.jpg
...
c/corridor/00000334.jpg
c/corridor/00000343.jpg
...
However, I would like to copy them while preserving their subdirectory structure. So the result would be:
...
newfolder/b/bamboo/forest/00000483.jpg
newfolder/b/bamboo/forest/00000456.jpg
...
newfolder/c/corridor/00000334.jpg
newfolder/c/corridor/00000343.jpg
...
I have this cat /path/to/files.txt | xargs cp -t /dest/path/. But it just copies everything to one directory.

You can use cp --parents:
--parents -- append source path to target directory
cat /path/to/files | xargs cp --parents -t new_directory
If that isn't working for you, then you can take the boring approach and iterate over each file in /path/to/files.txt and use mkdir -p to make target directories as needed, and then simply copy the file:
while read -r file; do
new_dir="new_directory/$(dirname "$file")"
# ^ this is the new directory root
mkdir -p "$new_dir"
cp "$file" "$new_dir/$file"
done < <(cat /path/to/files.txt)

Related

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

How to remove empty directories from a tarball in-place

I extracted a layer from a docker image which archived in a file called layer.tar. I want to remove empty directories from it.
I don't want to unpack then repack files in that archive, I want to keep the original info, so I want to do it in-place.
I know how to delete files from tar but I don't know any simple method to delete empty directories in-place.
Let's create a archive t.tar with a/b/c/ and a/b/c/d/ empty directories:
mkdir -p dir
cd dir
mkdir -p a/b/c/d
mkdir -p 1/2/3/4
touch a/fil_ea a/b/file_ab # directory a/b/c and a/b/c/d are empty
touch 1/2/3/file_123 1/2/3/4/file_1234 # directories 1/2/3/4 not empty
tar cf ../t.tar a 1
cd ..
Using tar tf and some filtering we can extract the directories and files in a tar archive. Then for each directory in tmpdirs we can check if it has any files in tmpfiles with a simple grep and then remove those directories using --delete tar option:
tar tf t.tar | tee >(grep '/$' > tmpdirs) | grep -v '/$' > tmpfiles
cat tmpdirs | xargs -n1 -- sh -c 'grep -q "$1" tmpfiles || echo "$1"' -- \
| tac \
| xargs -- tar --delete -f t.tar
Not that tac is a bit unneeded, but the files where sorted alphabetically in tar, so when tar removes the directory a/b/c/ with all subdirectories first and then tries to remove a/b/c/d/ directory it fails with an Not found in archive in error. tac is a cheap way to fix that, so tar first removes a/b/c/d/ and then a/b/c/.

Bulk copy and rename several files in Bash, using underscores

So, I have several files in bash in a directory called songs_old. I want to copy them to a folder called songs with a different name. Some names of the input files include:
unknownsong_song152.mid
town_song154.mid
nm_battle_song158.mid
fanfare_song159.mid
I want the results in "songs" to be like:
song152_unknownsong.mid
song154_town.mid
song158_nm_battle.mid
song159_fanfare.mid
I tried the following code to copy the files:
#!/bin/bash
for i in "./songs_old/"*".mid";do mkdir -p "./songs"
cp "$i" "./songs/`basename "${i%.*}" | cut -d'_' -f2`_`basename "${i%.*}" | cut -d'_' -f1`.mid"
done
And my results were as follows:
song152_unknownsong.mid
song154_town.mid
battle_nm.mid
song159_fanfare.mid
How can I copy the files with the desired results?
Using pure bash regex, you can do this:
cd songs_old
mkdir -p ../songs
for f in *.mid; do
[[ $f =~ ^(.+)_([^_]+)(\.[^.]+)$ ]] &&
cp "$f" ../songs/"${BASH_REMATCH[2]}_${BASH_REMATCH[1]}${BASH_REMATCH[3]}"
done
This will run following cp commands for given sample:
cp unknownsong_song152.mid ../songs/song152_unknownsong.mid
cp town_song154.mid ../songs/song154_town.mid
cp nm_battle_song158.mid ../songs/song158_nm_battle.mid
cp fanfare_song159.mid ../songs/song159_fanfare.mid
Your cp command is being piped to cut - that's the problem. You can rewrite the loop this way:
#!/bin/bash
mkdir -p songs
cd songs_old
for i in *.mid; do
[[ -f "$i" ]] || continue # make sure we have a file
new_file=$(sed -E 's/^(.*)_(song[0-9]+).mid$/\2_\1.mid/' <<< "$i")
echo "Copying $i to $new_file"
cp "$i" "../songs/$new_file"
done
makes more sense to create the "songs" directory just once
by cd'ing to songs_old and globbing, we eliminate the need for basename
use sed and a regex to create the new name
use relative path ../songs while copying
For your set of files, it gives this output:
Copying fanfare_song159.mid to song159_fanfare.mid
Copying nm_battle_song158.mid to song158_nm_battle.mid
Copying town_song154.mid to song154_town.mid
Copying unknownsong_song152.mid to song152_unknownsong.mid

copy content of folder to multiple folders bash

I want to copy the content of source folder to destination folders in one line, is that possible ?
I have this
cp -r source/* dest1 dest2 dest3
but it doesn't work, what it does it copies the 3 folders (source dest1 dest2) in dest3 and I want the content from source folder to each dest folder.
Do you mean you want to make three copies?
for dest in dest1 dest2 dest3 ; do
cp -r source/* "$dest"
done
Or, use xargs to avoid the loop:
echo dest1 dest2 dest3 | xargs -n1 cp -r source/*
Use a for:
for dest in dest1 dest2 dest3; do cp -r source/* "$dest"; done

How can I recursively copy same-named files from one directory structure to another in bash?

I have two directories, say dir1 and dir2, that have exactly the same directory structure. How do I recursively copy all the *.txt files from dir1 to dir2?
Example:
I want to copy from
dir1/subdir1/file.txt
dir1/subdir2/someFile.txt
dir1/.../..../anotherFile.txt
to
dir2/subdir1/file.txt
dir2/subdir2/someFile.txt
dir2/.../..../anotherFile.txt
The .../... in the last file example means this could be any sub-directory, which can have sub-directories itself.
Again I want to do this programmatically. Here's the pseudo-code
SRC=dir1
DST=dir2
for f in `find ./$SRC "*.txt"`; do
# $f should now be dir1/subdir1/file.txt
# I want to copy it to dir2/subdir1/file.txt
# the next line coveys the idea, but does not work
# I'm attempting to substitute "dir1" with "dir2" in $f,
# and store the new path in tmp.txt
echo `sed -i "s/$SRC/$DST/" $f` > tmp.txt
# Do the copy
cp -f $f `cat tmp.txt`
done
You can simply use rsync. This answer is based from this thread.
rsync -av --include='*.txt' --include='*/' --exclude='*' dir1/ dir2/
If you only have .txt files in dir1, this would work:
cp -R dir1/* dir2/
But if you have other file extensions, it will copy them too. In this case, this will work:
cd /path/to/dir1
cp --parents `find . -name '*.txt'` path/to/dir2/

Resources