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).