「F3.bam」という名前の複数のファイルを2レベルのディレクトリにコピーし、コピー後にこれらのファイルの名前をサブディレクトリの名前に変更しようとしています。
例えば:
/samples/mydata1/RUN1/ID_date/PCR2/TIME1/F3.bam
/samples/mydata2/RUN1/ID2_date4/PCR2/TIME7/F3.bam
/samples/mydataxxx/RUN1/IDxxx_datexxx/PCR2/TIMExxx/F3.bam
推測される結果:
/samples/mydata1/RUN1/ID_date/F3.bam
/samples/mydata2/RUN1/ID2_date4/F3.bam
/samples/mydataxxx/RUN1/IDxxx_datexxx/F3.bam
/samples/mydata1/RUN1/ID_date/ID_date_F3.bam
/samples/mydata2/RUN1/ID2_date4/ID2_date4_F3.bam
/samples/mydataxxx/RUN1/IDxxx_datexxx/IDxxx_datexxx_F3.bam
理想的には、bashループが素晴らしいでしょう(Macで動作します)。
find
と Shell (POSIX sh/bash/Korn/zsh) parameter substitution expansion
を次のように使用します。
find . -type f -name "F3.bam" -execdir sh -c '
trgt="${PWD%/*/*}"; echo cp -v "$1" "${trgt}/${trgt##*/}_${1#./}" ' _ '{}' \;
説明::
F3.bam
にのみ一致するファイルを探しており、ここでは-execdir
を使用して、find
は現在のディレクトリを、ファイルF3.bam
が見つかったディレクトリに変更し、そのディレクトリ内でsh -c ' ... '
を実行します。
trgt="${PWD%/*/*}"
"cut-up-to-first-suffix"の場合:filename自体とそのサブディレクトリの2つのレベルを/samples/mydata1/RUN1/ID_date**/PCR2/TIME1**
(bold/*/*
サフィックスに一致する部分は削除されます)、変数trgt
に割り当てます。そのため、最初のファイルのtrgt
は/samples/mydata1/RUN1/ID_date
に設定されます。
"$1"
は、現在の./filename
に対して相対的なファイルパス$PWD
です。
${trgt##*/}_
"cut-up-to-last-prefix":trgt
変数値を使用して、ファイル名の前に付けるサブディレクトリ名を取得しました。したがって、これはID_date
、ID2_date4
、またはIDxxx_datexxx
のみを返します。 、など(最後のスラッシュ/
が表示されるまですべてを削除)、アンダースコア_
を追加します。
この${1#./}
は、相対的な./
からポイントスラッシュ./filepath
を削除します。
これが私のソリューションのTLDRバージョンです。コピーコマンドのターゲットパスを構築するために、プロセス置換とともにdirname
およびbasename
コマンドを使用できます。
より長い説明が続きます。
これは、Bashループを使用しておおよそ必要なことを実行する(非常に詳細な)スクリプトです。
#!/bin/bash
# copy_and_rename.bash
#
# Copy multiple files 2 folders up and rename these files
# to contain their parent directory as a prefix.
#
# Set internal field separator to handle spaces in file names
IFS=$'\n'
# Iterate over the list of file paths
for _file_path in $@; do
# Get the file name
_file_name="$(basename ${_file_path})"
echo "${_file_name}"
# Get the path to the target directory (two levels above the file)
_target_directory_path=$(dirname $(dirname ${_file_path}))
echo "${_target_directory_path}"
# Get the parent directory of the target directory
_parent_directory_path=$(dirname ${_target_directory_path})
echo "${_parent_directory_path}"
# Get the name of the parent directory
_parent_directory_name=$(basename ${_parent_directory_path})
echo "${_parent_directory_name}"
# Construct the new file path
_new_file_path="${_target_directory_path}/${_parent_directory_name}_${_file_name}"
echo "${_new_file_path}"
# Copy and rename the file
echo "cp -i \"${_file_path}\" \"${_new_file_path}\""
cp -i "${_file_path}" "${_new_file_path}"
echo
done
明らかにこれをたくさん圧縮することができますが、説明のためにこのように保持しました。
コメントや余分な変数、またはecho
ステートメントがない場合、前述のスクリプトは次のようになります。
for _file_path in $@; do
cp -i "${_file_path}" \
"$(dirname $(dirname ${_file_path}))/$(basename $(dirname $(dirname $(dirname ${_file_path}))))_$(basename ${_file_path})"
done
それは非常に壊れやすく、エラー処理の方法ではあまり効果がありません。また、デバッグ用にいくつかのecho
ステートメントを残して、それが何をしているのかを確認し、初めて実行するときにサニティチェックできるようにしました。
それをテストするために、次のスクリプトを使用してファイルを作成しました。これは、さらにテストするのに役立つ場合に備えて、ここに含めます。
#!/bin/bash
# create_test_files.bash
# Set internal field separator to handle spaces in file names
IFS=$'\n'
# Choose an prefix for the file paths
_prefix="/tmp"
# Create array of sample files
_sample_files=(
"/samples/mydata1/RUN1/ID_date/PCR2/TIME1/F3.bam"
"/samples/mydata2/RUN1/ID2_date4/PCR2/TIME7/F3.bam"
"/samples/mydataxxx/RUN1/IDxxx_datexxx/PCR2/TIMExxx/F3.bam"
)
# Create directories and files
for _file in "${_sample_files[@]}"; do
# Add the prefix to the path
_path="${_prefix}${_file}"
# Create parent directory
mkdir -p "$(dirname ${_path})"
# Create file
touch "${_path}"
done
find
コマンドを使用して、ファイルが正しく作成されたことを確認します。
$ find /tmp/samples -type f
/tmp/samples/mydata1/RUN1/ID_date/PCR2/TIME1/F3.bam
/tmp/samples/mydata2/RUN1/ID2_date4/PCR2/TIME7/F3.bam
/tmp/samples/mydataxxx/RUN1/IDxxx_datexxx/PCR2/TIMExxx/F3.bam
次に、次のようなスクリプトを呼び出します。
bash copy_and_rename.bash \
/tmp/samples/mydata1/RUN1/ID_date/PCR2/TIME1/F3.bam \
/tmp/samples/mydata2/RUN1/ID2_date4/PCR2/TIME7/F3.bam \
/tmp/samples/mydataxxx/RUN1/IDxxx_datexxx/PCR2/TIMExxx/F3.bam
次に、find
を再度使用して、スクリプトが機能することを確認します。
$ find /tmp/samples -type f
/tmp/samples/mydata1/RUN1/ID_date/PCR2/ID_date_F3.bam
/tmp/samples/mydata1/RUN1/ID_date/PCR2/TIME1/F3.bam
/tmp/samples/mydata2/RUN1/ID2_date4/PCR2/ID2_date4_F3.bam
/tmp/samples/mydata2/RUN1/ID2_date4/PCR2/TIME7/F3.bam
/tmp/samples/mydataxxx/RUN1/IDxxx_datexxx/PCR2/IDxxx_datexxx_F3.bam
/tmp/samples/mydataxxx/RUN1/IDxxx_datexxx/PCR2/TIMExxx/F3.bam
最後に、find
を使用して、すべてのテストファイルを削除します。
find /tmp/samples -type f -exec rm {} \;
このバージョンでは、bashパラメーターの置換のみを使用してパスをスライスおよびダイスします。 1つ以上の絶対ファイルパスを渡します。
#!/bin/env bash
for path; do
dir="${path%/*}"
dest="${dir%/*/*}"
cp "$path" "${dest}/${dest##*/}_${path##*/}"
done
これが拡張バージョンです。これは相対パスも受け入れ、トラバースする親dirの数は調整可能です。
#!/bin/env bash
# Each param for this script is the path of a file. It
# accepts relative paths if you have appropriate tool to
# robustly determine absolute paths (not trivial). Here
# we're using GNU 'realpath' tool.
#
# Usage: copy2up filepath1 [filepath2...]
# for converting relative paths to absolute
# if it's missing replace realpath with available tool
# (or just always use absolute path arguments)
pathtool=realpath
# directory levels upwards to copy files
levels=2
# iterate over each parameter
for path; do
if [[ ! $path =~ ^/ ]]; then
# convert relative to absolute
path="$($pathtool $path)"
fi
file="${path##*/}"
dir="${path%/*}"
dest=$dir
# chdir upwards 'levels' times to destination
for (( i=0; i<$levels; i++ )); do
dest="${dest%/*}"
done
# to be prepended to original filename
destpfx="${dest##*/}"
newpath="${dest}/${destpfx}_${file}"
cp "$path" "$newpath"
done
特定のユースケースに関しては、「F3.bam」ファイルを見つける方法であれば、これをfind
で実行できます。例えば:
find /some/path -name F3.bam -exec copy2up.sh {} +
dirname
は、何度でもネストできます。
set /samples/mydata1/RUN1/ID_date/PCR2/TIME1/F3.bam \
/samples/mydata2/RUN1/ID2_date4/PCR2/TIME7/F3.bam \
/samples/mydataxxx/RUN1/IDxxx_datexxx/PCR2/TIMExxx/F3.bam
for bam; do
dir="$(dirname "$(dirname "$(dirname "$bam")")")"
mv "$bam" "$dir"/"$(basename "$dir")"_"$(basename "$bam")"
done