无法在 zsh 脚本中 sudo 自定义函数

无法在 zsh 脚本中 sudo 自定义函数

我想将 .dot 文件(而不是 .dot 目录)复制到目录中。为此,我的操作系统 (MacOS) 要求我输入用户密码。我创建了一个函数来执行此操作,并希望将其合并到一个更大的备份脚本中,如下所示:

#!/bin/zsh
source ~/.zshrc

set -o errexit
set -o nounset
set -o pipefail

copy_dotfiles()
{
  MAIN_PATH="/Users/user01/"
  DOTFILE_DIR="${MAIN_PATH}Documents/dot_files"
  echo $DOTFILE_DIR
  mkdir $DOTFILE_DIR
  for f in $MAIN_PATH.[!.]*; do
    if [[ -f $f ]]; then
      echo "$f" is a file
      DOTFILE=`echo $f | sed 's/\/Users\/user01\///g'`
      echo "$DOTFILE" is a file
      cp $f ${DOTFILE_DIR}/${DOTFILE} || exit 1
    elif [[ -d $f ]]; then
      echo "$f" is a dir
      continue
    else
      echo "$f is not valid"
      exit 1
    fi
  done
}

sudo copy_dotfiles || exit 1

但是,当我运行脚本时,我得到以下信息:

sudo: copy_dotfiles: command not found

答案1

sudo不是 shell 函数 – 它是执行可执行文件的单独可执行文件:)

因此,它无法运行 shell 函数。不过它可以运行 shell 脚本!因此,如果您希望它起作用,请sudo从该脚本中省略 ,并通过 sudo 运行整个脚本。

确实source ~/.zshrc不管怎样,应该删除你的,但是尤其以 root 身份运行脚本时。

答案2

该代码的内联注释:

#!/bin/zsh
source ~/.zshrc

为什么要在脚本中获取调用用户的交互式 shell 自定义文件?这是没有意义的。

set -o errexit

您设置了errexit,但随后您(尝试)使用somefunction || ...取消了 的效果errexit

set -o nounset
set -o pipefail

并不是说你也可以set -o errexit -o nounset -o pipefail在任何 POSIX shell 中这样做,或者setopt errexit nounset pipefail

copy_dotfiles()
{
  MAIN_PATH="/Users/user01/"
  DOTFILE_DIR="${MAIN_PATH}Documents/dot_files"

名称全部大写的变量通常用于环境变量或具有 glob 作用域的变量。在函数中设置具有固定值的全局变量似乎很奇怪。

copy_dotfiles() { # Arg: username
  local target_dir=~$1/Documents/dot_files

copy_dotfiles用户名作为参数并在其主目录中设置目标目录的本地变量会更有意义。

定义一个不以参数为目标的函数没有多大意义

  echo $DOTFILE_DIR

请注意echo在打印之前对其参数进行转换。最好使用:print -r -- $DOTFILE_DIRprintf '%s\n' $DOTFILE_DIR

  mkdir $DOTFILE_DIR

如果您运行它,则将使用其用户而不是目标用户root创建新目录。root您不会检查函数的退出状态,mkdir并且errexit如果正在处理函数的退出状态,则会被取消,因此其余代码最终可能会执行mkdir失败。这可能会产生严重的后果,例如,如果用户碰巧已经创建了~/Documents/dot_files某些敏感系统的符号链接,或者包含带有敏感文件符号链接的文件的目录,例如/etc/shadow/root/.bashrc...

另外,作为一般原则,cmd $anything_dynamiccmd -- $anything_dynamic确保$anything_dynamic如果它碰巧以-.

  for f in $MAIN_PATH.[!.]*; do

更常见的是,将 pathname/dir 变量定义为目录的路径并使用它,而不是在变量值中$dir/file-within添加尾随并执行 do 。/${dir}file-within

此外,zsh globs 永远不会包含.nor ..,在 zsh 中^优先于!设置否定,.[!.]*虽然它会排除.并且..也会排除..file文件。

您可能还希望使用Nglob 限定符来允许列表为空并声明f本地:

local f
for f in $source_dir/.*(N); do
    if [[ -f $f ]]; then
      echo "$f" is a file

更像print -r -- $f is a regular file or symlink to regular file

另外,zshglobs 可以按类型选择文件,因此您不需要[[...]]在这里循环使用。

      DOTFILE=`echo $f | sed 's/\/Users\/user01\///g'`
      echo "$DOTFILE" is a file
      cp $f ${DOTFILE_DIR}/${DOTFILE} || exit 1

使用或任何其他文本实用程序修改文件路径sed通常是错误的,因为sed它是基于行的,并且文件路径可以由多行组成。zsh具有内置文本替换运算符(包括${var/pattern/replacement}from ksh 和$var:s/string/replacement/from csh)并$var:t获取路径的尾部,但在这里,您不需要进行任何替换,因为cp两者都可以复制到复制到

cp -- $f $target_dir/

将复制$f到其路径存储在的目录中$target_dir

但同样,如果以 root 身份运行,那将是危险的。

    elif [[ -d $f ]]; then
      echo "$f" is a dir
      continue
    else
      echo "$f is not valid"
      exit 1
    fi
  done
}

sudo copy_dotfiles || exit 1

sudo是一个外部命令,它采用命令文件的名称或路径来执行。它不能从任何 shell 或其他编程语言获取函数名称。

您可以告诉它执行一个 shell,然后该 shell 定义并调用一个函数,在这里您可以使用以下命令:

exec sudo zsh -o errexit -o nounset -o pipefail -c '
  '"$(typeset -f -- copy_dotfile)"'
  "$0" "$@"' copy_dotfile "$@"

但在这里,由于整个脚本只是该函数,您不妨以 root 身份调用它,或者如果脚本不是超级用户,则尝试以 root 身份重新执行自身:

#! /bin/zsh -
set -o nounset

# reexecute as root if effecive uid is not 0 (not privileged).
(( EUID == 0 )) || exec sudo -u root -- "$0" "$@"

user=${1?}

ERRNO=0 USERNAME=$user # drop privileges by switching all uids and gids
                       # to those of the target user
(( ERRNO )) && exit 1 # abort if we couldn't drop privileges

# cd to the source directory
cd -P -- ~$user || exit

# hidden regular (.) files.
dotfiles=( .*(N.) )

destdir=Documents/dot_files
mkdir -p -- $destdir || exit

if (( $#dotfiles )) exec cp -pP -- $dotfiles $destdir/

(未经测试)

相关内容