Shell-Script

如何顯示提示並防止執行基本但危險的命令,如 mv 或 rm

  • May 15, 2022

客觀的

每當我使用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

雖然@glennJackman使用包裝器的方法檢查第一個參數是否以開頭-並包含i在大多數常見情況下可能已經足夠好,但在某些情況下它會失敗:

  • 它妨礙了mv --help(不是--version包含-i)。
  • 它無法檢測到-iin mv -v -i a b(或者當環境中沒有時mv a b -iGNU允許的)。mv``POSIXLY_CORRECT
  • 它不包括 GNU的mv --interactive// mv --inmv --i
  • 它錯過-imv --no-target-dir a bmv -Tdir file

對於涵蓋所有這些情況的包裝器,我們需要它以相同的方式解析其選項mv。沒有兩個實現以相同的方式解析它們的選項。您甚至會發現相同mv實現的版本之間的差異。

mv(和大多數實用程序)的 GNU 實現將用於getopt_long()解析它們的選項。

如果您的包裝器可以getopt_long()使用相同的參數呼叫,我們將被排序。這留下了兩個問題:

  1. 我們需要getopt_long()在shell中找到一個介面
  2. 我們需要弄清楚那些參數mv傳遞給getopt_long()

如果您在 GNU/Linux 系統上,有可能的方法。

getopt_long()雖然在 GNU 工具箱中沒有 shell CLI ,但在util-linux: 它與andgetopt一起使用時的實用程序中有一個。-o``-l

在 GNU/Linux 上,GNU 實用程序將從getopt_long()libc 呼叫該函式,因為它將是 GNU libc,因此您可以使用ltracewhich traces library calls 來查看解析選項的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

我會使用一個包裝函式:你可以將它添加到你的 .bashrc (未經測試)

mv () {
   case $1 in
       -*i*) # ok, used `mv -i ...`: invoke the mv command, passing all args
           command mv "$@"
           ;;
       *)
           echo "hey, use 'mv -i'" &gt;&2
           false
           ;;
   esac
}

引用自:https://unix.stackexchange.com/questions/702418