道法自然

夜阑卧听风吹雨,铁马冰河入梦来

  • Home
  • Categories
  • About
  • Archives
  • Tags

openwrt单个ipk编译过程

Posted on 2016-01-03   |   In openwrt   |  

本周是成胖子每周一博的第五周. 更好的阅读体验,请点击这里

<!--more-->


前言

 前一篇博客中,我们已经知道整个openwrt的编译顺序,本文我们来探讨与开发者息息相关的单个ipk的编译过程.在开发者进行二次开发的时候,我们既可以单个编译ipk也可以完整编译整个镜像文件.在完整编译的时候,我们选中的单个ipk同样会被编入镜像文件中,所以完整编译同样会进行单个ipk包的编译.

我们前面在stampfile函数部分提高过,当编译目标为package/stamp-compile的时候,实际执行的目标为package/compile;同时根据subdir函数的定义,package/compile将会依赖于package文件夹下被make menuconfig选中的子文件夹的compile.简而言之,当我们执行make package/compile相当于对所有选中的文件夹执行make package/XXX/compile.


ipk Makefile分析

我们以一个具体的包的编译过程来看看,本文我们以package/network/services/dropbear这个包为例.当我们在命令行中输入make package/network/services/dropbear/compile的时候,make将会读入dropbear下的Makefile文件,同时目标指定为compile.

因为空间问题,我在这里不展开具体的Makefile文件.相信能看这篇博客的同学应该都有源码,自己打开便是. 下面我们根据GNU make语法来分析这个Makefile文件.它包含了两个.mk文件:一个是rules.mk,另一个是package.mk.

rules.mk:

这个文件我们前文已经提到过了,主要是大量变量的定义.包括各种路径的定义,编译器的定义等等.其中要说明的是**.config**文件也是这里被包含进来的.

package.mk

这个文件首先定义和补充了一些变量.其次是openwrt为我们封装了BuildPackage函数,对于普通开发者而言,只需要参照模板定义相应的变量,最后调用这个函数即可.

其余的我们可以认为是变量的赋值语句,很明显使用它们的地方并不在这里.关于模板和变量值的说明及作用.,你可以参照官方说明,也可以在网上找到一大堆资料.

最后,最重要的语句是这一句:

1
$(eval $(call BuildPackage,dropbear))

这里将会把dropbear作为参数值传给函数BuildPackage

Tips : 不知道大家还记得我们Makefile的执行顺序么?Makefile是先读入所有信息,展开,然后生成依赖关系.最后再按依赖关系先后来执行.


依赖关系

BuildPackage分析

BuildPackage的定义在package.mk中,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
define BuildPackage
$(Build/IncludeOverlay)
$(eval $(Package/Default))
$(eval $(Package/$(1)))
ifdef DESCRIPTION
$$(error DESCRIPTION:= is obsolete, use Package/PKG_NAME/description)
endif
ifndef Package/$(1)/description
define Package/$(1)/description
$(TITLE)
endef
endif
BUILD_PACKAGES += $(1)
$(STAMP_PREPARED): $$(if $(QUILT)$(DUMP),,$(call find_library_dependencies,$(DEPENDS)))
$(foreach FIELD, TITLE CATEGORY SECTION VERSION,
ifeq ($($(FIELD)),)
$$(error Package/$(1) is missing the $(FIELD) field)
endif
)
$(if $(DUMP), \
$(Dumpinfo/Package), \
$(foreach target, \
$(if $(Package/$(1)/targets),$(Package/$(1)/targets), \
$(if $(PKG_TARGETS),$(PKG_TARGETS), ipkg) \
), $(BuildTarget/$(target)) \
) \
)
$(if $(PKG_HOST_ONLY)$(DUMP),,$(call Build/DefaultTargets,$(1)))
endef

那么这里的$(1)就是指的传入的参数dropbear.这里包含了一些检查和补充变量定义.继续深究下去的线索是第25~32行之间.这里我将它简化后就是展开BuildTarget/ipkg;同时第33行,将dropbear当作参数传给函数Build/DefaultTargets.

BuildTarget/ipkg定义在package-ipkg.mk中,我们需要重点关注其中的冒号,这个形成我们的依赖关系. Build/DefaultTargets定义在package.mk中,其中形成了我们stamp-*的依赖关系.根据这些依赖关系,我将关系图绘制如下: package/compile

执行

当我们得出依赖关系后,执行过程就是倒序进行而已,即从上图的右边向左执行.这也可以和我们预料的执行过程相印证.

$(stamp-prepared) : 主要完成代码包的准备工作,如果开发者定义了build/prepare,则执行build/prepare.如果开发者未定义,则执行build/prepare/default这其中包含了多个情形,最为常见的是将dl下的压缩包解压并打上patch.

$(stamp-built)

