La cp
utilidad sobrescribirá felizmente el archivo de destino si ese archivo ya existe, sin preguntar al usuario.
Una función que implementa la cp
capacidad básica , sin usar cp
sería
cp () {
cat "$1" >"$2"
}
Si desea solicitar al usuario antes de sobrescribir el destino (tenga en cuenta que puede que no sea conveniente hacerlo si un shell no interactivo llama a la función):
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"
}
Los mensajes de diagnóstico deben ir al flujo de error estándar. Esto es con lo que hago printf ... >&2
.
Tenga en cuenta que realmente no necesitamos rm
el archivo de destino ya que la redirección lo truncará. Si nosotros nos queremos rm
en primer lugar, a continuación, usted tendría que comprobar si se trata de un directorio, y si lo es, poner el archivo de destino dentro de ese directorio, tal y como cp
haría. Esto está haciendo eso, pero aún sin 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"
}
También puede asegurarse de que la fuente realmente exista, que es algo que cp
sí lo hace ( cat
también lo hace, por lo que puede omitirse por completo, por supuesto, pero hacerlo crearía un archivo de destino vacío):
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 función no utiliza "bashismos" y debería funcionar en todos los sh
shells.
Con un poco más de ajustes para admitir múltiples archivos fuente y una -i
marca que activa la solicitud interactiva al sobrescribir un archivo 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
}
Su código tiene mal if [ ... ]
espaciado (necesita espacio antes y después [
, y antes ]
). Tampoco debe intentar redirigir la prueba /dev/null
ya que la prueba en sí no tiene salida. Además, la primera prueba debe usar el parámetro posicional $2
, no la cadena file
.
Usando case ... esac
como lo hice, evitas tener que poner en minúscula / mayúscula la respuesta del usuario que usa tr
. En bash
, si hubiera querido hacer esto de todos modos, una forma más económica de hacerlo hubiera sido usar REPLY="${REPLY^^}"
(para mayúsculas) o REPLY="${REPLY,,}"
(para minúsculas).
Si el usuario dice "sí", con su código, la función coloca el nombre de archivo del archivo de destino en el archivo de destino. Esto no es una copia del archivo fuente. Debe pasar al bit de copia real de la función.
El bit de copia es algo que ha implementado usando una tubería. Una tubería se utiliza para pasar datos desde la salida de un comando a la entrada de otro comando. Esto no es algo que debamos hacer aquí. Simplemente invoque cat
en el archivo fuente y redirija su salida al archivo de destino.
Lo mismo está mal con tu llamado de tr
antes. read
establecerá el valor de una variable, pero no produce salida, por lo que canalizar read
cualquier cosa no tiene sentido.
No se necesita una salida explícita a menos que el usuario diga "no" (o la función se encuentra con alguna condición de error como en bits de mi código, pero como es una función que uso en return
lugar de exit
).
Además, dijiste "función", pero tu implementación es un script.
Eche un vistazo a https://www.shellcheck.net/ , es una buena herramienta para identificar partes problemáticas de scripts de shell.
Usar cat
es solo una forma de copiar el contenido de un archivo. Otras formas incluyen
dd if="$1" of="$2" 2>/dev/null
- Usar cualquier utilidad similar a un filtro que se pueda hacer para que simplemente pase datos, por ejemplo,
sed "" "$1" >"2"
or awk '1' "$1" >"$2"
o tr '.' '.' <"$1" >"$2"
etc.
- etc.
El truco es hacer que la función copie los metadatos (propiedad y permisos) del origen al destino.
Otra cosa a tener en cuenta es que la función que he escrito se comportará de manera bastante diferente a cp
si el objetivo es algo así como, /dev/tty
por ejemplo (un archivo no regular).