在Shell 脚本入门中,我通过创建一个非常方便的“despacer”脚本来删除文件名中令人讨厌的空格,介绍了 shell 脚本的基础知识。现在我将把这个想法扩展到一个也可以替换其他字符的应用程序。 这介绍了用于解析选项的 shift 方法以及创建自定义函数。
本文适用于 bash、ksh 和 zsh shell。它的大部分原理也适用于 tcsh,但 tcsh 在语法和逻辑流程方面存在显着差异,值得单独撰写一篇文章。如果您正在使用 tcsh 并想为您自己调整本课程,请研究函数技巧(tcsh 技巧)和条件语句的语法,然后尝试一下。 奖励积分将被授予。
参数和选项解析
如果您一直在使用 shell 脚本来自动化日常任务,那么您应该熟悉这样一种想法,即您可以从 POSIX shell 执行的任何命令也可以编写成脚本。 您可以稍后在任何时候运行该脚本,以执行一系列您原本必须手动执行的命令。 这种做法是开始编程的好方法,但 shell 脚本不仅仅是供您的计算机读取和盲目执行的脚本;它们可以即时修改,以便它们适应您在每个运行时需要的内容。
为了说明区别,请看上一篇文章中的简单脚本
$ ~/bin/depspacer "foo bar.txt"
$ ls
foobar.txt
更高级的版本可能会提供选项
$ ~/bin/hello-2.0.sh --from ' ' --to '_' "foo bar.txt"
$ ls
foo_bar.txt
更好吗? 当然是!
为了获取传递到脚本中的即时用户输入,您需要学习如何解析参数和选项。 有多种解析输入的方法,它们因语言而异,但对于 POSIX shell,您可以使用一系列 if/then 语句来顺序检查每个参数。
回想一下,POSIX 命令将自身视为 $0,第一个参数是 $1,然后是 $2,依此类推。 如果您在终端中键入 despace --from ' ',则 despace 是 $0,--from 是 $1,' ' 是 $2,依此类推。
当前的脚本看起来像这样
#!/bin/sh
if [ -z "$1" ]; then
echo "Provide a \"file name\", using quotes to nullify the space."
exit 1
fi
mv -i "$1" `ls "$1" | tr -d ' '`
它通过检查是否存在参数来解析恰好一个参数。 为了扩展这个概念,请使用 test 编写一个 if/then 语句来分析每个参数。 这是不完整的,但这是一个示例,假设您希望能够发出命令
despacer --from ' ' --to '_' foo\ bar.txt
然后
if [ "$1" = "--from" ]; then
FROM="$2"
elif [ "$3" = "--to" ]; then
TO="$4"
fi
您可能会看到一个潜在的问题。 它假设用户总是首先提供 --from 参数,或者根本不提供,然后提供 --to 参数。
解决这个问题的方法是不断循环遍历参数,依次处理每个参数,直到没有更多参数为止。 为了保持解析直到没有更多参数要解析,您可以使用带有 break 回退的 while 循环。 为了强制脚本从一个参数进行到下一个参数,您将使用 shift。
while [ True ]; do
if [ "$1" = "--from" ]; then
FROM="$2"
shift 2
elif [ "$1" = "--to" ]; then
TO="$2"
shift 2
elif [ "$1" = "--help" ]; then
echo "despacer [ options ] FILE"
echo "--from character to remove"
echo "--to character to insert"
echo "--help print this help message"
shift 1
else
break
fi
done
ARG="$1"
在此代码块中,$1 和 $2 的值相对于任何匹配项。 当解析器找到 --from 时,--from 是 $1,之后的所有内容都是 $2。 变量已设置 (FROM=$2),并且这些参数已移出(shift 2)。
当解析器找到 --to 时,--to 是 $2,之后的所有内容都是 $2。 变量已设置 (TO=$2),并且这些参数已移出(shift 2)。
每当找到 --help 时,它就变成 $1,并且不期望有任何后续内容(shift 1)。
当没有剩余要解析的内容时,会触发 else,并且脚本使用 break 跳出循环。
您可以合理地假设现在剩下的就是您想要去除空格的文件,因此将该值分配给 $ARG。
将其集成到 despacer 应用程序中
#!/bin/sh
while [ True ]; do
if [ "$1" = "--from" ]; then
FROM="$2"
shift 2
elif [ "$1" = "--to" ]; then
TO="$2"
shift 2
elif [ "$1" = "--help" ]; then
echo "despacer [ options ] FILE"
echo "--from character to remove"
echo "--to character to insert"
echo "--help print this help message"
shift 1
else
break
fi
done
ARG="$1"
mv -i "$ARG" `ls "$ARG" | tr "$FROM" "$TO"`
试用一下
$ ./despacer --from ' ' --to '_' "foo bar.txt"
$ ls
foo_bar.txt
它工作了! 再次尝试,仅使用默认操作
$ ./despacer.sh "foo bar.txt"
mv: target 'bar.txt' is not a directory
您引入了一个新功能,但也引入了一个导致它失败的错误。 更糟糕的是,它破坏了应用程序曾经的工作方式。
主要问题是,如果未设置 $FROM 和 $TO 的值,则 tr 命令无法成功。 此外,如果未设置其中一个或两个都未设置,则 tr 失败。
更棘手的是,如果设置了 $FROM 但 $TO 保留其默认值,则 tr 失败,因为 tr 需要两个参数,除非使用 --delete(或简写 -d)选项。
但是,如果您硬编码 -d 以使 tr 工作,则 $TO 将永远无效。
这真是一个难题。 您能猜到如何解决吗?
有很多可能的答案,但我在这里解决它的方法是操纵变量设置的顺序以及最终命令的启动方式。
但是,在继续之前,您需要了解自定义函数。
函数
大多数 shell 和编程语言都提供了一种方法,可以编写一系列语句一次,然后在代码中稍后重用它们。 这样,您只需编写一次代码,这意味着如果它正在执行的操作中存在任何错误或逻辑错误,您只需在一个地方查找错误。 此外,您通常最终会编写更少的代码行,这通常表明了良好的优化。
在这个小脚本中,您需要调整的代码已经减少到一行代码,因此使用函数对您没有太大好处,但它确实有助于保持代码的整洁并使其易于理解。
要在 bash、ksh 或 zsh 脚本中创建函数
myfunction() {
echo "world"
}
稍后在脚本中调用此函数,方法是将函数名称用作命令
printf "hello \n"
myfunction
您可以使用此技术根据变量的设置方式为脚本提供不同的命令序列。
函数式解决方案
我之前提出的难题通过函数和巧妙的变量设置相结合来解决。
首先,提供默认的 FROM 值:一个空格 (' ')。
此外,将默认的最终命令设置为包含 tr -d "$FROM" 短语的函数。
假设没有提供任何选项,这些默认值保持不变。
如果您提供新的 FROM 值,则默认命令保持不变,并且只有 tr 删除的字符不同。
但是,如果您提供新的 TO 值,则 tr 不再处于删除模式,因此您将创建一个不同的函数,该函数在其本机转换模式下使用 tr。
首先,设置初始值
#!/bin/sh
FROM=' '
ACTION=trdelete
添加两个函数
trtarget() {
mv -i "${ARG}" `ls "${ARG}" | tr "${FROM}" "${TO}"`
}
trdelete() {
mv -i "${ARG}" `ls "${ARG}" | tr -d "${FROM}"`
}
如果提供了 --to 选项,则添加 ACTION 变量的覆盖
while [ True ]; do
if [ "${1}" = "--from" ]; then
FROM="${2}"
shift 2
elif [ "${1}" = "--to" ]; then
TO="${2}"
ACTION=trtarget
shift 2
elif [ "${1}" = "--help" ]; then
echo "despacer [ options ] FILE"
echo "--from character to remove"
echo "--to character to insert"
echo "--help print this help message"
shift 1
else
break
fi
done
ARG="${1}"
最后,执行最终命令,无论它最终是什么
$ACTION
这是一个功能齐全的 bash、ksh 或 zsh 脚本,具有变量覆盖、函数和选项解析。
如果这种黑客技术让您感到兴奋,请不要止步于此! 找到您系统中经常执行的其他任务,并找到一种方法使它们对您自己更好。 您编写的每个脚本或应用程序都不必开创新局面。 有时,您编写代码只是为了避免自己犯愚蠢的错误,或者不必键入或单击太多。 换句话说,没有哪个任务小到不能改进,所以请参与其中并改善您的工作环境。 它只会导致以后更大更好的事情。
4 条评论