shell脚本入门
有关Shell脚本的世界上最好的概念介绍来自一个古老的AT&T培训视频 。 在视频中,Brian W. Kernighan( awk中的“ K”)和Lorinda L.Cherry( bc的合著者)演示了UNIX的一项创始原理是如何使用户能够利用现有实用程序来创建复杂的和自定义的工具。
用Kernighan的话来说:“将UNIX系统程序基本上看作是可以用来创建事物的构建块。管道内衬的概念是[UNIX]系统的根本贡献。 ;您可以使用一堆程序...并将它们首尾相连,以使数据从左侧的流向右侧的流,而系统本身负责所有连接。程序本身不会我对连接一无所知;就他们而言,他们只是在与终端交谈。”
他正在谈论让日常用户具有编程能力。
POSIX操作系统本身就是一个API。 如果可以弄清楚如何在POSIX Shell中完成任务,则可以使该任务自动化。 那就是编程,并且这种日常POSIX编程方法的主要工具是shell脚本。
就像它的名字一样,shell 脚本是您希望计算机执行的逐行配方,就像您手动完成一样。
由于外壳程序脚本由日常常用命令组成,因此熟悉UNIX或Linux(通常称为POSIX )外壳程序将很有帮助。 使用shell的实践越多,编写新脚本就越容易。 这就像学习一门外语:内在的词汇量越多,形成复杂句子的难度就越大。
打开终端窗口时,您正在打开外壳 。 那里有几个shell,本教程对bash , tcsh , ksh , zsh以及其他可能的对象有效。 在某些部分中,我确实提供了一些特定于bash的示例,但是最终的脚本放弃了这些示例,因此您可以切换到bash上有关设置变量的课程,也可以进行一些简单的语法调整 。
如果您不熟悉所有这些,只需使用bash即可 。 它是一个不错的外壳,具有许多友好的功能,并且在Linux,Cygwin,WSL,Mac和BSD上均是默认选项。
你好,世界
您可以从终端窗口生成自己的hello world脚本。 注意引号; 单人和双人有不同的效果。
$ echo "#\!/bin/sh" > hello.sh
$ echo "echo 'hello world' " >> hello.sh
如您所见,编写shell脚本除了第一行外,是将命令回显或粘贴到文本文件中。
要将脚本作为应用程序运行:
$ chmod +x hello.sh
$ ./hello.sh
hello world
或多或少,这就是全部!
现在让我们解决一些有用的问题。
分隔符
如果有一件事混淆了计算机和人类的互动,那就是文件名中的空格。 您已经在互联网上看到了它:诸如http://example.com/omg%2ccutest%20cat%20photo%21%211.jpg之类的URL。 或者运行一个简单的命令时,空格可能使您绊倒了:
$ cp llama pic.jpg ~/photos
cp: cannot stat 'llama': No such file or directory
cp: cannot stat 'pic.jpg': No such file or directory
解决方案是用反斜杠或引号“转义”该空间:
$ touch foo\ bar.txt
$ ls "foo bar.txt"
foo bar.txt
这些是很重要的技巧,但是很不方便,所以为什么不编写一个脚本从文件名中删除那些烦人的空格呢?
创建一个文件来保存脚本,以“ shebang”( #! )开头,以使您的系统知道该文件应在shell中运行:
$ echo '#!/bin/sh' > despace
好的代码始于文档。 定义目的可以使我们知道目标是什么。 这是一个很好的自述文件:
despace is a shell script for removing spaces from file names.
Usage:
$ despace "foo bar.txt"
现在,让我们弄清楚如何手动执行此操作,并在进行过程中构建脚本。
假设您在否则为空的目录中有一个名为“ foo bar.txt”的文件,请尝试以下操作:
$ ls
hello.sh
foo bar.txt
计算机都是关于输入和输出的。 在这种情况下,输入是对ls特定目录的请求。 输出就是您所期望的:该目录中文件的名称。
在UNIX中,可以通过“管道”将输出作为另一个命令的输入发送。 管道另一侧的任何东西都可以用作过滤器。 tr实用程序恰好被设计用来修改通过它的字符串。 对于此任务,请使用--delete选项删除引号中定义的字符。
$ ls "foo bar.txt" | tr --delete ' '
foobar.txt
现在,您已经有了所需的输出。
在BASH Shell中,您可以将输出存储为变量 。 将变量视为一个空盒,您可以在其中放置要存储的信息:
$ NAME=foo
当您需要返回信息时,可以通过引用以美元符号( $ )开头的变量名称来查找框。
$ echo $NAME
foo
要获取您的depacing命令的输出并将其保留以备后用,请使用一个变量。 要将命令的结果放入变量中,请使用反引号:
$ NAME=`ls "foo bar.txt" | tr -d ' '`
$ echo $NAME
foobar.txt
这可以使您完成目标的一半,您可以通过一种方法从源文件名确定目标文件名。
到目前为止,脚本看起来像这样:
#!/bin/sh
NAME=`ls "foo bar.txt" | tr -d ' '`
echo $NAME
脚本的第二部分必须执行重命名。 您现在可能已经有了该命令:
$ mv "foo bar.txt" foobar.txt
但是,请记住,在脚本中您正在使用变量来保存目标名称。 您知道如何引用变量:
#!/bin/sh
NAME=`ls "foo bar.txt" | tr -d ' '`
echo $NAME
mv "foo bar.txt" $NAME
您可以通过将其标记为可执行文件并在测试目录中运行来试用您的第一份草案。 确保您有一个名为“ foo bar.txt”的测试文件(或脚本中使用的任何文件)。
$ touch "foo bar.txt"
$ chmod +x despace
$ ./despace
foobar.txt
$ ls
foobar.txt
分隔符v2.0
该脚本有效,但与您的文档描述不完全相同。 它目前非常具体,仅适用于名为foo \ bar.txt的文件,仅此而已。
POSIX命令将自身称为$ 0,并将其后依次键入的任何内容称为$ 1 , $ 2 , $ 3 ,依此类推。 您的shell脚本算作POSIX命令,因此请尝试将foo \ bar.txt换成 $ 1 。
#!/bin/sh
NAME=`ls $1 | tr -d ' '`
echo $NAME
mv $1 $NAME
创建一些名称中带有空格的新测试文件:
$ touch "one two.txt"
$ touch "cat dog.txt"
然后测试您的新脚本:
$ ./despace "one two.txt"
ls: cannot access 'one': No such file or directory
ls: cannot access 'two.txt': No such file or directory
您似乎已发现错误!
这样的错误实际上并不是错误。 一切都按设计工作,而不是您希望的工作方式。 您的脚本会将$ 1变量“扩展”为确切的含义:“一个two.txt”,随之而来的是您要消除的烦人空间。
答案是将变量包装在引号中,就像将文件名包装在引号中一样:
#!/bin/sh
NAME=`ls "$1" | tr -d ' '`
echo $NAME
mv "$1" $NAME
另一个测试或两个:
$ ./despace "one two.txt"
onetwo.txt
$ ./despace c*g.txt
catdog.txt
此脚本的作用与任何其他POSIX命令相同。 您可以将其与其他命令结合使用,就像您希望能够使用任何POSIX实用程序一样。 您可以将其与命令结合使用:
$ find ~/test0 -type f -exec /path/to/despace {} \;
或者,您可以将其用作循环的一部分:
$ for FILE in ~/test1/* ; do /path/to/despace $FILE ; done
等等。
分隔符v2.5
despace脚本是功能性的,但是从技术上讲可以对其进行优化,并且可以使用一些可用性方面的改进。
首先,实际上不需要该变量。 Shell可以一次性计算所需的信息。
POSIX Shell具有一个操作顺序。 用数学上的方法,首先要解决方括号中的语句,在执行命令之前,shell会解析BASH中反引号( ` )或$()的语句。 因此,声明:
$ mv foo\ bar.txt `ls foo\ bar.txt | tr -d ' '`
变成:
$ mv foo\ bar.txt foobar.txt
然后执行实际的mv命令,只剩下foobar.txt 。
知道了这一点,您可以将Shell脚本压缩为:
#!/bin/sh
mv "$1" `ls "$1" | tr -d ' '`
这看起来简直令人失望。 您可能会认为将其简化为单行代码就不必使用脚本了,但是shell脚本不必使用很多行就可以使用。 即使输入一个简单的命令就可以保存打字的脚本,仍然可以使您免于致命的打字错误,这在涉及移动文件时尤其重要。
此外,您的脚本仍然可以使用改进。 额外的测试揭示了一些弱点。 例如, 不带参数运行despace会产生无用的错误:
$ ./despace
ls: cannot access '': No such file or directory
mv: missing destination file operand after ''
Try 'mv --help' for more information.
这些错误令人困惑,因为它们分别用于ls和mv ,但是据用户所知,它们运行的不是ls或mv ,而是despace 。
如果您考虑一下,如果这个小脚本最初没有在命令中获取文件,则甚至不要尝试重命名该文件,因此请尝试使用对变量了解的知识以及测试函数。
如果和测试
if语句使您的小空间工具从脚本变成程序。 这是一个严肃的代码领域,但是不用担心,它也很容易理解和使用。
if语句是一种切换。 如果某件事是正确的,那么您将做一件事;如果它是错误的,那么您将做另一件事。 这种if-then指令正是二进制决策计算机最擅长的一种。 您所要做的就是为计算机定义需要正确还是错误以及需要做什么。
测试True或False的最简单方法是测试实用程序。 您不直接调用它,而是使用它的语法。 在终端上尝试一下:
$ if [ 1 == 1 ]; then echo "yes, true, affirmative"; fi
yes, true, affirmative
$ if [ 1 == 123 ]; then echo "yes, true, affirmative"; fi
$
这就是测试的工作方式。 您可以选择各种速记方式,并且-z选项是您使用的一种-z选项,它检测字符串的长度是否为零(0)。 这个想法在您的despace脚本中转换为:
#!/bin/sh
if [ -z "$1" ]; then
echo "Provide a \"file name\", using quotes to nullify the space."
exit 1
fi
mv "$1" `ls "$1" | tr -d ' '`
if语句分为几行以提高可读性,但概念仍然存在:如果$ 1变量内的数据为空(存在零个字符),则输出错误语句。
试试吧:
$ ./despace
Provide a "file name", using quotes to nullify the space.
$
成功!
好吧,实际上这是一个失败,但这是一个相当大的失败,更重要的是,一个有用的失败。
注意语句退出1 。 这是POSIX应用程序向系统发送已遇到错误的警报的一种方式。 此功能对您自己以及对于可能希望在脚本中使用分隔符的其他人很重要,这些脚本必须成功取决于分隔符才能正确执行其他所有操作。
最终的改进是添加了一些保护用户免受意外覆盖的文件。 理想情况下,您应该将此选项传递给脚本,以便它是可选的,但是为了简单起见,您将对其进行硬编码。 -i选项告诉mv在覆盖已经存在的文件之前请求许可:
#!/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 ' '`
现在,您的Shell脚本是有用,有用和友好的-并且您是一名程序员,所以请立即停止。 学习新命令,在终端中使用它们,记下您的操作,然后编写脚本。 最终,您将失去工作,而您的余生将在机器人小仆运行Shell脚本时度过轻松时光。
骇客骇客!
翻译自: https://opensource.com/article/17/1/getting-started-shell-scripting
shell脚本入门