Para uma tarefa, sou solicitado a escrever de maneira inteligente uma função bash que tenha a mesma funcionalidade básica da função cp
(cópia). Ele só precisa copiar um arquivo para outro, portanto, não há vários arquivos copiados para um novo diretório.
Como sou novo na linguagem bash, não consigo entender por que meu programa não está funcionando. A função original pede para substituir um arquivo se ele já estiver presente, então tentei implementar isso. Falha.
Parece que o arquivo falha em várias linhas, mas o mais importante é na condição em que verifica se o arquivo a ser copiado já existe ( [-e "$2"]
). Mesmo assim, ainda mostra a mensagem que deve ser acionada se essa condição for atendida (O nome do arquivo...).
Alguém poderia me ajudar a consertar este arquivo, possivelmente fornecendo algumas informações úteis na minha compreensão básica da linguagem? O código é o seguinte.
#!/bin/sh
echo "file variable: $2"
if [-e file]&> /dev/null
then
echo "The file name already exists, want to overwrite? (yes/no)"
read | tr "[A-Z]" "[a-z]"
if [$REPLY -eq "yes"] ; then
rm "$2"
echo $2 > "$2"
exit 0
else
exit 1
fi
else
cat $1 | $2
exit 0
fi
Responder1
O cp
utilitário substituirá com prazer o arquivo de destino se esse arquivo já existir, sem avisar o usuário.
Uma função que implementa cp
capacidade básica, sem usar cp
seria
cp () {
cat "$1" >"$2"
}
Se você quiser 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. Isso é o que eu faço com printf ... >&2
.
Observe que realmente não precisamos do rm
arquivo de destino, pois o redirecionamento irá truncá-lo. Se nósfezquiser rm
primeiro, então você terá que verificar se é um diretório e, se for, coloque o arquivo de destino dentro desse diretório, exatamente como cp
faria. Isso está fazendo isso, 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 querer ter certeza de que a fonte realmente existe, o que é algocp
fazdo ( cat
faz isso também, então pode ser deixado completamente de fora, é 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 todos sh
os shells semelhantes.
Com um pouco mais de ajustes para suportar 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 ruins 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 tem 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 colocar letras minúsculas/maiúsculas na resposta do usuário usando tr
. Em bash
, se você quisesse fazer isso de qualquer maneira, uma maneira mais barata de fazer isso seria usar REPLY="${REPLY^^}"
(para letras maiúsculas) ou REPLY="${REPLY,,}"
(para letras minúsculas).
Se o usuário disser "sim", com o 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. Deve cair no bit de cópia real da função.
A parte 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
mais cedo. read
definirá o valor de uma variável, mas não produzirá saída, portanto, canalizar read
para qualquer coisa não faz 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 que uso return
em vez de exit
).
Além disso, você disse "função", mas sua implementação é um script.
Dê uma olhadahttps://www.shellcheck.net/, é uma boa ferramenta para identificar partes problemáticas de scripts de shell.
Usar cat
é apenasummaneira de copiar o conteúdo de um arquivo. Outras maneiras incluem
dd if="$1" of="$2" 2>/dev/null
- Usando qualquer utilitário semelhante a um filtro que possa ser feito apenas para passar dados, por exemplo,
sed "" "$1" >"2"
ouawk '1' "$1" >"$2"
outr '.' '.' <"$1" >"$2"
etc. - etc.
O complicado é 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).