\".*<\/div)
/, '\ '
164 |
165 | #  => 
166 | s /^!\[(.*)\]\([\.\/]*\/(pic\/.*)\)/, ''
167 | # "
168 |
169 | # Turns URLs into clickable links
170 | s /\`(http:\/\/[A-Za-z0-9\/\%\&\=\-\_\\\.]+)\`/, '<\1>'
171 | s /(\n\n)\t(http:\/\/[A-Za-z0-9\/\%\&\=\-\_\\\.]+)\n([^\t]|\t\n)/, '\1<\2>\1'
172 | # `
173 | # Process figures
174 | s /Insert\s18333fig\d+\.png\s*\n.*?\d{1,2}-\d{1,2}\. (.*)/, 'FIG: \1'
175 | end
176 | end
177 |
178 | def post_pandoc(string, config, lang, chapter=true)
179 | replace(string) do
180 | space = /\s/
181 |
182 | # Reformat for the book documentclass as opposed to article
183 | s '\section', '\chap'
184 | s '\sub', '\\'
185 | s /SUBSUBSECTION: (.*)/, '\subsubsection{\1}'
186 | s /PARASECTION: (.*)/, '\paragraph{\1}'
187 |
188 | # Enable proper cross-reference
189 | s /#{config['fig'].gsub(space, '\s')}\s*(\d+)\-\-(\d+)/, '\imgref{\1.\2}'
190 | s /#{config['tab'].gsub(space, '\s')}\s*(\d+)\-\-(\d+)/, '\tabref{\1.\2}'
191 | s /#{config['prechap'].gsub(space, '\s')}\s*(\d+)(\s*)#{config['postchap'].gsub(space, '\s')}/, '\chapref{\1}\2'
192 |
193 | # Miscellaneous fixes
194 | s /FIG: (.*)/, '\img{\1}'
195 | s '\begin{enumerate}[1.]', '\begin{enumerate}'
196 | s /(\w)--(\w)/, '\1-\2'
197 | s /``(.*?)''/, "#{config['dql']}\\1#{config['dqr']}"
198 |
199 | # Typeset the maths in the book with TeX
200 | s '\verb!p = (n(n-1)/2) * (1/2^160))!', '$p = \frac{n(n-1)}{2} \times \frac{1}{2^{160}}$)'
201 | s '2\^{}80', '$2^{80}$'
202 | s /\sx\s10\\\^\{\}(\d+)/, '\e{\1}'
203 |
204 | # Convert inline-verbatims into \texttt (which is able to wrap)
205 | s /\\verb(\W)(.*?)\1/ do
206 | "{\\texttt{#{verbatim_sanitize($2)}}}"
207 | end
208 |
209 | # Ensure monospaced stuff is in a smaller font
210 | s /(\\verb(\W).*?\2)/, '{\footnotesize\1}'
211 | #s /(\\begin\{verbatim\}.*?\\end\{verbatim\})/m, '{\footnotesize\1}'
212 |
213 | # Shaded verbatim block
214 | #s /(\\begin\{verbatim\}.*?\\end\{verbatim\})/m, '\begin{shaded}\1\end{shaded}'
215 | #s /\t/m, ' '
216 |
217 | # is using fancyvrb
218 | s /\\begin\{verbatim\}/,'\\begin{Verbatim}[tabsize=4,formatcom=\color{colorchapter},frame=lines]'
219 | s /\\end\{verbatim\}/,'\\end{Verbatim}'
220 |
221 | # try moreverb
222 | #s /\\begin\{verbatim\}/,'\\begin{verbatimtab}[4]'
223 | #s /\\end\{verbatim\}/,'\\end{verbatimtab}'
224 |
225 | if lang=="zh"
226 | # http://www.devdaily.com/blog/post/latex/control-line-spacing-in-itemize-enumerate-tags
227 | # http://wiki.ctex.org/index.php/LaTeX/%E5%88%97%E8%A1%A8
228 | # set the space of itemsize
229 | s /(\\begin\{itemize\})/m,'\begin{itemize}\setlength{\itemsep}{1pt}\setlength{\parskip}{0pt}\setlength{\parsep}{0pt}'
230 | s /(\\begin\{enumerate\})/m,'\begin{enumerate}\setlength{\itemsep}{1pt}\setlength{\parskip}{0pt}\setlength{\parsep}{0pt}'
231 | # hardcode for itemize to use * instead of dot, which is missed in some chinese fonts
232 | # and keep \item inside \enumerate env is not changed
233 | # \item -> \item[*]
234 | # solution is provided by Alexis, and it works under ruby 1.9+ only due to bug in 1.8.7
235 | # http://stackoverflow.com/questions/9115018/regular-expression-using-ruby-string-gsub-method-to-replace-multi-matches
236 | if RUBY_VERSION >= "1.9"
237 | s /^\\item(?=((?!\\begin\{itemize\}).)*\\end\{itemize\})/m, '\\item[*]'
238 | else
239 | s /(^\\item)/m,'\item[*]'
240 | end
241 |
242 | # change the width to standard .6 width
243 | s /\\includegraphics/m, '\\includegraphics[width=\\imgwidth]'
244 | end
245 |
246 | if chapter != true
247 | s /^\\chap\{(.*)\}/,'\chapter*{\1}'"\n"'\addcontentsline{toc}{chapter}{\1}'
248 | s /^\\section\{(.*)\}/,'\section*{\1}'
249 | s /^\\subsection\{(.*)\}/,'\subsection*{\1}'
250 | end
251 |
252 | s /\\ctable\[pos \= H/m, '\\ctable[pos = h'
253 |
254 | end
255 | end
256 |
257 | def check_jekyll(str)
258 | str.lines.to_a[4..-3].join
259 | end
260 |
261 | def generate_pdf(options)
262 | $config = YAML.load_file(options["config"])
263 | template = ERB.new(File.read(options["template"]))
264 | languages = [options["lang"]]
265 | missing = ['pandoc', 'xelatex'].reject{|command| command_exists?(command)}
266 | unless missing.empty?
267 | puts "Missing dependencies: #{missing.join(', ')}."
268 | puts "Install these and try again."
269 | exit 1
270 | end
271 |
272 | puts "Will generate pdf for the following languages using template #{options["template"]}:"
273 | puts " #{languages}"
274 | puts
275 |
276 | figures do
277 | languages.each do |lang|
278 | config = $config['default'].merge($config[lang]) rescue $config['default']
279 |
280 | puts "#{lang}:"
281 |
282 | cover_image1 = options['cover1']
283 | puts "\n\tUsing Cover Image 1: #{options['cover1']}\n"
284 |
285 | cover_desc1 = options['cover_desc1']
286 | puts "\tUsing Cover Description 1: #{options['cover_desc1']}\n\n"
287 |
288 | cover_image2 = options['cover2']
289 | puts "\n\tUsing Cover Image 2: #{options['cover2']}\n"
290 |
291 | cover_desc2 = options['cover_desc2']
292 | puts "\tUsing Cover Description 2: #{options['cover_desc2']}\n\n"
293 |
294 | prefacefiles = "#$root/#{lang}/#{options['preface-files']}"
295 |
296 | puts "\tParsing preface markdown... #{prefacefiles} "
297 | prefacemarkdown = Dir["#{prefacefiles}"].sort.map do |file|
298 | puts "\t =>"+file
299 | if options["jeykll"]
300 | check_jekyll(File.read(file))
301 | else
302 | File.read(file)
303 | end
304 | end.join("\n\n")
305 |
306 | preface = IO.popen('pandoc -p --no-wrap -f markdown -t latex', 'w+') do |pipe|
307 | pipe.write(pre_pandoc(prefacemarkdown, config))
308 | pipe.close_write
309 | post_pandoc(pipe.read, config, lang, false)
310 | end
311 |
312 | chapterfiles = "#$root/#{lang}/#{options['chapter-files']}"
313 |
314 | puts "\n\tParsing main chapters markdown... #{chapterfiles} "
315 | chaptersmarkdown = Dir["#{chapterfiles}"].sort{|a,b| [Integer(a[/\d+/]),a]<=>[Integer(b[/\d+/]),b]}.map do |file|
316 | puts "\t =>"+file
317 | if options["jeykll"]
318 | check_jekyll(File.read(file))
319 | else
320 | File.read(file)
321 | end
322 | end.join("\n\n")
323 | # puts "done"
324 |
325 | latex = IO.popen('pandoc -p --no-wrap -f markdown -t latex', 'w+') do |pipe|
326 | pipe.write(pre_pandoc(chaptersmarkdown, config))
327 | pipe.close_write
328 | post_pandoc(pipe.read, config, lang)
329 | end
330 | # puts "done"
331 |
332 | appendixfiles = "#$root/#{lang}/#{options['appendix-files']}"
333 |
334 | puts "\n\tParsing appendix markdown... #{appendixfiles} "
335 | appendixmarkdown = Dir["#{appendixfiles}"].sort.map do |file|
336 | puts "\t =>"+file
337 | if options["jeykll"]
338 | check_jekyll(File.read(file))
339 | else
340 | File.read(file)
341 | end
342 | end.join("\n\n")
343 |
344 | appendix = IO.popen('pandoc -p --no-wrap -f markdown -t latex', 'w+') do |pipe|
345 | pipe.write(pre_pandoc(appendixmarkdown, config))
346 | pipe.close_write
347 | post_pandoc(pipe.read, config, lang)
348 | end
349 | #puts "done"
350 |
351 | print "\n\tCreating main.tex for #{lang}... "
352 | dir = "latex/#{lang}"
353 | mkdir_p(dir)
354 |
355 | File.open("#{dir}/main.tex", 'w') do |file|
356 | file.write(template.result(binding))
357 | end
358 | puts "done"
359 |
360 | abort = false
361 |
362 | puts "\tRunning XeTeX:"
363 | cd($root)
364 | 3.times do |i|
365 | print "\t\tPass #{i + 1}... "
366 | line = ""
367 | IO.popen("xelatex -output-directory=\"#{dir}\" \"#{dir}/main.tex\" 2>&1") do |pipe|
368 | unless $DEBUG
369 | while line = pipe.gets and not abort
370 | # print line.encoding.name
371 | # use different way to handle encoding issues for ruby
372 | if (RUBY_VERSION >= "1.9" and line.valid_encoding? and line =~ /^!\s/) or (RUBY_VERSION < "1.9" and line =~ /^!\s/)
373 | puts "failed with:\n\t\t\t#{line.strip}"
374 | puts "\tConsider running this again with --debug."
375 | abort = true
376 | end
377 | end
378 |
379 | #print line while line = pipe.gets
380 | else
381 | STDERR.print while pipe.gets rescue abort = true
382 | end
383 | end
384 | break if abort
385 | puts "done"
386 | end
387 |
388 | unless abort
389 | print "\tMoving output to #$outDir/#{options["name"]}.#{lang}.pdf... "
390 | mv("#{dir}/main.pdf", "#$outDir/#{options["name"]}.#{lang}.pdf")
391 | puts "done"
392 | else
393 | print "\tConvert error, exit !\n"
394 | exit 1
395 | end
396 | end
397 | end
398 | end
399 |
400 | def generate_ebook(options)
401 | name = File.basename(Dir.getwd)
402 |
403 | missing = ['pandoc'].reject{|command| command_exists?(command)}
404 | unless missing.empty?
405 | puts "Missing dependencies: #{missing.join(', ')}."
406 | puts "Install these and try again."
407 | exit 1
408 | end
409 |
410 | puts " "
411 | puts "Will generate ebooks [#{options["outputformat"].join(',')}] for the following languages #{options["lang"]}"
412 | puts " "
413 |
414 | options["lang"].split(',').each do |lang|
415 | puts "convert content for '#{lang}' language"
416 |
417 | if lang == 'zh'
418 | figure_title = '图'
419 | else
420 | figure_title = 'Figure'
421 | end
422 |
423 | dir = File.expand_path(File.join(Dir.getwd, lang))
424 | #puts File.join(Dir.getwd, lang)
425 | book_content = ""
426 | Dir[File.join(dir, '**', '*.markdown')].sort.each do |input|
427 | puts "\tProcessing #{input}"
428 | content = File.read(input)
429 | content.gsub!(/Insert\s+(.*)(\.png)\s*\n?\s*#{figure_title}\s+(.*)/, '')
430 | book_content << content
431 | end
432 |
433 | File.open("#{name}.#{lang}.markdown", 'w') do |output|
434 | output.write(book_content)
435 | end
436 | # pandoc -S -s --epub-metadata=metadata.xml -o sdcamp.zh.html --epub-stylesheet=epub/ProGit.css epub/title.txt sdcamp.zh.markdown
437 | options["outputformat"].each do | format |
438 | system('pandoc',
439 | '--standalone',
440 | '--toc',
441 | '--template=template.html',
442 | '--epub-metadata', 'epub/metadata.xml',
443 | '--epub-stylesheet', 'epub/book.css', # this doesn't work
444 | '--output', "#{name}.#{lang}.#{format}",
445 | "title.#{lang}.txt", # little strange, if this put under epub, pandoc reports error
446 | "#{name}.#{lang}.markdown")
447 | # pandoc -S --epub-metadata=metadata.xml -o progit.epub title.txt
448 | puts("#{name}.#{lang}.#{format} is generated")
449 | end
450 | end
451 | end
452 |
453 | # http://stackoverflow.com/questions/5074327/most-appropriate-way-to-generate-directory-of-files-from-directory-of-template-f
454 | def generate_project(project)
455 | destination = project
456 | source = File.dirname(__FILE__)+"/../templates"
457 | #puts "generate project \"#{destination}\" from source \"#{source}\""
458 | FileUtils.rmtree(destination)
459 | FileUtils.mkdir_p(destination)
460 | sourceroot=Pathname.new(source)
461 | sourcerealpath = sourceroot.cleanpath
462 | puts "generate project \"#{destination}\" from source \"#{sourcerealpath}\""
463 | Dir.glob(File.join(source, '**/*')).each do |path|
464 | pathname = Pathname.new(path)
465 | relative = pathname.relative_path_from(sourceroot)
466 | #puts "parent:" , sourceroot
467 | #puts "relative:", relative
468 | if File.directory?(pathname)
469 | destdir = File.join(destination, relative.dirname)
470 | #puts "create #{destdir} "
471 | FileUtils.mkdir_p(destdir)
472 | else
473 | FileUtils.mkdir_p(File.join(destination, relative.dirname))
474 | if pathname.extname == '.erb'
475 | #puts pathname.basename.sub(/\.erb$/, '')
476 | #puts destination
477 | #puts File.join(destination,pathname.basename.sub(/\.erb$/, ''))
478 | File.open(File.join(destination,pathname.basename.sub(/\.erb$/, '')), 'w') do |file|
479 | file.puts(ERB.new(File.read(path)).result(binding))
480 | end
481 | else
482 | print pathname.cleanpath, " => ", File.join(destination, relative.dirname),"\n"
483 | FileUtils.cp(pathname, File.join(destination, relative.dirname))
484 | end
485 | end
486 | end
487 | end
488 |
489 | main
490 |
491 |
--------------------------------------------------------------------------------
/zh/appendix/02-chapter1.markdown:
--------------------------------------------------------------------------------
1 | **关注作者公众号**:
2 |
3 |

4 |
5 |
6 | # 附录
7 |
8 | - [Shell 编程学习笔记](#toc_19246_27800_1)
9 | - [前言](#toc_19246_27800_2)
10 | - [执行 Shell 脚本的方式](#toc_19246_27800_3)
11 | - [范例:输入重定向到Bash](#toc_19246_27800_4)
12 | - [范例:以脚本名作为参数](#toc_19246_27800_5)
13 | - [范例:以 . 来执行](#toc_19246_27800_6)
14 | - [范例:直接执行](#toc_19246_27800_7)
15 | - [Shell 的执行原理](#toc_19246_27800_8)
16 | - [变量赋值](#toc_19246_27800_9)
17 | - [范例:获取当前的工作目录并存放到变量中](#toc_19246_27800_10)
18 | - [数组](#toc_19246_27800_11)
19 | - [范例:对数组元素赋值](#toc_19246_27800_12)
20 | - [范例:访问某个数组元素](#toc_19246_27800_13)
21 | - [范例:数组组合赋值](#toc_19246_27800_14)
22 | - [范例:列出数组中所有内容](#toc_19246_27800_15)
23 | - [范例:获取数组元素个数](#toc_19246_27800_16)
24 | - [参数传递](#toc_19246_27800_17)
25 | - [设置环境变量](#toc_19246_27800_18)
26 | - [键盘读起变量值](#toc_19246_27800_19)
27 | - [设置变量的只读属性](#toc_19246_27800_20)
28 | - [条件测试命令 test](#toc_19246_27800_21)
29 | - [范例:数值比较](#toc_19246_27800_22)
30 | - [范例:测试文件属性](#toc_19246_27800_23)
31 | - [范例:字符传属性以及比较](#toc_19246_27800_24)
32 | - [范例:串比较](#toc_19246_27800_25)
33 | - [整数算术或关系运算 expr](#toc_19246_27800_26)
34 | - [控制执行流程命令](#toc_19246_27800_27)
35 | - [范例:条件分支命令 if](#toc_19246_27800_28)
36 | - [范例:case 命令举例](#toc_19246_27800_29)
37 | - [范例:循环语句 while, until](#toc_19246_27800_30)
38 | - [范例:有限循环命令 for](#toc_19246_27800_31)
39 | - [函数](#toc_19246_27800_32)
40 | - [后记](#toc_19246_27800_33)
41 |
42 |
43 |
44 | ## Shell编程学习笔记
45 |
46 |
47 | ### 前言
48 |
49 | 这是作者早期的 Shell 编程学习笔记,主要包括 Shell 概述、 Shell 变量、位置参数、特殊符号、别名、各种控制语句、函数等 Shell 编程知识。
50 |
51 | 要想系统地学 Shell,应该找些较系统的资料,例如:[《Shell 编程范例》](http://www.tinylab.org/shell-programming-paradigm-series-index-review/)和[《鸟哥学习Shell Scripts》](http://www.chinaunix.net/jh/24/628472.html)。
52 |
53 |
54 | ### 执行 Shell 脚本的方式
55 |
56 |
57 | #### 范例:输入重定向到 Bash
58 |
59 | ```
60 | $ bash < ex1
61 | ```
62 |
63 | 可以读入 `ex1` 中的程序,并执行
64 |
65 |
66 | #### 范例:以脚本名作为参数
67 |
68 | 其一般形式是:
69 |
70 | ```
71 | $ bash 脚本名 [参数]
72 | ```
73 |
74 | 例如:
75 |
76 | ```
77 | $ bash ex2 /usr/meng /usr/zhang
78 | ```
79 |
80 | 其执行过程与上一种方式一样,但这种方式的好处是能在脚本名后面带有参数,从而将参数值传递给程序中的命令,使一个 Shell 脚本可以处理多种情况,就如同函数调用时可根据具体问题传递相应的实参。
81 |
82 |
83 | #### 范例:以 . 来执行
84 |
85 |
86 | 如果以当前 Shell (以 `·` 表示)执行一个 Shell 脚本,则可以使用如下简便形式:
87 |
88 | ```
89 | $ · ex3[参数]
90 | ```
91 |
92 |
93 | #### 范例:直接执行
94 |
95 | 将 Shell 脚本的权限设置为可执行,然后在提示符下直接执行它。
96 |
97 | 具体办法:
98 |
99 | ```
100 | $ chmod a+x ex4
101 | $ ./ex4
102 | ```
103 |
104 | 这个要求在 Shell 脚本的开头指明执行该脚本的具体 Shell,例如 `/bin/bash`:
105 |
106 | ```
107 | #!/bin/bash
108 | ```
109 |
110 |
111 | ### Shell 的执行原理
112 |
113 |
114 | Shell 接收用户输入的命令(脚本名),并进行分析。如果文件被标记为可执行,但不是被编译过的程序,Shell 就认为它是一个 Shell 脚本。 Shell 将读取其中的内容,并加以解释执行。所以,从用户的观点看,执行 Shell 脚本的方式与执行一般的可执行文件的方式相似。
115 |
116 | 因此,用户开发的 Shell 脚本可以驻留在命令搜索路径的目录之下(通常是 `/bin`、`/usr/bin`等,可通过 `PATH` 环境变量设置和查看),像普通命令一样使用。这样,也就开发出自己的新命令。如果打算反复使用编好的 Shell 脚本,那么采用这种方式就比较方便。
117 |
118 |
119 | ### 变量赋值
120 |
121 |
122 | 可以将一个命令的执行结果赋值给变量。有两种形式的命令替换:一种是使用倒引号引用命令,其一般形式是: `命令表`。
123 |
124 |
125 | #### 范例:获取当前的工作目录并存放到变量中
126 |
127 | 例如:将当前工作目录的全路径名存放到变量dir中,输入以下命令行:
128 |
129 | ```
130 | $ dir=`pwd`
131 | ```
132 |
133 | 另一种形式是:`$(命令表)`。上面的命令行也可以改写为:
134 |
135 | ```
136 | $ dir=$(pwd)
137 | ```
138 |
139 |
140 | ### 数组
141 |
142 |
143 | `Bash` 只提供一维数组,并且没有限定数组的大小。类似与 C 语言,数组元素的下标由 0 开始编号。获取数组中的元素要利用下标。下标可以是整数或算术表达式,其值应大于或等于 0 。用户可以使用赋值语句对数组变量赋值。
144 |
145 |
146 | #### 范例:对数组元素赋值
147 |
148 |
149 | 对数组元素赋值的一般形式是:`数组名[下标]=值`,例如:
150 |
151 | ```
152 | $ city[0]=Beijing
153 | $ city[1]=Shanghai
154 | $ city[2]=Tianjin
155 | ```
156 |
157 | 也可以用 `declare` 命令显式声明一个数组,一般形式是:
158 |
159 | ```
160 | $ declare -a 数组名
161 | ```
162 |
163 |
164 | #### 范例:访问某个数组元素
165 |
166 |
167 | 读取数组元素值的一般格式是: `${数组名[下标]}`,例如:
168 |
169 | ```
170 | $ echo ${city[0]}
171 | Beijing
172 | ```
173 |
174 |
175 | #### 范例:数组组合赋值
176 |
177 |
178 | 一个数组的各个元素可以利用上述方式一个元素一个元素地赋值,也可以组合赋值。定义一个数组并为其赋初值的一般形式是:
179 |
180 | ```
181 | 数组名=(值1 值2 ... 值n)
182 | ```
183 |
184 | 其中,各个值之间以空格分开。例如:
185 |
186 | ```
187 | $ A=(this is an example of shell script)
188 | $ echo ${A[0]} ${A[2]} ${A[3]} ${A[6]}
189 | this an example script
190 | $ echo ${A[8]}
191 | ```
192 |
193 | 由于值表中初值共有 7 个,所以 `A` 的元素个数也是 7 。 `A[8]` 超出了已赋值的数组 `A` 的范围,就认为它是一个新元素,由于预先没有赋值,所以它的值是空串。
194 |
195 | 若没有给出数组元素的下标,则数组名表示下标为 0 的数组元素,如 `city` 就等价于 `city[0]`。
196 |
197 |
198 | #### 范例:列出数组中所有内容
199 |
200 |
201 | 使用 `*` 或 `@` 做下标,则会以数组中所有元素取代。
202 |
203 | ```
204 | $ echo ${A[*]}
205 | this is an example of shell script
206 | ```
207 |
208 |
209 | #### 范例:获取数组元素个数
210 |
211 |
212 | ```
213 | $ echo ${#A[*]}
214 | 7
215 | ```
216 |
217 |
218 |
219 | ### 参数传递
220 |
221 |
222 | 假如要编写一个 Shell 来求两个数的和,可以怎么实现呢?为了介绍参数传递的用法,编写这样一个脚本:
223 |
224 | ```
225 | $ cat > add
226 | let sum=$1+$2
227 | echo $sum
228 | ```
229 |
230 | 保存后,执行一下:
231 |
232 | ```
233 | $ chmod a+x ./add
234 | $ ./add 5 10
235 | 15
236 | ```
237 |
238 | 可以看出 5 和 10 分别传给了 `$1` 和 `$2`,这是 Shell 自己预设的参数顺序,其实也可以先定义好变量,然后传递进去。
239 |
240 | 例如,修改上述脚本得到:
241 |
242 | ```
243 | let sum=$X+$Y
244 | echo $sum
245 | ```
246 |
247 | 再次执行:
248 |
249 | ```
250 | $ X=5 Y=10 ./add
251 | 15
252 | ```
253 |
254 | 可以发现,同样可以得到正确结果。
255 |
256 |
257 | ### 设置环境变量
258 |
259 |
260 | export一个环境变量:
261 |
262 | ```
263 | $ export opid=True
264 | ```
265 |
266 | 这样子就可以,如果要登陆后都生效,可以直接添加到 `/etc/profile` 或者 `~/.bashrc` 里头。
267 |
268 |
269 | ### 键盘读起变量值
270 |
271 |
272 | 可以通过 `read` 来读取变量值,例如,来等待用户输入一个值并且显示出来:
273 |
274 | ```
275 | $ read -p "请输入一个值 : " input ; echo "你输入了一个值为 :" $input
276 | 请输入一个值 : 21500
277 | 你输入了一个值为 : 21500
278 | ```
279 |
280 |
281 |
282 | ### 设置变量的只读属性
283 |
284 |
285 | 有些重要的 Shell 变量,赋值后不应该修改,那么可设置它为 `readonly` :
286 |
287 | ```
288 | $ oracle_home=/usr/oracle7/bin
289 | $ readonly oracle_home
290 | ```
291 |
292 |
293 | ### 条件测试命令 test
294 |
295 |
296 | 语法:`test 表达式`
297 | 如果表达式为真,则返回真,否则,返回假。
298 |
299 |
300 | #### 范例:数值比较
301 |
302 |
303 | 先给出数值比较时常见的比较符:
304 |
305 | > `-eg =;-ne !=;-gt >;-ge >=;-lt <;-le <=`
306 |
307 | ```
308 | $ test var1 -gt var2
309 | ```
310 |
311 |
312 | #### 范例:测试文件属性
313 |
314 |
315 | 文件的可读、可写、可执行,是否为普通文件,是否为目录分别对应:
316 |
317 | > `-r; -w; -x; -f; -d`
318 |
319 | ```
320 | $ test -r filename
321 | ```
322 |
323 |
324 | #### 范例:字符传属性以及比较
325 |
326 |
327 | > 串的长度为零:`-z`; 非零:`-n`,如:
328 |
329 | ```
330 | $ test -z s1
331 | ```
332 |
333 | 如果串 `s1` 长度为零,返回真。
334 |
335 |
336 | #### 范例:串比较
337 |
338 |
339 | > 相等`"s1"="s2"`; 不相等 `"s1"!="s2"`
340 |
341 | 还有一种比较串的方法(可以按字典序来比较):
342 |
343 | ```
344 | $ if [[ 'abcde' < 'abcdf' ]]; then echo "yeah,果然是诶"; fi
345 | yeah,果然是诶
346 | ```
347 |
348 |
349 | ### 整数算术或关系运算 expr
350 |
351 |
352 | 可用该命令进行的运算有:
353 |
354 | > 算术运算:`+ - * / %`;逻辑运算`:= ! < <= > >=`
355 |
356 | 如:
357 |
358 | ```
359 | $ i=5;expr $i+5
360 | ```
361 |
362 | 另外,`bc` 是一个命令行计算器,可以进行一些算术计算。
363 |
364 |
365 | ### 控制执行流程命令
366 |
367 |
368 | #### 范例:条件分支命令 if
369 |
370 |
371 | `if` 命令举例:如果第一个参数是一个普通文件名,那么分页打印该文件;否则,如果它为目录名,则进入该目录并打印该目录下的所有文件,如果也不是目录,那么提示相关信息。
372 |
373 | ```
374 | if test -f $1
375 | then
376 | pr $1>/dev/lp0
377 | elif
378 | test-d $1
379 | then
380 | (cd $1;pr *>/dev/lp0)
381 | else
382 | echo $1 is neither a file nor a directory
383 | fi
384 | ```
385 |
386 |
387 | #### 范例:case 命令举例
388 |
389 |
390 | `case` 命令是一个基于模式匹配的多路分支命令,下面将根据用户键盘输入情况决定下一步将执行那一组命令。
391 |
392 | ```
393 | while [ $reply!="y" ] && [ $reply!="Y" ] #下面将学习的循环语句
394 | do
395 | echo "\nAre you want to continue?(Y/N)\c"
396 | read reply #读取键盘
397 | case $replay in
398 | (y|Y) break;; #退出循环
399 | (n|N) echo "\n\nTerminating\n"
400 | exit 0;;
401 | *) echo "\n\nPlease answer y or n"
402 | continue; #直接返回内层循环开始出继续
403 | esac
404 | done
405 | ```
406 |
407 |
408 | #### 范例:循环语句 while, until
409 |
410 |
411 | 语法:
412 |
413 | ```
414 | while/until 命令表1
415 | do
416 | 命令表2
417 | done
418 | ```
419 |
420 | 区别是,前者执行命令表 1 后,如果退出状态为零,那么执行 `do` 后面的命令表 2,然后回到起始处,而后者执行命令表 1 后,如果退出状态非零,才执行类似操作。例子同上。
421 |
422 |
423 | #### 范例:有限循环命令 for
424 |
425 |
426 | 语法:
427 |
428 | ```
429 | for 变量名 in 字符串表
430 | do
431 | 命令表
432 | done
433 | ```
434 |
435 | 举例:
436 |
437 | ```
438 | FILE="test1.c myfile1.f pccn.h"
439 | for i in $FILE
440 | do
441 | cd ./tmp
442 | cp $i $i.old
443 | echo "$i copied"
444 | done
445 | ```
446 |
447 |
448 | ### 函数
449 |
450 |
451 | 现在来看看 Shell 里头的函数用法,先看个例子:写一个函数,然后调用它显示 `Hello, World!`
452 |
453 | ```
454 | $ cat > show
455 | # 函数定义
456 | function show
457 | {
458 | echo $1$2;
459 | }
460 | H="Hello,"
461 | W="World!"
462 | # 调用函数,并传给两个参数H和W
463 | show $H $W
464 | ```
465 |
466 | 演示:
467 |
468 | ```
469 | $ chmod 770 show
470 | $./show
471 | Hello,World!
472 | ```
473 |
474 | 看出什么蹊跷了吗?
475 |
476 | ```
477 | $ show $H $W
478 | ```
479 |
480 | 咱们可以直接在函数名后面跟实参。
481 |
482 | 实参顺序对应“虚参”的 `$1,$2,$3`……
483 |
484 | 注意:假如要传入一个参数,如果这个参数中间带空格,怎么办? 先试试看。
485 |
486 | 来显示 `Hello World` (两个单词之间有个空格)
487 |
488 | ```
489 | function show
490 | {
491 | echo $1
492 | }
493 | HW="Hello World"
494 | show "$HW"
495 | ```
496 |
497 | 如果直接 `show $HW`,肯定不行,因为 `$1` 只接受到了 `Hello`,所以结果只显示 `Hello`,原因是字符串变量必须用 `"` 包含起来。
498 |
499 |
500 | ### 后记
501 |
502 | 感兴趣的话继续学习吧!
503 |
504 | 还有好多强大的东西等着呢,比如 `cut`,`expr`,`sed`,`awk` 等等。
505 |
--------------------------------------------------------------------------------
/zh/appendix/pic/mommy-tea.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/appendix/pic/mommy-tea.png
--------------------------------------------------------------------------------
/zh/chapters/01-chapter1.markdown:
--------------------------------------------------------------------------------
1 | **关注作者公众号**:
2 |
3 |

4 |
5 |
6 | # 准备工作
7 |
8 | - [前言](#toc_22391_32127_1)
9 | - [什么是 Shell](#toc_22391_32127_2)
10 | - [搭建运行环境](#toc_22391_32127_3)
11 | - [基本语法介绍](#toc_22391_32127_4)
12 | - [Shell 程序设计过程](#toc_22391_32127_5)
13 | - [调试方法介绍](#toc_22391_32127_6)
14 | - [小结](#toc_22391_32127_7)
15 | - [参考资料](#toc_22391_32127_8)
16 |
17 |
18 |
19 | ## 前言
20 |
21 | 到最后一节来写“开篇”,确实有点古怪。不过,在[第一篇(数值操作)][200]的开头实际上也算是一个小的开篇,那里提到整个系列的前提是需要有一定的 Shell 编程基础,因此,为了能够让没有 Shell 编程基础的读者也可以阅读这个系列,我到最后来重写这个开篇。开篇主要介绍什么是 Shell,Shell 运行环境,Shell 基本语法和调试技巧。
22 |
23 |
24 | [200]: 01-chapter2.markdown
25 |
26 |
27 | ## 什么是 Shell
28 |
29 | 首先让我们从下图看看 Shell 在整个操作系统中所处的位置吧,该图的外圆描述了整个操作系统(比如 `Debian/Ubuntu/Slackware` 等),内圆描述了操作系统的核心(比如 `Linux Kernel`),而 `Shell` 和 `GUI` 一样作为用户和操作系统之间的接口。
30 |
31 | 
32 |
33 | `GUI` 提供了一种图形化的用户接口,使用起来非常简便易学;而 `Shell` 则为用户提供了一种命令行的接口,接收用户的键盘输入,并分析和执行输入字符串中的命令,然后给用户返回执行结果,使用起来可能会复杂一些,但是由于占用的资源少,而且在操作熟练以后可能会提高工作效率,而且具有批处理的功能,因此在某些应用场合还非常流行。
34 |
35 | `Shell` 作为一种用户接口,它实际上是一个能够解释和分析用户键盘输入,执行输入中的命令,然后返回结果的一个解释程序(Interpreter,例如在 `linux` 下比较常用的 `Bash`),我们可以通过下面的命令查看当前的 `Shell` :
36 |
37 | ```
38 | $ echo $SHELL
39 | /bin/bash
40 | $ ls -l /bin/bash
41 | -rwxr-xr-x 1 root root 702160 2008-05-13 02:33 /bin/bash
42 | ```
43 |
44 | 该解释程序不仅能够解释简单的命令,而且可以解释一个具有特定语法结构的文件,这种文件被称作脚本(Script)。它具体是如何解释这些命令和脚本文件的,这里不深入分析,请看我在 2008 年写的另外一篇文章:[《Linux命令行上程序执行的一刹那》](http://tinylab.gitbooks.io/cbook/content/zh/chapters/02-chapter3.html)。
45 |
46 | 既然该程序可以解释具有一定语法结构的文件,那么我们就可以遵循某一语法来编写它,它有什么样的语法,如何运行,如何调试呢?下面我们以 `Bash` 为例来讨论这几个方面。
47 |
48 |
49 | ## 搭建运行环境
50 |
51 | 为了方便后面的练习,我们先搭建一个基本运行环境:在一个 Linux 操作系统中,有一个运行有 `Bash` 的命令行在等待我们键入命令,这个命令行可以是图形界面下的 `Terminal` (例如 `Ubuntu` 下非常厉害的 `Terminator`),也可以是字符界面的 `Console` (可以用 `CTRL+ALT+F1~6` 切换),如果你发现当前 `Shell` 不是 `Bash`,请用下面的方法替换它:
52 |
53 | ```
54 | $ chsh $USER -s /bin/bash
55 | $ su $USER
56 | ```
57 |
58 | 或者是简单地键入Bash:
59 |
60 | ```
61 | $ bash
62 | $ echo $SHELL # 确认一下
63 | /bin/bash
64 | ```
65 |
66 | 如果没有安装 Linux 操作系统,也可以考虑使用一些公共社区提供的 [Linux 虚拟实验服务](http://www.tinylab.org/free-online-linux-labs/),一般都有提供远程 `Shell`,你可以通过 `Telnet` 或者是 `Ssh` 的客户端登录上去进行练习。
67 |
68 | 有了基本的运行环境,那么如何来运行用户键入的命令或者是用户编写好的脚本文件呢 `?`
69 |
70 | 假设我们编写好了一个 Shell 脚本,叫 `test.sh` 。
71 |
72 | 第一种方法是确保我们执行的命令具有可执行权限,然后直接键入该命令执行它:
73 |
74 | ```
75 | $ chmod +x /path/to/test.sh
76 | $ /path/to/test.sh
77 | ```
78 |
79 | 第二种方法是直接把脚本作为 `Bash` 解释器的参数传入:
80 |
81 | ```
82 | $ bash /path/to/test.sh
83 | ```
84 |
85 | 或
86 |
87 | ```
88 | $ source /path/to/test.sh
89 | ```
90 |
91 | 或
92 |
93 | ```
94 | $ . /path/to/test.sh
95 | ```
96 |
97 |
98 | ## 基本语法介绍
99 |
100 | 先来一个 `Hello, World` 程序。
101 |
102 | 下面来介绍一个 Shell 程序的基本结构,以 `Hello, World` 为例:
103 |
104 | ```
105 | #!/bin/bash -v
106 | # test.sh
107 | echo "Hello, World"
108 | ```
109 |
110 | 把上述代码保存为 `test.sh`,然后通过上面两种不同方式运行,可以看到如下效果。
111 |
112 | 方法一:
113 |
114 | ```
115 | $ chmod +x test.sh
116 | $ ./test.sh
117 | ./test.sh
118 | #!/bin/bash -v
119 |
120 | echo "Hello, World"
121 | Hello, World
122 | ```
123 |
124 | 方法二:
125 |
126 | ```
127 | $ bash test.sh
128 | Hello, World
129 |
130 | $ source test.sh
131 | Hello, World
132 |
133 | $ . test.sh
134 | Hello, World
135 | ```
136 |
137 | 我们发现两者运行结果有区别,为什么呢?这里我们需要关注一下 `test.sh` 文件的内容,它仅仅有两行,第二行打印了 `Hello, World`,两种方法都达到了目的,但是第一种方法却多打印了脚本文件本身的内容,为什么呢?
138 |
139 | 原因在该文件的第一行,当我们直接运行该脚本文件时,该行告诉操作系统使用用`#!` 符号之后面的解释器以及相应的参数来解释该脚本文件,通过分析第一行,我们发现对应的解释器以及参数是 `/bin/bash -v`,而 `-v` 刚好就是要打印程序的源代码;但是我们在用第二种方法时没有给 `Bash` 传递任何额外的参数,因此,它仅仅解释了脚本文件本身。
140 |
141 | 其他语法细节请直接看[《Shell编程学习笔记》][100]即本书后面的附录一。
142 |
143 | [100]: ../appendix/02-chapter1.markdown
144 |
145 |
146 | ## Shell 程序设计过程
147 |
148 | Shell 语言作为解释型语言,它的程序设计过程跟编译型语言有些区别,其基本过程如下:
149 |
150 | - 设计算法
151 | - 用 Shell 编写脚本程序实现算法
152 | - 直接运行脚本程序
153 |
154 | 可见它没有编译型语言的"麻烦的"编译和链接过程,不过正是因为这样,它出错时调试起来不是很方便,因为语法错误和逻辑错误都在运行时出现。下面我们简单介绍一下调试方法。
155 |
156 |
157 | ## 调试方法介绍
158 |
159 | 可以直接参考资料:[Shell 脚本调试技术](http://www.ibm.com/developerworks/cn/linux/l-cn-shell-debug/index.html) 或者 [BASH 的调试手段](http://www.tinylab.org/bash-debugging-tools/)。
160 |
161 |
162 | ## 小结
163 |
164 | Shell 语言作为一门解释型语言,可以使用大量的现有工具,包括数值计算、符号处理、文件操作、网络操作等,因此,编写过程可能更加高效,但是因为它是解释型的,需要在执行过程中从磁盘上不断调用外部的程序并进行进程之间的切换,在运行效率方面可能有劣势,所以我们应该根据应用场合选择使用 Shell 或是用其他的语言来编程。
165 |
166 |
167 | ## 参考资料
168 |
169 | - [Linux命令行上程序执行的一刹那](http://tinylab.gitbooks.io/cbook/content/zh/chapters/02-chapter3.html)
170 | - [Linux Shell编程学习笔记][100]
171 | - [Shell 脚本调试技术](http://www.ibm.com/developerworks/cn/linux/l-cn-shell-debug/index.html)
172 | - [BASH 的调试手段](http://www.tinylab.org/bash-debugging-tools/)
173 |
--------------------------------------------------------------------------------
/zh/chapters/01-chapter10.markdown:
--------------------------------------------------------------------------------
1 | **关注作者公众号**:
2 |
3 |

4 |
5 |
6 | # 总结
7 |
8 | - [前言](#toc_30143_27506_1)
9 | - [Shell 编程范例回顾](#toc_30143_27506_2)
10 | - [常用 Shell 编程“框架”](#toc_30143_27506_3)
11 | - [程序优化技巧](#toc_30143_27506_4)
12 | - [其他注意事项](#toc_30143_27506_5)
13 |
14 |
15 |
16 | ## 前言
17 |
18 | 到这里,整个 Shell 编程系列就要结束了,作为总结篇,主要回顾一下各个小节的主要内容,并总结出 Shell 编程的一些常用框架和相关注意事项等。
19 |
20 |
21 | ## Shell 编程范例回顾
22 |
23 | TODO:主要回顾各小节的内容。
24 |
25 |
26 | ## 常用 Shell 编程“框架”
27 |
28 | TODO:通过分析一些实例总结各种常见问题的解决办法,比如如何保证同一时刻每个程序只有一个运行实体(进程)。
29 |
30 |
31 | ## 程序优化技巧
32 |
33 | TODO:多思考,总会有更简洁和高效的方式。
34 |
35 |
36 | ## 其他注意事项
37 |
38 | TODO:比如小心 `rm -rf` 的用法,如何查看系统帮助等。
39 |
40 | ### 正确使用 `source` 和 `.`
41 |
42 | 仅使用 `source` 和 `.` 来执行你的环境配置等功能,建议不要用于其它用途。
43 | 在Shell中使用脚本时,使用 `bash your_script.sh` 而不是 `source your_script.sh` 或
44 | `. your_script.sh`。
45 |
46 | 当使用 `bash` 的时候,当前的Shell会创建一个新的子进程执行你的脚本;当使用
47 | `source` 和 `.` 时,当前的Shell会直接解释执行 `your_script.sh` 中的代码。如果 `your_script.sh`
48 | 中包含了类似 `exit 0` 这样的代码,使用`source` 和 `.` 执行会导致当前Shell意外地退出。
49 |
--------------------------------------------------------------------------------
/zh/chapters/01-chapter2.markdown:
--------------------------------------------------------------------------------
1 | **关注作者公众号**:
2 |
3 |

4 |
5 |
6 | # 数值运算
7 |
8 | - [前言](#toc_22467_18587_1)
9 | - [整数运算](#toc_22467_18587_2)
10 | - [范例:对某个数加 1](#toc_22467_18587_3)
11 | - [范例:从 1 加到某个数](#toc_22467_18587_4)
12 | - [范例:求模](#toc_22467_18587_5)
13 | - [范例:求幂](#toc_22467_18587_6)
14 | - [范例:进制转换](#toc_22467_18587_7)
15 | - [范例:ascii 字符编码](#toc_22467_18587_8)
16 | - [浮点运算](#toc_22467_18587_9)
17 | - [范例:求 1 除以 13,保留 3 位有效数字](#toc_22467_18587_10)
18 | - [范例:余弦值转角度](#toc_22467_18587_11)
19 | - [范例:有一组数据,求人均月收入最高家庭](#toc_22467_18587_12)
20 | - [随机数](#toc_22467_18587_13)
21 | - [范例:获取一个随机数](#toc_22467_18587_14)
22 | - [范例:随机产生一个从 0 到 255 之间的数字](#toc_22467_18587_15)
23 | - [其他运算](#toc_22467_18587_16)
24 | - [范例:获取一系列数](#toc_22467_18587_17)
25 | - [范例:统计字符串中各单词出现次数](#toc_22467_18587_18)
26 | - [范例:统计指定单词出现次数](#toc_22467_18587_19)
27 | - [小结](#toc_22467_18587_20)
28 | - [资料](#toc_22467_18587_21)
29 | - [后记](#toc_22467_18587_22)
30 |
31 |
32 |
33 | ## 前言
34 |
35 | 从本文开始,打算结合平时积累和进一步实践,通过一些范例来介绍Shell编程。因为范例往往能够给人以学有所用的感觉,而且给人以动手实践的机会,从而激发人的学习热情。
36 |
37 | 考虑到易读性,这些范例将非常简单,但是实用,希望它们能够成为我们解决日常问题的参照物或者是“茶余饭后”的小点心,当然这些“点心”肯定还有值得探讨、优化的地方。
38 |
39 | 更复杂有趣的例子请参考 [Advanced Bash-Scripting Guide][2] (一本深入学习 Shell 脚本艺术的书籍)。
40 |
41 | [2]: http://www.tldp.org/LDP/abs/html/
42 |
43 | 该系列概要:
44 |
45 | * 目的:享受用 Shell 解决问题的乐趣;和朋友们一起交流和探讨。
46 | * 计划:先零散地写些东西,之后再不断补充,最后整理成册。
47 | * 读者:熟悉 Linux 基本知识,如文件系统结构、常用命令行工具、Shell 编程基础等。
48 | * 建议:看范例时,可参考[《Shell基础十二篇》][3]和[《Shell十三问》][4]。
49 | * 环境:如没特别说明,该系列使用的 Shell 将特指 Bash,版本在 3.1.17 以上。
50 | * 说明:该系列不是依据 Shell 语法组织,而是面向某些潜在的操作对象和操作本身,它们反应了现实应用。当然,在这个过程中肯定会涉及到 Shell 的语法。
51 |
52 | [3]: http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=2198159
53 | [4]: http://bbs.chinaunix.net/thread-218853-1-1.html
54 | [5]: http://bbs.chinaunix.net/forum.php?mod=forumdisplay&fid=24&page=1
55 |
56 | 这一篇打算讨论一下 Shell 编程中的基本数值运算,这类运算包括:
57 |
58 | * 数值(包括整数和浮点数)间的加、减、乘、除、求幂、求模等
59 | * 产生指定范围的随机数
60 | * 产生指定范围的数列
61 |
62 | Shell 本身可以做整数运算,复杂一些的运算要通过外部命令实现,比如 `expr`,`bc`,`awk` 等。另外,可通过 `RANDOM` 环境变量产生一个从 0 到 32767 的随机数,一些外部工具,比如 `awk` 可以通过 `rand()` 函数产生随机数。而 `seq` 命令可以用来产生一个数列。下面对它们分别进行介绍。
63 |
64 |
65 | ## 整数运算
66 |
67 |
68 | ### 范例:对某个数加 1
69 |
70 | ```
71 | $ i=0;
72 | $ ((i++))
73 | $ echo $i
74 | 1
75 |
76 | $ let i++
77 | $ echo $i
78 | 2
79 |
80 | $ expr $i + 1
81 | 3
82 | $ echo $i
83 | 2
84 |
85 | $ echo $i 1 | awk '{printf $1+$2}'
86 | 3
87 | ```
88 |
89 | 说明: `expr` 之后的 `$i`,`+`,1 之间有空格分开。如果进行乘法运算,需要对运算符进行转义,否则 Shell 会把乘号解释为通配符,导致语法错误; `awk` 后面的 `$1` 和 `$2` 分别指 `$i` 和 1,即从左往右的第 1 个和第 2 个数。
90 |
91 | 用 Shell 的内置命令查看各个命令的类型如下:
92 |
93 | ```
94 | $ type type
95 | type is a shell builtin
96 | $ type let
97 | let is a shell builtin
98 | $ type expr
99 | expr is hashed (/usr/bin/expr)
100 | $ type bc
101 | bc is hashed (/usr/bin/bc)
102 | $ type awk
103 | awk is /usr/bin/awk
104 | ```
105 |
106 | 从上述演示可看出: `let` 是 Shell 内置命令,其他几个是外部命令,都在 `/usr/bin` 目录下。而 `expr` 和 `bc` 因为刚用过,已经加载在内存的 `hash` 表中。这将有利于我们理解在上一章介绍的脚本多种执行方法背后的原理。
107 |
108 | 说明:如果要查看不同命令的帮助,对于 `let` 和 `type` 等 Shell 内置命令,可以通过 Shell 的一个内置命令 `help` 来查看相关帮助,而一些外部命令可以通过 Shell 的一个外部命令 `man` 来查看帮助,用法诸如 `help let`,`man expr` 等。
109 |
110 |
111 | ### 范例:从 1 加到某个数
112 |
113 | ```
114 | #!/bin/bash
115 | # calc.sh
116 |
117 | i=0;
118 | while [ $i -lt 10000 ]
119 | do
120 | ((i++))
121 | done
122 | echo $i
123 | ```
124 |
125 | 说明:这里通过 `while [ 条件表达式 ]; do .... done` 循环来实现。`-lt` 是小于号 `<`,具体见 `test` 命令的用法:`help test`。
126 |
127 | 如何执行该脚本?
128 |
129 | 办法一:直接把脚本文件当成子 Shell (Bash)的一个参数传入
130 |
131 | ```
132 | $ bash calc.sh
133 | $ type bash
134 | bash is hashed (/bin/bash)
135 | ```
136 |
137 | 办法二:是通过 `bash` 的内置命令 `.` 或 `source` 执行
138 |
139 | ```
140 | $ . ./calc.sh
141 | ```
142 |
143 | 或
144 |
145 | ```
146 | $ source ./calc.sh
147 | $ type .
148 | . is a shell builtin
149 | $ type source
150 | source is a shell builtin
151 | ```
152 |
153 | 办法三:是修改文件为可执行,直接在当前 Shell 下执行
154 |
155 | ```
156 | $ chmod ./calc.sh
157 | $ ./calc.sh
158 | ```
159 |
160 | 下面,逐一演示用其他方法计算变量加一,即把 `((i++))` 行替换成下面的某一个:
161 |
162 | ```
163 | let i++;
164 |
165 | i=$(expr $i + 1)
166 |
167 | i=$(echo $i+1|bc)
168 |
169 | i=$(echo "$i 1" | awk '{printf $1+$2;}')
170 | ```
171 |
172 | 比较计算时间如下:
173 |
174 | ```
175 | $ time calc.sh
176 | 10000
177 |
178 | real 0m1.319s
179 | user 0m1.056s
180 | sys 0m0.036s
181 | $ time calc_let.sh
182 | 10000
183 |
184 | real 0m1.426s
185 | user 0m1.176s
186 | sys 0m0.032s
187 | $ time calc_expr.sh
188 | 1000
189 |
190 | real 0m27.425s
191 | user 0m5.060s
192 | sys 0m14.177s
193 | $ time calc_bc.sh
194 | 1000
195 |
196 | real 0m56.576s
197 | user 0m9.353s
198 | sys 0m24.618s
199 | $ time ./calc_awk.sh
200 | 100
201 |
202 | real 0m11.672s
203 | user 0m2.604s
204 | sys 0m2.660s
205 | ```
206 |
207 | 说明: `time` 命令可以用来统计命令执行时间,这部分时间包括总的运行时间,用户空间执行时间,内核空间执行时间,它通过 `ptrace` 系统调用实现。
208 |
209 | 通过上述比较可以发现 `(())` 的运算效率最高。而 `let` 作为 Shell 内置命令,效率也很高,但是 `expr`,`bc`,`awk` 的计算效率就比较低。所以,在 Shell 本身能够完成相关工作的情况下,建议优先使用 Shell 本身提供的功能。但是 Shell 本身无法完成的功能,比如浮点运算,所以就需要外部命令的帮助。另外,考虑到 Shell 脚本的可移植性,在性能不是很关键的情况下,不要使用某些 Shell 特有的语法。
210 |
211 | `let`,`expr`,`bc` 都可以用来求模,运算符都是 `%`,而 `let` 和 `bc` 可以用来求幂,运算符不一样,前者是 `**`,后者是 `^` 。例如:
212 |
213 |
214 | ### 范例:求模
215 |
216 | ```
217 | $ expr 5 % 2
218 | 1
219 |
220 | $ let i=5%2
221 | $ echo $i
222 | 1
223 |
224 | $ echo 5 % 2 | bc
225 | 1
226 |
227 | $ ((i=5%2))
228 | $ echo $i
229 | 1
230 | ```
231 |
232 |
233 | ### 范例:求幂
234 |
235 | ```
236 | $ let i=5**2
237 | $ echo $i
238 | 25
239 |
240 | $ ((i=5**2))
241 | $ echo $i
242 |
243 | 25
244 | $ echo "5^2" | bc
245 | 25
246 | ```
247 |
248 |
249 | ### 范例:进制转换
250 |
251 | 进制转换也是比较常用的操作,可以用 `Bash` 的内置支持也可以用 `bc` 来完成,例如把 8 进制的 11 转换为 10 进制,则可以:
252 |
253 | ```
254 | $ echo "obase=10;ibase=8;11" | bc -l
255 | 9
256 |
257 | $ echo $((8#11))
258 | 9
259 | ```
260 |
261 | 上面都是把某个进制的数转换为 10 进制的,如果要进行任意进制之间的转换还是 `bc` 比较灵活,因为它可以直接用 `ibase` 和 `obase` 分别指定进制源和进制转换目标。
262 |
263 |
264 | ### 范例:ascii 字符编码
265 |
266 | 如果要把某些字符串以特定的进制表示,可以用 `od` 命令,例如默认的分隔符 `IFS` 包括空格、 `TAB` 以及换行,可以用 `man ascii` 佐证。
267 |
268 | ```
269 | $ echo -n "$IFS" | od -c
270 | 0000000 t n
271 | 0000003
272 | $ echo -n "$IFS" | od -b
273 | 0000000 040 011 012
274 | 0000003
275 | ```
276 |
277 |
278 | ## 浮点运算
279 |
280 | `let` 和 `expr` 都无法进行浮点运算,但是 `bc` 和 `awk` 可以。
281 |
282 |
283 | ### 范例:求 1 除以 13,保留 3 位有效数字
284 |
285 | ```
286 | $ echo "scale=3; 1/13" | bc
287 | .076
288 |
289 | $ echo "1 13" | awk '{printf("%0.3f\n",$1/$2)}'
290 | 0.077
291 | ```
292 |
293 | 说明: `bc` 在进行浮点运算时需指定精度,否则默认为 0,即进行浮点运算时,默认结果只保留整数。而 `awk` 在控制小数位数时非常灵活,仅仅通过 `printf` 的格式控制就可以实现。
294 |
295 | 补充:在用 `bc` 进行运算时,如果不用 `scale` 指定精度,而在 `bc` 后加上 `-l` 选项,也可以进行浮点运算,只不过这时的默认精度是 20 位。例如:
296 |
297 | ```
298 | $ echo 1/13100 | bc -l
299 | .00007633587786259541
300 | ```
301 |
302 |
303 | ### 范例:余弦值转角度
304 |
305 | 用 `bc -l` 计算,可以获得高精度:
306 |
307 | ```
308 | $ export cos=0.996293; echo "scale=100; a(sqrt(1-$cos^2)/$cos)*180/(a(1)*4)" | bc -l
309 | 4.934954755411383632719834036931840605159706398655243875372764917732
310 | 5495504159766011527078286004072131
311 | ```
312 |
313 | 当然也可以用 `awk` 来计算:
314 |
315 | ```
316 | $ echo 0.996293 | awk '{ printf("%s\n", atan2(sqrt(1-$1^2),$1)*180/3.1415926535);}'
317 | 4.93495
318 | ```
319 |
320 |
321 | ### 范例:有一组数据,求人均月收入最高家庭
322 |
323 | 在这里随机产生了一组测试数据,文件名为 `income.txt`。
324 |
325 | ```
326 | 1 3 4490
327 | 2 5 3896
328 | 3 4 3112
329 | 4 4 4716
330 | 5 4 4578
331 | 6 6 5399
332 | 7 3 5089
333 | 8 6 3029
334 | 9 4 6195
335 | 10 5 5145
336 | ```
337 |
338 | 说明:上面的三列数据分别是家庭编号、家庭人数、家庭月总收入。
339 |
340 | 分析:为了求月均收入最高家庭,需要对后面两列数进行除法运算,即求出每个家庭的月均收入,然后按照月均收入排序,找出收入最高家庭。
341 |
342 | 实现:
343 |
344 | ```
345 | #!/bin/bash
346 | # gettopfamily.sh
347 |
348 | [ $# -lt 1 ] && echo "please input the income file" && exit -1
349 | [ ! -f $1 ] && echo "$1 is not a file" && exit -1
350 |
351 | income=$1
352 | awk '{
353 | printf("%d %0.2f\n", $1, $3/$2);
354 | }' $income | sort -k 2 -n -r
355 | ```
356 |
357 | 说明:
358 |
359 | * `[ $# -lt 1 ]`:要求至少输入一个参数,`$#` 是 Shell 中传入参数的个数
360 | * `[ ! -f $1 ]`:要求输入参数是一个文件,`-f` 的用法见 `test` 命令,`help test`
361 | * `income=$1`:把输入参数赋给 income 变量,再作为 `awk` 的参数,即需处理的文件
362 | * `awk`:用文件第三列除以第二列,求出月均收入,考虑到精确性,保留了两位精度
363 | * `sort -k 2 -n -r`:这里对结果的 `awk` 结果的第二列 `-k 2`,即月均收入进行排序,按照数字排序 `-n`,并按照递减的顺序排序 `-r`。
364 |
365 | 演示:
366 |
367 | ```
368 | $ ./gettopfamily.sh income.txt
369 | 7 1696.33
370 | 9 1548.75
371 | 1 1496.67
372 | 4 1179.00
373 | 5 1144.50
374 | 10 1029.00
375 | 6 899.83
376 | 2 779.20
377 | 3 778.00
378 | 8 504.83
379 | ```
380 |
381 | 补充:之前的 `income.txt` 数据是随机产生的。在做一些实验时,往往需要随机产生一些数据,在下一小节,我们将详细介绍它。这里是产生 `income.txt` 数据的脚本:
382 |
383 | ```
384 | #!/bin/bash
385 | # genrandomdata.sh
386 |
387 | for i in $(seq 1 10)
388 | do
389 | echo $i $(($RANDOM/8192+3)) $((RANDOM/10+3000))
390 | done
391 | ```
392 |
393 | 说明:上述脚本中还用到`seq`命令产生从1到10的一列数,这个命令的详细用法在该篇最后一节也会进一步介绍。
394 |
395 |
396 | ## 随机数
397 |
398 | 环境变量 `RANDOM` 产生从 0 到 32767 的随机数,而 `awk` 的 `rand()` 函数可以产生 0 到 1 之间的随机数。
399 |
400 |
401 | ### 范例:获取一个随机数
402 |
403 | ```
404 | $ echo $RANDOM
405 | 81
406 |
407 | $ echo "" | awk '{srand(); printf("%f", rand());}'
408 | 0.237788
409 | ```
410 |
411 | 说明: `srand()` 在无参数时,采用当前时间作为 `rand()` 随机数产生器的一个 `seed` 。
412 |
413 |
414 | ### 范例:随机产生一个从 0 到 255 之间的数字
415 |
416 | 可以通过 `RANDOM` 变量的缩放和 `awk` 中 `rand()` 的放大来实现。
417 |
418 | ```
419 | $ expr $RANDOM / 128
420 |
421 | $ echo "" | awk '{srand(); printf("%d\n", rand()*255);}'
422 | ```
423 |
424 | 思考:如果要随机产生某个 IP 段的 IP 地址,该如何做呢?看例子:友善地获取一个可用的 IP 地址。
425 |
426 | ```
427 | #!/bin/bash
428 | # getip.sh -- get an usable ipaddress automatically
429 | # author: falcon <zhangjinw@gmail.com>
430 | # update: Tue Oct 30 23:46:17 CST 2007
431 |
432 | # set your own network, default gateway, and the time out of "ping" command
433 | net="192.168.1"
434 | default_gateway="192.168.1.1"
435 | over_time=2
436 |
437 | # check the current ipaddress
438 | ping -c 1 $default_gateway -W $over_time
439 | [ $? -eq 0 ] && echo "the current ipaddress is okey!" && exit -1;
440 |
441 | while :; do
442 | # clear the current configuration
443 | ifconfig eth0 down
444 | # configure the ip address of the eth0
445 | ifconfig eth0 \
446 | $net.$(($RANDOM /130 +2)) \
447 | up
448 | # configure the default gateway
449 | route add default gw $default_gateway
450 | # check the new configuration
451 | ping -c 1 $default_gateway -W $over_time
452 | # if work, finish
453 | [ $? -eq 0 ] && break
454 | done
455 | ```
456 |
457 | 说明:如果你的默认网关地址不是 `192.168.1.1`,请自行配置 `default_gateway`(可以用 `route -n` 命令查看),因为用 `ifconfig` 配置地址时不能配置为网关地址,否则你的IP地址将和网关一样,导致整个网络不能正常工作。
458 |
459 |
460 | ## 其他运算
461 |
462 | 其实通过一个循环就可以产生一系列数,但是有相关工具为什么不用呢!`seq` 就是这么一个小工具,它可以产生一系列数,你可以指定数的递增间隔,也可以指定相邻两个数之间的分割符。
463 |
464 |
465 | ### 范例:获取一系列数
466 |
467 | ```
468 | $ seq 5
469 | 1
470 | 2
471 | 3
472 | 4
473 | 5
474 | $ seq 1 5
475 | 1
476 | 2
477 | 3
478 | 4
479 | 5
480 | $ seq 1 2 5
481 | 1
482 | 3
483 | 5
484 | $ seq -s: 1 2 5
485 | 1:3:5
486 | $ seq 1 2 14
487 | 1
488 | 3
489 | 5
490 | 7
491 | 9
492 | 11
493 | 13
494 | $ seq -w 1 2 14
495 | 01
496 | 03
497 | 05
498 | 07
499 | 09
500 | 11
501 | 13
502 | $ seq -s: -w 1 2 14
503 | 01:03:05:07:09:11:13
504 | $ seq -f "0x%g" 1 5
505 | 0x1
506 | 0x2
507 | 0x3
508 | 0x4
509 | 0x5
510 | ```
511 |
512 | 一个比较典型的使用 `seq` 的例子,构造一些特定格式的链接,然后用 `wget` 下载这些内容:
513 |
514 | ```
515 | $ for i in `seq -f"http://thns.tsinghua.edu.cn/thnsebooks/ebook73/%02g.pdf" 1 21`;do wget -c $i; done
516 | ```
517 |
518 | 或者
519 |
520 | ```
521 | $ for i in `seq -w 1 21`;do wget -c "http://thns.tsinghua.edu.cn/thnsebooks/ebook73/$i"; done
522 | ```
523 |
524 | 补充:在 `Bash` 版本 3 以上,在 `for` 循环的 `in` 后面,可以直接通过 `{1..5}` 更简洁地产生自 1 到 5 的数字(注意,1 和 5 之间只有两个点),例如:
525 |
526 | ```
527 | $ for i in {1..5}; do echo -n "$i "; done
528 | 1 2 3 4 5
529 | ```
530 |
531 |
532 | ### 范例:统计字符串中各单词出现次数
533 |
534 | 我们先给单词一个定义:由字母组成的单个或者多个字符系列。
535 |
536 | 首先,统计每个单词出现的次数:
537 |
538 | ```
539 | $ wget -c http://tinylab.org
540 | $ cat index.html | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | uniq -c
541 | ```
542 |
543 | 接着,统计出现频率最高的前10个单词:
544 |
545 | ```
546 | $ wget -c http://tinylab.org
547 | $ cat index.html | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | uniq -c | sort -n -k 1 -r | head -10
548 | 524 a
549 | 238 tag
550 | 205 href
551 | 201 class
552 | 193 http
553 | 189 org
554 | 175 tinylab
555 | 174 www
556 | 146 div
557 | 128 title
558 | ```
559 |
560 | 说明:
561 |
562 | * `cat index.html`: 输出 index.html 文件里的内容
563 | * `sed -e "s/[^a-zA-Z]/\n/g"`: 把非字母字符替换成空格,只保留字母字符
564 | * `grep -v ^$`: 去掉空行
565 | * `sort`: 排序
566 | * `uniq -c`:统计相同行的个数,即每个单词的个数
567 | * `sort -n -k 1 -r`:按照第一列 `-k 1` 的数字 `-n` 逆序 `-r` 排序
568 | * `head -10`:取出前十行
569 |
570 |
571 | ### 范例:统计指定单词出现次数
572 |
573 | 可以考虑采取两种办法:
574 |
575 | * 只统计那些需要统计的单词
576 | * 用上面的算法把所有单词的个数都统计出来,然后再返回那些需要统计的单词给用户
577 |
578 | 不过,这两种办法都可以通过下面的结构来实现。先看办法一:
579 |
580 | ```
581 | #!/bin/bash
582 | # statistic_words.sh
583 |
584 | if [ $# -lt 1 ]; then
585 | echo "Usage: basename $0 FILE WORDS ...."
586 | exit -1
587 | fi
588 |
589 | FILE=$1
590 | ((WORDS_NUM=$#-1))
591 |
592 | for n in $(seq $WORDS_NUM)
593 | do
594 | shift
595 | cat $FILE | sed -e "s/[^a-zA-Z]/\n/g" \
596 | | grep -v ^$ | sort | grep ^$1$ | uniq -c
597 | done
598 | ```
599 |
600 | 说明:
601 |
602 | * `if 条件部分`:要求至少两个参数,第一个单词文件,之后参数为要统计的单词
603 | * `FILE=$1`: 获取文件名,即脚本之后的第一个字符串
604 | * `((WORDS_NUM=$#-1))`:获取单词个数,即总的参数个数 `$#` 减去文件名参数(1个)
605 | * `for 循环部分`:首先通过 `seq` 产生需要统计的单词个数系列,`shift` 是 Shell 内置变量(请通过 `help shift` 获取帮助),它把用户从命令行中传入的参数依次往后移动位置,并把当前参数作为第一个参数即 `$1`,这样通过 `$1`就可以遍历用户所有输入的单词(仔细一想,这里貌似有数组下标的味道)。你可以考虑把 `shift` 之后的那句替换成 `echo $1` 测试 `shift` 的用法
606 |
607 | 演示:
608 |
609 | ```
610 | $ chmod +x statistic_words.sh
611 | $ ./statistic_words.sh index.html tinylab linux python
612 | 175 tinylab
613 | 43 linux
614 | 3 python
615 | ```
616 |
617 | 再看办法二,我们只需要修改 `shift` 之后的那句即可:
618 |
619 | ```
620 | #!/bin/bash
621 | # statistic_words.sh
622 |
623 | if [ $# -lt 1 ]; then
624 | echo "ERROR: you should input 2 words at least";
625 | echo "Usage: basename $0 FILE WORDS ...."
626 | exit -1
627 | fi
628 |
629 | FILE=$1
630 | ((WORDS_NUM=$#-1))
631 |
632 | for n in $(seq $WORDS_NUM)
633 | do
634 | shift
635 | cat $FILE | sed -e "s/[^a-zA-Z]/\n/g" \
636 | | grep -v ^$ | sort | uniq -c | grep " $1$"
637 | done
638 | ```
639 |
640 | 演示:
641 |
642 | ```
643 | $ ./statistic_words.sh index.html tinylab linux python
644 | 175 tinylab
645 | 43 linux
646 | 3 python
647 | ```
648 |
649 | 说明:很明显,办法一的效率要高很多,因为它提前找出了需要统计的单词,然后再统计,而后者则不然。实际上,如果使用 `grep` 的 `-E` 选项,我们无须引入循环,而用一条命令就可以搞定:
650 |
651 | ```
652 | $ cat index.html | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | grep -E "^tinylab$|^linux$" | uniq -c
653 | 43 linux
654 | 175 tinylab
655 | ```
656 |
657 | 或者
658 |
659 | ```
660 | $ cat index.html | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | egrep "^tinylab$|^linux$" | uniq -c
661 | 43 linux
662 | 175 tinylab
663 | ```
664 |
665 | 说明:需要注意到 `sed` 命令可以直接处理文件,而无需通过 `cat` 命令输出以后再通过管道传递,这样可以减少一个不必要的管道操作,所以上述命令可以简化为:
666 |
667 | ```
668 | $ sed -e "s/[^a-zA-Z]/\n/g" index.html | grep -v ^$ | sort | egrep "^tinylab$|^linux$" | uniq -c
669 | 43 linux
670 | 175 tinylab
671 | ```
672 |
673 | 所以,可见这些命令 `sed`,`grep`,`uniq`,`sort` 是多么有用,它们本身虽然只完成简单的功能,但是通过一定的组合,就可以实现各种五花八门的事情啦。对了,统计单词还有个非常有用的命令 `wc -w`,需要用到的时候也可以用它。
674 |
675 | 补充:在 [Advanced Bash-Scripting Guide][2] 一书中还提到 `jot` 命令和 `factor` 命令,由于机器上没有,所以没有测试,`factor` 命令可以产生某个数的所有素数。如:
676 |
677 | ```
678 | $ factor 100
679 | 100: 2 2 5 5
680 | ```
681 |
682 |
683 |
684 | ## 小结
685 |
686 | 到这里,Shell 编程范例之数值计算就结束啦。该篇主要介绍了:
687 |
688 | * Shell 编程中的整数运算、浮点运算、随机数的产生、数列的产生
689 | * Shell 的内置命令、外部命令的区别,以及如何查看他们的类型和帮助
690 | * Shell 脚本的几种执行办法
691 | * 几个常用的 Shell 外部命令: `sed`,`awk`,`grep`,`uniq`,`sort` 等
692 | * 范例:数字递增;求月均收入;自动获取 `IP` 地址;统计单词个数
693 | * 其他:相关用法如命令列表,条件测试等在上述范例中都已涉及,请认真阅读之
694 |
695 | 如果您有时间,请温习之。
696 |
697 |
698 | ## 资料
699 |
700 | * [Advanced Bash-Scripting Guide][2]
701 | * [shell 十三问][4]
702 | * [shell 基础十二篇][3]
703 | * SED 手册
704 | * AWK 使用手册
705 | * 几个 Shell 讨论区
706 | * [LinuxSir.org][6]
707 | * [ChinaUnix.net][7]
708 |
709 | [6]: http://www.linuxsir.org/bbs/forumdisplay.php?f=60
710 | [7]: http://bbs.chinaunix.net/forum-24-1.html
711 |
712 |
713 | ## 后记
714 |
715 | 大概花了 3 个多小时才写完,目前是 23:33,该回宿舍睡觉啦,明天起来修改错别字和补充一些内容,朋友们晚安!
716 |
717 | 10 月 31 号,修改部分措辞,增加一篇统计家庭月均收入的范例,添加总结和参考资料,并用附录所有代码。
718 |
719 | Shell 编程是一件非常有趣的事情,如果您想一想:上面计算家庭月均收入的例子,然后和用 `M$ Excel` 来做这个工作比较,你会发现前者是那么简单和省事,而且给您以运用自如的感觉。
720 |
--------------------------------------------------------------------------------
/zh/chapters/01-chapter3.markdown:
--------------------------------------------------------------------------------
1 | **关注作者公众号**:
2 |
3 |

4 |
5 |
6 | # 布尔运算
7 |
8 | - [前言](#toc_17877_9500_1)
9 | - [常规的布尔运算](#toc_17877_9500_2)
10 | - [在 Shell 下如何进行逻辑运算](#toc_17877_9500_3)
11 | - [范例:true or false](#toc_17877_9500_4)
12 | - [范例:与运算](#toc_17877_9500_5)
13 | - [范例:或运算](#toc_17877_9500_6)
14 | - [范例:非运算,即取反](#toc_17877_9500_7)
15 | - [Bash 里头的 true 和 false 是我们通常认为的 1 和 0 么?](#toc_17877_9500_8)
16 | - [范例:返回值 v.s. 逻辑值](#toc_17877_9500_9)
17 | - [范例:查看 true 和 false 帮助和类型](#toc_17877_9500_10)
18 | - [条件测试](#toc_17877_9500_11)
19 | - [条件测试基本使用](#toc_17877_9500_12)
20 | - [范例:数值测试](#toc_17877_9500_13)
21 | - [范例:字符串测试](#toc_17877_9500_14)
22 | - [范例:文件测试](#toc_17877_9500_15)
23 | - [各种逻辑测试的组合](#toc_17877_9500_16)
24 | - [范例:如果 a,b,c 都等于下面对应的值,那么打印 YES,通过 -a 进行与测试](#toc_17877_9500_17)
25 | - [范例:测试某个“东西”是文件或者目录,通过 -o 进行“或”运算](#toc_17877_9500_18)
26 | - [范例:测试某个“东西”是否为文件,测试 `!` 非运算](#toc_17877_9500_19)
27 | - [比较 -a 与 &&, -o 与 ||, ! test 与 test !](#toc_17877_9500_20)
28 | - [范例:要求某文件可执行且有内容,用 -a 和 && 分别实现](#toc_17877_9500_21)
29 | - [范例:要求某个字符串要么为空,要么和某个字符串相等](#toc_17877_9500_22)
30 | - [范例:测试某个数字不满足指定的所有条件](#toc_17877_9500_23)
31 | - [命令列表](#toc_17877_9500_24)
32 | - [命令列表的执行规律](#toc_17877_9500_25)
33 | - [范例:如果 ping 通 www.lzu.edu.cn,那么打印连通信息](#toc_17877_9500_26)
34 | - [命令列表的作用](#toc_17877_9500_27)
35 | - [范例:在脚本里判断程序的参数个数,和参数类型](#toc_17877_9500_28)
36 | - [小结](#toc_17877_9500_29)
37 |
38 |
39 |
40 | ## 前言
41 |
42 | 上个礼拜介绍了[Shell编程范例之数值运算][100],对 Shell 下基本数值运算方法做了简单的介绍,这周将一起探讨布尔运算,即如何操作“真假值”。
43 |
44 | [100]: 01-chapter2.markdown
45 |
46 | 在 Bash 里有这样的常量(实际上是两个内置命令,在这里我们姑且这么认为,后面将介绍),即 true 和 false,一个表示真,一个表示假。对它们可以进行与、或、非运算等常规的逻辑运算,在这一节,我们除了讨论这些基本逻辑运算外,还将讨论Shell编程中的**条件测试**和**命令列表**,并介绍它们和布尔运算的关系。
47 |
48 |
49 | ## 常规的布尔运算
50 |
51 | 这里主要介绍 `Bash` 里头常规的逻辑运算,与、或、非。
52 |
53 |
54 | ### 在 Shell 下如何进行逻辑运算
55 |
56 |
57 | #### 范例:true or false
58 |
59 | 单独测试 `true` 和 `false`,可以看出 `true` 是真值,`false` 为假
60 |
61 | $ if true;then echo "YES"; else echo "NO"; fi
62 | YES
63 | $ if false;then echo "YES"; else echo "NO"; fi
64 | NO
65 |
66 |
67 | #### 范例:与运算
68 |
69 | $ if true && true;then echo "YES"; else echo "NO"; fi
70 | YES
71 | $ if true && false;then echo "YES"; else echo "NO"; fi
72 | NO
73 | $ if false && false;then echo "YES"; else echo "NO"; fi
74 | NO
75 | $ if false && true;then echo "YES"; else echo "NO"; fi
76 | NO
77 |
78 |
79 | #### 范例:或运算
80 |
81 | $ if true || true;then echo "YES"; else echo "NO"; fi
82 | YES
83 | $ if true || false;then echo "YES"; else echo "NO"; fi
84 | YES
85 | $ if false || true;then echo "YES"; else echo "NO"; fi
86 | YES
87 | $ if false || false;then echo "YES"; else echo "NO"; fi
88 | NO
89 |
90 |
91 | #### 范例:非运算,即取反
92 |
93 | $ if ! false;then echo "YES"; else echo "NO"; fi
94 | YES
95 | $ if ! true;then echo "YES"; else echo "NO"; fi
96 | NO
97 |
98 | 可以看出 `true` 和 `false` 按照我们对逻辑运算的理解进行着,但是为了能够更好的理解 Shell 对逻辑运算的实现,我们还得弄清楚,`true` 和 `false` 是怎么工作的?
99 |
100 |
101 | ### Bash 里头的 true 和 false 是我们通常认为的 1 和 0 么?
102 |
103 | 回答是:否。
104 |
105 |
106 | #### 范例:返回值 v.s. 逻辑值
107 |
108 | `true` 和 `false` 它们本身并非逻辑值,它们都是 Shell 的内置命令,只是它们的返回值是一个“逻辑值”:
109 |
110 | $ true
111 | $ echo $?
112 | 0
113 | $ false
114 | $ echo $?
115 | 1
116 |
117 | 可以看到 `true` 返回了 0,而 `false` 则返回了 1 。跟我们离散数学里学的真值 1 和 0 并不是对应的,而且相反的。
118 |
119 |
120 | #### 范例:查看 true 和 false 帮助和类型
121 |
122 | $ help true false
123 | true: true
124 | Return a successful result.
125 | false: false
126 | Return an unsuccessful result.
127 | $ type true false
128 | true is a shell builtin
129 | false is a shell builtin
130 |
131 | 说明:`$?` 是一个特殊变量,存放有上一次进程的结束状态(退出状态码)。
132 |
133 | 从上面的操作不难联想到在 C 语言程序设计中为什么会强调在 `main` 函数前面加上 `int`,并在末尾加上 `return 0` 。因为在 Shell 里,将把 0 作为程序是否成功结束的标志,这就是 Shell 里头 `true` 和 `false` 的实质,它们用以反应某个程序是否正确结束,而并非传统的真假值(1 和 0),相反地,它们返回的是 0 和 1 。不过庆幸地是,我们在做逻辑运算时,无须关心这些。
134 |
135 |
136 | ## 条件测试
137 |
138 | 从上节中,我们已经清楚地了解了 Shell 下的“逻辑值”是什么:是进程退出时的返回值,如果成功返回,则为真,如果不成功返回,则为假。
139 |
140 | 而条件测试正好使用了 `test` 这么一个指令,它用来进行数值测试(各种数值属性测试)、字符串测试(各种字符串属性测试)、文件测试(各种文件属性测试),我们通过判断对应的测试是否成功,从而完成各种常规工作,再加上各种测试的逻辑组合后,将可以完成更复杂的工作。
141 |
142 |
143 | ### 条件测试基本使用
144 |
145 |
146 | #### 范例:数值测试
147 |
148 | $ if test 5 -eq 5;then echo "YES"; else echo "NO"; fi
149 | YES
150 | $ if test 5 -ne 5;then echo "YES"; else echo "NO"; fi
151 | NO
152 |
153 |
154 | #### 范例:字符串测试
155 |
156 | $ if test -n "not empty";then echo "YES"; else echo "NO"; fi
157 | YES
158 | $ if test -z "not empty";then echo "YES"; else echo "NO"; fi
159 | NO
160 | $ if test -z "";then echo "YES"; else echo "NO"; fi
161 | YES
162 | $ if test -n "";then echo "YES"; else echo "NO"; fi
163 | NO
164 |
165 |
166 | #### 范例:文件测试
167 |
168 | $ if test -f /boot/System.map; then echo "YES"; else echo "NO"; fi
169 | YES
170 | $ if test -d /boot/System.map; then echo "YES"; else echo "NO"; fi
171 | NO
172 |
173 |
174 | ### 各种逻辑测试的组合
175 |
176 |
177 | #### 范例:如果 a,b,c 都等于下面对应的值,那么打印 YES,通过 -a 进行"与"测试
178 |
179 | $ a=5;b=4;c=6;
180 | $ if test $a -eq 5 -a $b -eq 4 -a $c -eq 6; then echo "YES"; else echo "NO"; fi
181 | YES
182 |
183 |
184 | #### 范例:测试某个“东西”是文件或者目录,通过 -o 进行“或”运算
185 |
186 | $ if test -f /etc/profile -o -d /etc/profile;then echo "YES"; else echo "NO"; fi
187 | YES
188 |
189 |
190 | #### 范例:测试某个“东西”是否为文件,测试 `!` 非运算
191 |
192 | $ if test ! -f /etc/profile; then echo "YES"; else echo "NO"; fi
193 | NO
194 |
195 | 上面仅仅演示了 `test` 命令一些非常简单的测试,你可以通过 `help test` 获取 `test` 的更多用法。需要注意的是,`test` 命令内部的逻辑运算和 Shell 的逻辑运算符有一些区别,对应的为 `-a` 和 `&&`,`-o` 与 `||`,这两者不能混淆使用。而非运算都是 `!`,下面对它们进行比较。
196 |
197 |
198 | ### 比较 -a 与 &&, -o 与 ||, ! test 与 test !
199 |
200 |
201 | #### 范例:要求某文件可执行且有内容,用 -a 和 && 分别实现
202 |
203 | $ cat > test.sh
204 | #!/bin/bash
205 | echo "test"
206 | [CTRL+D] # 按下组合键CTRL与D结束cat输入,后同,不再注明
207 | $ chmod +x test.sh
208 | $ if test -s test.sh -a -x test.sh; then echo "YES"; else echo "NO"; fi
209 | YES
210 | $ if test -s test.sh && test -x test.sh; then echo "YES"; else echo "NO"; fi
211 | YES
212 |
213 |
214 | #### 范例:要求某个字符串要么为空,要么和某个字符串相等
215 |
216 | $ str1="test"
217 | $ str2="test"
218 | $ if test -z "$str2" -o "$str2" == "$str1"; then echo "YES"; else echo "NO"; fi
219 | YES
220 | $ if test -z "$str2" || test "$str2" == "$str1"; then echo "YES"; else echo "NO"; fi
221 | YES
222 |
223 |
224 | #### 范例:测试某个数字不满足指定的所有条件
225 |
226 | $ i=5
227 | $ if test ! $i -lt 5 -a $i -ne 6; then echo "YES"; else echo "NO"; fi
228 | YES
229 | $ if ! test $i -lt 5 -a $i -eq 6; then echo "YES"; else echo "NO"; fi
230 | YES
231 |
232 | 很容易找出它们的区别,`-a` 和 `-o` 作为测试命令的参数用在测试命令的内部,而 `&&` 和 `||` 则用来运算测试的返回值,`!` 为两者通用。需要关注的是:
233 |
234 | - 有时可以不用 `!` 运算符,比如 `-eq` 和 `-ne` 刚好相反,可用于测试两个数值是否相等; `-z` 与 `-n` 也是对应的,用来测试某个字符串是否为空
235 | - 在 `Bash` 里,`test` 命令可以用[] 运算符取代,但是需要注意,[` 之后与 `] 之前需要加上额外的空格
236 | - 在测试字符串时,所有变量建议用双引号包含起来,以防止变量内容为空时出现仅有测试参数,没有测试内容的情况
237 |
238 | 下面我们用实例来演示上面三个注意事项:
239 |
240 | - `-ne` 和 `-eq` 对应的,我们有时候可以免去 `!` 运算
241 |
242 | $ i=5
243 | $ if test $i -eq 5; then echo "YES"; else echo "NO"; fi
244 | YES
245 | $ if test $i -ne 5; then echo "YES"; else echo "NO"; fi
246 | NO
247 | $ if test ! $i -eq 5; then echo "YES"; else echo "NO"; fi
248 | NO
249 |
250 | - 用 `[ ]` 可以取代 `test`,这样看上去会“美观”很多
251 |
252 | $ if [ $i -eq 5 ]; then echo "YES"; else echo "NO"; fi
253 | YES
254 | $ if [ $i -gt 4 ] && [ $i -lt 6 ]; then echo "YES"; else echo "NO"; fi
255 | YES
256 |
257 | - 记得给一些字符串变量加上 `""`,记得 `[` 之后与 `]` 之前多加一个空格
258 |
259 | $ str=""
260 | $ if [ "$str" = "test"]; then echo "YES"; else echo "NO"; fi
261 | -bash: [: missing `]'
262 | NO
263 | $ if [ $str = "test" ]; then echo "YES"; else echo "NO"; fi
264 | -bash: [: =: unary operator expected
265 | NO
266 | $ if [ "$str" = "test" ]; then echo "YES"; else echo "NO"; fi
267 | NO
268 |
269 | 到这里,**条件测试**就介绍完了,下面介绍**命令列表**,实际上在上面我们已经使用过了,即多个test命令的组合,通过 `&&`,`||` 和 `!` 组合起来的命令序列。这种命令序列可以有效替换 `if/then` 的条件分支结构。这不难想到我们在 C 语言程序设计中经常做的如下的选择题(很无聊的例子,但是有意义):下面是否会打印 `j`,如果打印,将打印什么?
270 |
271 | #include
272 | int main()
273 | {
274 | int i, j;
275 |
276 | i=5;j=1;
277 | if ((i==5) && (j=5)) printf("%d\n", j);
278 |
279 | return 0;
280 | }
281 |
282 | 很容易知道将打印数字 5,因为 `i==5` 这个条件成立,而且随后是 `&&`,要判断整个条件是否成立,我们得进行后面的判断,可是这个判断并非常规的判断,而是先把 `j` 修改为 5,再转换为真值,所以条件为真,打印出 5 。因此,这句可以解释为:如果 `i` 等于 5,那么把 `j` 赋值为 5,如果 `j` 大于 1 (因为之前已经为真),那么打印出 `j` 的值。这样用 `&&` 连结起来的判断语句替代了两个 `if` 条件分支语句。
283 |
284 | 正是基于逻辑运算特有的性质,我们可以通过 `&&`,`||` 来取代 `if/then` 等条件分支结构,这样就产生了命令列表。
285 |
286 |
287 | ## 命令列表
288 |
289 |
290 | ### 命令列表的执行规律
291 |
292 | 命令列表的执行规律符合逻辑运算的运算规律,用 `&&` 连接起来的命令,如果前者成功返回,将执行后面的命令,反之不然;用 `||` 连接起来的命令,如果前者成功返回,将不执行后续命令,反之不然。
293 |
294 |
295 | #### 范例:如果 ping 通 www.lzu.edu.cn,那么打印连通信息
296 |
297 | $ ping -c 1 www.lzu.edu.cn -W 1 && echo "=======connected======="
298 |
299 | 非常有趣的问题出来了,即我们上面已经提到的:为什么要让 C 程序在 `main()` 函数的最后返回 0 ?如果不这样,把这种程序放入命令列表会有什么样的结果?你自己写个简单的 C 程序,然后放入命令列表看看。
300 |
301 |
302 | ### 命令列表的作用
303 |
304 | 有时用命令列表取代 `if/then` 等条件分支结构可以省掉一些代码,而且使得程序比较美观、易读,例如:
305 |
306 |
307 | #### 范例:在脚本里判断程序的参数个数,和参数类型
308 |
309 | #!/bin/bash
310 |
311 | echo $#
312 | echo $1
313 | if [ $# -eq 1 ] && (echo $1 | grep '^[0-9]*$' >/dev/null);then
314 | echo "YES"
315 | fi
316 |
317 | 说明:上例要求参数个数为 1 并且类型为数字。
318 |
319 | 再加上 `exit 1`,我们将省掉 `if/then` 结构
320 |
321 | #!/bin/bash
322 |
323 | echo $#
324 | echo $1
325 | ! ([ $# -eq 1 ] && (echo $1 | grep '^[0-9]*$' >/dev/null)) && exit 1
326 |
327 | echo "YES"
328 |
329 | 这样处理后,对程序参数的判断仅仅需要简单的一行代码,而且变得更美观。
330 |
331 |
332 | ## 小结
333 |
334 | 这一节介绍了 Shell 编程中的逻辑运算,条件测试和命令列表。
335 |
--------------------------------------------------------------------------------
/zh/chapters/01-chapter5.markdown:
--------------------------------------------------------------------------------
1 | **关注作者公众号**:
2 |
3 |
4 |
5 |
6 | # 文件操作
7 |
8 | - [前言](#toc_11738_32168_1)
9 | - [文件的各种属性](#toc_11738_32168_2)
10 | - [文件类型](#toc_11738_32168_3)
11 | - [范例:在命令行简单地区分各类文件](#toc_11738_32168_4)
12 | - [范例:简单比较它们的异同](#toc_11738_32168_5)
13 | - [范例:普通文件再分类](#toc_11738_32168_6)
14 | - [文件属主](#toc_11738_32168_7)
15 | - [范例:修改文件的属主](#toc_11738_32168_8)
16 | - [范例:查看文件的属主](#toc_11738_32168_9)
17 | - [范例:分析文件属主实现的背后原理](#toc_11738_32168_10)
18 | - [文件权限](#toc_11738_32168_11)
19 | - [范例:给文件添加读、写、可执行权限](#toc_11738_32168_12)
20 | - [范例:授权普通用户执行root所属命令](#toc_11738_32168_13)
21 | - [范例:给重要文件加锁](#toc_11738_32168_14)
22 | - [文件大小](#toc_11738_32168_15)
23 | - [范例:查看普通文件和链接文件](#toc_11738_32168_16)
24 | - [范例:查看设备文件](#toc_11738_32168_17)
25 | - [范例:查看目录](#toc_11738_32168_18)
26 | - [文件访问、更新、修改时间](#toc_11738_32168_19)
27 | - [文件名](#toc_11738_32168_20)
28 | - [文件的基本操作](#toc_11738_32168_21)
29 | - [范例:创建文件](#toc_11738_32168_22)
30 | - [范例:删除文件](#toc_11738_32168_23)
31 | - [范例:复制文件](#toc_11738_32168_24)
32 | - [范例:修改文件名](#toc_11738_32168_25)
33 | - [范例:编辑文件](#toc_11738_32168_26)
34 | - [范例:压缩/解压缩文件](#toc_11738_32168_27)
35 | - [范例:文件搜索(文件定位)](#toc_11738_32168_28)
36 | - [参考资料](#toc_11738_32168_29)
37 | - [后记](#toc_11738_32168_30)
38 |
39 |
40 |
41 | ## 前言
42 |
43 | 这周来探讨文件操作。
44 |
45 | 在日常学习和工作中,总是在不断地和各种文件打交道,这些文件包括普通文本文件,可以执行的程序,带有控制字符的文档、存放各种文件的目录、网络套接字文件、设备文件等。这些文件又具有诸如属主、大小、创建和修改日期等各种属性。文件对应文件系统的一些数据块,对应磁盘等存储设备的一片连续空间,对应于显示设备却是一些具有不同形状的字符集。
46 |
47 | 在这一节,为了把关注点定位在文件本身,不会深入探讨文件系统以及存储设备是如何组织文件的(在后续章节再深入探讨),而是探讨对它最熟悉的一面,即把文件当成是一系列的字符(一个 `byte`)集合看待。因此之前介绍的[《 Shell 编程范例之字符串操作》][100]在这里将会得到广泛的应用,关于普通文件的读写操作已经非常熟练,那就是“重定向”,这里会把这部分独立出来介绍。关于文件在 Linux 下的“数字化”(文件描述符)高度抽象,“一切皆为文件”的哲学在 Shell 编程里也得到了深刻的体现。
48 |
49 | [100]: 01-chapter4.markdown
50 |
51 | 下面先来介绍文件的各种属性,然后介绍普通文件的一般操作。
52 |
53 |
54 | ## 文件的各种属性
55 |
56 | 首先通过文件的结构体来看看文件到底有哪些属性:
57 |
58 | struct stat {
59 | dev_t st_dev; /* 设备 */
60 | ino_t st_ino; /* 节点 */
61 | mode_t st_mode; /* 模式 */
62 | nlink_t st_nlink; /* 硬连接 */
63 | uid_t st_uid; /* 用户ID */
64 | gid_t st_gid; /* 组ID */
65 | dev_t st_rdev; /* 设备类型 */
66 | off_t st_off; /* 文件字节数 */
67 | unsigned long st_blksize; /* 块大小 */
68 | unsigned long st_blocks; /* 块数 */
69 | time_t st_atime; /* 最后一次访问时间 */
70 | time_t st_mtime; /* 最后一次修改时间 */
71 | time_t st_ctime; /* 最后一次改变时间(指属性) */
72 | };
73 |
74 | 下面逐次来了解这些属性,如果需要查看某个文件属性,用 `stat` 命令就可,它会按照上面的结构体把信息列出来。另外,`ls` 命令在跟上一定参数后也可以显示文件的相关属性,比如 `-l` 参数。
75 |
76 |
77 | ### 文件类型
78 |
79 | 文件类型对应于上面的 `st_mode`, 文件类型有很多,比如常规文件、符号链接(硬链接、软链接)、管道文件、设备文件(符号设备、块设备)、socket文件等,不同的文件类型对应不同的功能和作用。
80 |
81 |
82 | #### 范例:在命令行简单地区分各类文件
83 |
84 | $ ls -l
85 | total 12
86 | drwxr-xr-x 2 root root 4096 2007-12-07 20:08 directory_file
87 | prw-r--r-- 1 root root 0 2007-12-07 20:18 fifo_pipe
88 | brw-r--r-- 1 root root 3, 1 2007-12-07 21:44 hda1_block_dev_file
89 | crw-r--r-- 1 root root 1, 3 2007-12-07 21:43 null_char_dev_file
90 | -rw-r--r-- 2 root root 506 2007-12-07 21:55 regular_file
91 | -rw-r--r-- 2 root root 506 2007-12-07 21:55 regular_file_hard_link
92 | lrwxrwxrwx 1 root root 12 2007-12-07 20:15 regular_file_soft_link -> regular_file
93 | $ stat directory_file/
94 | File: `directory_file/'
95 | Size: 4096 Blocks: 8 IO Block: 4096 directory
96 | Device: 301h/769d Inode: 521521 Links: 2
97 | Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
98 | Access: 2007-12-07 20:08:18.000000000 +0800
99 | Modify: 2007-12-07 20:08:18.000000000 +0800
100 | Change: 2007-12-07 20:08:18.000000000 +0800
101 | $ stat null_char_dev_file
102 | File: `null_char_dev_file'
103 | Size: 0 Blocks: 0 IO Block: 4096 character special file
104 | Device: 301h/769d Inode: 521240 Links: 1 Device type: 1,3
105 | Access: (0644/crw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
106 | Access: 2007-12-07 21:43:38.000000000 +0800
107 | Modify: 2007-12-07 21:43:38.000000000 +0800
108 | Change: 2007-12-07 21:43:38.000000000 +0800
109 |
110 | 说明:通过 `ls` 命令结果每行的第一个字符可以看到,它们之间都不相同,这正好反应了不同文件的类型。 `d` 表示目录,`-` 表示普通文件(或者硬链接),`l` 表示符号链接,`p` 表示管道文件,`b` 和 `c` 分别表示块设备和字符设备(另外 `s` 表示 `socket` 文件)。在 `stat` 命令的结果中,可以在第二行的最后找到说明,从上面的操作可以看出,`directory_file` 是目录,`stat` 命令的结果中用 `directory` 表示,而 `null_char_dev_file` 它则用 `character special file` 说明。
111 |
112 |
113 | #### 范例:简单比较它们的异同
114 |
115 | 通常只会用到目录、普通文件、以及符号链接,很少碰到其他类型的文件,不过这些文件还是各有用处的,如果要做嵌入式开发或者进程通信等,可能会涉及到设备文件、有名管道(FIFO)。下面通过简单的操作来反应它们之间的区别(具体原理会在下一节[《Shell 编程范例之文件系统》][101]介绍,如果感兴趣,也可以提前到网上找找设备文件的作用、块设备和字符设备的区别、以及驱动程序中如何编写相关设备驱动等)。
116 |
117 | [101]: 01-chapter6.markdown
118 |
119 | 对于普通文件:就是一系列字符的集合,所以可以读、写等
120 |
121 | $ echo "hello, world" > regular_file
122 | $ cat regular_file
123 | hello, world
124 |
125 | 在目录中可以创建新文件,所以目录还有叫法:文件夹,到后面会分析目录文件的结构体,它实际上存放了它下面的各个文件的文件名。
126 |
127 | $ cd directory_file
128 | $ touch file1 file2 file3
129 |
130 | 对于有名管道,操作起来比较有意思:如果要读它,除非有内容,否则阻塞;如果要写它,除非有人来读,否则阻塞。它常用于进程通信中。可以打开两个终端 `terminal1` 和 `terminal2`,试试看:
131 |
132 | terminal1$ cat fifo_pipe #刚开始阻塞在这里,直到下面的写动作发生,才打印test字符串
133 | terminal2$ echo "test" > fifo_pipe
134 |
135 | 关于块设备,字符设备,设备文件对应于 `/dev/hda1` 和 `/dev/null`,如果用过 U 盘,或者是写过简单的脚本的话,这样的用法应该用过:
136 | :-)
137 |
138 | $ mount hda1_block_dev_file /mnt #挂载硬盘的第一个分区到/mnt下(关于挂载的原理,在下一节讨论)
139 | $ echo "fewfewfef" > /dev/null #/dev/null像个黑洞,什么东西丢进去都消失殆尽
140 |
141 | 最后两个文件分别是 `regular_file` 文件的硬链接和软链接,去读写它们,他们的内容是相同的,不过去删除它们,他们却互不相干,硬链接和软链接又有何不同呢?前者可以说就是原文件,后者呢只是有那么一个 `inode`,但没有实际的存储空间,建议用 `stat` 命令查看它们之间的区别,包括它们的 `Blocks`,`inode` 等值,也可以考虑用 `diff` 比较它们的大小。
142 |
143 | $ ls regular_file*
144 | ls regular_file* -l
145 | -rw-r--r-- 2 root root 204800 2007-12-07 22:30 regular_file
146 | -rw-r--r-- 2 root root 204800 2007-12-07 22:30 regular_file_hard_link
147 | lrwxrwxrwx 1 root root 12 2007-12-07 20:15 regular_file_soft_link -> regular_file
148 | $ rm regular_file # 删除原文件
149 | $ cat regular_file_hard_link # 硬链接还在,而且里头的内容还有呢
150 | fefe
151 | $ cat regular_file_soft_link
152 | cat: regular_file_soft_link: No such file or directory
153 |
154 | 虽然软链接文件本身还在,不过因为它本身不存储内容,所以读不到东西,这就是软链接和硬链接的区别。
155 |
156 | 需要注意的是,硬链接不可以跨文件系统,而软链接则可以。另外,也不允许给目录创建硬链接。
157 |
158 |
159 | #### 范例:普通文件再分类
160 |
161 | 文件类型从 Linux 文件系统那么一个级别分了以上那么多类型,不过普通文件还是可以再分的(根据文件内容的”数据结构“分),比如常见的文本文件,可执行的 `ELF` 文件,`odt` 文档,`jpg` 图片格式,`swap` 分区文件,`pdf` 文件。除了文本文件外,它们大多是二进制文件,有特定的结构,因此需要有专门的工具来创建和编辑它们。关于各类文件的格式,可以参考相关文档标准。不过非常值得深入了解 Linux 下可执行的 `ELF` 文件的工作原理,如果有兴趣,建议阅读一下参考资料中和 `ELF` 文件相关部分,这一部分对于嵌入式 Linux 工程师至关重要。
162 |
163 | 虽然各类普通文件都有专属的操作工具,但是还是可以直接读、写它们,这里先提到这么几个工具,回头讨论细节。
164 |
165 | - `od` :以八进制或者其他格式“导出”文件内容。
166 | - `strings` :读出文件中的字符(可打印的字符)
167 | - `gcc`,`gdb`,`readelf`,objdump` 等: `ELF` 文件分析、处理工具(`gcc` 编译器、 `gdb` 调试器、 `readelf` 分析 ELF 文件,`objdump` 反编译工具)
168 |
169 | 再补充一个非常重要的命令,`file`,这个命令用来查看各类文件的属性。和 `stat` 命令相比,它可以进一步识别普通文件,即 `stat` 命令显示的 `regular file` 。因为 `regular file` 可以有各种不同的结构,因此在操作系统的支持下得到不同的解释,执行不同的动作。虽然,Linux 下,文件也会加上特定的后缀以便用户能够方便地识别文件的类型,但是 Linux 操作系统根据文件头识别各类文件,而不是文件后缀,这样在解释相应的文件时就更不容易出错。下面简单介绍 `file` 命令的用法。
170 |
171 | $ file ./
172 | ./: directory
173 | $ file /etc/profile
174 | /etc/profile: ASCII English text
175 | $ file /lib/libc-2.5.so
176 | /lib/libc-2.5.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped
177 | $ file /bin/test
178 | /bin/test: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), stripped
179 | $ file /dev/hda
180 | /dev/hda: block special (3/0)
181 | $ file /dev/console
182 | /dev/console: character special (5/1)
183 | $ cp /etc/profile .
184 | $ tar zcf profile.tar.gz profile
185 | $ file profile.tar.gz
186 | profile.tar.gz: gzip compressed data, from Unix, last modified: Tue Jan 4 18:53:53 2000
187 | $ mkfifo fifo_test
188 | $ file fifo_test
189 | fifo_test: fifo (named pipe)
190 |
191 | 更多用法见 `file` 命令的手册,关于 `file` 命令的实现原理,请参考 `magic` 的手册(看看 `/etc/file/magic` 文件,了解什么是文件的 `magic number` 等)。
192 |
193 |
194 | ### 文件属主
195 |
196 | Linux 作为一个多用户系统,为多用户使用同一个系统提供了极大的方便,比如对于系统上的文件,它通过属主来区分不同的用户,以便分配它们对不同文件的操作权限。为了更方便地管理,文件属主包括该文件所属用户,以及该文件所属的用户组,因为用户可以属于多个组。先来简单介绍 Linux 下用户和组的管理。
197 |
198 | Linux 下提供了一组命令用于管理用户和组,比如用于创建用户的 `useradd` 和 `groupadd`,用于删除用户的 `userdel` 和 `groupdel`,另外,`passwd` 命令用于修改用户密码。当然,Linux 还提供了两个相应的配置,即 `/etc/passwd` 和 `/etc/group`,另外,有些系统还把密码单独放到了配置文件 `/etc/shadow` 中。关于它们的详细用法请参考后面的资料,这里不再介绍,仅介绍文件和用户之间的一些关系。
199 |
200 |
201 | #### 范例:修改文件的属主
202 |
203 | $ chown 用户名:组名 文件名
204 |
205 | 如果要递归地修改某个目录下所有文件的属主,可以添加 `-R` 选项。
206 |
207 | 从本节开头列出的文件结构体中,可以看到仅仅有用户 `ID` 和组 `ID` 的信息,但 `ls -l` 的结果却显示了用户名和组名信息,这个是怎么实现的呢?下面先看看 `-n` 的结果:
208 |
209 |
210 | #### 范例:查看文件的属主
211 |
212 | $ ls -n regular_file
213 | -rw-r--r-- 1 0 0 115 2007-12-07 23:45 regular_file
214 | $ ls -l regular_file
215 | -rw-r--r-- 1 root root 115 2007-12-07 23:45 regular_file
216 |
217 |
218 | #### 范例:分析文件属主实现的背后原理
219 |
220 | 可以看到,`ls -n` 显示了用户 `ID` 和组 `ID`,而 `ls -l` 显示了它们的名字。还记得上面提到的两个配置文件 `/etc/passwd` 和 `/etc/group` 文件么?它们分别存放了用户 `ID` 和用户名,组 `ID` 和组名的对应关系,因此很容易想到 `ls -l` 命令在实现时是如何通过文件结构体的 `ID` 信息找到它们对应的名字信息的。如果想对 `ls -l` 命令的实现有更进一步的了解,可以用 `strace` 跟踪看看它是否读取了这两个配置文件。
221 |
222 | $ strace -f -o strace.log ls -l regular_file
223 | $ cat strace.log | egrep "passwd|group|shadow"
224 | 2989 open("/etc/passwd", O_RDONLY) = 3
225 | 2989 open("/etc/group", O_RDONLY) = 3
226 |
227 | 说明: `strace` 可以用来跟踪系统调用和信号。如同 `gdb` 等其他强大的工具一样,它基于系统的 `ptrace` 系统调用实现。
228 |
229 | 实际上,把属主和权限分开介绍不太好,因为只有它们两者结合才使得多用户系统成为可能,否则无法隔离不同用户对某个文件的操作,所以下面来介绍文件操作权限。
230 |
231 |
232 | ### 文件权限
233 |
234 | 从 `ls -l` 命令的结果的第一列的后 9 个字符中,可以看到类似这样的信息 `rwxr-xr-x`,它们对应于文件结构体的 `st_mode` 部分(`st_mode` 包含文件类型信息和文件权限信息两部分)。这类信息可以分成三部分,即 `rwx`,`r-x`,`r-x`,分别对应该文件所属用户、所属组、其他组对该文件的操作权限,如果有 `rwx` 中任何一个表示可读、可写、可执行,如果为 `-` 表示没有这个权限。对应地,可以用八进制来表示它,比如 `rwxr-xr-x` 就可表示成二进制 111101101,对应的八进制则为 755 。正因为如此,要修改文件的操作权限,也可以有多种方式来实现,它们都可通过 `chmod` 命令来修改。
235 |
236 |
237 | #### 范例:给文件添加读、写、可执行权限
238 |
239 | 比如,把 `regular_file` 的文件权限修改为所有用户都可读、可写、可执行,即 `rwxrwxrwx`,也可表示为 111111111,翻译成八进制,则为 777 。这样就可以通过两种方式修改这个权限。
240 |
241 | $ chmod a+rwx regular_file
242 |
243 | 或
244 |
245 | $ chmod 777 regular_file
246 |
247 | 说明: `a` 指所有用户,如果只想给用户本身可读可写可执行权限,那么可以把 `a` 换成 `u` ;而 `+` 就是添加权限,相反的,如果想去掉某个权限,用 `-`,而 `rwx` 则对应可读、可写、可执行。更多用法见 `chmod` 命令的帮助。
248 |
249 | 实际上除了这些权限外,还有两个涉及到安全方面的权限,即 `setuid/setgid` 和只读控制等。
250 |
251 | 如果设置了文件(程序或者命令)的 `setuid/setgid` 权限,那么用户将可用 `root` 身份去执行该文件,因此,这将可能带来安全隐患;如果设置了文件的只读权限,那么用户将仅仅对该文件将有可读权限,这为避免诸如 `rm -rf` 的“可恶”操作带来一定的庇佑。
252 |
253 |
254 | #### 范例:授权普通用户执行root所属命令
255 |
256 | 默认情况下,系统是不允许普通用户执行 `passwd` 命令的,通过 `setuid/setgid`,可以授权普通用户执行它。
257 |
258 | $ ls -l /usr/bin/passwd
259 | -rwx--x--x 1 root root 36092 2007-06-19 14:59 /usr/bin/passwd
260 | $ su #切换到root用户,给程序或者命令添加“粘着位”
261 | $ chmod +s /usr/bin/passwd
262 | $ ls -l /usr/bin/passwd
263 | -rws--s--x 1 root root 36092 2007-06-19 14:59 /usr/bin/passwd
264 | $ exit
265 | $ passwd #普通用户通过执行该命令,修改自己的密码
266 |
267 | 说明:
268 |
269 | > `setuid` 和 `setgid` 位是让普通用户可以以 `root` 用户的角色运行只有 `root` 帐号才能运行的程序或命令。
270 |
271 | 虽然这在一定程度上为管理提供了方便,比如上面的操作让普通用户可以修改自己的帐号,而不是要 `root` 帐号去为每个用户做这些工作。关于 `setuid/setgid` 的更多详细解释,请参考最后推荐的资料。
272 |
273 |
274 | #### 范例:给重要文件加锁
275 |
276 | 只读权限示例:给重要文件加锁(添加不可修改位 [immutable])),以避免各种误操作带来的灾难性后果(例如 `:`
277 | `rm -rf`)
278 |
279 | $ chattr +i regular_file
280 | $ lsattr regular_file
281 | ----i-------- regular_file
282 | $ rm regular_file #加immutable位后就无法对文件进行任何“破坏性”的活动啦
283 | rm: remove write-protected regular file `regular_file'? y
284 | rm: cannot remove `regular_file': Operation not permitted
285 | $ chattr -i regular_file #如果想对它进行常规操作,那么可以把这个位去掉
286 | $ rm regular_file
287 |
288 | 说明: `chattr` 可以用于设置文件的特殊权限,更多用法请参考 `chattr` 的帮助。
289 |
290 |
291 | ### 文件大小
292 |
293 | 文件大小对于普通文件而言就是文件内容的大小,而目录作为一个特殊的文件,它存放的内容是以目录结构体组织的各类文件信息,所以目录的大小一般都是固定的,它存放的文件个数自然也就有上限,即它的大小除以文件名的长度。设备文件的“文件大小”则对应设备的主、次设备号,而有名管道文件因为特殊的读写性质,所以大小常是 0 。硬链接(目录文件不能创建硬链接)实质上是原文件的一个完整的拷贝,因此,它的大小就是原文件的大小。而软链接只是一个 `inode`,存放了一个指向原文件的指针,因此它的大小仅仅是原文件名的字节数。下面我们通过演示增加记忆。
294 |
295 |
296 | #### 范例:查看普通文件和链接文件
297 |
298 | 原文件,链接文件文件大小的示例:
299 |
300 | $ echo -n "abcde" > regular_file #往regular_file写入5字节
301 | $ ls -l regular_file*
302 | -rw-r--r-- 2 root root 5 2007-12-08 15:28 regular_file
303 | -rw-r--r-- 2 root root 5 2007-12-08 15:28 regular_file_hard_file
304 | lrwxrwxrwx 1 root root 12 2007-12-07 20:15 regular_file_soft_link -> regular_file
305 | lrwxrwxrwx 1 root root 22 2007-12-08 15:21 regular_file_soft_link_link -> regular_file_soft_link
306 | $ i="regular_file"
307 | $ j="regular_file_soft_link"
308 | $ echo ${#i} ${#j} #软链接存放的刚好是它们指向的原文件的文件名的字节数
309 | 12 22
310 |
311 |
312 | #### 范例:查看设备文件
313 |
314 | 设备号对应的文件大小:主、次设备号
315 |
316 | $ ls -l hda1_block_dev_file
317 | brw-r--r-- 1 root root 3, 1 2007-12-07 21:44 hda1_block_dev_file
318 | $ ls -l null_char_dev_file
319 | crw-r--r-- 1 root root 1, 3 2007-12-07 21:43 null_char_dev_file
320 |
321 | 补充:主 `(major)、次 `(minor)设备号的作用有不同。当一个设备文件被打开时,内核会根据主设备号(`major number`)去查找在内核中已经以主设备号注册的驱动(可以 `cat /proc/devices` 查看已经注册的驱动号和主设备号的对应情况),而次设备号(`minor number`)则是通过内核传递给了驱动本身(参考《The Linux Primer》第十章)。因此,对于内核而言,通过主设备号就可以找到对应的驱动去识别某个设备,而对于驱动而言,为了能够更复杂地访问设备,比如访问设备的不同部分(如硬件通过分区分成不同部分,而出现 `hda1`,`hda2`,`hda3` 等),比如产生不同要求的随机数(如 `/dev/random` 和 `/dev/urandom` 等)。
322 |
323 |
324 | #### 范例:查看目录
325 |
326 | 目录文件的大小,为什么是这样呢?看看下面的目录结构体的大小,目录文件的 Block 中存放了该目录下所有文件名的入口。
327 |
328 | $ ls -ld directory_file/
329 | drwxr-xr-x 2 root root 4096 2007-12-07 23:14 directory_file/
330 |
331 | 目录的结构体如下:
332 |
333 | struct dirent {
334 | long d_ino;
335 | off_t d_off;
336 | unsigned short d_reclen;
337 | char d_name[NAME_MAX+1]; /* 文件名称 */
338 | }
339 |
340 |
341 | ### 文件访问、更新、修改时间
342 |
343 | 文件的时间属性可以记录用户对文件的操作信息,在系统管理、判断文件版本信息等情况下将为管理员提供参考。因此,在阅读文件时,建议用 `cat` 等阅读工具,不要用编辑工具 `vim` 去阅读,因为即使没有做任何修改操作,一旦执行了保存命令,将修改文件的时间戳信息。
344 |
345 |
346 | ### 文件名
347 |
348 | 文件名并没有存放在文件结构体内,而是存放在它所在的目录结构体中。所以,在目录的同一级别中,文件名必须是唯一的。
349 |
350 |
351 | ## 文件的基本操作
352 |
353 | 对于文件,常见的操作包括创建、删除、修改、读、写等。关于各种操作对应的“背后动作”将在下一章[《Shell编程范例之文件系统操作》][101]详细分析。
354 |
355 |
356 | ### 范例:创建文件
357 |
358 | `socket` 文件是一类特殊的文件,可以通过 C 语言创建,这里不做介绍(暂时不知道是否可以用命令直接创建),其他文件将通过命令创建。
359 |
360 | $ touch regular_file #创建普通文件
361 | $ mkdir directory_file #创建目录文件,目录文件里头可以包含更多文件
362 | $ ln regular_file regular_file_hard_link #硬链接,是原文件的一个完整拷比
363 | $ ln -s regular_file regular_file_soft_link #类似一个文件指针,指向原文件
364 | $ mkfifo fifo_pipe #或者通过 "mknod fifo_pipe p" 来创建,FIFO满足先进先出的特点
365 | $ mknod hda1_block_dev_file b 3 1 #块设备
366 | $ mknod null_char_dev_file c 1 3 #字符设备
367 |
368 | 创建一个文件实际上是在文件系统中添加了一个节点(`inode),该节点信息将保存到文件系统的节点表中。更形象地说,就是在一颗树上长了一颗新的叶子(文件)或者枝条(目录文件,上面还可以长叶子的那种),这些可以通过 `tree` 命令或者 `ls` 命令形象地呈现出来。文件系统从日常使用的角度,完全可以当成一颗倒立的树来看,因为它们太像了,太容易记忆啦。
369 |
370 | $ tree 当前目录
371 |
372 | 或者
373 |
374 | $ ls 当前目录
375 |
376 |
377 | ### 范例:删除文件
378 |
379 | 删除文件最直接的印象是这个文件再也不存在了,这同样可以通过 `ls` 或者 `tree` 命令呈现出来,就像树木被砍掉一个分支或者摘掉一片叶子一样。实际上,这些文件删除之后,并不是立即消失了,而是仅仅做了删除标记,因此,如果删除之后,没有相关的磁盘写操作把相应的磁盘空间“覆盖”,那么原理上是可以恢复的(虽然如此,但是这样的工作往往很麻烦,所以在删除一些重要数据时,请务必三思而后行,比如做好备份工作),相应的做法可以参考后续资料。
380 |
381 | 具体删除文件的命令有 `rm`,如果要删除空目录,可以用 `rmdir` 命令。例如:
382 |
383 | $ rm regular_file
384 | $ rmdir directory_file
385 | $ rm -r directory_file_not_empty
386 |
387 | `rm` 有两个非常重要的参数,一个是 `-f`,这个命令是非常“野蛮的”,它估计给很多 Linux user 带来了痛苦,另外一个是 `-i`,这个命令是非常“温柔的”,它估计让很多用户感觉烦躁不已。用哪个还是根据您的“心情”吧,如果做好了充分的备份工作,或者采取了一些有效避免灾难性后果的动作的话,您在做这些工作的时候就可以放心一些啦。
388 |
389 |
390 | ### 范例:复制文件
391 |
392 | 文件的复制通常是指文件内容的“临时”复制。通过这一节开头的介绍,我们应该了解到,文件的硬链接和软链接在某种意义上说也是“文件的复制”,前者同步复制文件内容,后者在读写的情况下同步“复制”文件内容。例如:
393 |
394 | 用 `cp` 命令常规地复制文件(复制目录需要 `-r` 选项)
395 |
396 | $ cp regular_file regular_file_copy
397 | $ cp -r diretory_file directory_file_copy
398 |
399 | 创建硬链接(`link` 和 `copy` 不同之处是:`link` 为同步更新,`copy` 则不然,复制之后两者不再相关)
400 |
401 | $ ln regular_file regular_file_hard_link
402 |
403 | 创建软链接
404 |
405 | $ ln -s regular_file regluar_file_soft_link
406 |
407 |
408 | ### 范例:修改文件名
409 |
410 | 修改文件名实际上仅仅修改了文件名标识符。可以通过 `mv` 命令来实现修改文件名操作(即重命名)。
411 |
412 | $ mv regular_file regular_file_new_name
413 |
414 |
415 | ### 范例:编辑文件
416 |
417 | 编辑文件实际上是操作文件的内容,对应普通文本文件的编辑,这里主要涉及到文件内容的读、写、追加、删除等。这些工作通常会通过专门的编辑器来做,这类编辑器有命令行下的 `vim` 、 `emacs` 和图形界面下的 `gedit,kedit` 等。如果是一些特定的文件,会有专门的编辑和处理工具,比如图像处理软件 `gimp`,文档编辑软件 `OpenOffice` 等。这些工具一般都会有专门的教程。
418 |
419 | 下面主要简单介绍 Linux 下通过重定向来实现文件的这些常规的编辑操作。
420 |
421 | 创建一个文件并写入 `abcde`
422 |
423 | $ echo "abcde" > new_regular_file
424 |
425 | 再往上面的文件中追加一行 `abcde`
426 |
427 | $ echo "abcde" >> new_regular_file
428 |
429 | 按行读一个文件
430 |
431 | $ while read LINE; do echo $LINE; done < test.sh
432 |
433 | 提示:如果要把包含重定向的字符串变量当作命令来执行,请使用 `eval` 命令,否则无法解释重定向。例如,
434 |
435 | $ redirect="echo \"abcde\" >test_redirect_file"
436 | $ $redirect #这里会把>当作字符 > 打印出来,而不会当作 重定向 解释
437 | "abcde" >test_redirect_file
438 | $ eval $redirect #这样才会把 > 解释成 重定向
439 | $ cat test_redirect_file
440 | abcde
441 |
442 |
443 | ### 范例:压缩/解压缩文件
444 |
445 | 压缩和解压缩文件在一定意义上来说是为了方便文件内容的传输,不过也可能有一些特定的用途,比如内核和文件系统的映像文件等(更多相关的知识请参考后续资料)。
446 |
447 | 这里仅介绍几种常见的压缩和解压缩方法:
448 |
449 | tar
450 |
451 | $ tar -cf file.tar file #压缩
452 | $ tar -xf file.tar #解压
453 |
454 | gz
455 |
456 | $ gzip -9 file
457 | $ gunzip file
458 |
459 | tar.gz
460 |
461 | $ tar -zcf file.tar.gz file
462 | $ tar -zxf file.tar.gz
463 |
464 | bz2
465 |
466 | $ bzip2 file
467 | $ bunzip2 file
468 |
469 | tar.bz2
470 |
471 | $ tar -jcf file.tar.bz2 file
472 | $ tar -jxf file.tar.bz2
473 |
474 | 通过上面的演示,应该已经非常清楚 `tar`,`bzip2,`bunzip2,`gzip,`gunzip` 命令的角色了吧?如果还不清楚,多操作和比较一些上面的命令,并查看它们的手册: `man tar`...
475 |
476 |
477 | ### 范例:文件搜索(文件定位)
478 |
479 | 文件搜索是指在某个目录层次中找出具有某些属性的文件在文件系统中的位置,这个位置如果扩展到整个网络,那么可以表示为一个 `URL` 地址,对于本地的地址,可以表示为 `file://+` 本地路径。本地路径在 Linux 系统下是以 `/` 开头,例如,每个用户的家目录可以表示为: `file:///home/` 。下面仅仅介绍本地文件搜索的一些办法。
480 |
481 | `find` 命令提供了一种“及时的”搜索办法,它根据用户的请求,在指定的目录层次中遍历所有文件直到找到需要的文件为止。而 `updatedb+locate` 提供了一种“快速的”的搜索策略,`updatedb` 更新并产生一个本地文件数据库,而 `locate` 通过文件名检索这个数据库以便快速找到相应的文件。前者支持通过各种文件属性进行搜索,并且提供了一个接口(`-exec` 选项)用于处理搜索后的文件。因此为“单条命令”脚本的爱好者提供了极大的方便,不过对于根据文件名的搜索而言,`updatedb+locate` 的方式在搜索效率上会有明显提高。下面简单介绍这两种方法:
482 |
483 | `find` 命令基本使用演示
484 |
485 | $ find ./ -name "*.c" -o -name "*.h" #找出所有的C语言文件,-o是或者
486 | $ find ./ \( -name "*.c" -o -name "*.h" \) -exec mv '{}' ./c_files/ \;
487 | # 把找到的文件移到c_files下,这种用法非常有趣
488 |
489 | 上面的用法可以用 `xargs` 命令替代
490 |
491 | $ find ./ -name "*.c" -o -name "*.h" | xargs -i mv '{}' ./c_files/
492 | # 如果要对文件做更复杂的操作,可以考虑把mv改写为你自己的处理命令,例如,我需要修
493 |
494 | 改所有的文件名后缀为大写。
495 |
496 | $ find ./ -name "*.c" -o -name "*.h" | xargs -i ./toupper.sh '{}' ./c_files/
497 |
498 | `toupper.sh` 就是我们需要实现的转换小写为大写的一个处理文件,具体实现如下:
499 |
500 | $ cat toupper.sh
501 | #!/bin/bash
502 |
503 | # the {} will be expended to the current line and becomen the first argument of this script
504 | FROM=$1
505 | BASENAME=${FROM##*/}
506 |
507 | BASE=${BASENAME%.*}
508 | SUFFIX=${BASENAME##*.}
509 |
510 | TOSUFFIX="$(echo $SUFFIX | tr '[a-z]' '[A-Z]')"
511 | TO=$2/$BASE.$TOSUFFIX
512 | COM="mv $FROM $TO"
513 | echo $COM
514 | eval $COM
515 |
516 | `updatedb+locate` 基本使用演示
517 |
518 | $ updatedb #更新库
519 | $ locate find*.gz #查找包含find字符串的所有gz压缩包
520 |
521 | 实际上,除了上面两种命令外,Linux 下还有命令查找工具:`which` 和 `whereis`,前者用于返回某个命令的全路径,而后者用于返回某个命令、源文件、`man 文件的路径。例如,查找 `find` 命令的绝对路径:
522 |
523 | $ which find
524 | /usr/bin/find
525 | $ whereis find
526 | find: /usr/bin/find /usr/X11R6/bin/find /usr/bin/X11/find /usr/X11/bin/find /usr/man/man1/find.1.gz /usr/share/man/man1/find.1.gz /usr/X11/man/man1/find.1.gz
527 |
528 | 需要提到的是,如果想根据文件的内容搜索文件,那么 `find` 和 `updatedb+locate` 以及 `which`,`whereis` 都无能为力啦,可选的方法是 `grep`,`sed` 等命令,前者在加上 `-r` 参数以后可以在指定目录下文件中搜索指定的文件内容,后者再使用 `-i` 参数后,可以对文件内容进行替换。它们的基本用法在前面的章节中已经详细介绍了,这里就不再赘述。
529 |
530 | 值得强调的是,这些命令对文件的操作非常有意义。它们在某个程度上把文件系统结构给抽象了,使得对整个文件系统的操作简化为对单个文件的操作,而单个文件如果仅仅考虑文本部分,那么最终却转化成了之前的字符串操作,即上一节讨论过的内容。为了更清楚地了解文件的组织结构,文件之间的关系,在下一节将深入探讨文件系统。
531 |
532 |
533 | ## 参考资料
534 |
535 | - [从文件 I/O 看 Linux 的虚拟文件系统](http://www.ibm.com/developerworks/cn/linux/l-cn-vfs/)
536 | - [Linux 文件系统剖析](http://www.ibm.com/developerworks/cn/linux/l-linux-filesystem/index.html?ca=drs-cn)
537 | - [《Linux 核心》第九章 文件系统](http://man.chinaunix.net/tech/lyceum/linuxK/fs/filesystem.html)
538 | - [Linux Device Drivers, 3rd Edition](http://lwn.net/Kernel/LDD3/)
539 | - [技巧:Linux I/O 重定向的一些小技巧](http://www.ibm.com/developerworks/cn/linux/l-iotips/index.html)
540 | - Intel 平台下 Linux 中 ELF 文件动态链接的加载、解析及实例分析:
541 | - [part1](http://www.ibm.com/developerworks/cn/linux/l-elf/part1/index.html),
542 | - [part2](http://www.ibm.com/developerworks/cn/linux/l-elf/part2/index.html)
543 | - [Shell 脚本调试技术](http://www.ibm.com/developerworks/cn/linux/l-cn-shell-debug/index.html)
544 | - [ELF 文件格式及程序加载执行过程总汇](http://www.linuxsir.org/bbs/thread206356.html)
545 | - [Linux下 C 语言编程——文件的操作](http://fanqiang.chinaunix.net/a4/b2/20010508/113315.html)
546 | - ["Linux下 C 语言编程" 的 文件操作部分](http://www.mwjx.com/aboutfish/private/book/linux_c.txt)
547 | - [Filesystem Hierarchy Standard](http://www.pathname.com/fhs/pub/fhs-2.3.html#INTRODUCTION)
548 | - [学会恢复 Linux系统里被删除的 Ext3 文件](http://tech.ccidnet.com/art/237/20070720/1150559_1.html)
549 | - [使用mc恢复被删除文件](http://bbs.tech.ccidnet.com/read.php?tid=48372)
550 | - [linux ext3 误删除及恢复原理](http://www.linuxdiyf.com/viewarticle.php?id=30866)
551 | - [Linux压缩/解压缩方式大全](http://www.cnblogs.com/eoiioe/archive/2008/09/20/1294681.html)
552 | - [Everything is a byte](http://www.reteam.org/papers/e56.pdf)
553 |
554 |
555 | ## 后记
556 |
557 | - 考虑到文件和文件系统的重要性,将把它分成三个小节来介绍:文件、文件系统、程序与进程。在“文件”这一部分,主要介绍文件的基本属性和常规操作,在“文件系统”那部分,将深入探讨 Linux 文件系统的各个部分(包括 Linux 文件系统的结构、具体某个文件系统的大体结构分析、底层驱动的工作原理),在“程序与进程”一节将专门讨论可执行文件的相关内容(包括不同的程序类型、加载执行过程、不同进程之间的交互[命令管道和无名管道、信号通信]、对进程的控制等)
558 | - 有必要讨论清楚 目录大小 的含义,另外,最好把一些常规的文件操作全部考虑到,包括文件的读、写、执行、删除、修改、复制、压缩/解压缩等
559 | - 下午刚从上海回来,比赛结果很“糟糕”,不过到现在已经不重要了,关键是通过决赛发现了很多不足,发现了设计在系统开发中的关键角色,并且发现了上海是个美丽的城市,上交也是个美丽的大学。回来就开始整理这个因为比赛落下了两周的 Blog
560 | - 12月15日,添加文件搜索部分内容
561 |
--------------------------------------------------------------------------------
/zh/chapters/01-chapter6.markdown:
--------------------------------------------------------------------------------
1 | **关注作者公众号**:
2 |
3 |
4 |
5 |
6 | # 文件系统操作
7 |
8 | - [前言](#toc_23937_24032_1)
9 | - [文件系统在 Linux 操作系统中的位置](#toc_23937_24032_2)
10 | - [硬件管理和设备驱动](#toc_23937_24032_3)
11 | - [范例:查找设备所需的驱动文件](#toc_23937_24032_4)
12 | - [范例:查看已经加载的设备驱动](#toc_23937_24032_5)
13 | - [范例:卸载设备驱动](#toc_23937_24032_6)
14 | - [范例:挂载设备驱动](#toc_23937_24032_7)
15 | - [范例:查看设备驱动对应的设备文件](#toc_23937_24032_8)
16 | - [范例:访问设备文件](#toc_23937_24032_9)
17 | - [理解、查看磁盘分区](#toc_23937_24032_10)
18 | - [磁盘分区基本原理](#toc_23937_24032_11)
19 | - [通过分析 MBR 来理解分区原理](#toc_23937_24032_12)
20 | - [分区和文件系统的关系](#toc_23937_24032_13)
21 | - [常见分区类型](#toc_23937_24032_14)
22 | - [范例:格式化文件系统](#toc_23937_24032_15)
23 | - [分区、逻辑卷和文件系统的关系](#toc_23937_24032_16)
24 | - [文件系统的可视化结构](#toc_23937_24032_17)
25 | - [范例:挂载文件系统](#toc_23937_24032_18)
26 | - [范例:卸载某个分区](#toc_23937_24032_19)
27 | - [如何制作一个文件系统](#toc_23937_24032_20)
28 | - [范例:用 dd 创建一个固定大小的文件](#toc_23937_24032_21)
29 | - [范例:用 mkfs 格式化文件](#toc_23937_24032_22)
30 | - [范例:挂载刚创建的文件系统](#toc_23937_24032_23)
31 | - [范例:对文件系统进行读、写、删除等操作](#toc_23937_24032_24)
32 | - [如何开发自己的文件系统](#toc_23937_24032_25)
33 | - [后记](#toc_23937_24032_26)
34 |
35 |
36 |
37 | ## 前言
38 |
39 | 准备了很久,找了好多天资料,还不知道应该如何动笔写:因为担心拿捏不住,所以一方面继续查找资料,一方面思考如何来写。作为《Shell编程范例》的一部分,希望它能够很好地帮助 Shell 程序员理解如何用 Shell 命令来完成和 Linux 系统关系非常大的文件系统的各种操作,希望让 Shell 程序员中对文件系统"混沌"的状态从此消失,希望文件系统以一种更为清晰的样子呈现在眼前。
40 |
41 |
42 | ## 文件系统在 Linux 操作系统中的位置
43 |
44 | 如何来认识文件系统呢?从 Shell 程序员的角度来看,文件系统就是一个用来组织各种文件的方法。但是文件系统无法独立于硬件存储设备和操作系统而存在,因此还是有必要来弄清楚硬件存储设备、分区、操作系统、逻辑卷、文件系统等各种概念之间的联系,以便理解文件系统常规操作的一些“细节”。这个联系或许(也许会有一些问题)可以通过这样一种方式来呈现:
45 |
46 | 
47 |
48 | 从图中可以清晰地看到各个“概念”之间的关系,它们以不同层次分布,覆盖硬件设备、系统内核空间、系统用户空间。在用户空间,用户可以不管内核如何操作具体硬件设备,仅仅使用程序员设计的各种界面就可以,而普通程序员也仅仅需要利用内核提供的各种接口(System Call)或者一些C库来和内核进行交互,而无须关心具体的实现细节。不过对于操作系统开发人员,他们需要在内核空间设计特定的数据结构来管理和组织底层的硬件设备。
49 |
50 | 下面从下到上的方式(即从底层硬件开始),用工具来分析和理解图中几个重要概念。(如果有兴趣,可以先看看下面的几则资料)
51 |
52 | 参考资料:
53 |
54 | - [从文件 I/O 看 Linux 的虚拟文件系统](http://www.ibm.com/developerworks/cn/linux/l-cn-vfs/)
55 | - [Linux 文件系统剖析](http://www.ibm.com/developerworks/cn/linux/l-linux-filesystem/index.html?ca=drs-cn)
56 | - [第九章 文件系统](http://man.chinaunix.net/tech/lyceum/linuxK/fs/filesystem.html)
57 | - [Linux 逻辑盘卷管理 LVM 详解](http://unix-cd.com/vc/www/28/2007-06/1178.html)
58 |
59 |
60 | ## 硬件管理和设备驱动
61 |
62 | Linux 系统通过设备驱动管理硬件设备。如果添加了新的硬件设备,那么需要编写相应的硬件驱动来管理它。对于一些常见的硬件设备,系统已经自带了相应的驱动,编译内核时,选中它们,然后编译成内核的一部分或者以模块的方式编译。如果以模块的方式编译,那么可以在系统的 `/lib/modules/$(uname -r)`目录下找到对应的模块文件。
63 |
64 |
65 | ### 范例:查找设备所需的驱动文件
66 |
67 | 比如,可以这样找到相应的 scsi 驱动和 usb 驱动对应的模块文件:
68 |
69 | 更新系统中文件索引数据库(有点慢)
70 |
71 | $ updatedb
72 |
73 | 查找 scsi 相关的驱动
74 |
75 | $ locate scsi*.ko
76 |
77 | 查找 usb 相关的驱动
78 |
79 | $ locate usb*.ko
80 |
81 | 这些驱动以 `.ko` 为后缀,在安装系统时默认编译为了模块。实际上可以把它们编译为内核的一部分,仅仅需要在编译内核时选择为`[*]`即可。但是,很多情况下会以模块的方式编译它们,这样可以减少内核的大小,并根据需要灵活地加载和卸载它们。下面简单地演示如何卸载模块、加载模块以及查看已加载模块的状态。
82 |
83 | 可通过 `/proc` 文件系统的 `modules` 文件检查内核中已加载的各个模块的状态,也可以通过 `lsmod` 命令直接查看它们。
84 |
85 | $ cat /proc/modules
86 |
87 | 或者
88 |
89 | $ lsmod
90 |
91 |
92 | ### 范例:查看已经加载的设备驱动
93 |
94 | 查看 scsi 和 usb 相关驱动,结果各列为模块名、模块大小、被其他模块的引用情况(引用次数、引用它们的模块)
95 |
96 | $ lsmod | egrep "scsi|usb"
97 | usbhid 29536 0
98 | hid 28928 1 usbhid
99 | usbcore 138632 4 usbhid,ehci_hcd,ohci_hcd
100 | scsi_mod 147084 4 sg,sr_mod,sd_mod,libata
101 |
102 |
103 | ### 范例:卸载设备驱动
104 |
105 | 下面卸载 `usbhid` 模块看看(不要卸载scsi的驱动!因为你的系统可能就跑在上面,如果确实想玩玩,卸载前记得保存数据),通过 `rmmod` 命令就可以实现,先切换到 Root 用户:
106 |
107 | $ sudo -s
108 | # rmmod usbhid
109 |
110 | 再查看该模块的信息,已经看不到了吧
111 |
112 | $ lsmod | grep ^usbhid
113 |
114 |
115 | ### 范例:挂载设备驱动
116 |
117 | 如果有个 usb 鼠标,那么移动一下,是不是发现动不了啦?因为设备驱动都没有了,设备自然就没法用罗。不过不要紧张,既然知道原因,那么重新加载驱动就可以,下面用 `insmod` 把 `usbhid` 模块重新加载上。
118 |
119 | $ sudo -s
120 | # insmod `locate usbhid.ko`
121 |
122 | `locate usbhid.ko` 是为了找出 `usbhid.ko` 模块的路径,如果之前没有 `updatedb`,估计用它是找不到了,不过也可以直接到 `/lib/modules` 目录下用 `find` 把 `usbhid.ko` 文件找到。
123 |
124 | # insmod $(find /lib/modules -name "*usbhid.ko*" | grep `uname -r`)
125 |
126 | 现在鼠标又可以用啦,不信再动一下鼠标 :-)
127 |
128 | 到这里,硬件设备和设备驱动之间关系应该是比较清楚了。如果没有,那么继续下面的内容。
129 |
130 |
131 | ### 范例:查看设备驱动对应的设备文件
132 |
133 | Linux 设备驱动关联着相应的设备文件,而设备文件则和硬件设备一一对应。这些设备文件都统一存放在系统的 `/dev/` 目录下。
134 |
135 | 例如,scsi 设备对应`/dev/sda`,`/dev/sda1`,`/dev/sda2`... 下面查看这些设备信息。
136 |
137 | $ ls -l /dev/sda*
138 | brw-rw---- 1 root disk 8, 0 2007-12-28 22:49 /dev/sda
139 | brw-rw---- 1 root disk 8, 1 2007-12-28 22:50 /dev/sda1
140 | brw-rw---- 1 root disk 8, 3 2007-12-28 22:49 /dev/sda3
141 | brw-rw---- 1 root disk 8, 4 2007-12-28 22:49 /dev/sda4
142 | brw-rw---- 1 root disk 8, 5 2007-12-28 22:50 /dev/sda5
143 | brw-rw---- 1 root disk 8, 6 2007-12-28 22:50 /dev/sda6
144 | brw-rw---- 1 root disk 8, 7 2007-12-28 22:50 /dev/sda7
145 | brw-rw---- 1 root disk 8, 8 2007-12-28 22:50 /dev/sda8
146 |
147 | 可以看到第一列第一个字符都是 `b`,第五列都是数字 8 。 `b` 表示该文件是一个块设备文件,对应地,如果是 `c` 则表示字符设备(例如 `/dev/ttyS0),关于块设备和字符设备的区别,可以看这里:
148 |
149 | > - 字符设备:字符设备就是能够像字节流一样访问的设备,字符终端和串口就属于字符设备。
150 |
151 |
152 | > - 块设备:块设备上可以容纳文件系统。与字符设备不同,在读写时,块设备每次只能传输一个或多个完整的块。在 Linux 操作系统中,应用程序可以像访问字符设备一样读写块设备(一次读取或写入任意的字节数据)。因此,块设备和字符设备的区别仅仅是在内核中对于数据的管理不同。
153 |
154 | 数字 8 则是该硬件设备在内核中对应的设备编号,可以在内核的 `Documentation/devices.txt` 和 `/proc/devices` 文件中找到设备号分配情况。但是为什么同一个设备会对应不同的设备文件(`/dev/sda` 后面为什么还有不同的数字,而且 `ls` 结果中的第 6 列和它们对应起来)。这实际上是为了区分不同设备的不同部分。对于硬盘,这样可以处理硬盘内部的不同分区。就内核而言,它仅仅需要通过第 5 列的设备号就可以找到对应的硬件设备,但是对于驱动模块来说,它还需要知道如何处理不同的分区,于是就多了一个辅设备号,即第 6 列对应的内容。这样一个设备就有了主设备号(第 5 列)和辅设备号(第 6 列),从而方便地实现对各种硬件设备的管理。
155 |
156 | 因为设备文件和硬件是对应的,这样可以直接从 `/dev/sda` (如果是 `IDE` 的硬盘,那么对应的设备就是 `/dev/hda` 啦)设备中读出硬盘的信息,例如:
157 |
158 |
159 | ### 范例:访问设备文件
160 |
161 | 用 `dd` 命令复制出硬盘的前 512 个字节,要 Root 用户
162 |
163 | $ sudo dd if=/dev/sda of=mbr.bin bs=512 count=1
164 |
165 | 用 `file` 命令查看相应的信息
166 |
167 | $ file mbr.bin
168 | mbr.bin: x86 boot sector, LInux i386 boot LOader; partition 3: ID=0x82, starthead 254, startsector 19535040, 1959930 sectors; partition 4: ID=0x5, starthead 254, startsector 21494970, 56661255 sectors, code offset 0x48
169 |
170 | 也可以用 `od` 命令以 16 进制的形式读取并进行分析
171 |
172 | $ od -x mbr.bin
173 |
174 | `bs` 是块的大小(以字节 `bytes` 为单位),`count` 是块数
175 |
176 | 因为这些信息并不直观(而且下面会进一步深入分析),那么先来看看另外一个设备文件,将可以非常直观地演示设备文件和硬件的对应关系。还是以鼠标为例吧,下面来读取鼠标对应的设备文件的信息。
177 |
178 | $ sudo -s
179 | # cat /dev/input/mouse1 | od -x
180 |
181 | 你的鼠标驱动可能不太一样,所以设备文件可能是其他的,但是都会在 `/dev/input` 下。
182 |
183 | 移动鼠标看看,是不是发现有不同信息输出。基于这一原理,我们经常通过在一端读取设备文件 `/dev/ttyS0` 中的内容,而在另一端往设备文件 `/dev/ttyS0` 中写入内容来检查串口线是否被损坏。
184 |
185 | 到这里,对设备驱动、设备文件和硬件设备之间的关联应该是印象更深刻了。如果想深入了解设备驱动的工作原理和设备驱动的编写,那么看看下面列出的相关资料,开始设备驱动的编写历程吧。
186 |
187 | 参考资料:
188 |
189 | - [Compile linux kernel 2.6](http://www.cyberciti.biz/tips/compiling-linux-kernel-26.html)
190 | - [Linux 系统的硬件驱动程序编写原理](http://www.blue1000.com/bkhtml/2001-02/2409.htm)
191 | - [Linux 下 USB设备的原理、配置、常见问题](http://soft.zdnet.com.cn/software_zone/2007/1108/617545.shtml)
192 | - [The Linux Kernel Module Programming Guide](http://www.tldp.org/LDP/lkmpg/2.6/html/lkmpg.html)
193 | - [Linux 设备驱动开发](http://lwn.net/Kernel/LDD3/)
194 |
195 |
196 | ## 理解、查看磁盘分区
197 |
198 | 实际上内存、u 盘等都可以作为文件系统底层的“存储”设备,但是这里仅用硬盘作为实例来介绍磁盘和分区的关系。
199 |
200 | 目前 Linux 的分区依然采用第一台PC硬盘所使用的分区原理,下面逐步分析和演示这一分区原理。
201 |
202 |
203 | ### 磁盘分区基本原理
204 |
205 | 先来看看几个概念:
206 |
207 | - 设备管理和分区
208 |
209 | Linux 下,每一个存储设备对应一个系统的设备文件,对于硬盘等 `IDE` 和 `SCSI` 设备,在系统的 `/dev` 目录下可以找到对应的包含字符 `hd` 和 `sd` 的设备文件。而根据硬盘连接的主板设备接口和数据线接口的不同,在 `hd` 或者 `sd` 字符后面可以添加一个从 `a` 到 `z` 的字符,例如 `hda`,`hdb`,`hdc` 和 `sda`,`sdb`,`sdc` 等,另外为了区别同一个硬件设备的不同分区,在后面还可以添加了一个数字,例如 `hda1`,`hda2`,`hda3` 和 `sda1`,`sda2`,`sda3`,所以在 `/dev` 目录下,可以看到很多类似的设备文件。
210 |
211 | - 各分区的作用
212 |
213 | 在分区时常遇到主分区和逻辑分区的问题,这实际上是为了方便扩展分区,正如后面的逻辑卷的引入是为了更好地管理多个硬盘一样,引入主分区和逻辑分区可以方便地进行分区的管理。
214 |
215 | Linux 系统中每一个硬盘设备最多由 4 个主分区(包括扩展分区)构成。
216 |
217 | 主分区的作用是计算机用来进行启动操作系统的,因此每一个操作系统的启动程序或者称作是引导程序,都应该存放在主分区上。 Linux 规定主分区(或者扩展分区)占用分区编号中的前 4 个。所以会看到主分区对应的设备文件为 `/dev/hda1-4` 或者 `/dev/sda1-4`,而不会是 `hda5` 或者 `sda5` 。
218 |
219 | 扩展分区则是为了扩展更多的逻辑分区的,在 Linux 下,逻辑分区占用了 `hda5-16` 或者 `sda5-16` 等 12 个编号。
220 |
221 | - 分区类型
222 |
223 | 它规定了这个分区上的文件系统的类型。Linux支持诸如msdoc,vfat,ext2,ext3等诸多的文件系统类型,更多信息在下一小节进行进一步的介绍。
224 |
225 |
226 | ### 通过分析 MBR 来理解分区原理
227 |
228 | 下面通过分析硬盘的前 512 个字节(即 `MBR`)来分析和理解分区。
229 |
230 | 先来看看这张图:
231 |
232 | 
233 |
234 | 它用来描述 `MBR` 的结构。 `MBR` 包括引导部分、分区表、以及结束标记 `(55AAH),分别占用了 512 字节中 446 字节、 64 字节和 2 字节。这里仅仅关注分区表部分,即中间的 64 字节以及图中左边的部分。
235 |
236 | 由于我用的是 `SCSI` 的硬盘,下面从 `/dev/sda` 设备中把硬盘的前 512 个字节拷贝到文件 `mbr.bin` 中。
237 |
238 | $ sudo -s
239 | # dd if=/dev/sda of=mbr.bin bs=512 count=1
240 |
241 | 下面用 `file`,`od`,`fdisk` 等命令来分析这段 `MBR` 的数据,并对照上图以便加深理解。
242 |
243 | $ file mbr.bin
244 | mbr.bin: x86 boot sector, LInux i386 boot LOader; partition 3: ID=0x82, starthead 254, startsector 19535040, 1959930 sectors; partition 4: ID=0x5, starthead 254, startsector 21494970, 56661255 sectors, code offset 0x48
245 | $ od -x mbr.bin | tail -6 #仅关注中间的64字节,所以截取了结果中后6行
246 | 0000660 0000 0000 0000 0000 a666 a666 0000 0180
247 | 0000700 0001 fe83 ffff 003f 0000 1481 012a 0000
248 | 0000720 0000 0000 0000 0000 0000 0000 0000 fe00
249 | 0000740 ffff fe82 ffff 14c0 012a e7fa 001d fe00
250 | 0000760 ffff fe05 ffff fcba 0147 9507 0360 aa55
251 | $ sudo -s
252 | # fdisk -l | grep ^/ #仅分析MBR相关的部分,不分析逻辑分区部分
253 | /dev/sda1 * 1 1216 9767488+ 83 Linux
254 | /dev/sda3 1217 1338 979965 82 Linux swap / Solaris
255 | /dev/sda4 1339 4865 28330627+ 5 Extended
256 |
257 | `file` 命令的结果显示,刚拷贝的 512 字节是启动扇区,用分号分开的几个部分分别是 `bootloader`,分区 3 和分区 4 。分区 3 的类型是 82,即 `swap` 分区(可以通过 `fdisk` 命令的 `l` 命令列出相关信息),它对应 `fdisk` 的结果中 `/dev/sda3` 所在行的第 5 列,分区 3 的扇区数是 1959930,转换成字节数是 `1959930\*512` (目前,硬盘的默认扇区大小是 512 字节),而 `swap` 分区的默认块大小是 1024 字节,这样块数就是 `:`
258 |
259 | $ echo 1959930*512/1024 | bc
260 | 979965
261 |
262 | 正好是 `fdisk` 结果中 `/dev/sda3` 所在行的第四列对应的块数,同样地,可以对照 `fdisk` 和 `file` 的结果分析分区 4 。
263 |
264 | 再来看看 `od` 命令以十六进制显示的结果,同样考虑分区 3,计算一下发现,分区 3 对应的 `od` 命令的结果为:
265 |
266 | fe00 ffff fe82 ffff 14c0 012a e7fa 001d
267 |
268 | 首先是分区标记,`00H`,从上图中,看出它就不是引导分区(`80H` 标记的才是引导分区),而分区类型呢?为 `82H`,和 `file` 显示结果一致,现在再来关注一下分区大小,即 `file` 结果中的扇区数。
269 |
270 | $ echo "ibase=10;obase=16;1959930" | bc
271 | 1DE7FA
272 |
273 | 刚好对应 `e7fa 001d`,同样地考虑引导分区的结果:
274 |
275 | > 0180 0001 fe83 ffff 003f 0000 1481 012a
276 |
277 | 分区标记: `80H`,正好反应了这个分区是引导分区,随后是引导分区所在的磁盘扇区情况,010100,即 1 面 0 道 1 扇区。其他内容可以对照分析。
278 |
279 | 考虑到时间关系,更多细节请参考下面的资料或者查看看系统的相关手册。
280 |
281 | 补充:安装系统时,可以用 `fdisk`,`cfdisk` 等命令进行分区。如果要想从某个分区启动,那么需要打上 `80H` 标记,例如可通过 `cfdisk` 把某个分区设置为 `bootable` 来实现。
282 |
283 | 参考资料:
284 |
285 | - [Inside the linux boot process](http://www-128.ibm.com/developerworks/linux/library/l-linuxboot/)
286 | - [Develop your own OS: booting](http://docs.huihoo.com/gnu_linux/own_os/booting.htm)
287 | - [Redhat9 磁盘分区简介](http://blog.csdn.net/fowse/article/details/7220021)
288 | - [Linux partition HOWTO](http://www.tldp.org/HOWTO/Partition/)
289 |
290 |
291 | ## 分区和文件系统的关系
292 |
293 | 在没有引入逻辑卷之前,分区类型和文件系统类型几乎可以同等对待,设置分区类型的过程就是格式化分区,建立相应的文件系统类型的过程。
294 |
295 | 下面主要介绍如何建立分区和文件系统类型的联系,即如何格式化分区为指定的文件系统类型。
296 |
297 |
298 | ### 常见分区类型
299 |
300 | 先来看看 Linux 下文件系统的常见类型(如果要查看所有 Linux 支持的文件类型,可以用 `fdisk` 命令的 `l` 命令查看,或者通过 `man fs` 查看,也可通过 `/proc/filesystems` 查看到当前内核支持的文件系统类型)
301 |
302 | - `ext2`,`ext3`,`ext4` :这三个是 Linux 根文件系统通常采用的类型
303 | - `swap` :这个是实现 Linux 虚拟内存时采用的一种文件系统,安装时一般需要建立一个专门的分区,并格式化为 `swap` 文件系统(如果想添加更多 `swap` 分区,可以参考本节的[参考资料](http://soft.zdnet.com.cn/software_zone/2007/1010/545261.shtml),熟悉 `dd`,`mkswap`,`swapon`,`swapoff` 等命令的用法)
304 | - `proc` :这是一种比较特别的文件系统,作为内核和用户之间的一个接口存在,建立在内存中(可以通过 `cat` 命令查看 `/proc` 系统下的文件,甚至可以通过修改 `/proc/sys` 下的文件实时调整内核配置,当前前提是需要把 `proc` 文件系统挂载上: `mount -t proc proc /proc`
305 |
306 | 除了上述文件系统类型外,Linux 支持包括 `vfat`,`iso`,`xfs`,`nfs` 在内各种常见的文件系统类型,在 Linux 下,可以自由地查看和操作 Windows 等其他操作系统使用的文件系统。
307 |
308 | 那么如何建立磁盘和这些文件系统类型的关联呢?格式化。
309 |
310 | 格式化的过程实际上就是重新组织分区的过程,可通过 `mkfs` 命令来实现,当然也可以通过 `fdisk` 等命令来实现。这里仅介绍 `mkfs`,`mkfs` 可用来对一个已有的分区进行格式化,不能实现分区操作(如果要对一个磁盘进行分区和格式化,那么可以用 `fdisk`)。格式化后,相应分区上的数据就会通过某种特别的文件系统类型进行组织。
311 |
312 |
313 | ### 范例:格式化文件系统
314 |
315 | 例如:把 `/dev/sda9` 分区格式化为 `ext3` 的文件系统。
316 |
317 | $ sudo -s
318 | # mkfs -t ext3 /dev/sda9
319 |
320 | 如果要列出各个分区的文件系统类型,那么可以用 `fdisk -l` 命令。
321 |
322 | 更多信息请参考下列资料。
323 |
324 | 参考资料:
325 |
326 | - [Linux 下加载 swap 分区的步骤](http://soft.zdnet.com.cn/software_zone/2007/1010/545261.shtml)
327 | - [Linux 下 ISO 镜像文件的制作与刻录](http://www.examda.com/linux/fudao/20071212/113445321.html)
328 | - RAM 磁盘分区解释:
329 | [\[1\]](http://oldlinux.org/oldlinux/viewthread.php?tid=2677),
330 | [\[2\]](http://www.ibm.com/developerworks/cn/linux/l-initrd.html)
331 | - [高级文件系统实现者指南](http://www.ibm.com/Search/?q=%E9%AB%98%E7%BA%A7%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E5%AE%9E%E7%8E%B0%E8%80%85%E6%8C%87%E5%8D%97&v=17&en=utf&lang=en&cc=us)
332 |
333 |
334 | ## 分区、逻辑卷和文件系统的关系
335 |
336 | 上一节直接把分区格式化为某种文件系统类型,但是考虑到扩展新的存储设备的需要,开发人员在文件系统和分区之间引入了逻辑卷。考虑到时间关系,这里不再详述,请参考资料:[Linux 逻辑卷管理详解](http://unix-cd.com/vc/www/28/2007-06/1178.html)
337 |
338 |
339 | ## 文件系统的可视化结构
340 |
341 | 文件系统最终呈现出来的是一种可视化的结构,可用ls,find,tree等命令把它呈现出来。它就像一颗倒挂的“树”,在树的节点上还可以挂载新的“树”。
342 |
343 | 下面简单介绍文件系统的挂载。
344 |
345 | 一个文件系统可以通过一个设备挂载(`mount`)到某个目录下,这个目录被称为挂载点。有趣的是,在 Linux 下,一个目录本身还可以挂载到另外一个目录下,一个格式化了的文件也可以通过一个特殊的设备 `/dev/loop` 进行挂载(如 `iso` 文件)。另外,就文件系统而言,Linux 不仅支持本地文件系统,还支持远程文件系统(如 `nfs`)。
346 |
347 |
348 | ### 范例:挂载文件系统
349 |
350 | 下面简单介绍文件系统挂载的几个实例。
351 |
352 | - 根文件系统的挂载
353 |
354 | 挂载需要 Root 权限,例如,挂载系统根文件系统 `/dev/sda1` 到 `/mnt`
355 |
356 | $ sudo -s
357 | # mount -t ext3 /dev/sda1 /mnt/
358 |
359 | 查看 `/dev/sda1` 的挂载情况,可以看到,一个设备可以多次挂载
360 |
361 | $ mount | grep sda1
362 | /dev/sda1 on / type ext3 (rw,errors=remount-ro)
363 | /dev/sda1 on /mnt type ext3 (rw)
364 |
365 | 对于一个已经挂载的文件系统,为支持不同属性可以重新挂载
366 |
367 | $ mount -n -o remount, rw /
368 |
369 | - 挂载一个新增设备
370 |
371 | 如果内核已经支持 USB 接口,那么插入 u 盘时,可以通过 `dmesg` 命令查看对应的设备号,并挂载它。
372 |
373 | 查看 `dmesg` 结果中的最后几行内容,找到类似 `/dev/sdN` 的信息,找出 u 盘对应的设备号
374 |
375 | $ dmesg
376 |
377 | 这里假设 u 盘是 `vfat` 格式,以便在一些打印店里的 Windows 上也可使用
378 |
379 | # mount -t vfat /dev/sdN /path/to/mountpoint_directory
380 |
381 | - 挂载一个 iso 文件或者是光盘
382 |
383 | 对于一些iso文件或者是 iso 格式的光盘,同样可以通过 `mount` 命令挂载。
384 |
385 | 对于 iso 文件:
386 |
387 | # mount -t iso9660 /path/to/isofile /path/to/mountpoint_directory
388 |
389 | 对于光盘:
390 |
391 | # mount -t iso9660 /dev/cdrom /path/to/mountpoint_directory
392 |
393 | - 挂载一个远程文件系统
394 |
395 |
396 |
397 | # mount -t nfs remote_ip:/path/to/share_directory /path/to/local_directory
398 |
399 | - 挂载一个 proc 文件系统
400 |
401 |
402 |
403 | # mount -t proc proc /proc
404 |
405 | `proc` 文件系统组织在内存中,但是可以把它挂载到某个目录下。通常把它挂载在 `/proc` 目录下,以便一些系统管理和配置工具使用它。例如 `top` 命令用它分析内存的使用情况(读取 `/proc/meminfo` 和 `/proc/stat` 等文件中的内容); `lsmod` 命令通过它获取内核模块的状态(读取 `/proc/modules`); `netstat` 命令通过它获取网络的状态(读取 `/proc/net/dev` 等文件)。当然,也可以编写相关工具。除此之外,通过调整 `/proc/sys` 目录下的文件,可以动态地调整系统配置,比如往 `/proc/sys/net/ipv4/ip_forward` 文件中写入数字 1 就可以让内核支持数据包转发。(更多信息请参考 `proc` 的帮助,`man`
406 | `proc`)
407 |
408 | - 挂载一个目录
409 |
410 |
411 |
412 | $ mount --bind /path/to/needtomount_directory /path/to/mountpoint_directory
413 |
414 | 这个非常有意思,比如可以把某个目录挂载到 ftp 服务的根目录下,而无须把内容复制过去,就可以把相应目录中的资源提供给别人共享。
415 |
416 |
417 | ### 范例:卸载某个分区
418 |
419 | 以上都只提到了挂载,那怎么卸载呢?用 `umount` 命令跟上挂载的源地址或者挂载点(设备,文件,远程目录等)就可以。例如:
420 |
421 | $ umount /path/to/mountpoint_directory
422 |
423 | 或者
424 |
425 | $ umount /path/to/mount_source
426 |
427 | 如果想管理大量的或者经常性的挂载服务,那么每次手动挂载是很糟糕的事情。这时就可利用 `mount` 的配置文件 `/etc/fstab`,把 `mount` 对应的参数写到 `/etc/fstab` 文件对应的列中即可实现批量挂载( `mount -a` )和卸载( `umount -a` )。 `/etc/fstab` 中各列分别为文件系统、挂载点、类型、相关选项。更多信息可参考 `fstab` 的帮助( `man fstab` )。
428 |
429 | 参考资料:
430 |
431 | - [Linux 硬盘分区以及其挂载原理](http://www.xxlinux.com/linux/article/accidence/technique/20070521/8493.html)
432 | - [从文件 I/O 看 Linux 的虚拟文件系统](http://www.ibm.com/developerworks/cn/linux/l-cn-vfs/)
433 | - [源码分析:静态分析 C 程序函数调用关系图](http://www.tinylab.org/callgraph-draw-the-calltree-of-c-functions/)
434 |
435 |
436 | ## 如何制作一个文件系统
437 |
438 | Linux 文件系统下有一些最基本的目录,不同的目录下存放着不同作用的各类文件。最基本的目录有 `/etc`,`/lib`,`/dev`,`/bin` 等,它们分别存放着系统配置文件,库文件,设备文件和可执行程序。这些目录一般情况下是必须的,在做嵌入式开发时,需要手动或者是用 `busybox` 等工具来创建这样一个基本的文件系统。这里仅制作一个非常简单的文件系统,并对该文件系统进行各种常规操作,以便加深对文件系统的理解。
439 |
440 |
441 | ### 范例:用 dd 创建一个固定大小的文件
442 |
443 | 还记得 `dd` 命令么?就用它来产生一个固定大小的文件,这个为 `1M(1024\*1024 bytes)` 的文件
444 |
445 | $ dd if=/dev/zero of=minifs bs=1024 count=1024
446 |
447 | 查看文件类型,这里的 `minifs` 是一个充满 `\\0` 的文件,没有任何特定的数据结构
448 |
449 | $ file minifs
450 | minifs: data
451 |
452 | 说明: `/dev/zero` 是一个非常特殊的设备,如果读取它,可以获取任意多个 `\\0` 。
453 |
454 | 接着把该文件格式化为某个指定文件类型的文件系统。(是不是觉得不可思议,文件也可以格式化?是的,不光是设备可以,文件也可以以某种文件系统类型进行组织,但是需要注意的是,某些文件系统(如 `ext3`)要求被格式化的目标最少有 `64M` 的空间)。
455 |
456 |
457 | ### 范例:用 mkfs 格式化文件
458 |
459 | $ mkfs.ext2 minifs
460 |
461 | 查看此时的文件类型,这时文件 `minifs` 就以 `ext2` 文件系统的格式组织了
462 |
463 | $ file minifs
464 | minifs: Linux rev 1.0 ext2 filesystem data
465 |
466 |
467 | ### 范例:挂载刚创建的文件系统
468 |
469 | 因为该文件以文件系统的类型组织了,那么可以用 `mount` 命令挂载并使用它。
470 |
471 | 请切换到 `root` 用户挂载它,并通过 `-o loop` 选项把它关联到一个特殊设备 `/dev/loop`
472 |
473 | $ sudo -s
474 | # mount minifs /mnt/ -o loop
475 |
476 | 查看该文件系统信息,仅可以看到一个目录文件 `lost+found`
477 |
478 | $ ls /mnt/
479 | lost+found
480 |
481 |
482 | ### 范例:对文件系统进行读、写、删除等操作
483 |
484 | 在该文件系统下进行各种常规操作,包括读、写、删除等。(每次操作前先把 `minifs` 文件保存一份,以便比较,结合相关资料就可以深入地分析各种操作对文件系统的改变情况,从而深入理解文件系统作为一种组织数据的方式的实现原理等)
485 |
486 | $ cp minifs minifs.bak
487 | $ cd /mnt
488 | $ touch hello
489 | $ cd -
490 | $ cp minifs minifs-touch.bak
491 | $ od -x minifs.bak > orig.od
492 | $ od -x minifs-touch.bak > touch.od
493 |
494 | 创建一个文件后,比较此时文件系统和之前文件系统的异同
495 |
496 | $ diff orig.od touch.od
497 | diff orig.od touch.od
498 | 61,63c61,64
499 | < 0060020 000c 0202 2e2e 0000 000b 0000 03e8 020a
500 | < 0060040 6f6c 7473 662b 756f 646e 0000 0000 0000
501 | < 0060060 0000 0000 0000 0000 0000 0000 0000 0000
502 | ---
503 | > 0060020 000c 0202 2e2e 0000 000b 0000 0014 020a
504 | > 0060040 6f6c 7473 662b 756f 646e 0000 000c 0000
505 | > 0060060 03d4 0105 6568 6c6c 006f 0000 0000 0000
506 | > 0060100 0000 0000 0000 0000 0000 0000 0000 0000
507 |
508 | 通过比较发现:添加文件,文件系统的相应位置发生了明显的变化
509 |
510 | $ echo "hello, world" > /mnt/hello
511 |
512 | 执行 `sync` 命令,确保缓存中的数据已经写入磁盘(还记得本节图 1 的 `buffer cache` 吧,这里就是把 `cache` 中的数据写到磁盘中)
513 |
514 | $ sync
515 | $ cp minifs minifs-echo.bak
516 | $ od -x minifs-echo.bak > echo.od
517 |
518 | 写入文件内容后,比较文件系统和之前的异同
519 |
520 | $ diff touch.od echo.od
521 |
522 | 查看文件系统中的字符串
523 |
524 | $ strings minifs
525 | lost+found
526 | hello
527 | hello, world
528 |
529 | 删除 `hello` 文件,查看文件系统变化
530 |
531 | $ rm /mnt/hello
532 | $ cp minifs minifs-rm.bak
533 | $ od -x minifs-rm.bak > rm.od
534 | $ diff echo.od rm.od
535 |
536 | 通过查看文件系统的字符串发现:删除文件时并没有覆盖文件内容,所以从理论上说内容此时还是可恢复的
537 |
538 | $ strings minifs
539 | lost+found
540 | hello
541 | hello, world
542 |
543 | 上面仅仅演示了一些分析文件系统的常用工具,并分析了几个常规的操作,如果想非常深入地理解文件系统的实现原理,请熟悉使用上述工具并阅读相关资料。
544 |
545 | 参考资料:
546 |
547 | - [Build a mini filesystem in linux from scratch](http://202.201.1.130:8080/docs/summer_school_2007/team3/doc/build_a_mini_filesystem_from_scratch)
548 | - [Build a mini filesystem in linux with BusyBox](http://202.201.1.130:8080/docs/summer_school_2007/team3/doc/build_a_mini_filesystem_with_busybox)
549 | - [ext2 文件系统](http://man.chinaunix.net/tech/lyceum/linuxK/fs/filesystem.html)
550 |
551 |
552 | ## 如何开发自己的文件系统
553 |
554 | 随着 `fuse` 的出现,在用户空间开发文件系统成为可能,如果想开发自己的文件系统,那么推荐阅读:[使用 fuse 开发自己的文件系统](http://www.ibm.com/developerworks/cn/linux/l-fuse/)。
555 |
556 |
557 | ## 后记
558 |
559 | - 2007 年 12 月 22 日,收集了很多资料,写了整体的框架
560 | - 2007 年 12 月 28 日下午,完成初稿,考虑到时间关系,很多细节也没有进一步分析,另外有些部分可能存在理解上的问题,欢迎批评指正
561 | - 2007 年 12 月 28 日晚,修改部分资料,并正式公开该篇文档
562 | - 29 号,添加设备驱动和硬件设备一小节
563 |
--------------------------------------------------------------------------------
/zh/chapters/01-chapter7.markdown:
--------------------------------------------------------------------------------
1 | **关注作者公众号**:
2 |
3 |
4 |
5 |
6 | # 进程操作
7 |
8 | - [前言](#toc_10579_14683_1)
9 | - [什么是程序,什么又是进程](#toc_10579_14683_2)
10 | - [进程的创建](#toc_10579_14683_3)
11 | - [范例:让程序在后台运行](#toc_10579_14683_4)
12 | - [范例:查看进程 ID](#toc_10579_14683_5)
13 | - [范例:查看进程的内存映像](#toc_10579_14683_6)
14 | - [查看进程的属性和状态](#toc_10579_14683_7)
15 | - [范例:通过 ps 命令查看进程属性](#toc_10579_14683_8)
16 | - [范例:通过 pstree 查看进程亲缘关系](#toc_10579_14683_9)
17 | - [范例:用 top 动态查看进程信息](#toc_10579_14683_10)
18 | - [范例:确保特定程序只有一个副本在运行](#toc_10579_14683_11)
19 | - [调整进程的优先级](#toc_10579_14683_12)
20 | - [范例:获取进程优先级](#toc_10579_14683_13)
21 | - [范例:调整进程的优先级](#toc_10579_14683_14)
22 | - [结束进程](#toc_10579_14683_15)
23 | - [范例:结束进程](#toc_10579_14683_16)
24 | - [范例:暂停某个进程](#toc_10579_14683_17)
25 | - [范例:查看进程退出状态](#toc_10579_14683_18)
26 | - [进程通信](#toc_10579_14683_19)
27 | - [范例:无名管道(pipe)](#toc_10579_14683_20)
28 | - [范例:有名管道(named pipe)](#toc_10579_14683_21)
29 | - [范例:信号(Signal)](#toc_10579_14683_22)
30 | - [作业和作业控制](#toc_10579_14683_23)
31 | - [范例:创建后台进程,获取进程的作业号和进程号](#toc_10579_14683_24)
32 | - [范例:把作业调到前台并暂停](#toc_10579_14683_25)
33 | - [范例:查看当前作业情况](#toc_10579_14683_26)
34 | - [范例:启动停止的进程并运行在后台](#toc_10579_14683_27)
35 | - [参考资料](#toc_10579_14683_28)
36 |
37 |
38 |
39 | ## 前言
40 |
41 | 进程作为程序真正发挥作用时的“形态”,我们有必要对它的一些相关操作非常熟悉,这一节主要描述进程相关的概念和操作,将介绍包括程序、进程、作业等基本概念以及进程状态查询、进程通信等相关的操作。
42 |
43 |
44 | ## 什么是程序,什么又是进程
45 |
46 | 程序是指令的集合,而进程则是程序执行的基本单元。为了让程序完成它的工作,必须让程序运行起来成为进程,进而利用处理器资源、内存资源,进行各种 `I/O` 操作,从而完成某项特定工作。
47 |
48 | 从这个意思上说,程序是静态的,而进程则是动态的。
49 |
50 | 进程有区别于程序的地方还有:进程除了包含程序文件中的指令数据以外,还需要在内核中有一个数据结构用以存放特定进程的相关属性,以便内核更好地管理和调度进程,从而完成多进程协作的任务。因此,从这个意义上可以说“高于”程序,超出了程序指令本身。
51 |
52 | 如果进行过多进程程序的开发,又会发现,一个程序可能创建多个进程,通过多个进程的交互完成任务。在 Linux 下,多进程的创建通常是通过 `fork` 系统调用来实现。从这个意义上来说程序则”包含”了进程。
53 |
54 | 另外一个需要明确的是,程序可以由多种不同程序语言描述,包括 C 语言程序、汇编语言程序和最后编译产生的机器指令等。
55 |
56 | 下面简单讨论 Linux 下面如何通过 Shell 进行进程的相关操作。
57 |
58 |
59 | ## 进程的创建
60 |
61 | 通常在命令行键入某个程序文件名以后,一个进程就被创建了。例如,
62 |
63 |
64 | ### 范例:让程序在后台运行
65 |
66 | $ sleep 100 &
67 | [1] 9298
68 |
69 |
70 | ### 范例:查看进程 ID
71 |
72 | 用`pidof`可以查看指定程序名的进程ID:
73 |
74 | $ pidof sleep
75 | 9298
76 |
77 |
78 | ### 范例:查看进程的内存映像
79 |
80 | $ cat /proc/9298/maps
81 | 08048000-0804b000 r-xp 00000000 08:01 977399 /bin/sleep
82 | 0804b000-0804c000 rw-p 00003000 08:01 977399 /bin/sleep
83 | 0804c000-0806d000 rw-p 0804c000 00:00 0 [heap]
84 | b7c8b000-b7cca000 r--p 00000000 08:01 443354
85 | ...
86 | bfbd8000-bfbed000 rw-p bfbd8000 00:00 0 [stack]
87 | ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]
88 |
89 | 程序被执行后,就被加载到内存中,成为了一个进程。上面显示了该进程的内存映像(虚拟内存),包括程序指令、数据,以及一些用于存放程序命令行参数、环境变量的栈空间,用于动态内存申请的堆空间都被分配好。
90 |
91 | 关于程序在命令行执行过程的细节,请参考[《Linux 命令行下程序执行的一刹那》][100]。
92 |
93 | [100]: 02-chapter3.markdown
94 |
95 | 实际上,创建一个进程,也就是说让程序运行,还有其他的办法,比如,通过一些配置让系统启动时自动启动程序(具体参考 `man init`),或者是通过配置 `crond` (或者 `at`)让它定时启动程序。除此之外,还有一个方式,那就是编写 Shell 脚本,把程序写入一个脚本文件,当执行脚本文件时,文件中的程序将被执行而成为进程。这些方式的细节就不介绍,下面了解如何查看进程的属性。
96 |
97 | 需要补充一点的是:在命令行下执行程序,可以通过 `ulimit` 内置命令来设置进程可以利用的资源,比如进程可以打开的最大文件描述符个数,最大的栈空间,虚拟内存空间等。具体用法见 `help ulimit` 。
98 |
99 |
100 | ## 查看进程的属性和状态
101 |
102 | 可以通过 `ps` 命令查看进程相关属性和状态,这些信息包括进程所属用户,进程对应的程序,进程对 `cpu` 和内存的使用情况等信息。熟悉如何查看它们有助于进行相关的统计分析等操作。
103 |
104 |
105 | ### 范例:通过 ps 命令查看进程属性
106 |
107 | 查看系统当前所有进程的属性:
108 |
109 | $ ps -ef
110 |
111 | 查看命令中包含某字符的程序对应的进程,进程 `ID` 是 1 。 `TTY` 为?表示和终端没有关联:
112 |
113 | $ ps -C init
114 | PID TTY TIME CMD
115 | 1 ? 00:00:01 init
116 |
117 | 选择某个特定用户启动的进程:
118 |
119 | $ ps -U falcon
120 |
121 | 按照指定格式输出指定内容,下面输出命令名和 `cpu` 使用率:
122 |
123 | $ ps -e -o "%C %c"
124 |
125 | 打印 `cpu` 使用率最高的前 4 个程序:
126 |
127 | $ ps -e -o "%C %c" | sort -u -k1 -r | head -5
128 | 7.5 firefox-bin
129 | 1.1 Xorg
130 | 0.8 scim-panel-gtk
131 | 0.2 scim-bridge
132 |
133 | 获取使用虚拟内存最大的 5 个进程:
134 |
135 | $ ps -e -o "%z %c" | sort -n -k1 -r | head -5
136 | 349588 firefox-bin
137 | 96612 xfce4-terminal
138 | 88840 xfdesktop
139 | 76332 gedit
140 | 58920 scim-panel-gtk
141 |
142 |
143 | ### 范例:通过 pstree 查看进程亲缘关系
144 |
145 | 系统所有进程之间都有“亲缘”关系,可以通过 `pstree` 查看这种关系:
146 |
147 | $ pstree
148 |
149 | 上面会打印系统进程调用树,可以非常清楚地看到当前系统中所有活动进程之间的调用关系。
150 |
151 |
152 | ### 范例:用top动态查看进程信息
153 |
154 | $ top
155 |
156 | 该命令最大特点是可以动态地查看进程信息,当然,它还提供了一些其他的参数,比如 `-S` 可以按照累计执行时间的大小排序查看,也可以通过 `-u` 查看指定用户启动的进程等。
157 |
158 | 补充: `top` 命令支持交互式,比如它支持 `u` 命令显示用户的所有进程,支持通过 `k` 命令杀掉某个进程;如果使用 `-n 1` 选项可以启用批处理模式,具体用法为:
159 |
160 | $ top -n 1 -b
161 |
162 |
163 | ### 范例:确保特定程序只有一个副本在运行
164 |
165 | 下面来讨论一个有趣的问题:如何让一个程序在同一时间只有一个在运行。
166 |
167 | 这意味着当一个程序正在被执行时,它将不能再被启动。那该怎么做呢?
168 |
169 | 假如一份相同的程序被复制成了很多份,并且具有不同的文件名被放在不同的位置,这个将比较糟糕,所以考虑最简单的情况,那就是这份程序在整个系统上是唯一的,而且名字也是唯一的。这样的话,有哪些办法来回答上面的问题呢?
170 |
171 | 总的机理是:在程序开头检查自己有没有执行,如果执行了则停止否则继续执行后续代码。
172 |
173 | 策略则是多样的,由于前面的假设已经保证程序文件名和代码的唯一性,所以通过 `ps` 命令找出当前所有进程对应的程序名,逐个与自己的程序名比较,如果已经有,那么说明自己已经运行了。
174 |
175 | ps -e -o "%c" | tr -d " " | grep -q ^init$ #查看当前程序是否执行
176 | [ $? -eq 0 ] && exit #如果在,那么退出, $?表示上一条指令是否执行成功
177 |
178 | 每次运行时先在指定位置检查是否存在一个保存自己进程 `ID` 的文件,如果不存在,那么继续执行,如果存在,那么查看该进程 `ID` 是否正在运行,如果在,那么退出,否则往该文件重新写入新的进程 `ID`,并继续。
179 |
180 | pidfile=/tmp/$0".pid"
181 | if [ -f $pidfile ]; then
182 | OLDPID=$(cat $pidfile)
183 | ps -e -o "%p" | tr -d " " | grep -q "^$OLDPID$"
184 | [ $? -eq 0 ] && exit
185 | fi
186 |
187 | echo $$ > $pidfile
188 |
189 | #... 代码主体
190 |
191 | #设置信号0的动作,当程序退出时触发该信号从而删除掉临时文件
192 | trap "rm $pidfile" 0
193 |
194 | 更多实现策略自己尽情发挥吧!
195 |
196 |
197 | ## 调整进程的优先级
198 |
199 | 在保证每个进程都能够顺利执行外,为了让某些任务优先完成,那么系统在进行进程调度时就会采用一定的调度办法,比如常见的有按照优先级的时间片轮转的调度算法。这种情况下,可以通过 `renice` 调整正在运行的程序的优先级,例如:`
200 |
201 |
202 | ### 范例:获取进程优先级
203 |
204 | $ ps -e -o "%p %c %n" | grep xfs
205 | 5089 xfs 0
206 |
207 |
208 | ### 范例:调整进程的优先级
209 |
210 | $ renice 1 -p 5089
211 | renice: 5089: setpriority: Operation not permitted
212 | $ sudo renice 1 -p 5089 #需要权限才行
213 | [sudo] password for falcon:
214 | 5089: old priority 0, new priority 1
215 | $ ps -e -o "%p %c %n" | grep xfs #再看看,优先级已经被调整过来了
216 | 5089 xfs 1
217 |
218 |
219 | ## 结束进程
220 |
221 | 既然可以通过命令行执行程序,创建进程,那么也有办法结束它。可以通过 `kill` 命令给用户自己启动的进程发送某个信号让进程终止,当然“万能”的 `root` 几乎可以 `kill` 所有进程(除了 `init` 之外)。例如,
222 |
223 |
224 | ### 范例:结束进程
225 |
226 | $ sleep 50 & #启动一个进程
227 | [1] 11347
228 | $ kill 11347
229 |
230 | `kill` 命令默认会发送终止信号( `SIGTERM` )给程序,让程序退出,但是 `kill` 还可以发送其他信号,这些信号的定义可以通过 `man 7 signal` 查看到,也可以通过 `kill -l` 列出来。
231 |
232 | $ man 7 signal
233 | $ kill -l
234 | 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
235 | 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
236 | 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
237 | 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT
238 | 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
239 | 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU
240 | 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH
241 | 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN
242 | 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4
243 | 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
244 | 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12
245 | 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14
246 | 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10
247 | 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6
248 | 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
249 | 63) SIGRTMAX-1 64) SIGRTMAX
250 |
251 |
252 | ### 范例:暂停某个进程
253 |
254 | 例如,用 `kill` 命令发送 `SIGSTOP` 信号给某个程序,让它暂停,然后发送 `SIGCONT` 信号让它继续运行。
255 |
256 | $ sleep 50 &
257 | [1] 11441
258 | $ jobs
259 | [1]+ Running sleep 50 &
260 | $ kill -s SIGSTOP 11441 #这个等同于我们对一个前台进程执行CTRL+Z操作
261 | $ jobs
262 | [1]+ Stopped sleep 50
263 | $ kill -s SIGCONT 11441 #这个等同于之前我们使用bg %1操作让一个后台进程运行起来
264 | $ jobs
265 | [1]+ Running sleep 50 &
266 | $ kill %1 #在当前会话(session)下,也可以通过作业号控制进程
267 | $ jobs
268 | [1]+ Terminated sleep 50
269 |
270 | 可见 `kill` 命令提供了非常好的功能,不过它只能根据进程的 `ID` 或者作业来控制进程,而 `pkill` 和 `killall` 提供了更多选择,它们扩展了通过程序名甚至是进程的用户名来控制进程的方法。更多用法请参考它们的手册。
271 |
272 |
273 | ### 范例:查看进程退出状态
274 |
275 | 当程序退出后,如何判断这个程序是正常退出还是异常退出呢?还记得 Linux 下,那个经典 `hello world` 程序吗?在代码的最后总是有条 `return 0` 语句。这个 `return 0` 实际上是让程序员来检查进程是否正常退出的。如果进程返回了一个其他的数值,那么可以肯定地说这个进程异常退出了,因为它都没有执行到 `return 0` 这条语句就退出了。
276 |
277 | 那怎么检查进程退出的状态,即那个返回的数值呢?
278 |
279 | 在 `Shell` 中,可以检查这个特殊的变量 `$?`,它存放了上一条命令执行后的退出状态。
280 |
281 | $ test1
282 | bash: test1: command not found
283 | $ echo $?
284 | 127
285 | $ cat ./test.c | grep hello
286 | $ echo $?
287 | 1
288 | $ cat ./test.c | grep hi
289 | printf("hi, myself!\n");
290 | $ echo $?
291 | 0
292 |
293 | 貌似返回 0 成为了一个潜规则,虽然没有标准明确规定,不过当程序正常返回时,总是可以从 `$?` 中检测到 0,但是异常时,总是检测到一个非 0 值。这就告诉我们在程序的最后最好是跟上一个 `exit 0` 以便任何人都可以通过检测 `$?` 确定程序是否正常结束。如果有一天,有人偶尔用到你的程序,试图检查它的退出状态,而你却在程序的末尾莫名地返回了一个 `-1` 或者 1,那么他将会很苦恼,会怀疑他自己编写的程序到底哪个地方出了问题,检查半天却不知所措,因为他太信任你了,竟然从头至尾都没有怀疑你的编程习惯可能会与众不同!
294 |
295 |
296 | ## 进程通信
297 |
298 | 为便于设计和实现,通常一个大型的任务都被划分成较小的模块。不同模块之间启动后成为进程,它们之间如何通信以便交互数据,协同工作呢?在《UNIX 环境高级编程》一书中提到很多方法,诸如管道(无名管道和有名管道)、信号(`signal`)、报文(`Message`)队列(消息队列)、共享内存(`mmap/munmap`)、信号量(`semaphore`,主要是同步用,进程之间,进程的不同线程之间)、套接口(`Socket`,支持不同机器之间的进程通信)等,而在 Shell 中,通常直接用到的就有管道和信号等。下面主要介绍管道和信号机制在 Shell 编程时的一些用法。
299 |
300 |
301 | ### 范例:无名管道(pipe)
302 |
303 | 在 Linux 下,可以通过 `|` 连接两个程序,这样就可以用它来连接后一个程序的输入和前一个程序的输出,因此被形象地叫做个管道。在 C 语言中,创建无名管道非常简单方便,用 `pipe` 函数,传入一个具有两个元素的 `int` 型的数组就可以。这个数组实际上保存的是两个文件描述符,父进程往第一个文件描述符里头写入东西后,子进程可以从第一个文件描述符中读出来。
304 |
305 | 如果用多了命令行,这个管子 `|` 应该会经常用。比如上面有个演示把 `ps` 命令的输出作为 `grep` 命令的输入:
306 |
307 | $ ps -ef | grep init
308 |
309 | 也许会觉得这个“管子”好有魔法,竟然真地能够链接两个程序的输入和输出,它们到底是怎么实现的呢?实际上当输入这样一组命令时,当前 Shell 会进行适当的解析,把前面一个进程的输出关联到管道的输出文件描述符,把后面一个进程的输入关联到管道的输入文件描述符,这个关联过程通过输入输出重定向函数 `dup` (或者 `fcntl` )来实现。
310 |
311 |
312 | ### 范例:有名管道(named pipe)
313 |
314 | 有名管道实际上是一个文件(无名管道也像一个文件,虽然关系到两个文件描述符,不过只能一边读另外一边写),不过这个文件比较特别,操作时要满足先进先出,而且,如果试图读一个没有内容的有名管道,那么就会被阻塞,同样地,如果试图往一个有名管道里写东西,而当前没有程序试图读它,也会被阻塞。下面看看效果。
315 |
316 | $ mkfifo fifo_test #通过mkfifo命令创建一个有名管道
317 | $ echo "fewfefe" > fifo_test
318 | #试图往fifo_test文件中写入内容,但是被阻塞,要另开一个终端继续下面的操作
319 | $ cat fifo_test #另开一个终端,记得,另开一个。试图读出fifo_test的内容
320 | fewfefe
321 |
322 | 这里的 `echo` 和 `cat` 是两个不同的程序,在这种情况下,通过 `echo` 和 `cat` 启动的两个进程之间并没有父子关系。不过它们依然可以通过有名管道通信。
323 |
324 | 这样一种通信方式非常适合某些特定情况:例如有这样一个架构,这个架构由两个应用程序构成,其中一个通过循环不断读取 `fifo_test` 中的内容,以便判断,它下一步要做什么。如果这个管道没有内容,那么它就会被阻塞在那里,而不会因死循环而耗费资源,另外一个则作为一个控制程序不断地往 `fifo_test` 中写入一些控制信息,以便告诉之前的那个程序该做什么。下面写一个非常简单的例子。可以设计一些控制码,然后控制程序不断地往 `fifo_test` 里头写入,然后应用程序根据这些控制码完成不同的动作。当然,也可以往 `fifo_test` 传入除控制码外的其他数据。
325 |
326 | - 应用程序的代码
327 |
328 | $ cat app.sh
329 | #!/bin/bash
330 |
331 | FIFO=fifo_test
332 | while :;
333 | do
334 | CI=`cat $FIFO` #CI --> Control Info
335 | case $CI in
336 | 0) echo "The CONTROL number is ZERO, do something ..."
337 | ;;
338 | 1) echo "The CONTROL number is ONE, do something ..."
339 | ;;
340 | *) echo "The CONTROL number not recognized, do something else..."
341 | ;;
342 | esac
343 | done
344 |
345 | - 控制程序的代码
346 |
347 | $ cat control.sh
348 | #!/bin/bash
349 |
350 | FIFO=fifo_test
351 | CI=$1
352 |
353 | [ -z "$CI" ] && echo "the control info should not be empty" && exit
354 |
355 | echo $CI > $FIFO
356 |
357 | - 一个程序通过管道控制另外一个程序的工作
358 |
359 | $ chmod +x app.sh control.sh #修改这两个程序的可执行权限,以便用户可以执行它们
360 | $ ./app.sh #在一个终端启动这个应用程序,在通过./control.sh发送控制码以后查看输出
361 | The CONTROL number is ONE, do something ... #发送1以后
362 | The CONTROL number is ZERO, do something ... #发送0以后
363 | The CONTROL number not recognized, do something else... #发送一个未知的控制码以后
364 | $ ./control.sh 1 #在另外一个终端,发送控制信息,控制应用程序的工作
365 | $ ./control.sh 0
366 | $ ./control.sh 4343
367 |
368 | 这样一种应用架构非常适合本地的多程序任务设计,如果结合 `web cgi`,那么也将适合远程控制的要求。引入 `web cgi` 的唯一改变是,要把控制程序 `./control.sh` 放到 `web` 的 `cgi` 目录下,并对它作一些修改,以使它符合 `CGI` 的规范,这些规范包括文档输出格式的表示(在文件开头需要输出 `content-tpye: text/html` 以及一个空白行)和输入参数的获取 `(web` 输入参数都存放在 `QUERY_STRING` 环境变量里头)。因此一个非常简单的 `CGI` 控制程序可以写成这样:
369 |
370 | #!/bin/bash
371 |
372 | FIFO=./fifo_test
373 | CI=$QUERY_STRING
374 |
375 | [ -z "$CI" ] && echo "the control info should not be empty" && exit
376 |
377 | echo -e "content-type: text/html\n\n"
378 | echo $CI > $FIFO
379 |
380 | 在实际使用时,请确保 `control.sh` 能够访问到 `fifo_test` 管道,并且有写权限,以便通过浏览器控制 `app.sh` :
381 |
382 | http://ipaddress\_or\_dns/cgi-bin/control.sh?0
383 |
384 | 问号 `?` 后面的内容即 `QUERY_STRING`,类似之前的 `$1` 。
385 |
386 | 这样一种应用对于远程控制,特别是嵌入式系统的远程控制很有实际意义。在去年的暑期课程上,我们就通过这样一种方式来实现马达的远程控制。首先,实现了一个简单的应用程序以便控制马达的转动,包括转速,方向等的控制。为了实现远程控制,我们设计了一些控制码,以便控制马达转动相关的不同属性。
387 |
388 | 在 C 语言中,如果要使用有名管道,和 Shell 类似,只不过在读写数据时用 `read`,`write` 调用,在创建 `fifo` 时用 `mkfifo` 函数调用。
389 |
390 |
391 | ### 范例:信号(Signal)
392 |
393 | 信号是软件中断,Linux 用户可以通过 `kill` 命令给某个进程发送一个特定的信号,也可以通过键盘发送一些信号,比如 `CTRL+C` 可能触发 `SGIINT` 信号,而 `CTRL+\` 可能触发 `SGIQUIT` 信号等,除此之外,内核在某些情况下也会给进程发送信号,比如在访问内存越界时产生 `SGISEGV` 信号,当然,进程本身也可以通过 `kill`,`raise` 等函数给自己发送信号。对于 Linux 下支持的信号类型,大家可以通过 `man 7 signal` 或者 `kill -l` 查看到相关列表和说明。
394 |
395 | 对于有些信号,进程会有默认的响应动作,而有些信号,进程可能直接会忽略,当然,用户还可以对某些信号设定专门的处理函数。在 Shell 中,可以通过 `trap` 命令(Shell 内置命令)来设定响应某个信号的动作(某个命令或者定义的某个函数),而在 C 语言中可以通过 `signal` 调用注册某个信号的处理函数。这里仅仅演示 `trap` 命令的用法。
396 |
397 | $ function signal_handler { echo "hello, world."; } #定义signal_handler函数
398 | $ trap signal_handler SIGINT #执行该命令设定:收到SIGINT信号时打印hello, world
399 | $ hello, world #按下CTRL+C,可以看到屏幕上输出了hello, world字符串
400 |
401 | 类似地,如果设定信号 0 的响应动作,那么就可以用 `trap` 来模拟 C 语言程序中的 `atexit` 程序终止函数的登记,即通过 `trap signal_handler SIGQUIT` 设定的 `signal_handler` 函数将在程序退出时执行。信号 0 是一个特别的信号,在 `POSIX.1` 中把信号编号 0 定义为空信号,这常被用来确定一个特定进程是否仍旧存在。当一个程序退出时会触发该信号。
402 |
403 | $ cat sigexit.sh
404 | #!/bin/bash
405 |
406 | function signal_handler {
407 | echo "hello, world"
408 | }
409 | trap signal_handler 0
410 | $ chmod +x sigexit.sh
411 | $ ./sigexit.sh #实际Shell编程会用该方式在程序退出时来做一些清理临时文件的收尾工作
412 | hello, world
413 |
414 |
415 | ## 作业和作业控制
416 |
417 | 当我们为完成一些复杂的任务而将多个命令通过 `|,\>,<, ;, (,)` 等组合在一起时,通常这个命令序列会启动多个进程,它们间通过管道等进行通信。而有时在执行一个任务的同时,还有其他的任务需要处理,那么就经常会在命令序列的最后加上一个&,或者在执行命令后,按下 `CTRL+Z` 让前一个命令暂停。以便做其他的任务。等做完其他一些任务以后,再通过 `fg` 命令把后台任务切换到前台。这样一种控制过程通常被成为作业控制,而那些命令序列则被成为作业,这个作业可能涉及一个或者多个程序,一个或者多个进程。下面演示一下几个常用的作业控制操作。
418 |
419 |
420 | ### 范例:创建后台进程,获取进程的作业号和进程号
421 |
422 | $ sleep 50 &
423 | [1] 11137
424 |
425 |
426 | ### 范例:把作业调到前台并暂停
427 |
428 | 使用 Shell 内置命令 `fg` 把作业 1 调到前台运行,然后按下 `CTRL+Z` 让该进程暂停
429 |
430 | $ fg %1
431 | sleep 50
432 | ^Z
433 | [1]+ Stopped sleep 50
434 |
435 |
436 | ### 范例:查看当前作业情况
437 |
438 | $ jobs #查看当前作业情况,有一个作业停止
439 | [1]+ Stopped sleep 50
440 | $ sleep 100 & #让另外一个作业在后台运行
441 | [2] 11138
442 | $ jobs #查看当前作业情况,一个正在运行,一个停止
443 | [1]+ Stopped sleep 50
444 | [2]- Running sleep 100 &
445 |
446 |
447 | ### 范例:启动停止的进程并运行在后台
448 |
449 | $ bg %1
450 | [2]+ sleep 50 &
451 |
452 | 不过,要在命令行下使用作业控制,需要当前 Shell,内核终端驱动等对作业控制支持才行。
453 |
454 |
455 | ## 参考资料
456 |
457 | - 《UNIX 环境高级编程》
458 |
--------------------------------------------------------------------------------
/zh/chapters/01-chapter8.markdown:
--------------------------------------------------------------------------------
1 | **关注作者公众号**:
2 |
3 |
4 |
5 |
6 | # 网络操作
7 |
8 | - [前言](#toc_5782_1744_1)
9 | - [网络原理介绍](#toc_5782_1744_2)
10 | - [我们的网络世界](#toc_5782_1744_3)
11 | - [网络体系结构和网络协议介绍](#toc_5782_1744_4)
12 | - [Linux 下网络“实战”](#toc_5782_1744_5)
13 | - [如何把我们的 Linux 主机接入网络](#toc_5782_1744_6)
14 | - [范例:通过dhclient获取IP地址](#toc_5782_1744_7)
15 | - [范例:静态配置IP地址](#toc_5782_1744_8)
16 | - [用 Linux 搭建网桥](#toc_5782_1744_9)
17 | - [用 Linux 做路由](#toc_5782_1744_10)
18 | - [用 Linux 搭建各种常规的网络服务](#toc_5782_1744_11)
19 | - [Linux 下网络问题诊断与维护](#toc_5782_1744_12)
20 | - [Linux 下网络编程与开发](#toc_5782_1744_13)
21 | - [后记](#toc_5782_1744_14)
22 | - [参考资料](#toc_5782_1744_15)
23 |
24 |
25 |
26 | ## 前言
27 |
28 | 前面章节已经介绍了Shell编程范例之数值、布尔值、字符串、文件、文件系统、进程等的操作。这些内容基本覆盖了网络中某个独立机器正常工作的“方方面面”,现在需要把视角从单一的机器延伸到这些机器通过各种网络设备和协议连接起来的网络世界,分析网络拓扑结构、网络工作原理、了解各种常见网络协议、各种常见硬件工作原理、网络通信与安全相关软件以及工作原理分析等。
29 |
30 | 不过,因为网络相关的问题确实太复杂了,这里不可能介绍具体,因此如果想了解更多细节,还是建议参考相关资料。但Linux是一个网络原理学习和实践的好平台,不仅因为它本身对网络体系结构的实现是开放源代码的,而且各种相关的分析工具和函数库数不胜数,因此,如果你是学生,千万不要错过通过它来做相关的实践工作。
31 |
32 |
33 | ## 网络原理介绍
34 |
35 |
36 | ### 我们的网络世界
37 |
38 | 在进行所有介绍之前,来直观地感受一下那个真真实实存在的网络世界吧。当我在 Linux 下通过 `Web` 编辑器写这篇 Blog 时,一边用 `mplayer` 听着远程音乐,累了时则打开兰大的网络 `TV` 频道开始看看凤凰卫视……这些“现代化”的生活,我想,如果没有网络,将变得无法想象。
39 |
40 | 下面来构想一下这样一个网络世界的优美图画:
41 |
42 | > 一边盯着显示器,一边敲击着键盘,一边挂着耳机。
43 |
44 | > 主机电源灯灿烂得很,发着绿光,这时很容易想象主机背后的那个网卡位置肯定有两个不同颜色的灯光在闪烁,它显示着主机正在与计算机网络世界打着交道。
45 |
46 | > 就在实验室的某个角落,有一个交换机上的一个网口的网线连到主机上,这个交换机接到了一个局域网的网关上,然后这个网关再接到了信息楼的某个路由器上,再转接到学校网络中心的另外一个路由器上……
47 |
48 | > 期间,有一个路由器连接到了这个 Blog 服务器上,而另外一个则可能连到了那个网络 `TV` 服务器上,还有呢,另外一些则连接到了电信网络里头的某个音乐服务器上……
49 |
50 | 下面用 `dia` 绘制一个简单的“网络地图”:
51 |
52 | 
53 |
54 | 该图把一些最常见的网络设备和网络服务基本都呈现出来了,包括本地主机、路由、交换机、网桥,域名服务器,万维网服务,视频服务,防火墙服务,动态 `IP` 地址服务等。其中各种设备构成了整个物理网络,而网络服务则是构建在这些设备上的各种网络应用。
55 |
56 | 现在的网络应用越来越丰富多样,比如即时聊天(`IM`)、 `p2p` 资源共享、网络搜索等,它们是如何实现的,它们如何构建在各种各样的网络设备之上,并且能够安全有效的工作呢?这取决于这背后逐步完善的网络体系结构和各种相关网络协议的开发、实现和应用。
57 |
58 |
59 | ### 网络体系结构和网络协议介绍
60 |
61 | 那么网络体系结构是怎么样的呢?涉及到哪些相关的网络协议呢?什么又是网络协议呢?
62 |
63 | 在《计算机网络——自顶向下的方法》一书中非常巧妙地给出了网络体系结构分层的比喻,把网络中各层跟交通运输体系中的各个环节对照起来,让人通俗易懂。在交通运输体系中,运输的是人和物品,在计算机网络体系中,运输的是电子数据。考虑到交通运输网络和计算机网络中最终都可以划归为点对点的信息传输。这里考虑两点之间的信息传递过程,得到这样一个对照关系,见下图:
64 |
65 | 
66 |
67 | 对照上图,更容易理解右侧网络体系结构的分层原理(如果比照一封信发出到收到的这一中间过程可能更容易理解),上图右侧是 `TCP/IP` 网络体系结构的一个网络分层示意图,在把数据发送到网络之前,在各层中需要进行各种“打包”的操作,而从网络接收到数据后,就需要进行“解包”操作,最终把纯粹的数据信息给提取出来。这种分层的方式是为了传输数据的需要,也是两个主机之间如何建立连接以及如何保证数据传输的完整性和可靠性的需要。通过把各种需要分散在不同的层次,使得整个体系结构更加清晰和明了。这些“需求”具体通过各种对应的协议来规范,这些规范统成为网络协议。
68 |
69 | 关于 `OSI` 模型(7 层)比照 `TCP/IP` 模型(4 层)的协议栈可以从下图(来自网络)看个明了:
70 |
71 | 
72 |
73 | 而下图(来自网络)则更清晰地体现了 `TCP/IP` 分层模型。
74 |
75 | 
76 |
77 | 上面介绍了网络原理方面的基本内容,如果想了解更多网络原理和操作系统对网络支持的实现,可以考虑阅读后面的参考资料。下面将做一些实践,即在 Linux 下如何联网,如何用 Linux 搭建各种网络服务,并进行网络安全方面的考量以及基本的网络编程和开发的介绍。
78 |
79 |
80 | ## Linux 下网络“实战”
81 |
82 |
83 | ### 如何把我们的 Linux 主机接入网络
84 |
85 | 如果要让一个系统能够联网,首先当然是搭建好物理网络了。接入网络的物理方式还是蛮多的,比如直接用网线接入以太网,用无线网卡上网,用 `ADSL` 拨号上网……
86 |
87 | 对于用以太网网卡接入网络的常见方式,在搭建好物理网络并确保连接正常后,可以通过配置 `IP` 地址和默认网关来接入网络,这个可以通过手工配置和动态获取两种方式。
88 |
89 |
90 | #### 范例:通过dhclient获取IP地址
91 |
92 | 如果所在的局域网有 `DHCP` 服务,那么可以这么获取,`N` 是设备名称,如果只有一块网卡,一般是 0 或者 1 。
93 |
94 | $ dhclient ethN
95 |
96 |
97 | #### 范例:静态配置IP地址
98 |
99 | 当然,也可以考虑采用静态配置的方式,`ip_address` 是本地主机的 `IP` 地址,`gw_ip_address` 是接入网络的网关的 `IP` 地址。
100 |
101 | $ ifconfig eth0 ip_address on
102 | $ route add deafult gw gw_ip_address
103 |
104 | 如果上面不工作,记得通过 `ifconfig/mii-tool/ethtool` 等工具检查网卡是否有被驱动起来,然后通过 `lspci/dmesg` 等检查网卡类型(或者通过主板手册和独立网卡自带的手册查看),接着安装或者编译相关驱动,最后把驱动通过 `insmod/modprobe` 等工具加载到内核中。
105 |
106 |
107 | ### 用 Linux 搭建网桥
108 |
109 | 网桥工作在 `OSI` 模型的第二层,即数据链路层,它只需要知道目标主机的 `MAC` 地址就可以工作。 Linux 内核在 `2.2` 开始就已经支持了这个功能,具体怎么配置看看后续[参考资料](http://www.ibm.com/developerworks/cn/linux/kernel/l-netbr/index.html)吧。如果要把 Linux 主机配置成一个网桥,至少需要两个网卡。
110 |
111 | 网桥的作用相当于一根网线,用户无须关心里头有什么东西,把它的两个网口连接到两个主机上就可以让这两个主机支持相互通信。不过它比网线更厉害,如果配上防火墙,就可以隔离连接在它两端的网段(注意这里是网络,因为它不识别 `IP`),另外,如果这个网桥有多个网口,那么可以实现一个功能复杂的交换机,而如果有效组合多个网桥,则有可能实现一个复杂的可实现流量控制和负载平衡的防火墙系统。
112 |
113 |
114 | ### 用 Linux 做路由
115 |
116 | 路由工作在 `OSI` 模型的第三层,即网络层,通过 `router` 可以配置 Linux 的路由,当然,Linux 下也有很多工具支持动态路由的。相关的资料在网路中铺天盖地,由于时间关系,这里不做介绍。
117 |
118 |
119 | ### 用 Linux 搭建各种常规的网络服务
120 |
121 | 需要什么网络服务呢?
122 |
123 | - 给局域网弄个 `DHCP` 服务器,那就弄个 `dhcpd`,看看[参考资料](http://tldp.org/HOWTO/DHCP/);
124 | - 如果想弄个邮件发送服务器,那就安装个 `sendmail` 或者 `exim4` ;
125 | - 如果再想弄个邮件列表服务器呢,那就装个 `mailman` ;
126 | - 如果想弄个接收邮件的服务器呢,那就安装个 `pop3` 服务器;
127 | - 如果想弄个 `web` 站点,那就弄个 `apache` 或者 `nginx` 服务器;
128 | - 如果想弄上防火墙服务,那么通过 `iptables` 工具配置 `netfilter` 就可以
129 |
130 | What's more?如果你能想到,Linux上基本都有相应的实现。
131 |
132 |
133 | ### Linux 下网络问题诊断与维护
134 |
135 | 如果出现网络问题,不要惊慌,逐步检查网络的各个层次:物理链接、链路层、网络层直到应用层,熟悉使用各种如下的工具,包括 `ethereal/tcpdump`,`hping`,`nmap`,`netstat`,`netpipe`,`netperf`,`vnstat`,`ntop` 等。
136 |
137 | 关于这些工具的详细用法和网络问题诊断和维护的相关知识,请看后续相关资料。
138 |
139 |
140 | ## Linux 下网络编程与开发
141 |
142 | 如果想做网络编程开发,比如:
143 |
144 | - 要实现一个客户端 `/` 服务器架构的应用,可以采用 Linux 下的 `socket` 编程了;
145 | - 如果想写一个数据包抓获和协议分析的程序,可以采用 `libpap` 等函数库;
146 | - 如果想实现某个协议呢,那就可以参考相关的 `RFC` 文档,并通过 `socket` 编程来实现。
147 |
148 | 这个可以参考相关的 `Linux socket` 编程等资料。
149 |
150 |
151 | ## 后记
152 |
153 | 本来介绍网络相关的一些基本内容,但因时间关系,没有详述,更多细节请参考相关资料。
154 |
155 | 到这里,整个《Shell编程范例》算是很粗略地完成了,不过“范例”却缺少实例,特别是这一节。因此,如果时间允许,会逐步补充一些实例。
156 |
157 |
158 | ## 参考资料
159 |
160 | - 计算机网络——自上而下的分析方法
161 | - Linux 网络体系结构(清华大学出版社出版)
162 | - Linux 系统故障诊断与排除 第13章 网络问题(人民邮电出版社)
163 | - 在 Linux 下用 ADSL 拨号上网
164 | - Linux 下无线网络相关资料收集
165 | - [Linux网桥的实现分析与使用](http://www.ibm.com/developerworks/cn/linux/kernel/l-netbr/index.html)
166 | - [DHCP mini howto](http://tldp.org/HOWTO/DHCP/)
167 | - 最佳的 75 个安全工具
168 | - 网络管理员必须掌握的知识
169 | - Linux 上检测 rootkit 的两种工具: Rootkit Hunter 和 Chkrootkit
170 | - 数据包抓获与 ip 协议的简单分析(基于 pcap 库)
171 | - [RFC](http://www.ietf.org/rfc)
172 | - [HTTP 协议的 C 语言编程实现实例](http://zhoulifa.bokee.com/4640913.html)
173 |
--------------------------------------------------------------------------------
/zh/chapters/01-chapter9.markdown:
--------------------------------------------------------------------------------
1 | **关注作者公众号**:
2 |
3 |
4 |
5 |
6 | # 用户管理
7 |
8 | - [用户帐号](#toc_13359_11834_1)
9 | - [添加](#toc_13359_11834_2)
10 | - [删除](#toc_13359_11834_3)
11 | - [修改](#toc_13359_11834_4)
12 | - [禁用](#toc_13359_11834_5)
13 | - [用户口令](#toc_13359_11834_6)
14 | - [设置](#toc_13359_11834_7)
15 | - [删除](#toc_13359_11834_8)
16 | - [修改](#toc_13359_11834_9)
17 | - [禁用](#toc_13359_11834_10)
18 | - [用户组别](#toc_13359_11834_11)
19 | - [添加](#toc_13359_11834_12)
20 | - [删除](#toc_13359_11834_13)
21 | - [修改](#toc_13359_11834_14)
22 | - [用户和组](#toc_13359_11834_15)
23 | - [增加](#toc_13359_11834_16)
24 | - [删除](#toc_13359_11834_17)
25 | - [用户切换](#toc_13359_11834_18)
26 | - [切换帐号](#toc_13359_11834_19)
27 | - [免密码切到 Root](#toc_13359_11834_20)
28 |
29 |
30 | 在初次撰写本书时,都只讨论到了“物”,而没有关注“人”。而在实际使用中,Linux 系统首先是面向用户的系统,所有之前介绍的内容全部是提供给不同的用户使用的。实际使用中常常碰到各类用户操作,所以这里添加一个独立的章节来介绍。
31 |
32 | Linux 支持多用户,也就是说允许不同的人使用同一个系统,每个人有一个属于自己的帐号。而且允许大家设置不同的认证密码,确保大家的私有信息得到保护。另外,为了确保整个系统的安全,用户权限又做了进一步划分,包括普通用户和系统管理员。普通用户只允许访问自己账户授权下的信息,而系统管理员才能访问所有资源。普通用户如果想行使管理员的职能,必须获得系统管理员的许可。
33 |
34 | 为避免分散注意力,咱们不去介绍背后的那些数据文件:
35 | `/etc/passwd`,`/etc/shadow`,`/etc/group`,`/etc/gshadow`
36 |
37 | 如果确实有需要,大家可通过如下命令查看帮助:
38 | `man 5 passwd`,`man shadow`, `man group` 和 `man gshadow`
39 |
40 | 下面我们分如下几个部分来介绍:
41 |
42 | * 用户帐号
43 | * 用户口令
44 | * 用户组别
45 | * 用户和组
46 | * 用户切换
47 |
48 |
49 | ## 用户帐号
50 |
51 | 帐号操作主要是增、删、改、禁。Linux 系统提供了底层的 `useradd`, `userdel` 和 `usermod` 来完成相关操作,也提供了进一步的简化封装:`adduser`, `deluser`。为了避免混淆,咱们这里只介绍最底层的指令,这些指令设计上已经够简洁明了方便。
52 |
53 | 由于只有系统管理员才能创建新用户,请确保以 root 帐号登录或者可以通过 sudo 切换为管理员帐号。
54 |
55 |
56 | ### 添加
57 |
58 | 创建家目录并指定登录 Shell:
59 |
60 | # useradd -s /bin/bash -m test
61 | # groups test
62 | test : test
63 |
64 | 并加入所属组:
65 |
66 | # useradd -s /bin/bash -m -G docker test
67 | # groups test
68 | test : test docker
69 |
70 |
71 |
72 | ### 删除
73 |
74 | 删除用户以及家目录等:
75 |
76 | # userdel -r test
77 |
78 |
79 | ### 修改
80 |
81 | 常常用来修改默认的 Shell:
82 |
83 | # usermod -s /bin/bash test
84 |
85 | 或者把用户加入某个新安装软件所属的组:
86 |
87 | # usermod -a -G docker test
88 |
89 | 修改登录用户名并搬到新家:
90 |
91 | # usermod -d /home/new_test -m -l new_test test
92 |
93 |
94 | ### 禁用
95 |
96 | 如果想禁用某个帐号:
97 |
98 | # usermod -L test
99 | # usermod --expiredate 1 test
100 |
101 |
102 | ## 用户口令
103 |
104 | 口令操作主要是设置、删除、修改和禁用。Linux 系统提供了 `passwd` 命令来管理用户口令。
105 |
106 |
107 | ### 设置
108 |
109 | 设置用户 test 的初始密码:
110 |
111 | $ passwd test
112 | Enter new UNIX password:
113 | Retype new UNIX password:
114 | passwd: password updated successfully
115 |
116 |
117 |
118 | ### 删除
119 |
120 | 让用户 test 无须密码登录(密码为空):
121 |
122 | $ passwd -d test
123 |
124 | 这个很方便某些安全无关紧要的条件下(比如已登录主机中的虚拟机),可避免每次频繁输入密码。
125 |
126 |
127 | ### 修改
128 |
129 | $ passwd test
130 | Changing password for test.
131 | (current) UNIX password:
132 | Enter new UNIX password:
133 | Retype new UNIX password:
134 | passwd: password updated successfully
135 |
136 |
137 | ### 禁用
138 |
139 | 禁止用户通过密码登录:
140 |
141 | $ passwd -l user
142 |
143 | 为了安全起见或者为了避免暴力破解,我们通常可以禁用密码登录,而只允许通过 SSH Key 登录。
144 |
145 | 如果要真地禁用整个帐号的使用,需要用上一节提到的 `usermod --expiredate 1`。
146 |
147 |
148 | ## 用户组别
149 |
150 | 类似帐号,主要操作也是增、删、改。
151 |
152 | Linux 系统提供了底层的 `groupadd`, `groupdel` 和 `groupmod` 来完成相关操作,也提供了进一步的简化封装:`addgroup`, `delgroup`。
153 |
154 | 用户组别通常用来管理不同的资源,确保只有某个组别的用户才可以访问某类资源。当然,实际案例中,有些软件也为自己定义一个组别,只有该组别的用户才能访问该软件的一些功能。
155 |
156 |
157 | ### 添加
158 |
159 | 添加一个新组别:
160 |
161 | # groupadd test
162 |
163 |
164 | ### 删除
165 |
166 | # groupdel test
167 |
168 |
169 | ### 修改
170 |
171 | 修改组别名:
172 |
173 | # groupmod -n new_test test
174 |
175 |
176 | ## 用户和组
177 |
178 | 用户和组别不能独立存在,`gpasswd` 可以用来处理两者的关系。
179 |
180 |
181 | ### 增加
182 |
183 | 从 docker 组中增加用户 test(等同于把 test 增加到 docker 组中):
184 |
185 | # gpasswd -a test docker
186 |
187 | 或
188 |
189 | # usermod -a -G docker test
190 |
191 |
192 | ### 删除
193 |
194 | 从 test 组中删除用户 test:
195 |
196 | # gpasswd -d test test
197 |
198 |
199 | ## 用户切换
200 |
201 | 由于支持多用户,那么在登录一个帐号后,可能需要切换到另外一个帐号下,可以通过 `su` 命令完成,而 `sudo` 则可以用来作为另外一个用户来执行命令。
202 |
203 |
204 | ### 切换帐号
205 |
206 | 切换到 Root 并启用 Bash:
207 |
208 | $ su -s /bin/bash -
209 | root@falcon-desktop:~#
210 |
211 | 或者
212 |
213 | $ sudo -s
214 |
215 | 切换到普通用户:
216 |
217 | $ su -s /bin/bash - test
218 | test@falcon-desktop:~$
219 |
220 | 或者
221 |
222 | $ sudo -i -u test
223 | test@falcon-desktop:~$
224 |
225 |
226 | ### 免密码切到 Root
227 |
228 | 首先得把用户加入到 sudo 用户组:
229 |
230 | # usermod -a -G sudo falcon
231 |
232 | 否则,会看到如下信息:
233 |
234 | $ sudo -s
235 | [sudo] password for test:
236 | test is not in the sudoers file. This incident will be reported.
237 |
238 | 加入 sudo 用户组以后:
239 |
240 | $ sudo -s
241 | [sudo] password for test:
242 |
243 | 要实现免密切换,需要先修改 `/etc/sudoers`,加入如下一行:
244 |
245 | test ALL=(ALL) NOPASSWD: ALL
246 |
247 | 或者在 `/etc/sudoers.d/` 下创建一个文件并加入上述内容。
248 |
249 | # echo "test ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/test
250 | # chmod 440 /etc/sudoers.d/test
251 |
--------------------------------------------------------------------------------
/zh/chapters/pic/Linux_FileSystem_Architecture-150x150.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/Linux_FileSystem_Architecture-150x150.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/Linux_FileSystem_Architecture-197x300.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/Linux_FileSystem_Architecture-197x300.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/Linux_FileSystem_Architecture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/Linux_FileSystem_Architecture.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/MBR_Architecture-150x150.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/MBR_Architecture-150x150.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/MBR_Architecture-300x139.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/MBR_Architecture-300x139.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/MBR_Architecture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/MBR_Architecture.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/Network_Architecture-150x150.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/Network_Architecture-150x150.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/Network_Architecture-300x176.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/Network_Architecture-300x176.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/Network_Architecture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/Network_Architecture.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/Network_Layer_Compare-150x150.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/Network_Layer_Compare-150x150.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/Network_Layer_Compare-300x118.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/Network_Layer_Compare-300x118.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/Network_Layer_Compare.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/Network_Layer_Compare.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/Network_Layer_OSI-150x150.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/Network_Layer_OSI-150x150.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/Network_Layer_OSI-300x221.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/Network_Layer_OSI-300x221.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/Network_Layer_OSI.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/Network_Layer_OSI.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/Network_Layer_TCP_IP-118x300.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/Network_Layer_TCP_IP-118x300.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/Network_Layer_TCP_IP-150x150.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/Network_Layer_TCP_IP-150x150.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/Network_Layer_TCP_IP.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/Network_Layer_TCP_IP.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/UI_Shell_and_GUI-150x150.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/UI_Shell_and_GUI-150x150.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/UI_Shell_and_GUI-300x190.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/UI_Shell_and_GUI-300x190.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/UI_Shell_and_GUI.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/UI_Shell_and_GUI.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/cover-tinylab.org.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/cover-tinylab.org.png
--------------------------------------------------------------------------------
/zh/chapters/pic/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/cover.png
--------------------------------------------------------------------------------
/zh/chapters/pic/mommy-tea.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/mommy-tea.png
--------------------------------------------------------------------------------
/zh/chapters/pic/tinylab-targets-150x110.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/tinylab-targets-150x110.gif
--------------------------------------------------------------------------------
/zh/chapters/pic/tinylab-targets-150x110.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/tinylab-targets-150x110.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/tinylab-targets-300x54.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/tinylab-targets-300x54.gif
--------------------------------------------------------------------------------
/zh/chapters/pic/tinylab-targets-300x54.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/tinylab-targets-300x54.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/tinylab-targets.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/tinylab-targets.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/tinylab.org-sina-weibo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/tinylab.org-sina-weibo.jpg
--------------------------------------------------------------------------------
/zh/chapters/pic/tinylab.org-weixin-logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/chapters/pic/tinylab.org-weixin-logo.jpg
--------------------------------------------------------------------------------
/zh/preface/01-chapter0.markdown:
--------------------------------------------------------------------------------
1 | # 版本修订历史
2 |
3 | |Revision | Author | From | Date | Description |
4 | |---------|------------------|--------------------|------------|---------------------|
5 | | 0.3 |[@吴章金falcon][1]|[@泰晓科技][2] | 2016/08/30 | 增加一章: 用户管理 |
6 | | 0.2 |[@吴章金falcon][1]|[@泰晓科技][2] | 2015/07/23 | 调整格式,修复链接 |
7 | | 0.1 |[@吴章金falcon][1]|[@泰晓科技][2] | 2014/01/07 | 初稿 |
8 |
9 | [1]: https://www.zhihu.com/people/wuzhangjin
10 | [2]: http://tinylaborg
11 |
--------------------------------------------------------------------------------
/zh/preface/01-chapter1.markdown:
--------------------------------------------------------------------------------
1 | **关注作者公众号**:
2 |
3 |
4 |
5 |
6 | # 前言
7 |
8 | - [背景](#toc_18682_17841_1)
9 | - [现状](#toc_18682_17841_2)
10 | - [计划](#toc_18682_17841_3)
11 |
12 |
13 |
14 | ## 背景
15 |
16 | 早在 2007 年 11 月,为了系统地学习和总结 Shell 编程,作者专门制定了一个 Shell 编程范例的总结计划,当时的计划是:
17 |
18 | > 这个系列将以面向“对象”(即我们操作的对象)来展开,并引入大量的实例,这样有助于让我们真正去学以致用,并在用的过程中提高兴趣。所以这个系列将不会专门介绍 Shell 的语法, 而是假设读者对 Shell 编程有了一定的基础。
19 | >
20 | > 另外,该系列到最后可能会涵盖:数值、逻辑值、字符串、文件、进程、文件系统等所有我们可以操作的“对象”,这个操作对象也将从低级到高级,进而上升到网络层面,整个通过各种方式连接起来的计算机的集合。实际上这也未尝不是在摸索 UNIX 的哲学,那"K.I.S.S"(Keep It Simple, Stupid)蕴藏的巨大能量。
21 |
22 | > —— 摘自《兰大开源社区 >> 脚本编程 >> Shell 编程范例》
23 |
24 | 2008 年 4 月底,整个系列大部分内容和框架基本完成,后来由于实习和工作原因,并没有持续完善。不过相关章节却获得了较好的反响,很多热心网友有大量评论和转载,例如,在百度文库转载的一份《Shell编程范例之字符串操作》的访问量已接近 3000。说明整个系列还是有比较大的阅读群体。
25 |
26 |
27 | ## 现状
28 |
29 | 考虑到整个 Linux 世界的蓬勃发展,Shell 的使用环境越来越多,相关使用群体会不断增加,所以最近已经将该系列重新整理,并以自由书籍的方式发布,以便惠及更多的读者。
30 |
31 | 整个系列已经用 [Markdown](http://www.tinylab.org/start-posting-with-markdown/) 重新组织,并发布到了 [泰晓科技|TinyLab.org](http://tinylab.org)。
32 |
33 | 整理到[TinyLab.org](http://tinylab.org)的索引篇是:[《Shell编程范例之索引篇》](http://www.tinylab.org/shell-programming-paradigm-series-index-review/),其内容结构如下:
34 |
35 | - [Shell编程范例之开篇](http://www.tinylab.org/shell-programming-paradigm-begins-with/) (更新时间:2007-07-21)
36 | - [Shell编程范例之数值运算](http://www.tinylab.org/shell-numeric-calculation/) (更新时间:2007-11-9)
37 | - [Shell编程范例之布尔运算](http://www.tinylab.org/shell-programming-paradigm-of-boolean-operations/) (更新时间:2007-10-30)
38 | - [Shell编程范例之字符串操作](http://www.tinylab.org/shell-programming-paradigm-of-string-manipulation/) (更新时间:2007-11-21)
39 | - [Shell编程范例之文件操作](http://www.tinylab.org/shell-programming-paradigms-of-file-operations/) (更新时间:2007-12-5)
40 | - [Shell编程范例之文件系统操作](http://www.tinylab.org/shell-programming-paradigm-in-file-system-operations/) (更新时间:2007-12-29)
41 | - [Shell编程范例之进程操作](http://www.tinylab.org/shell-programming-paradigm-of-process-operations/) (更新时间:2008-02-22)
42 | - [Shell编程范例之网络操作](http://www.tinylab.org/shell-programming-paradigm-of-network-operations/) (更新时间:2008-04-19)
43 | - [Shell编程范例之总结篇](http://www.tinylab.org/summary-of-shell-programming-paradigm-article/) (更新时间:2008-07-21)
44 |
45 | 最近,基于一个 Markdown 的[开源书籍模版](http://tinylab.org/docker-quick-start-docker-gitbook-writing-a-book/):Gitbook,已经把该系列整理成了自由书籍,并维护在 TinyLab 的[项目仓库](https://github.com/tinyclub/open-shell-book)中。项目相关信息如下:
46 |
47 | - 项目首页:
48 | - 代码仓库:[https://github.com/tinyclub/open-shell-book.git](https://github.com/tinyclub/open-shell-book)
49 |
50 |
51 | ## 计划
52 |
53 | 后续除了继续在 [泰晓科技|TinyLab.org](http://tinylab.org) 以 Blog 形式持续更新外,还打算重新规划、增补整个系列,并以自由书籍的方式持续维护,并通过 [TinLab.org](http://tinylab.org) 平台接受读者的反馈,直到正式发行出版。
54 |
55 | 欢迎大家指出本书初稿中的不足,甚至参与到相关章节的写作、校订和完善中来。
56 |
57 | 如果有时间和兴趣,欢迎参与。可以通过 [泰晓科技](http://www.tinylab.org/about/) 联系我们,或者直接联系微信号 tinylab。
58 |
--------------------------------------------------------------------------------
/zh/preface/pic/Linux_FileSystem_Architecture-150x150.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/Linux_FileSystem_Architecture-150x150.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/Linux_FileSystem_Architecture-197x300.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/Linux_FileSystem_Architecture-197x300.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/Linux_FileSystem_Architecture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/Linux_FileSystem_Architecture.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/MBR_Architecture-150x150.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/MBR_Architecture-150x150.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/MBR_Architecture-300x139.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/MBR_Architecture-300x139.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/MBR_Architecture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/MBR_Architecture.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/Network_Architecture-150x150.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/Network_Architecture-150x150.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/Network_Architecture-300x176.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/Network_Architecture-300x176.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/Network_Architecture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/Network_Architecture.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/Network_Layer_Compare-150x150.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/Network_Layer_Compare-150x150.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/Network_Layer_Compare-300x118.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/Network_Layer_Compare-300x118.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/Network_Layer_Compare.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/Network_Layer_Compare.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/Network_Layer_OSI-150x150.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/Network_Layer_OSI-150x150.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/Network_Layer_OSI-300x221.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/Network_Layer_OSI-300x221.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/Network_Layer_OSI.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/Network_Layer_OSI.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/Network_Layer_TCP_IP-118x300.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/Network_Layer_TCP_IP-118x300.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/Network_Layer_TCP_IP-150x150.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/Network_Layer_TCP_IP-150x150.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/Network_Layer_TCP_IP.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/Network_Layer_TCP_IP.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/UI_Shell_and_GUI-150x150.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/UI_Shell_and_GUI-150x150.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/UI_Shell_and_GUI-300x190.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/UI_Shell_and_GUI-300x190.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/UI_Shell_and_GUI.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/UI_Shell_and_GUI.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/cover-tinylab.org.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/cover-tinylab.org.png
--------------------------------------------------------------------------------
/zh/preface/pic/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/cover.png
--------------------------------------------------------------------------------
/zh/preface/pic/mommy-tea.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/mommy-tea.png
--------------------------------------------------------------------------------
/zh/preface/pic/tinylab-targets-150x110.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/tinylab-targets-150x110.gif
--------------------------------------------------------------------------------
/zh/preface/pic/tinylab-targets-150x110.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/tinylab-targets-150x110.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/tinylab-targets-300x54.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/tinylab-targets-300x54.gif
--------------------------------------------------------------------------------
/zh/preface/pic/tinylab-targets-300x54.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/tinylab-targets-300x54.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/tinylab-targets.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/tinylab-targets.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/tinylab.org-sina-weibo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/tinylab.org-sina-weibo.jpg
--------------------------------------------------------------------------------
/zh/preface/pic/tinylab.org-weixin-logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinyclub/open-shell-book/752ffde2badb9b2291af79208fa2f85d958ca9b1/zh/preface/pic/tinylab.org-weixin-logo.jpg
--------------------------------------------------------------------------------