客观的

客观的

客观的

每当我使用mv(或类似性质的命令)时,我希望我的 shell 提示我类似“嘿,你用了mv。抱歉,请使用 mv -i 再次执行此操作”。我想知道完成这种交互的合理方法。

目的是让我的电脑每次使用时都会抱怨mv,所以我必须重新输入mv -i。这样我就会培养进入的肌肉记忆mv -i

让我的电脑将我的电脑转换mvmv -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)。
  • 它无法检测到-iin mv -v -i a b(或者当环境中没有 is 时mv a b -iGNU允许的)。mvPOSIXLY_CORRECT
  • 它不包括 GNU的mv --interactive// mv --in...mv --i
  • 它错过了失踪-imv --no-target-dir a bmv -Tdir file......

对于覆盖所有这些情况的包装器,我们需要它以相同的方式解析其选项mv。没有两个实现以相同的方式解析它们的选项。您甚至会发现相同mv实现的版本之间存在差异。

GNU 实现mv(以及大多数实用程序)将用来getopt_long()解析它们的选项。

如果你的包装器可以getopt_long()使用相同的参数调用,我们就会被排序。这就留下了两个问题:

  1. 我们需要getopt_long()在 shell 中找到一个接口
  2. 我们需要弄清楚那些传递mvgetopt_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"

更复杂但更可定制的方法是创建一个mvshell 脚本,将其放入/bin,然后真正mv移动到/sbin或任何其他“秘密”目录。替换脚本将发出警告并mv通过完整路径调用原始脚本。

如果您想完全避免mv重命名或删除该工具。

是的,其他脚本可能会由于此类修改而失败。在这种情况下,您可以进入这些脚本并修复它们,方法是替换mv/secret/path/mv

另一种方法 - 为日常生活创建一个特殊用户。为他创建$HOME/bin一个真实的副本(通常在 中)/bin,排除危险工具。为日常用户设置一个 PATH,它将使用/home/everyday/bin而不是/bin.
如果某些应用程序现在需要“危险”工具 - 该应用程序不适合“日常”用户 - 切换到具有更多权限的用户。

相关内容