O utilitário cp ficará feliz em sobrescrever o arquivo de destino se esse arquivo já existir, sem avisar o usuário.
Uma função que implementa a capacidade básica de cp , sem usar cp , seria
cp () {
cat "$1" >"$2"
}
Se você quiser avisar o usuário antes de sobrescrever o alvo (note que pode não ser desejável fazer isso se a função for chamada por um shell não interativo):
cp () {
if [ -e "$2" ]; then
printf '"%s" exists, overwrite (y/n): ' "$2" >&2
read
case "$REPLY" in
n*|N*) return ;;
esac
fi
cat "$1" >"$2"
}
As mensagens de diagnóstico devem ir para o fluxo de erro padrão. Isso é o que eu faço com printf ... >&2 .
Observe que realmente não precisamos rm do arquivo de destino, pois o redirecionamento truncará o arquivo. Se nós fizemos queremos rm primeiro, então você teria que verificar se é um diretório, e se for, coloque o arquivo de destino dentro daquele diretório, da mesma forma que cp faria. Isso está fazendo isso, mas ainda sem rm explícito:
cp () {
target="$2"
if [ -d "$target" ]; then
target="$target/$1"
fi
if [ -d "$target" ]; then
printf '"%s": is a directory\n' "$target" >&2
return 1
fi
if [ -e "$target" ]; then
printf '"%s" exists, overwrite (y/n): ' "$target" >&2
read
case "$REPLY" in
n*|N*) return ;;
esac
fi
cat "$1" >"$target"
}
Você também pode querer ter certeza de que a fonte realmente existe, o que é algo que cp faz ( cat também, então pode ser deixado de fora completamente, é claro, mas isso criaria um arquivo de destino vazio):
cp () {
if [ ! -f "$1" ]; then
printf '"%s": no such file\n' "$1" >&2
return 1
fi
target="$2"
if [ -d "$target" ]; then
target="$target/$1"
fi
if [ -d "$target" ]; then
printf '"%s": is a directory\n' "$target" >&2
return 1
fi
if [ -e "$target" ]; then
printf '"%s" exists, overwrite (y/n): ' "$target" >&2
read
case "$REPLY" in
n*|N*) return ;;
esac
fi
cat "$1" >"$target"
}
Esta função não usa "bashisms" e deve funcionar em todas as shells parecidas com sh .
Com um pouco mais de ajustes para suportar vários arquivos de origem e um sinalizador -i que ativa o aviso interativo ao sobrescrever um arquivo existente:
cp () {
local interactive=0
# Handle the optional -i flag
case "$1" in
-i) interactive=1
shift ;;
esac
# All command line arguments (not -i)
local -a argv=( "$@" )
# The target is at the end of argv, pull it off from there
local target="${argv[-1]}"
unset argv[-1]
# Get the source file names
local -a sources=( "${argv[@]}" )
for source in "${sources[@]}"; do
# Skip source files that do not exist
if [ ! -f "$source" ]; then
printf '"%s": no such file\n' "$source" >&2
continue
fi
local _target="$target"
if [ -d "$_target" ]; then
# Target is a directory, put file inside
_target="$_target/$source"
elif (( ${#sources[@]} > 1 )); then
# More than one source, target needs to be a directory
printf '"%s": not a directory\n' "$target" >&2
return 1
fi
if [ -d "$_target" ]; then
# Target can not be overwritten, is directory
printf '"%s": is a directory\n' "$_target" >&2
continue
fi
if [ "$source" -ef "$_target" ]; then
printf '"%s" and "%s" are the same file\n' "$source" "$_target" >&2
continue
fi
if [ -e "$_target" ] && (( interactive )); then
# Prompt user for overwriting target file
printf '"%s" exists, overwrite (y/n): ' "$_target" >&2
read
case "$REPLY" in
n*|N*) continue ;;
esac
fi
cat -- "$source" >"$_target"
done
}
Seu código tem maus espaçamentos em if [ ... ] (precisa de espaço antes e depois de [ e antes de ] ). Você também não deve tentar redirecionar o teste para /dev/null , pois o teste em si não tem saída. O primeiro teste deve, além disso, usar o parâmetro posicional $2 , não a string file .
Usando case ... esac como eu fiz, você evita que a resposta do usuário seja minúscula / maiúscula usando tr . Em bash , se você quisesse fazer isso de qualquer maneira, uma maneira mais barata de fazer isso seria usar REPLY="${REPLY^^}" (para maiúsculas) ou REPLY="${REPLY,,}" (para minúsculas).
Se o usuário disser "sim", com seu código, a função colocará o nome do arquivo de destino no arquivo de destino. Esta não é uma cópia do arquivo de origem. Ele deve cair até o bit de cópia real da função.
O bit de cópia é algo que você implementou usando um pipeline. Um pipeline é usado para transmitir dados da saída de um comando para a entrada de outro comando. Isso não é algo que precisamos fazer aqui. Basta invocar cat no arquivo de origem e redirecionar sua saída para o arquivo de destino.
O mesmo acontece com você chamando tr antes. read definirá o valor de uma variável, mas não produzirá nenhuma saída, portanto, canalizar read para qualquer coisa é sem sentido.
Nenhuma saída explícita é necessária a menos que o usuário diga "não" (ou a função encontre alguma condição de erro como em bits do meu código, mas como é uma função eu uso return em vez de exit ).
Além disso, você disse "função", mas sua implementação é um script.
Dê uma olhada no link , é uma boa ferramenta para identificar bits problemáticos de scripts de shell.
Usar o modo cat é apenas um para copiar o conteúdo de um arquivo. Outras maneiras incluem
-
dd if="$1" of="$2" 2>/dev/null - Uso de qualquer utilitário do tipo filtro que pode ser usado apenas para transmitir dados, por exemplo
sed "" "$1" >"2"ouawk '1' "$1" >"$2"outr '.' '.' <"$1" >"$2"etc. - etc.
O problema é fazer com que a função copie os metadados (propriedade e permissões) da origem para o destino.
Outra coisa a notar é que a função que escrevi se comportará de maneira bem diferente de cp se o destino for algo como /dev/tty , por exemplo (um arquivo não regular).