Shell 脚本:shift 方法和自定义函数简介

374 位读者喜欢这个。
Open in sky

Nasjonalbiblioteket。由 Opensource.com 修改。CC BY-SA 4.0

Shell 脚本入门中,我介绍了 shell 脚本的基础知识,创建了一个非常方便的 “despacer” 脚本来删除文件名中令人讨厌的空格。现在我将把这个想法扩展到一个也可以替换其他字符的应用程序。 这介绍了解析选项的 shift 方法以及创建自定义函数。

本文适用于 bashkshzsh 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 和编程语言都提供了一种编写一系列语句一次,然后在代码中稍后重用它们的方法。 这样,您只需编写一次代码,这意味着如果它正在执行的操作中存在任何错误或逻辑错误,您只需在一个地方查找错误。 此外,您通常最终会编写更少的代码行,这通常表明良好的优化。

在这个小脚本中,您需要调整的代码已经减少到一行,因此使用函数不会给您带来太多好处,但它确实有助于保持代码的整洁并使其易于理解。

要在 bashkshzsh 脚本中创建函数

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

这是一个功能齐全的 bashkshzsh 脚本,具有变量覆盖、函数和选项解析。

如果这种黑客行为让您兴奋,请不要止步于此! 在您的系统中找到您经常执行的其他任务,并找到一种方法来为您自己改进它们。 您编写的每个脚本或应用程序都不必开辟新天地。 有时,您编写代码只是为了避免自己犯愚蠢的错误,或者避免不得不输入或单击那么多。 换句话说,没有哪个任务小到无法改进,所以请参与其中并改善您的工作环境。 这只会导致以后更大更好的事情。

标签
Seth Kenlon
Seth Kenlon 是一位 UNIX 极客、自由文化倡导者、独立多媒体艺术家和 D&D 爱好者。 他曾在电影和计算机行业工作,通常同时进行。

4 条评论

Excelente!

很高兴您喜欢这篇文章!

回复 作者:Marcos Oliveira (未验证)

一篇很棒的文章,正是我一直在寻找的。 谢谢!

知识共享许可协议本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.