0
votes

Let's say I have a directory with many subdirectories and files. I want to copy every file with a certain extension to another directory while adding the subdirectory name to the filename. For example, a .txt file at ./directory/subdirectory/example.txt would be copied to ./destinationdir/subdirectory_example.txt. The program can be a bash script, a python script, or whatever.

I have tried things like

 find ./ -name '*.txt' -exec cp -r '{}' './destination/' ';'

but it doesn't add the subdirectory name to the file, and files with the same name will just get overwritten. Any solutions?

2
Not sure if I understood this. Is the target directory a child of the source directory or a sibling of it?oguz ismail
The target directory is a sibling of the source directory.sean Yao

2 Answers

2
votes

Have find -exec execute an inline shell script.

find ./ -name '*.txt' -exec sh -c 'for p; do d="${p%/*}"; d="${d##*/}"; d="${d#.}"; d="${d:+${d}_}"; f="${p##*/}"; cp -- "$p" "./destinationdir/$d$f"; done' {} \+

Here is a commented fancy version of the inline shell script called by find -exec one-liner above:

#!/usr/bin/env sh

# Iterate the arguments
for path; do
  # Extract the base directory path by stripping the shortest trailing /*
  base_dir="${path%/*}"

  # Extract the parent directory name by stripping the longest leading */
  parent_dir="${base_dir##*/}"

  # Make a prefix of the parent dir name by stripping any leading dot .
  prefix="${parent_dir#.}"

  # If prefix is not empty, expand it with a trailing underscore _
  prefix="${prefix:+${prefix}_}"

  # Extract the file's base name by stripping longest leading */
  base_name="${path##*/}"

  # Copy the file's path at destination with new name
  cp -- "$path" "./destinationdir/$prefix$base_name"
done
1
votes

Try this:

#!/bin/bash

find . -name "*.txt" -print0 | while read -d $'\0' found
do
    directoryname=$(dirname -- "$found" | cut -d'/' -f2-)
    filename=$(basename -- "$found")

    cp "$found" "./${directoryname}_${filename}"
done
  • this method with -print0 and while read ... ensures it will work with any weird filename you might encounter.
  • the cut after dirname is to remove the leading ./ that find puts on all results.
  • there might be way to do it with a one liner, but I find this easier to read and it is reusable for many other scenarios.

EDIT Please see comments for an explanation of why variable expansion is better than basename and dirname.