这个将会进入到**build_dir/target-XXX/**下对应的文件夹进行编译.同时将会带入一些定义好的变量.比如CFLAGS,LDFLAGS.

IPKG_(1)

这个目标将会将编译好的文件安装到对应的ipkg-arch目录下,同时将这个目录打包为ipk文件.


尾记

本周博客基本就到这里,本来私心想着元旦没啥大事,可以写两篇的.结果混着混着就到第三天晚上了.剩下的最后一篇我们看看单个的ipk编译好了,内核的编译过程,最后的打包过程.整个镜像文件由哪些部分组成.下周再见.

openwrt主Makefile解析

Posted on 2015-12-26   |   In openwrt   |  

本周成胖子每周一博到了第四周

前言

前一篇,我们大概描述了整个镜像文件的生成过程.本周我们来解析主Makefile,看看主要编译过程是怎么产生的.

<!--more-->

主Makefile结构

我们以chaos calmer的代码为例,整个编译的入口是在源码根目录下的Makefile.编译的各种命令都应该在源码根目录下键入. 整个主Makefile的结构如下:

1
2
3
4
5
6
world:
ifneq ($(OPENWRT_BUILD),1)
顶层
else
第二层
endif

开始部分是一些注释和变量定义及路径检查. 根据Makefile的规则,在没有指定编译目标的时候,Makefile中的第一个目标将作为默认目标. 换句话说,当我们执行make V=s时,这个时候编译的目标就是world.和我们执行make world V=s效果是一样的.

顶层

通常在编译时,我们不会定义变量OPENWRT_BUILD的值,所以通常我们是会走到顶层的. 顶层代码如下:

1
2
3
4
5
6
7
8
9
_SINGLE=export MAKEFLAGS=$(space);
override OPENWRT_BUILD=1
export OPENWRT_BUILD
GREP_OPTIONS=
export GREP_OPTIONS
include $(TOPDIR)/include/debug.mk
include $(TOPDIR)/include/depends.mk
include $(TOPDIR)/include/toplevel.mk

这里我们看到变量OPENWRT_BUILD被置为1.然后包含了3个.mk文件. 这里稍微解释下.mk文件.它们一般没有什么执行动作,都是一些变量的定义还有依赖关系的说明.可以类比于C语言的头文件来理解.

debug.mk:

可以通过定义DEBUG的值来控制编译过程

depends.mk

主要定义了rdep这个变量

toplevel.mk

这个是我们跟踪编译过程的重要的文件.这个文件在源码根目录下的include文件夹下.

核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
%::
@+$(PREP_MK) $(NO_TRACE_MAKE) -r -s prereq
@( \
cp .config tmp/.config; \
./scripts/config/conf --defconfig=tmp/.config -w tmp/.config Config.in > /dev/null 2>&1; \
if ./scripts/kconfig.pl '>' .config tmp/.config | grep -q CONFIG; then \
printf "$(_R)WARNING: your configuration is out of sync. Please run make menuconfig, oldconfig or defconfig!$(_N)\n" >&2; \
fi \
)
@+$(ULIMIT_FIX) $(SUBMAKE) -r $@ $(if $(WARN_PARALLEL_ERROR), || { \
printf "$(_R)Build failed - please re-run with -j1 to see the real error message$(_N)\n" >&2; \
false; \
} )

除了少数在toplevel中被定义的目标外,其他编译目标都会走到这里.将之简化后:

1
2
3
%::
make prereq
make $@

首先执行prereq,然后再执行我们指定的目标或者默认目标world. prereq整理后的依赖关系如下: prereq 其中 staging_dir/host/.prereq-build:

将会执行一系列主机检查,是否安装了必要的软件.

prepare-tmpinfo:

根据scan.mk,扫描target/linux和package目录,生成packageinfo和targetinfo.

总之,顶层完成一系列必要的准备工作.对于绝大多数的目标而言,顶层是必经之路.当然,在toplevel.mk中,我们也可以看到目标menuconfig.也就是说对于目标menuconfig而言,将不会执行到第二层的逻辑.

第二层

在上面执行完make prereq之后,将执行make world. 还记得我们进入顶层后修改了变量OPENWRT_BUILD么?当再次执行make world的时候,由于条件不满足,我们将直接进入第二层来执行.

1
2
3
4
5
6
7
include rules.mk
include $(INCLUDE_DIR)/depends.mk
include $(INCLUDE_DIR)/subdir.mk
include target/Makefile
include package/Makefile
include tools/Makefile
include toolchain/Makefile

rules.mk:

很重要的一个mk文件,其中规定了很多有用的变量,包括各种目录路径的定义,交叉编译器等等.其中

1
2
3
4
> ifeq ($(DUMP),)
-include $(TOPDIR)/.config
endif
>

就是包含了我们的配置文件.对于Makefile而言,.config文件就是一大串变量的定义.Makefile可以直接读取这些定义,从而控制编译过程.

subdir.mk:

这个是读懂我们整个编译过程的关键所在,其中主要定义了两个函数:subdir和stampfile,我们稍后加以解释.

接下来,包含了4个Makefile文件.我们以target/Makefile为例.该文件位于target目录下. 核心部分为:

1
2
3
4
5
$(eval $(call stampfile,$(curdir),target,prereq,.config))
$(eval $(call stampfile,$(curdir),target,compile,$(TMP_DIR)/.build))
$(eval $(call stampfile,$(curdir),target,install,$(TMP_DIR)/.build))
$(eval $(call subdir,$(curdir)))

这里调用了subdir.mk中定义的stampfile函数.将会生成target/stamp-prereq,target/stamp-compile,target/stamp-install三个变量. 以target/stamp-prereq为例,执行部分为make target/prereq.同理target/stamp-compile,执行部分为make target/compile.

最后又调用了sbudir函数,这个函数规定了目标和各子文件夹之间的依赖关系.如果有一定的Makefile基础可以去读读subdir.mk文件. 举例而言就是:

当执行目标为target/compile,这个目标将依赖于target/linux/compile. 当执行目标为package/compile,这个目标将依赖于package目录下,各子文件夹的compile.

下面就是规定了一系列的依赖关系,我已经将他们梳理了出来,如下图: world

这里就是第二层解析后的依赖关系.当依赖关系生成后,将会从最先被依赖的目标开始执行. 比如我们可以看到进入第二层后,tools/stamp-install将会最先被执行,也就是主机工具将会最先被编译,安装.我们上一篇提高的整个编译过程能从上图中得出.

尾记

  1. 想要读懂Makefile,首先要梳理各个依赖关系.而要梳理各个依赖关系,关键要关注冒号和make -C
  2. 本周我们解析了主Makefile,在Makefile的执行过程中要理解make的执行过程.先读入Makefile,然后构建依赖关系,最后最先被依赖的目标将会先执行.
  3. 我主要描绘了主要枝干,如果希望了解更多细节,还是要自己去阅读Makefile.
  4. 接下来两篇,我们将主要分析下,和我们开发者比较相关的两个目标的执行过程:package/stamp-compile和target/stamp-install.下周再会^_^

openwrt编译过程概述

Posted on 2015-12-20   |   In openwrt   |  

前言

又到了成胖子每周一博的时间了,本周是第三周. 本周我们继续了解openwrt的编译过程,如果还有没写过简单ipk或者编译过openwrt的朋友,可以参见我之前的博客或者网上的其他文章

<!--more-->

一 年轻的冲动

为什么我在学习的过程中,有先学习整个编译过程和Makefile的冲动呢?

1.我们知道电脑的运算速度是很快的.即使如此,一个完整的编译过程往往需要好几个小时.这中间到底发生了些什么?屏幕上一闪而过的像天书一样的东西,我怎么才能有所了解?

2.经历漫长的等待,我们得到了一个镜像bin文件.这里面到底包含了什么东西?我可以知道么?

3.编译单个ipk的时候,我按照模板写的Makefile怎么和我在书上见的长的不太一样呢?

4.在单个ipk中,怎么包含头文件,动态库,静态库?怎么解决依赖关系?

5.在输入make menuconfig之后,又发生了什么?弹出的图形界面中,怎么会有我放在package目录中的源码的信息?

6.有的时候,编译报错了.提示信息它认识我,我不认识它.我要怎么排查呢?

...

零零散散的总是有很多疑问困扰着我.我相信有很多刚接触openwrt的朋友都和我有同感.而所有这些都是可以通过完整学习编译过程来解答的.这样想想是不是更有学习的动力了呢?

二 学习曲线

2.1 Makefile基本语法

 我们知道编译过程是通过Makefile来控制的.这样而言,Makefile的基本语法就必须有所了解.网上有很多零散的资料往往不成体系.推荐阅读一个是陈皓自己写的和翻译的GNU Make的手册.还是比较全的.英文还凑合的朋友,我推荐还是尽力读读官方的手册,简介精炼,没事学点英文也是好的.后面的文章,假定读者对Makefile语法有所了解.

2.2 bash基础

Makefile中的执行部分(recipe)是有bash脚本组成的,所以我们同时应当对bash shell有所了解.

2.3 world

有了这两部分的预备知识,我们将开始我们的征途.我们的目标是world.

三 编译过程概述

###3.1 主机预装工具

在编译源码之前,我们必须手动安装一部分工具 .

1
sudo apt-get install gcc g++ binutils patch bzip2 flex bison make autoconf gettext texinfo unzip sharutils subversion libncurses5-dev ncurses-term zlib1g-dev

这部分是在执行编译工作之前的.

3.2 编译host工具

除了我们在第一步安装的工具,编译过程中还需要其他一些主机工具.这部分工具将首先编译.

3.3 编译交叉工具链

openwrt自带交叉编译链,当然在编译目标平台软件前,需要先编译.

3.4 编译内核模块

因为部分内核模块将会生成独立的ipk,所以内核模块需要首先编译.

3.5 编译ipk

这里将编译package目录下的各个软件包,这也是和我们最为息息相关的.之后的博客将会重点介绍这个部分.

3.6 安装ipk

将生成的ipk安装到文件系统之中(比如build_dir/target-XXX/root-ramips目录).

3.7 编译内核

在完成ipk编译之后,将会编译内核,压缩内核.同时使用mkimage工具,在内核前面生成一个用于uboot识别的头部.

3.8 合成

在最后一步,将squashfs格式的文件系统和内核连接在一起,即生成了目标镜像文件.

尾声

本周到此为止,下一篇我们将介绍根目录下的Makefile,从而知道为什么编译的大致过程是上面提及的八步.

openwrt目录概述

Posted on 2015-12-13   |   In openwrt   |  

前言

 这段时间总是在和openwrt打交道,之前也零零散散地写过一点,还是希望能有点体系。还记得我刚看到源代码的时候,觉得无从下手.我想从Makefile的整个执行过程入手,搞清楚编译源代码的几个小时中,到底发生了哪些故事.

 本文是这个系列的第一篇.主要讲一下我对openwrt整个目录结构的理解.我们将源代码从官方下载而来的目录称之为原始目录,将编译后生成的目录称之为生成目录,分两部分介绍各个目录.

<!--more-->


原始目录

下载源码后,源文件如下图所示,下面我们来一一解释; 原始目录

1. scripts

存放了一些脚本,使用了bash,python,perl等多种脚本语言.编译过程中,用于第三方软件包管理的feeds文件也是在这个目录当中.在编译过程中,使用到的脚本也统一放在这个目录中.

2. tools

编译时,主机需要使用一些工具软件,tools 里包含了获取和编译这些工具的命令.软件包里面有Makefile文件,有的还包含了patch.每个Makefile当中都有一句$(eval $(call HostBuild)),这表明编译这个工具是为了在主机上使用的.

3. config

存放着整个系统的配置文件

4. docs

包含了整个宿主机的文件源码的介绍, 里面还有Makefile为目标系统生成docs.使用make -C docs/可以为目标系统生成文档.

5. toolchain

嵌入式的童鞋应该都知道交叉编译链,这个文件中存放的就是编译交叉编译链的软件包.包括:binutils,gcc,libc等等.

6. target

openwrt的源码可以编译出各个平台适用的二进制文件,各平台在这个目录里定义了firmware和kernel的编译过程。

7. package

存放了openwrt系统中适用的软件包,包含针对各个软件包的Makefile。openwrt定义了一套Makefile模板.各软件参照这个模板定义了自己的信息,如软件包的版本、下载地址、编译方式、安装地址等。在二次开发过程中,这个文件夹我们会经常打交道. 事实上,通过./scripts/feed update -a和./scripts/feed install -a的软件包也会存放在这个目录之中.

8. include

openwrt的Makefile都存放在这里。文件名为 *.mk 。这里的文件上是在Makefile里被include的,类似于库文件.这些文件定义了编译过程.

9. 其他

主要目录就是前面提及的8个,剩下的是单个文件.

9.1 Makefile:

在顶层目录执行make命令的入口文件.

9.2 rules.mk

定义了Makefile中使用的一些通用变量和函数

9.3 Config.in

在include/toplevel.mk中我们可以看到,这是和make menuconfig相关联的文件.

9.4 feeds.conf.default

是下载第三方一些软件包时所使用的地址

9.5 LICENSE & README

即软件许可证和软件基本说明.其中README描述了编译软件的基本过程和依赖文件.

至此我们把原始目录大致浏览了一遍,下面我们看看生成目录.

生成目录

在我们编译完成后除了下载的源码文件,多出来的部分很明显就是编译过程中新生成的.如下图: 生成目录

1. feeds

openwrt的附加软件包管理器的扩展包索引目录.有点绕,简单来说就是下载管理软件包的.默认的feeds下载有packages、management、luci、routing、telephony。如要下载其他的软件包,需打开源码根目录下面的feeds.conf.default文件,去掉相应软件包前面的#号,然后更新源: ./scripts/feeds update -a 安装下载好的包: ./scripts/feeds install -a

2. build_dir

在前面的原始目录中,我们提到了host工具,toolchain工具还有目标文件.openwrt将在这个目录中展开各个软件包,进行编译.所以这个文件夹中包含3个子文件夹:

2.1 host

在该文件夹中编译主机使用的工具软件

2.2 toolchain-XXX

在该文件夹中编译交叉工具链

2.3 target-XXX

在此编译目标平台的目标文件,包括各个软件包和内核文件.

3. bin

保存编译完成后的二进制文件,包括:完整的bin文件,所有的ipk文件.

4. dl

在编译过程中使用的很多软件,刚开始下载源码并没有包含,而是在编译过程中从其他服务器下载的,这里是统一的保存目录

5. staging_dir

用于保存在build_dir目录中编译完成的软件.所以这里也和build_dir有同样的子目录结构. 比如,在target-XXX文件夹中保存了目标平台编译好的头文件,库文件.在我们开发自己的ipk文件时,编译过程中,预处理头文件,链接动态库,静态库都是到这个子文件夹中.

6.tmp

从名字来看,是临时文件夹.在编译过程中,有大量中间临时文件需要保存,都是在这里.

7.logs

这个文件夹,有时可以看到,有时没有.这是因为这个文件夹保存的是,编译过程中出错的信息,只有当编译出错了才会出现.我们可以从这里获取信息,从而分析我们的软件编译为什么没有完成.

至此我们把openwrt的目录结构大体浏览了一遍.

尾记

  1. 本文中不少内容都是从网上看到的,时间长了,我已经找不到出处了.这也是我写博客的一个原因,我从互联网学到不少知识,现在再反馈给网络.感谢所有有自由共享精神的朋友.
  2. 知识学习是不断递进的过程.这部分知识是我目前知道的一个大概,还不够深刻.更深入的学习Makefile和openwrt之后,再来慢慢更新.

Openwrt LuCI模块练习详细步骤

Posted on 2015-09-02   |   In openwrt   |  

前言

又到了成胖子每周一博的时间了.最近在学习openwrt luci方面的知识,为了贯穿整个知识体系,练习题目为:

通过页面配置周期性地往/tmp/addtest文件写入内容和时间戳

1.在web主页面的下拉菜单做一个按钮,进入设置页面;

2.两个设置项:输入的内容和周期;

3.读取/tmp/addtest中的内容并显示在页面上;

代码已经开源,欢迎交流~

<!--more-->


知识准备

源码编译及ipk生成

这部分网上相关文章很多,也可以参见拙作

LuCI

首先回答一个问题:什么是Luci?

>LuCI是OpenWrt上的Web管理界面,LuCI采用了MVC三层架构,使用Lua脚本开发.

简单地说,Luci就是用来做openwrt的页面的.不同于常见的html+css+javascript,Openwrt是用lua脚本语言开发的.

怎么开发一个页面呢?

要开发一个新的功能页面,开发者只要根据MVC框架写些简单的lua脚本,剩下的部分由openwrt为你自动完成.

说到MVC框架了,什么是MVC框架呢?

MVC是model+view+controller的简写.为了便于开发,openwrt将实现不同功能的lua脚本放在不同的文件夹中.请看下图:

MVC架构

什么是controller控制器?

我们在这里设置功能在页面的位置,同时设置点击页面后,将要调用的功能.是要去Model模型读写配置数据呢?还是要呈现一个静态页面,或者是直接执行lua脚本函数.

什么是model模型?

这里我们常用的是,通过cbi模块和UCI(统一配置接口)进行交互.简单地说,就是我们在这里将页面和路由器里面的配置关联起来,从而将页面的设置写到路由器当中.

什么是view视图?

这个应该是最容易理解的,就是呈现的页面的样式,有点类似于传统的html页面.

上面说到了UCI(Unified Configuartion Interface),这是什么龟?

openwrt将配置用统一的格式书写,放在规定的地方(/etc/config/),同时提供接口函数进行读取和设置.

如果还不太明白,接着向下看.如果有可能跟着我动动手,相信你很快就会掌握:)


正文

我们先看下最终效果图:

最终效果

我们在页面上面的System下拉框的下面加了一个AddTest按钮,下面有两个子选项:Set和Info.其中Set用于选择是否开启功能,设置时间间隔和内容.Info用于显示/tmp/addtest文件中的内容.

准备工作

首先,嗯~ 你得有环境,得有电,有源码,编译过简单的ipk.如果没有,请回炉重造.

其次,建立相应的文件夹及文件.至于linux操作神马的,我相信你一定没有问题.

1
2
$mkdir -p ~/temp/addtest
$cd ~/temp/addtest

最终文件树形图

树形图

骨架已经有了,下面只需要往里面填肉了,是不是感觉很快~ 不要管为什么要这样,我们后面慢慢解释.


controller

前面我们提到,controller主要用于控制页面按钮位置,以及调用的功能.首先来编辑这个文件.

1
$vim ~/temp/addtest/files/usr/lib/lua/luci/controller/addtest.lua

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module("luci.controller.addtest",package.seeall)
function index()
entry({"admin","system","addtest"},alias("admin","system","addtest","set"),_("AddTest"),99).index=true
entry({"admin","system","addtest","set"},cbi("addtest"),_("Set"),1)
entry({"admin","system","addtest","info"},call("action_info"),_("Info"),2)
end
function action_info()
if not nixio.fs.access("/tmp/addtest") then
return
end
local info = nixio.fs.readfile("/tmp/addtest")
luci.template.render("addtest_info",{info=info})
end

格式模板:

1
2
3
4
5
module("luci.controller.控制器名", package.seeall)
function index()
entry(路径, 调用目标, _("显示名称"), 显示顺序)
end

这个脚本文件可以分为3块:第1行,3~7行,9~16行

第1行

说明了模块的名称,本文在controller目录下创建了addtest.lua文件,将模板中的控制器名替换为addtest即可.

第3行

第3~7行定义按钮的位置,调用的功能,显示名称.其中第3行和第7行是固定的模板格式,不需要修改

第4行

entry表示添加新的模块. 第一个参数{"admin","system","addtest"}表示按钮的位置.admin表示我们这个功能只有以管理员身份登录页面才可以看到.system表示一级菜单名,addtest则是一级菜单下的子菜单.第二个参数alias("admin","system","addtest","set")表示调用的功能.这个按钮没有独立的功能,而是将它关联到它的下一级子菜单set. 第三个参数_("AddTest")表示显示名称,可选.如果页面按钮想做成中文,可以在这里设置. 第四个参数99表示显示顺序的优先级,Luci根据这个值为同一父菜单的所有子菜单排序.

第5行

第一个参数{"admin","system","addtest","set"}表示在addtest下再增加一个子选项set. 第二个参数cbi("addtest")表示调用cbi模块,这里将会调用到/usr/lib/lua/luci/model/cbi/addtest.lua

第6行

第二个参数call("action_info")表示执行指定方法,这里将会调用我们下面写的acttion_info函数.

备注 : 关于entry第二个参数调用目标.我们还有一个template没有涉及,它表示访问指定页面.比如template(addtest_info)将会直接访问/usr/lib/lua/luci/view/addtest_info.htm.

9~16行

这里使用lua语言调用nixio接口写了一个简单的函数,首先判断文件是否存在,然后读取其中的内容赋值给变量info,最后访问指定页面/usr/lib/lua/luci/view/addtest_info.htm,同时将变量info传递过去. luci接口手册 nixio接口手册


UCI

UCI是openwrt的配置管理机制,它将配置统一放到/etc/config文件夹下.详细地介绍请参考这里. 下面来编辑这个文件

1
$vim ~/temp/addtest/files/etc/config/addtest

代码如下:

1
2
3
config arguments
option interval ''
option content ''

Section开始语法: config '类型' '名字' 参数定义语法: option '键' '值' 列表定义语法: list '集合名字' '值'

简单解释下,我们在/etc/config下新建一个名为addtest的配置文件,其中类型为arguments,名字省略.有两个键,一个名为interval用来存时间间隔.一个名为content用来存准备周期性输入的内容.


Model

在controller章节中,我们提到cbi会调用到model文件夹中的addtest.lua文件.下面我们来编辑它.

1
$vim ~/temp/addtest/files/usr/lib/lua/luci/model/cbi/addtest.lua

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
m=Map("addtest",translate("Luci practice"),translate("fat cheng's test"))
s=m:section(TypedSection,"arguments","")
s.addremove=true
s.anonymous=false
s:option(Flag,"enable",translate("Enable"))
s:option(Value,"interval",translate("Interval"))
s:option(Value,"content",translate("Content"))
local apply=luci.http.formvalue("cbi.apply")
if apply then
io.popen("/etc/init.d/addtestd restart")
end
return m

下面我们来解释下这个文件.

第1行

模板m = Map("配置文件文件名", "配置页面标题", "配置页面说明")
第一个参数:上一步我们新建配置文件/etc/config/addtest.这里就是建立与配置文件的联系. 第二,三两个参数,则是页面的主标题和副标题.还不清楚的话,翻上去看看最终效果图,看看它们在哪里.

第3行

在一个配置文件中可能有很多Section,所以我们需要创建与配置文件中我们想要的Section的联系. 有两种方式可以选择:NamedSection(name,type,title,description)和TypedSection(type,title,description),前者根据配置文件中的Section名,而后者根据配置文件中的Section类型.我们选用了第二种.

第4行

设定不允许增加或删除Section

第5行

设定显示Section的名称,这里建议你可以试试设定为true,看看会发生什么.

7~9行

接着则是建立与Section中的option之间的联系.模板s:option(交互形式,option键值,显示名称). 第一个参数:常见的交互形式有Value(文本框),ListValue(下拉框),Flag(选择框).,不知道为啥我打不开官方文档,这里也可以参考 第二个参数表示在配置文件中的option的键值 第三个参数表示,你希望在页面上呈现的名称. 创建后开发者无需考虑读取以及写入配置文件的问题,系统会自动处理.

11~14行

系统会为我们在页面上自动创建一些按钮Save&Apply,Save,Reset.我们仅仅将配置写入/etc/config下对应的文件是不够的,我们还希望可以根据这个配置进行一些操作. 这部分代码的作用是,当你按下页面的apply按钮后,相当于在串口shell下输入/etc/init.d/addtestd restart


init.d

上一节我们已经可以读写配置了,怎么根据配置来进行操作呢?这是我们这一节要谈的.我们来编辑~/temp/addtest/files/etc/init.d/addtestd这个文件. 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/sh /etc/rc.common
START=50
run_addtest()
{
local enable
config_get_bool enable $1 enable
if [ $enable ]; then
local interval
local content
config_get interval $1 interval
config_get content $1 content
addtest $interval $content
fi
}
start()
{
config_load addtest
config_foreach run_addtest arguments
}
stop()
{
result=`pidof addtest`
kill -9 $result
echo "addtest has stoped"
}

第1行

Linux 系统根据 "#!" 及该字串后面的信息确定该文件的类型,表示这个文件需要由/bin/sh和/etc/rc.common来解释执行.

第2行

表示启动的优先级,这里暂时用不到

4~17行

是一个函数,主要作用是读取/etc/config/addtest中的内容,然后根据是否打开开关在第15行将配置传递给可执行文件addtest,由它根据配置执行指定的操作. 读取配置的方法,我强烈推荐你阅读官方文档,精炼而简洁. 获取布尔值类型:config_get_bool 变量名 Section名 Section参数名 获取变量值:config_get 变量名 Section名 Section参数名

19~23行

对应于/etc/init.d/addtestd start.首先使用config_load 配置文件名的方法载入配置文件,然后使用config_foreach 遍历函数名 Section类型的方法,遍历配置文件中的Section.

25~30行

对应于/etc/init.d/addtestd stop.找到addtest这个进程的进程号,然后杀死它

备注 : 前一节提到的/etc/init.d/addtestd restart中的restart命令,在/etc/rc.common进行了定义,简单来讲就是先执行了stop命令,再执行start命令. 最后务必执行**$sudo chmod 755 ~/temp/addtest/files/etc/init.d/addtestd**.


src

前一节,我们谈到run_addtest调用可执行文件addtest,现在我们编辑这部分内容

1
$vim ~/temp/addtest/files/src/addtest.c

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int index;
for(index=0; index<10; index++)
{
FILE *fp=fopen("/tmp/addtest","at");
system("date >> /tmp/addtest");
fprintf(fp, "%s\n", argv[2]);
fclose(fp);
printf("interval=%d\n",atoi(argv[1]));
sleep( atoi(argv[1]) );
}
return 0;
}

这部分代码比较简短,我们不再解释.需要掌握的点有:

1.argc和argv[]的使用方法

2.fopen函数,fclose函数以及fprintf函数的使用方法

3.system函数的使用方法

4.sleep函数和atoi函数的使用方法,argv[1]的类型为char需要转换为整型.

通过这个可执行文件,我们周期性地将时间戳和内容写入了/tmp/addtest文件. 最后我们写一个简单的Makefile:

1
$vim $vim ~/temp/addtest/files/src/Makefile

代码如下:

1
2
3
4
5
6
7
8
addtest : addtest.o
$(CC) addtest.o -o addtest
addtest.o : addtest.c
$(CC) -c addtest.c
clean :
rm *.o addtest


View

上一节,我们已经根据配置将指定的内容周期性地写入了/tmp/addtest.在controller那一节,我们的函数action_info读取了/tmp/addtest中的内容并访问指定页面/usr/lib/lua/luci/view/addtest_info.htm,同时将读取的内容通过变量info传递过去.

下面我们来编辑这个页面, $vim ~/temp/addtest/files/usr/lib/lua/luci/view/addtest_info.htm 代码如下:

1
2
3
4
5
6
<%+header%>
<h2><a id="content" name="content"><%:Addtest Info%></a></h2>
<div id="content_addtest_info">
<textarea readonly="readonly" wrap="off" rows="<%=info:cmatch("\n")+2%>" id="info"><%=info:pcdata()%></textarea>
</div>
<%+footer%>

这部分和传统的html很类似,我主要是根据其他页面照猫画虎,不是很美观.有机会还要加强这个方面的学习.


Makefile

不知不觉,我们居然已经将代码全部写完了,竟还有点恋恋不舍呢.下面我们用一个Makefie文件将它们打包生成一个ipk文件.

1
$vim ~/temp/addtest/Makefile

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
include $(TOPDIR)/rules.mk
PKG_NAME:=addtest
PKG_VERSION=1.0
PKG_RELEASE:=1
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
define Package/addtest
SECTION:=utils
CATEGORY:=Utilities
TITLE:=Addtest--print something to /var/addtest
endef
define Package/addtest/description
It's a test,print something to /var/addtest cyclicaliy
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Package/addtest/postinst
#!/bin/sh
rm -rf /tmp/luci*
endef
define Build/Configure
endef
define Build/Compile
$(call Build/Compile/Default)
endef
define Package/$(PKG_NAME)/install
$(CP) ./files/* $(1)/
$(INSTALL_DIR) $(1)/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/addtest $(1)/bin
endef
$(eval $(call BuildPackage,$(PKG_NAME)))

Makefile的解释,请参见拙作.我们这里稍作补充.

26~29行

由于luci会将模块加载到/tmp目录下运行,每次新加载luci模块后,需要执行$rm -rf /tmp/luci*.这里表示安装了ipk之后,将会自动执行删除命令,重新载入.

39行

$(1)是传入的参数,表示系统镜像目录,你可以将之视为路由器最后的文件系统.所以这句的意思就是将我们files下的内容拷贝到路由器的文件系统中.这也是我们为什么要建立一开始那么复杂的目录树的原因.


编译&安装

简直像裹脚布一样,又臭又长.不要说读了,我自己写的都快有点受不了了.读到这里的人真是辛苦了,下面到了我们收获果实的时候了. 将文件拷贝到源码目录的package目录下.其余部分,请参考拙作

1
$cp ~/temp/addtest ~/openwrt/package

把它拷贝到你的开发板中,试试看.


调试方法

我们当然希望可以一次成功,不过世间不如意之事十之八九.我来谈谈我自己的调试方法.

src部分

src文件下有Makefile文件,你可以直接在编译机上执行$make生成可执行文件addtest,然后在编译机上src目录下执行$./addtest 参数1 参数2.最后记得执行$make clean.

luci部分

将ipk安装到开发板后,可以通过串口或者ssh的方式登录开发板,然后直接在开发板中修改文件内容,再执行$rm -rf /tmp/luci*.最后重新载入设备页面.


尾记

不知不觉到了分手的时候,竟感觉有些忧桑呢.

不足

  1. 我自己刚接触学习,难免很多不足
  2. 页面输入没有防呆机制

多多包含:)

感谢

除了官方文档之外,这两篇博客给我很多指导: 开发OpenWrt路由器上LuCI的模块, openwrt中luci学习笔记. 我的同事宁财神给我们做了luci的框架介绍,同时在我的调试过程中,给予我很多帮助. 最后感谢管工给出这样一个练习题,虽然很小巧,居然可以贯通整个知识体系.我现在还是为他的高屋建瓴感到惊叹.

Q&A

在整篇文章学习完成后,我们希望可以回答以下几个问题:

1.MVC是什么?各部分有哪些功能?

2.怎么在页面上指定位置做出一个子页面.

3.怎么将配置写入到路由器中,又怎么读取?

4.页面怎么和可执行文件关联起来?或者通俗地说,页面点了一下,开发板怎么就执行了命令.

5.ipk怎么生成,安装过程中发生了什么?

openwrt简单ipk生成及Makefile解释

Posted on 2015-08-31   |   In openwrt   |  

##前言

类似的文章其实网上比较多了,我写这个的目的:

1,网上文章良莠不齐,有些自己都没实际动手操作,随便复制粘贴,实际操作不可行. 2,基本只讲了操作,我当时最关心的Makefile文件的解释没有.

所以我自己总结了一篇.

<!--more-->


###说明 开发板为MT7620a,openwrt版本为:barrier_breaker_14.07.编译主机为ubuntu 14.04 32位. git clone git://git.openwrt.org/14.07/openwrt.git 关于怎么搭建编译环境以及编译请参考网上


##正文 下面我们开始,我们遵循传统以helloworld开始.

1.创建helloworld项目

首先我们新建helloworld.c文件和对应的Makefile文件

1
2
3
$mkdir -p ~/temp/hellworld/src
$cd ~/temp/helloworld/src
$touch helloworld.c Makefile

如下为helloworld.c的内容:

1
2
3
4
5
6
#include <stdio.h>
int main()
{
printf("This is my helloworld!\n");
return 0;
}

如下为Makefile文件的内容:

1
2
3
4
5
6
7
8
helloworld : helloworld.o
$(CC) $(LDFLAGS) helloworld.o -o helloworld
helloworld.o : helloworld.c
$(CC) $(CFLAGS) -c helloworld.c
clean :
rm *.o helloworld

$(CC)

这个值由其他Makefile文件规定,表示我们使用编译器.

$(LDFLAGS)$(CFLAGS)

这个表示编译器的一些选项,这里是可选的,去掉也没有问题.

下面可以输入$make看看有没有问题,注意Makefile文件的书写格式. 最后,输入$make clean来清理掉生成的二进制文件.因为上一步make所使用的编译器并不是我们的交叉编译链,生成的二进制文件并不能在开发板中运行.上一步只是验证我们的src中的内容正确与否.


2.创建helloworld包

下一步我们要创建一个新的Makefile文件,在这个文件中我们要描述的是helloworld包的信息,比如:如何配置,如何编译,如何打包,安装位置等.

1
2
$cd ~/temp/helloworld
$touch Makefile

如下为Makefile内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
include $(TOPDIR)/rules.mk
PKG_NAME:=helloworld
PKG_RELEASE:=1
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
define Package/helloworld
SECTION:=utils
CATEGORY:=Utilities
TITLE:=Helloworld -- prints a snarky message
endef
define Package/helloworld/description
It's my first package demo.
endef
define Build/Prepare
echo "Here is Package/Prepare"
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Package/helloworld/install
echo "Here is Package/install"
$(INSTALL_DIR) $(1)/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/helloworld $(1)/bin/
endef
$(eval $(call BuildPackage,helloworld))

如下是最后的文件树形图:

树形图


3.Makefile注释

第1行include $(TOPDIR)/rules.mk

一般在Makefile的开头,包含了包的基本信息,

比如Makefile中的$(BUILD_DIR),$(INCLUDE_DIR),$(CP),$(INSTALL_DIR),$(INSTALL_BIN)都是这里定义的.具体内容可以到源码主目录下,查看rules.mk文件.

3~5行,软件包的信息均以“PKG_”开头,其意思和作用如下

PKG_NAME:软件包名称,将在menuconfig和ipkg可以看到。

PKG_VERSION:软件版本号。

PKG_RELEASE:Makefile的版本号

PKG_SOURCE:源代码的文件名。

PKG_SOURCE_URL:源代码的下载网站位置。

PKG_MD5SUM:源代码文件的效验码。用于核对软件包是否下载正确。

PKG_CAT:源代码文件的解压方法。包括zcat, bzcat, unzip等。

PKG_BUILD_DIR:软件包编译目录。它的父目录为$(BUILD_DIR)。

第7行include $(INCLUDE_DIR)/package.mk

一般在软件包的基本信息完成后再引入,他定义了用户态软件包的规则。

编译包分为用户态和内核模块,用户态软件包使用Package,内核模块使用KernelPackage.

$(INCLUDE_DIR)/Kernel.mk文件对于软件包为内核时不可缺少,

$(INCLUDE_DIR)/package.mk应用在用户态。

接下来讲述用户态软件包。用户程序的编译包以Package/开头,然后接着软件名,在Package定义中的软件名可以与软件包名不一样,而且可以多个定义。

9~13行

定义包的名称为helloworld

SECTION : 包的类型为utils

CATEGORY : 目录为Utilitis,即文件在menuconfig中的位置;有时还会有SUBMENU项,即子目录.

TITLE : 用于软件包的简短描述,将显示在menuconfig中.

URL : 软件包的下载位置。

MAINTAINER : 维护者选项。

DEPENDS : 与其他软件的依赖。即如编译或安装需要其他软件时需要说明。如果存在多个依赖,则每个依赖需用空格分开。依赖前使用+号表示默认显示,即对象沒有选中时也会显示,使用@则默认为不显示,即当依赖对象选中后才显示。

15~17行

软件包的详细描述,将显示在make menuconfig中

19~23行

编译准备方法,对于网上下载的软件包不需要再描述。对于非网上下载或自行开发的软件包必须说明编译准备方法。

本文所用的准备方法就是首先创建软件包目录,然后将源码拷贝到刚刚创建的目录中。按OpenWrt的习惯,一般把自己设计的程序全部放在src目录下。

25~29行

软件包的安装方法,包括一系列拷贝编译好的文件到指定位置。调用时会带一个参数,就是嵌入系统的镜像文件系统目录,因此$(1)表示嵌入系统的镜像目录。

INSTALL_DIR:=install -d -m0755 : 创建所属用戶可读写、执行,其他用戶可读可执行的目录

INSTALL_BIN:=install -m0755 : 编译好的文件到镜像文件目录

31行 $(eval $(call BuildPackage,helloworld))

完成前面定义后,必须使用eval函数实现各种定义。其格式为:

对于一般软件包:$(eval $(call Package,$(PKG_NAME)))

或对于内核模块:$(eval $(call KernelPackage,$(PKG_NAME)))

如果一个软件包有多个程序,例如:一个应用程序有自己的内核模块,上面使用的PKG_NAME需要灵活变通。eval函数可能设计多个。也可以当成多个软件包处理。

这里简单地解释了Makefile文件,更具体地请参考


4.编译软件

至此我们的软件已经基本完成,下面进行编译 首先将文件文件夹拷贝到openwrt目录中的package文件中,这里我的源码目录为~/openwrt,你需要把openwrt目录替换为你的openwrt源码目录.

1
$mv ~/temp/helloworld ~/openwrt/package

然后回到项目主目录运行make menuconfig

1
2
$cd ~/openwrt
$make menuconfig

按"/"后,输入helloworld,搜索对应的路径

搜索 搜索结果

接着到Utilities目录下,找到helloworld并按空格打开; 打开编译开关

保存后退出;

1
2
$cd ~/openwrt
$make package/helloworld/compile V=s

编译完成后,ipk应该已经生成

1
$find bin/ -name "helloworld*.ipk"

至此我们已经生成简单的ipk,恭喜:) 最后可以通过winscp,将ipk安装到开发板中.

结局


##尾记 我比较薄弱的是Makefile方面的知识,刚好加强下这个方面的学习,欢迎交流~

xargs用法详解

Posted on 2015-08-20   |   In 命令行的艺术   |  

##前言  最近我从svn上checkout出来了一个文件夹,然后加入了git的跟踪目录.用过svn的同学可能知道,这个文件夹里面每一层级都有个.svn隐藏文件夹,需要删除他们.本来我准备笨拙地一个一个手动删除的,一位同事在我面前敲了大概是$find . -type d -name "*.svn" | xargs rm -rf这样的命令,顿时觉得很高端大气上档次.刚好新学了Markdown,就顺便整理下xargs的用法,练练手.参考网址,当然更主要的参考来自于伟大的$man xargs.

<!--more-->


1.概述

xargs从标准输入(stdin)中读取数据进行处理

  • 数据以空格进行分隔
  • 可以根据参数进行一次或多次处理,默认的处理命令是/bin/echo
  • 空行不进行处理,会被忽略
  • 遇到命令状态为255时,xargs会立刻停止,譬如发生错误时.

下面我们来看看xargs有哪些参数可以选择.

2.options

  • -a file : 从file中读入数据

1
2
3
4
5
$cat 1.txt
aaa bbb ccc ddd
a b
$xargs -a 1.txt
aaa bbb ccc ddd a b

  • -0 : 当输入有特殊字符时,将其当作一般字符处理,比如""和空格

1
2
3
4
$echo "// " | xargs
//
$echo "// " | xargs -0
//

  • -d : 指定分隔符

1
2
3
4
5
6
$cat 1.txt
aaa bbb ccc ddd
a b
$cat 1.txt | xargs -d 'c'
aaa bbb ddd
a b

  • -E eof-str : 指定结束标志为eof-str,xargs处理到这个标志就会停止

1
2
3
4
5
6
$xargs -E 'ddd' -a 1.txt
aaa bbb ccc
$xargs -E 'dd' -a 1.txt
aaa bbb ccc ddd a b
$cat 1.txt | xargs -E 'ddd'
aaa bbb ccc

  • -I replace-str : 将每行输入输入内容替换为replace-str

1
2
3
4
5
6
7
8
9
10
11
$cat 1.txt
aaa bbb ccc ddd
a b
$cat 1.txt | xargs -t -I {} echo {} >> 1.txt
echo aaa bbb ccc ddd
echo a b
$cat 1.txt
aaa bbb ccc ddd
a b
aaa bbb ccc ddd
a b

  • -i : 等同于-I{}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$cat 1.txt
aaa bbb ccc ddd
a b
$cat 1.txt | xargs -t -i echo {} >> 1.txt
echo aaa bbb ccc ddd
echo a b
$cat 1.txt
aaa bbb ccc ddd
a
aaa bbb ccc ddd
a b
$cat 1.txt |xargs -L 2
aaa bbb ccc ddd a b
$cat 1.txt |xar -L 1
aaa bbb ccc ddd
a b

  • -l : 类似于-L,区别在于-l可以不指定参数,默认为1.

  • -n max-args : 每行执行max-args个输入,默认执行所有

1
2
3
4
$cat 1.txt | xargs -n 2
aaa bbb
ccc ddd
a b

  • -p : 交互模式,执行前询问是否执行

1
2
3
4
5
$cat 1.txt | xargs -p
/bin/echo aaa bbb ccc ddd a b ?...y
aaa bbb ccc ddd a b
$cat 1.txt | xargs -p
/bin/echo aaa bbb ccc ddd a b ?...n

  • -r : 无输入则停止执行,默认至少执行1次

1
2
3
4
5
$ echo ""|xargs -t mv
mv
mv: missing file operand
Try `mv --help` for more information.
$ echo ""|xargs -t -r mv #直接退出

  • -s max-chars : xargs每次执行命令的最大长度(含空格)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cat 1.txt
aaa bbb ccc ddd a b
$ cat 1.txt |xargs -t -s 30
/bin/echo aaa bbb ccc ddd a b
aaa bbb ccc ddd a b
#length(/bin/echo aaa bbb ccc ddd a b )=30
$cat 1.txt |xargs -t -s 14
/bin/echo aaa
aaa
/bin/echo bbb
bbb
/bin/echo ccc
ccc
/bin/echo ddd
ddd
/bin/echo a b
a b
#length(/bin/echo aaa )=14

  • -t : 先打印执行的命令,然后执行

1
2
3
$cat 1.txt | xargs -t
/bin/echo aaa bbb ccc ddd a b
aaa bbb ccc ddd a b

  • -x : 当xargs执行的命令长度大于-s max-char时,停止执行

  • -P max-procs : 修改线程数,默认为单线程.max-procs为0时,as many processes as possible


3. find和xargs

在使用find命令的-exec选项处理匹配到的文件时,find命令将所有匹配到的文件一起传递给exec执行。但有些系统对能够传递给exec的命令长度有限制,这样在find命令运行几分钟之后,就会出现溢出错误。错误信息通常是“参数列太长”或“参数列溢出”。这就是xargs命令的用处所在,特别是与find命令一起使用。find命令把匹配到的文件 传递给xargs命令,而xargs命令每次只获取一部分文件而不是全部,不像-exec选项那样。这样它可以先处理最先获取的一部分文件,然后是下一 批,并如此继续下去。

在有些系统中,使用-exec选项会为处理每一个匹配到的文件而发起一个相应的进程,并非将匹配到的文件全部作为参数一次执行;这样在有些情况下就会出现进程过多,系统性能下降的问题,因而效率不高;而使用xargs命令则只有一个进程。另外,在使用xargs命令时,究竟是一次获取所有的参数,还是分批取得参数,以及每一次获取参数的数目 都会根据该命令的选项及系统内核中相应的可调参数来确定。

管道是把一个命令的输出传递给另一个命令作为输入,比如:command1 | command2但是command2仅仅把command1输出的内容作为输入参数。find . -name "install.log" -print打印出的是install.log这个字符串,如果仅仅使用管道,那么command2能够使用的仅仅是install.log这个字符串,不能把它当作文件来进行处理。  当然这个command2除了xargs。xargs就是为了能够对find搜索到的文件进行操作而编写的。它能把管道传来的字符串当作文件交给其后的命令执行。

###4 Example

1
2
3
$find . -name "1.txt" | cat
./1.txt
#显示从管道传来的内容,仅仅作为字符串来处理

1
2
3
4
$find . -name "1.txt" | xargs cat
aaa bbb ccc ddd
a b
#将管道传来的内容作为文件,交给cat执行。也就是说,该命令执行的是如果存在1.txt,那么就打印出这个文件的内容。

1
2
$find . -perm -7 -print | xargs chmod o-w
#在当前目录下查找所有用户具有读、写和执行权限的文件,并收回相应的写权限

1
2
$ find . -type f -print | xargs file
#查找系统中的每一个普通文件,然后使用xargs命令来测试它们分别属于哪类文件

1
2
$find ~ -name '*.log' -print0 | xargs -i -0 rm -f {}
#尝试用rm 删除太多的文件,你可能得到一个错误信息:/bin/rm Argument list too long. 用xargs 去避免这个问题

1
2
$find / -name *.jpg -type f -print | xargs tar -cvzf images.tar.gz
#查找所有的jpg 文件,并且压缩它

1
2
$ls *.jpg | xargs -n 1 -i cp {} /external-hard-drive/directory
#拷贝所有的图片文件到一个外部的硬盘驱动


##尾记 这里只是最简略的用法,目的是快速理解上手使用.如果遇到问题,请使用man xargs或者拨打10086:)

特别鸣谢:

http://blog.csdn.net/zhangfn2011/article/details/6776925 http://blog.csdn.net/hittata/article/details/8021500 http://www.cnblogs.com/wdpp/archive/2012/02/28/2386683.html http://blog.163.com/ly_89/blog/static/18690229920117208314257/ http://aix.chinaunix.net/doc/2008/03/01/1108340.shtml http://blog.chinaunix.net/uid-15117916-id-4920.html

1…34
成祎

成祎

闻之我也野,视之我也饶,行之我也明

37 posts
8 categories
25 tags
RSS
© 2017 成祎
Powered by Hexo
Theme - NexT.Pisces