sean Yao
sean Yao

Reputation: 1

copy every file with a certain extension from all subdirectories and adding the subdirectory name to the file

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?

Upvotes: 0

Views: 322

Answers (2)

Nic3500
Nic3500

Reputation: 8611

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.

Upvotes: 1

Léa Gris
Léa Gris

Reputation: 19585

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

Upvotes: 2

Related Questions