客观的
每当我使用mv
(或类似性质的命令)时,我希望我的 shell 提示我类似“嘿,你用了mv。抱歉,请使用 mv -i 再次执行此操作”。我想知道完成这种交互的合理方法。
目的是让我的电脑每次使用时都会抱怨mv
,所以我必须重新输入mv -i
。这样我就会培养进入的肌肉记忆mv -i
。
让我的电脑将我的电脑转换mv
到mv -i
引擎盖下是不是目的,并且完全违背了意图。
如果这听起来像是一个愚蠢的想法,我想知道原因和更好的解决方案。
背景
过去我曾使用过mv
并错误地覆盖了文件。尽管此评论建议mv
以实际工作方式使用别名mv -i
, 我同意关于这种定制的后果的评论。与其习惯打字mv
,我宁愿养成打字的习惯mv -i
,这样我就不会在其他机器上搞砸了。
担忧
我担心此类修改会影响调用mv
.有没有一种聪明的方法来中止并仅在mv
我自己通过键入并按 Enter 执行时显示提示?
笔记
- 我备份了文件,但它并不相关
- 我知道我可以在桌子上贴便利贴或在墙上贴海报来提高认识
- 环境:Debian、XFCE、xfce4-terminal、Bash
答案1
我会使用包装函数:您可以将其添加到您的 .bashrc (未经测试)
mv () {
case $1 in
-*i*) # ok, used `mv -i ...`: invoke the mv command, passing all args
command mv "$@"
;;
*)
echo "hey, use 'mv -i'" >&2
false
;;
esac
}
答案2
尽管@glennJackman 的方法-
在大多数常见情况下,使用检查第一个参数是否以和 contains开头的包装器i
可能足够好,但在某些情况下会失败:
- 它妨碍
mv --help
(不--version
包含-i
)。 - 它无法检测到
-i
inmv -v -i a b
(或者当环境中没有 is 时mv a b -i
GNU允许的)。mv
POSIXLY_CORRECT
- 它不包括 GNU的
mv --interactive
//mv --in
...mv --i
- 它错过了失踪
-i
的mv --no-target-dir a b
或mv -Tdir file
......
对于覆盖所有这些情况的包装器,我们需要它以相同的方式解析其选项mv
。没有两个实现以相同的方式解析它们的选项。您甚至会发现相同mv
实现的版本之间存在差异。
GNU 实现mv
(以及大多数实用程序)将用来getopt_long()
解析它们的选项。
如果你的包装器可以getopt_long()
使用相同的参数调用,我们就会被排序。这就留下了两个问题:
- 我们需要
getopt_long()
在 shell 中找到一个接口 - 我们需要弄清楚那些传递
mv
给getopt_long()
如果您使用的是 GNU/Linux 系统,则有多种可行的方法。
getopt_long()
虽然GNU 工具箱中没有 shell CLI ,但util-linux
:与和 一起getopt
使用时的实用程序。-o
-l
在 GNU/Linux 上,GNU 实用程序getopt_long()
将从 libc 调用该函数,因为它将是 GNU libc,因此您可以使用ltrace
跟踪库调用来查看对解析选项进行的getopt_long()
调用。mv
$ ltrace -e getopt_long mv -:
mv->getopt_long(2, 0x7ffcf7febd68, "bfint:uvS:TZ", 0x5650dd02fb20, nilmv: invalid option -- ':'
) = 63
Try 'mv --help' for more information.
+++ exited (status 1) +++
(其中-:
保证是一个虚假选项)
不够好,因为我们只看到了简短的选项。然而,可以配置ltrace
为在那里解码long_options
参数,甚至隐藏我们不关心的参数。
作为概念证明,这里有一个 zsh 脚本,它将输出sh
包装函数的兼容代码,该函数在调用之前检查-i
/ --interactive
(或--help
/ --version
)或其缩写mv
:
#! /bin/zsh -
set -o extendedglob
die() {
print -ru2 -- "$@"
exit 1
}
for cmd do
getopt_long_call=$(
ltrace -F/dev/fd/3 3<<'EOF' -o/dev/fd/4 4>&1 > /dev/null 2>&1 -s 999 -A999 -e getopt_long "$cmd" -:
int getopt_long(hide(int),hide(addr),string,array(struct(string,int,hide(int*),hide(int)),zero),hide(int*));
EOF
)
getopt_long_call=${getopt_long_call%%$'\n'*}
[[ $getopt_long_call = (#b)[^\"]#'getopt_long("'([^\"]#)'", [ '(*)' ]) = '<-> ]] ||
die "Can't determine what getopt_long call $cmd does"
short_opts=() long_opts=()
: ${match[1]//(#m)?:#/${short_opts[1+$#short_opts]::=$MATCH}}
: ${match[2]//(#b)'{ "'([^\"]#)'", '(<->)' }'/${long_opts[1+$#long_opts]::=$match[1]${(l($match[2])(:))}}}
opts_with_args=(-${(M)^short_opts:#*:} --${(M)^long_opts:#*:})
opts_with_args=(${opts_with_args%%:#})
print -r -- $cmd'() {
(
opt=$(getopt -qo '${(j[]qq)short_opts}' '${(qq)long_opts/#/-l}' -- "$@") || exit 0
eval "set -- $opt"
while [ "$#" -gt 0 ]; do
case $1 in
(-i | --interactive | --version | --help) exit;;
(--) printf >&2 "%s\n" "Please run '$cmd' with -i/--interactive"
exit 1;;
('${(j[ | ])${(qq)opts_with_args}}') shift;;
esac
shift
done
echo >&2 "Oops. Something when wrong"
exit 1
) || return
command '$cmd' "$@"
}'
done
例如,在我的系统上,that-script mv rm
输出:
mv() {
(
opt=$(getopt -qo 'bfint:uvS:TZ' '-lbackup::' '-lcontext' '-lforce' '-linteractive' '-lno-clobber' '-lno-target-directory' '-lstrip-trailing-slashes' '-lsuffix:' '-ltarget-directory:' '-lupdate' '-lverbose' '-lhelp' '-lversion' -- "$@") || exit 0
eval "set -- $opt"
while [ "$#" -gt 0 ]; do
case $1 in
(-i | --interactive | --version | --help) exit;;
(--) printf >&2 "%s\n" "Please run mv with -i/--interactive"
exit 1;;
('-t' | '-S' | '--backup' | '--suffix' | '--target-directory') shift;;
esac
shift
done
echo >&2 "Oops. Something when wrong"
exit 1
) || return
command mv "$@"
}
rm() {
(
opt=$(getopt -qo 'dfirvIR' '-lforce' '-linteractive::' '-lone-file-system' '-lno-preserve-root' '-lpreserve-root::' '-l-presume-input-tty' '-lrecursive' '-ldir' '-lverbose' '-lhelp' '-lversion' -- "$@") || exit 0
eval "set -- $opt"
while [ "$#" -gt 0 ]; do
case $1 in
(-i | --interactive | --version | --help) exit;;
(--) printf >&2 "%s\n" "Please run rm with -i/--interactive"
exit 1;;
('--interactive' | '--preserve-root') shift;;
esac
shift
done
echo >&2 "Oops. Something when wrong"
exit 1
) || return
command rm "$@"
}
你会这样做:
eval "$(that-script mv rm)"
在 shell 的交互模式配置中(~/.zshrc
, ~/.bashrc
...)。
定义那些包装器。进而:
$ rm a -i
rm: remove regular file 'a'? n
$ rm a --int
rm: remove regular file 'a'? n
$ mv --version
mv (GNU coreutils) 8.32
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Mike Parker, David MacKenzie, and Jim Meyering.
$ POSIXLY_CORRECT=1 rm a -i
Please run rm with -i/--interactive
答案3
最简单的方法是创建别名。
alias mv="mv -i"
更复杂但更可定制的方法是创建一个mv
shell 脚本,将其放入/bin
,然后真正mv
移动到/sbin
或任何其他“秘密”目录。替换脚本将发出警告并mv
通过完整路径调用原始脚本。
如果您想完全避免mv
重命名或删除该工具。
是的,其他脚本可能会由于此类修改而失败。在这种情况下,您可以进入这些脚本并修复它们,方法是替换mv
为/secret/path/mv
另一种方法 - 为日常生活创建一个特殊用户。为他创建$HOME/bin
一个真实的副本(通常在 中)/bin
,排除危险工具。为日常用户设置一个 PATH,它将使用/home/everyday/bin
而不是/bin
.
如果某些应用程序现在需要“危险”工具 - 该应用程序不适合“日常”用户 - 切换到具有更多权限的用户。