O cp
utilitário substituirá alegremente o arquivo de destino se esse arquivo já existir, sem avisar o usuário.
Uma função que implementa a cp
capacidade básica , sem o uso, cp
seria
cp () {
cat "$1" >"$2"
}
Se você deseja avisar o usuário antes de substituir o destino (observe 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 erros padrão. É com isso que eu faço printf ... >&2
.
Observe que realmente não precisamos rm
do arquivo de destino, pois o redirecionamento o truncará. Se se quiser rm
lo primeiro, então você teria que verificar se é um diretório, e se for, coloque o arquivo de destino dentro deste diretório em vez disso, apenas a forma como cp
faria. Isso é feito, mas ainda sem explícito rm
:
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 ter certeza de que a fonte realmente existe, o que é algo cp
que faz ( cat
também existe, portanto pode ser deixada 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 "bashismos" e deve funcionar em sh
conchas de todos os tipos.
Com um pouco mais de ajustes para oferecer suporte a vários arquivos de origem e um -i
sinalizador que ativa o prompt interativo ao substituir 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 espaçamentos incorretos if [ ... ]
(precisa de espaço antes e depois [
e antes ]
). Você também não deve tentar redirecionar o teste, /dev/null
pois o teste em si não possui saída. Além disso, o primeiro teste deve usar o parâmetro posicional $2
, não a string file
.
Usando case ... esac
como eu fiz, você evita ter que minúsculas / maiúsculas a resposta do usuário usando tr
. Em bash
, se você queria fazer isso de qualquer maneira, uma maneira mais barata de fazê-lo teria sido a utilização REPLY="${REPLY^^}"
(para maiúscula) ou REPLY="${REPLY,,}"
(para lowercasing).
Se o usuário disser "sim", com o seu código, a função colocará o nome do arquivo do arquivo de destino no arquivo de destino. Esta não é uma cópia do arquivo de origem. Ele deve passar para 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 passar dados da saída de um comando para a entrada de outro comando. Isso não é algo que precisamos fazer aqui. Simplesmente invoque cat
o arquivo de origem e redirecione sua saída para o arquivo de destino.
A mesma coisa está errada com você ligando tr
antes. read
definirá o valor de uma variável, mas não produzirá saída; portanto, a canalização read
para qualquer coisa é absurda.
Nenhuma saída explícita é necessária, a menos que o usuário diga "não" (ou a função se depara com alguma condição de erro como nos bits do meu código, mas como é uma função que eu uso em return
vez de exit
).
Você também disse "função", mas sua implementação é um script.
Dê uma olhada em https://www.shellcheck.net/ , é uma boa ferramenta para identificar bits problemáticos de scripts de shell.
O uso cat
é apenas uma maneira de copiar o conteúdo de um arquivo. Outras maneiras incluem
dd if="$1" of="$2" 2>/dev/null
- Utilizando qualquer utilitário semelhante a um filtro, que pode ser feito apenas para passar dados, por exemplo,
sed "" "$1" >"2"
ou awk '1' "$1" >"$2"
ou tr '.' '.' <"$1" >"$2"
etc.
- etc.
A parte complicada é 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 eu escrevi se comportará de maneira bem diferente de cp
se o destino for algo como, /dev/tty
por exemplo (um arquivo não regular).