我想将 .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_DIR
或printf '%s\n' $DOTFILE_DIR
mkdir $DOTFILE_DIR
如果您运行它,则将使用其用户而不是目标用户root
创建新目录。root
您不会检查函数的退出状态,mkdir
并且errexit
如果正在处理函数的退出状态,则会被取消,因此其余代码最终可能会执行mkdir
失败。这可能会产生严重的后果,例如,如果用户碰巧已经创建了~/Documents/dot_files
某些敏感系统的符号链接,或者包含带有敏感文件符号链接的文件的目录,例如/etc/shadow
,/root/.bashrc
...
另外,作为一般原则,cmd $anything_dynamic
应cmd -- $anything_dynamic
确保$anything_dynamic
如果它碰巧以-
.
for f in $MAIN_PATH.[!.]*; do
更常见的是,将 pathname/dir 变量定义为目录的路径并使用它,而不是在变量值中$dir/file-within
添加尾随并执行 do 。/
${dir}file-within
此外,zsh globs 永远不会包含.
nor ..
,在 zsh 中^
优先于!
设置否定,.[!.]*
虽然它会排除.
并且..
也会排除..file
文件。
您可能还希望使用N
glob 限定符来允许列表为空并声明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
。
另外,zsh
globs 可以按类型选择文件,因此您不需要[[...]]
在这里循环使用。
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/
(未经测试)