copie e renomeie os arquivos 2 diretório para cima

2

Estou tentando copiar vários arquivos denominados "F3.bam" para dois níveis de diretórios e renomeá-los com o nome do subdiretório após a cópia.

Por exemplo:

/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

Resultados esperados:

1. Os arquivos são primeiro copiados em dois diretórios de nível:

/samples/mydata1/RUN1/ID_date/F3.bam
/samples/mydata2/RUN1/ID2_date4/F3.bam
/samples/mydataxxx/RUN1/IDxxx_datexxx/F3.bam

2. Os arquivos são renomeados de acordo com o nome do subdiretório atual:

/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

Idealmente, um loop bash seria ótimo (trabalhando em um Mac).

    
por antoine 16.10.2017 / 00:51

4 respostas

1

Aqui está a versão TLDR da minha solução: você pode usar os comandos dirname e basename junto com a substituição do processo para construir o caminho de destino para o seu comando de cópia.

Uma explicação mais longa segue.

Aqui está um script (super detalhado) que faz aproximadamente o que você deseja usando um loop de 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

Você pode, obviamente, compactar muito isso, mas eu mantive assim por um valor explicativo.

Aqui está o que o script precedente parece sem comentários ou variáveis supérfluas ou echo declarações:

for _file_path in $@; do
    cp -i "${_file_path}" \
    "$(dirname $(dirname ${_file_path}))/$(basename $(dirname $(dirname $(dirname ${_file_path}))))_$(basename ${_file_path})"
done

É muito frágil e não ajuda muito na manipulação de erros. Também deixei em algumas instruções echo para depuração, para que você veja o que está fazendo e possa verificá-lo quando for executá-lo pela primeira vez.

Para testá-lo, criei seus arquivos usando o seguinte script, que incluo aqui caso você ache útil para testes adicionais:

#!/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

Eu verifiquei que os arquivos foram criados corretamente usando o comando 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

Então invoco o script assim:

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

Em seguida, verifiquei se o script funcionava usando find novamente:

$ 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

Por fim, excluo todos os arquivos de teste, também usando find :

find /tmp/samples -type f -exec rm {} \;
    
por 16.10.2017 / 02:47
1

Esta versão usa apenas substituição de parâmetro bash para cortar e ditar caminhos. Passe um ou mais caminhos de arquivo absolutos:

#!/bin/env bash
for path; do
    dir="${path%/*}"
    dest="${dir%/*/*}"
    cp "$path" "${dest}/${dest##*/}_${path##*/}"
done

Aqui está uma versão expandida. Este também aceita caminhos relativos e o número de diretórios pai a serem percorridos é ajustável:

#!/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

Quanto ao seu caso de uso específico, você poderia executar isso com find se é assim que você está localizando seus arquivos 'F3.bam'. Por exemplo:

find /some/path -name F3.bam -exec copy2up.sh {} +
    
por 16.10.2017 / 07:59
1

Usando find e shell (POSIX sh/bash/Korn/zsh) parameter substitution expansion como a seguir.

find . -type f -name "F3.bam" -execdir sh -c '
    trgt="${PWD%/*/*}"; echo cp -v "$1" "${trgt}/${trgt##*/}_${1#./}" ' _ '{}' \;

Explicações: :

Estamos procurando os arquivos que correspondem apenas a F3.bam e com -execdir , find está alterando o diretório atual para o diretório em que o arquivo F3.bam found executou sh -c ' ... ' dentro desse diretório propriamente dito. / p>

Com trgt="${PWD%/*/*}" "recortado-para-primeiro-sufixo": Estamos recebendo o próprio nome do arquivo e dois níveis de seus subdiretórios em /samples/mydata1/RUN1/ID_date**/PCR2/TIME1** ( negrito parte que corresponde ao sufixo /*/* irá remover) e atribuir à variável trgt . Portanto, trgt está agora definido como /samples/mydata1/RUN1/ID_date para o primeiro arquivo.

O "$1" é relativo caminho do arquivo ./filename ao atual $PWD .

Em ${trgt##*/}_ "cut-up-last-prefix": Usamos o valor da variável trgt para obter o nome do subdiretório que deve ser pré-pendurado no nome do arquivo. Portanto, isso retornará apenas ID_date , ID2_date4 ou IDxxx_datexxx , etc (removendo tudo até a última barra / vista) e adicione um sublinhado _ .

Este ${1#./} remove a barra de pontos ./ do ./filepath relativo.

    
por 16.10.2017 / 10:20
0

Você pode aninhar dirname quantas vezes quiser:

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
    
por 16.10.2017 / 14:52