Como evitar que o cp mescle dois diretórios com nomes idênticos?

7

Eu tenho dois diretórios com nomes idênticos:

$ ls mydir
file1 file2

$ ls other/mydir
file3 file4

Se eu copiar mydir para other , os dois mydir s serão mesclados:

$ cp -r mydir other

$ ls other/mydir
file1 file2 file3 file4

Onde na página man (ou info) de cp ele diz que faz isso por padrão?

O mesmo acontece se eu usar cp -rn mydir other .

Eu preferiria que cp me perguntasse se gostaria de mesclar os dois mydir s; para que, se eu copiar mydir para other , esquecendo que já existe um mydir em other diferente, eu poderia abortar a operação. Isso é possível?

    
por EmmaV 19.12.2015 / 20:48

3 respostas

5

Eu não vejo isso documentado no manual do GNU Coreutils. É especificado por POSIX :

2. If source_file is of type directory, the following steps shall be taken:

[snip steps that don't apply in recursive mode when the target file is an existing directory]

    f. The files in the directory source_file shall be copied to the directory dest_file […]

cp -rn não ajuda porque a opção -n apenas diz "não sobrescrever", mas a fusão de diretórios não sobregrava nada.

Não vejo nenhuma opção para rsync ou pax que possa ajudar você.

Você pode obter esse comportamento com um wrapper em torno de cp . Analisar as opções da linha de comando é complicado. Código não testado. Problema conhecido: isso não suporta opções longas abreviadas.

function cp {
  typeset source target=
  typeset -a args sources
  args=("$@") sources=()
  while [[ $# -ne 0 ]]; do
    case "$1" in
      --target|-t) target=$2; shift args;;
      --target=*) target=${1#*=};;
      -t?*) target=${1#??};;
      --no-preserve|--suffix|-S) shift;;
      --) break;;
      -|[^-]*) if [ -n "$POSIXLY_CORRECT" ]; then break; else sources+=($1); fi;;
    esac
    shift
  done
  sources+=("$@")
  if [[ -z $target && ${#sources[@]} -ne 0 ]]; then
    target=${sources[-1]}
    unset sources[-1]
  fi
  for source in "${sources[@]}"; do
    source=${source%"${source##*[^/]}"}
    if [ -e "$target/${source##*/}" ]; then
      echo >&2 "Refusing to copy $source to $target/${source##*/} because the target already exists"
      return 1
    fi
  done
  command cp "$@"
}
    
por 20.12.2015 / 03:17
1

Você pode criar um script de wrapper para copiar diretórios (cpDirs) que verificarão se alguma fusão ocorreria:

#!/bin/sh
test -d "$1" && test -d "$2" || { >&2 echo "Not directories"; exit 1; }

conflicts="'for d in "$1" "$2"; do (cd "$d"; find -mindepth 1 -type d); done | 
            sort |uniq -d'"
if [ -n "$conflicts" ]; then
  >&2 printf 'The following directories would be merged:\n%s\n' "$conflicts"
  exit 1
else
  cp -r "$@"
fi
    
por 21.12.2015 / 03:03
0
cd /src/path &&
find .  -type d ! -name . -prune \
\(      -exec   test -e /tgt/path/{} \; \
        \(      -ok     echo cp -rt /tgt/path {} \; \
             -o -exec   printf 'not copied:\t%s\n' {} \; \
\)      \) -o ! -name . -exec echo cp -rt /tgt/path {} +
O find primitivas de% p_de -ok funciona como -exec , exceto que primeiro solicita ao seu stderr uma descrição do comando que está prestes a executar e aguarda uma resposta afirmativa ou negativa (como y ou n ) seguido por enter. O script find acima solicitará confirmação se um diretório em /src/path também existir em /tgt/path antes de copiá-lo, mas todos os arquivos encontrados em /src/path são copiados sem aviso.

(você terá que remover o echo s para fazer algo mais do que fingir que trabalha, no entanto)

Outro script find que chama um shell para diretórios de primeiro nível abaixo de /src/path pode ser semelhante:

cd /src/path &&
find . ! -name . -prune -exec sh -c '
    [ -t 0 ] && 
    trap "stty $(stty -g;stty -icanon)
          trap - 0 1 2;  exit" 0 1 2 
    for f
    do    [ -e "$0/$f" ] &&
          case $(printf "%b:\n%s\n" >&2 \
                     \nsource "$(ls -ld -- "$PWD/$f")" \
                     \ntarget "$(ls -ld -- "$0/$f")"   \
                     "copy source over target?(\"y\"es|a\"no\"ther key): \c"
                 dd count=1 2>/dev/null
                 echo >&2) in ([yY]) ! :
          esac|| set -- "$@" "$f"; shift
    done; cp -r "$@" "$0"
'   /tgt/path {} +
    
por 21.12.2015 / 02:04

Tags