Open solomonxie opened 6 years ago
以前经常会听到这些词,还有标准输入之类,完全不明所以。直到最近需要让python脚本里的print内容打印到日志文件里,才开始研究这到底是什么。
原来,标准输出(stdout)
指的就是在命令行里,每次你输入指令后,终端上打印出来的那些话,那些反馈。标准错误(stderr)
跟标准输出差不多,只不过是程序出错时反馈的内容。标准输入(stdin)
就是程序指示让你输入用户名密码之类的这种,这里不多谈输入。
问题是,我们很常用的会让一些脚本自己在后台24/7运行,这种时候脚本的输出内容到屏幕上(标准输出)也没什么意义,我们看不到也保存不了。所以最好让它把反馈的内容全部直接写如一个文件里,我们叫日志文件,其实就是个txt。然后我们自己可以查看日志来看到底发生了什么。
这种把显示到屏幕的程序反馈,变成存到文件里的动作,我们叫做
输出重定向(stdout redirection)
在命令行里,我们可以用符号直接把程序输出转向
到某个文件或某个程序,如下:
$ git push > log.txt
然后,理论上我们平常git push
后的反馈就会保存到log.txt
这个文件里了,且屏幕上不会显示任何东西。
但其实这个还是有问题的,因为事后我们发现有一些存到了log.txt,还有一些话漏网显示到了屏幕上,没存进去文档里。
其实原来这些显示到屏幕上的反馈有些是stdout
有些是stderr
,我们用>
或>>
符号重定向,只是默认重定向stdout
,没有重定向stderr
,所以会有漏网之鱼。对此,我们需要了解下这个符号的设定,和怎么把stderr
也包括进来,一起重定向过去。
稍微会一点点linux命令的,都会用到cmd > file
这样的语句,把命令反馈的输出到一个文件里。当然还有cmd >> file
,这是把内容追加到文件里,而不是重新擦写一遍。>
这个符号可以念redirect to
。
实际上,重定向有很多种设置和配合,让你可以分别重定向标准输出和标准错误,或者一起重定向,然后还可以选择是只输出到文件里还是同时输出大显示屏上和文件里。
这里我们就要了解一下设置重定向的基本语法了,如下:
>
以擦写的模式重定向至... >>
以追加的模式重定向至...1
代表stdout
标准输出2
代表stderr
标准错误所以,cmd > file
实际上是缩略了的写法,理解起来,应该是cmd &1> file
,也就是只把标准输出转出去。
那么同理,只把标准错误转出去,就应该是cmd &2> file
。
其中,&
符号没任何实际意义,只是以至区分,代表后面的符号是要设置重定向用的,而不是某个文件的名字。
每次查重定向问题时,我们总会看到这句话,一般人很难理解这到底是在干嘛。我一开始以为是2要大于1什么的,真是笑话。
其实这是个重定向的设置,设置让2重定向到1,也就是让stderr
标准错误重定向到stdout
标准输出,然后两个并在一起再重定向。其中&
没什么意思只是区分开来1是代表stdout
而不是代表一个文件名。
用起来的格式是:cmd > file 2>&1
。
为什么设置要放在后面呢?
具体暂时还不知道,只知道是这么用,放在前面还不行只能放在后面。
比如:
$ git push > log.txt 2>&1
那么这时候,屏幕上就真的不会显示任何东西了,标准输出、标准错误,全部都会存到log.txt文件里了。
这个是比较好用的方法:即程序输出到屏幕,也输出到文件。
command > output.txt
The standard output stream will be redirected to the file only, it will not be visible in the terminal. If the file already exists, it gets overwritten.
command >> output.txt
The standard output stream will be redirected to the file only, it will not be visible in the terminal. If the file already exists, the new data will get appended to the end of the file.
command 2> output.txt
The standard error stream will be redirected to the file only, it will not be visible in the terminal. If the file already exists, it gets overwritten.
command 2>> output.txt
The standard error stream will be redirected to the file only, it will not be visible in the terminal. If the file already exists, the new data will get appended to the end of the file.
command &> output.txt
Both the standard output and standard error stream will be redirected to the file only, nothing will be visible in the terminal. If the file already exists, it gets overwritten.
command &>> output.txt
Both the standard output and standard error stream will be redirected to the file only, nothing will be visible in the terminal. If the file already exists, the new data will get appended to the end of the file..
command | tee output.txt
The standard output stream will be copied to the file, it will still be visible in the terminal. If the file already exists, it gets overwritten.
command | tee -a output.txt
The standard output stream will be copied to the file, it will still be visible in the terminal. If the file already exists, the new data will get appended to the end of the file.
(*)
Bash has no shorthand syntax that allows piping only StdErr to a second command, which would be needed here in combination with tee again to complete the table. If you really need something like that, please look at "How to pipe stderr, and not stdout?" on Stack Overflow for some ways how this can be done e.g. by swapping streams or using process substitution.
command |& tee output.txt
Both the standard output and standard error streams will be copied to the file while still being visible in the terminal. If the file already exists, it gets overwritten.
command |& tee -a output.txt
Both the standard output and standard error streams will be copied to the file while still being visible in the terminal. If the file already exists, the new data will get appended to the end of the file.
set
命令可以查看所有的环境变量,echo $xx
可以打印某一个环境变量。
$USER 查看账户信息
$logname 登录相关信息
$UID
$Shell
$HOME 家目录
$pwd
$PATH 用户所输入的命令是在哪些目录中查找
$PS1
$PS2
$RANDOM 随机数
epoll
是Linux内核采用的处理多任务高并发的一种方式。不同于多线程、多进程、协程、非阻塞等方式,epoll只通过单进程、单线程即可完成多任务同时处理。
select
和poll
是Unix的内核处理多任务方式,而epoll
是在前两者基础上衍生出来的Linux多任务处理方式。
但是epoll和其它那些有本质上的区别:多进程、多线程、协程无论怎么运用,都逃不出Application
的内存空间,之后还是要一个一个的复制到Kernel内核中去运行,而且还不是一口气,需要和其它application应用程序互相穿插着、排队去kernel运行。
但是由于我们要处理的是高并发socket
问题,那就必然遇到某一些socket等待对方传数据的过程,这就造成了对kernel的极大浪费。
而epoll
的作用就是,让我们“跳出”Application的盒子,达到Kernel内核级的运算。
现今所有流行的HTTP Servers都是采用的epoll方式来加速处理多任务,包括Apache、Nginx等。包括协程的Python库gevent也是采用epoll底层处理。所有这些多进程、多线程、协程、非堵塞的方式,在和epoll高效率比起来,都是一个天上一个地下。这也就是为什么epoll这么流行的原因。
epoll
两大特点:
mmap
内存映射技术,使用单独的epoll
内存空间:
epoll
方式:即在内存中创建一片独立的空间,让Kernle和App共用。这样一来二者互不干扰,还能互相协作。事件通知
方式,而不使用轮询:
轮询
去看谁有数据是不理智的。Socket Reminder: 回想HTTP Server的服务,其中TCP三次握手,每次握手、每次挥手、每次上传、每次下载,都是一次向对方电脑的一个socket文本文件写入的过程。 但是从socket文件建立,到写入数据,到完成写入,是有不确定的时间间隔的。 只有当这个socket写入完成时,作为应用程序的HTTP Server才会去处理。
而epoll
要消除的,就是socket建立 -> socket写入完成
这之间的堵塞时间。
epoll
的实现是需要用C语言等最底层的语言去与Linux Kernel沟通才能实现。一般来讲没有kernel级别的编程能力,就只要理解实现原理就够了。
IO多路复用
就是select
, poll
, epoll
,即Event driven I/O
的IO方式。
基本工作方式:
采用select -> poll -> epoll
模型,不断循环,不断轮询所有socket,只有当某个socket的数据变化了(接收到数据了),才通知用户进程来处理。
高级语言中的闭包或装饰器,是个非常好用的功能。Bash没有直接的闭包,但是Bash可以将函数作为参数传递给另一个函数,这样我们就可以达到闭包功能了。
function inner() {
echo "Hello, world!"
}
function outter() {
echo "before"
eval $1
echo "after"
}
outter inner
然后就会输出:
before
Hello world
after
经常要在bash脚本里面或者直接对脚本本身加上sudo
运行命令,但是这引发了一系列的问题。
比如用sudo的时候,脚本里的~
或$HOME
指代用户文件夹的这个变量,到底是应该指向我真正的用户文件夹如/home/pi
呢,还是指向了超级管理员的用户文件夹/root/
呢?
实际上它指向了/root/
文件夹,这是我们绝对不想要的。但是很多命令如安装个程序,都不得不用sudo
,那怎么办?
首先要说下经验:命令行的权限执行,从表现上来看,可以分为以下5种情况:
admin-manual
: 普通用户手敲命令sudo-manual
: 手敲命令加sudoadmin-bash
: 以普通用户执行bash脚本sudo-bash
: 以sudo执行bash脚本root-any
: 以root用户登录很多变量、环境变量在这4中情况下,会经常出现混乱!(混乱指的是我们自己,不是电脑)
另外,说个小技巧。 我们都直到
~
变量是指向当前
用户目录,实际上~abc
格式的变量可以指向指定用户的
用户目录,如~pi
会指向/home/pi
,或~ubuntu
指向/home/ubuntu
.
理清一下思路:
在正常执行脚本如./test.sh
时是没有任何问题的,即使脚本里面出现了sudo如sudo apt-get update
这样也是没有问题的。
也就是说,就只有对整个脚本执行sudo的情况下如sudo ./test.sh
,才会出现严重问题的!
那么假设我的真实用户是pi
,而HOME目录在/home/pi
,现在我要在sudo ./test.sh
这样的执行方式下找出正确的解决方案。
以下为脚本中的各种语句和变量以及显示结果:
# (不推荐!)
$ whoami
>>> root
# 不同于whoami,能够指出当前有哪些用户登录电脑,包括本机登录和ssh登录的所有人
$ who am i
>>> 有些机器上显示为空
>>> Mac上显示: pi ttys001 Nov 26 16:57
# 等同于whoami (不推荐!)
$ echo $USER
>>> root
# 用户主目录位置 (不靠谱不推荐!)
echo $HOME
>>> /root
$ 用户主目录位置,等同于$HOME (不推荐!)
$ echo ~
>>> /root
# 直接使用环境变量LOGNAME
$ echo $LOGNAME
>>> root
# 显式调用环境变量LOGNAME
$ printenv LOGNAME
>>> root
# SUDO_USER是root的ENV中的环境变量,
# 同时普通用户的env是没有的,只有root用户才能显示出来
$ sudo echo $SUDO_USER
>>> pi
# 显示调用环境变量SUDO_USER (不推荐!)
# 从结果中可以看到,即使是sudo身份执行的脚本,脚本里面是否加sudo也会不同!
$ printenv SUDO_USER
>>> pi
$ sudo printenv SUDO_USER
>>> root
从上面测试中可以看出,如果我们是用sudo执行bash脚本的话,很多变量都是“不靠谱”的。
Stackoverflow中,比较一致性的倾向就是使用$SUDO_USER
这个环境变量
。而测试中也的确,它是最“稳定的”,即在不同的权限、OS系统下,都能始终如一(只限有sudo的系统)。
那么现在我们有了用户名,就可以用~pi
这样的命令获取主目录/home/pi
了,但是!
这时候问题又出现了:手敲时候,我们可以获得~pi
的正确地址,但是脚本中却不识别~pi
是个什么东西,顶多是个字符串,没法像变量一样。
那既然是这样,我们就不能用~abc
方法了,改用虽然老套但是绝对不混乱的方法:
从/etc/passwd
中直接看。
手动的话可以直接打开passwd查看,脚本里面就比较麻烦,最方便的是用系统命令getent
即Get Entries命令,获得指定用户的信息:
$ getent passwd pi
>>> pi:x:1000:1000:,,,:/home/pi:/bin/bash
那么,剩下的是有把其中的/home/pi
取出来了,我们用cut
就轻松取出。
所以全部过程如下:
me=$SUDO_USER
myhome=`getent passwd $me | cut -d: -f 6`
顺利得到/home/pi
!
再进一步,如果脚本没有以sudo方式运行呢?这时候root用户和普通用户的环境变量下都是没有SUDO_USER
这个变量的。那么就需要加一步判断了:
me=${SUDO_USER:-$LOGNAME}
myhome=`getent passwd $me | cut -d: -f 6`
即如果SUDO_USER为空,则正常使用$LOGNAME获取当前用户。为什么不用$USER而是用$LOGNAME呢?因为USER不是每个系统都有,但是LOGNAME是*nix系统下都会有的。
由于部分OS不能正确获取LOGNAME,所以统一采用uid的方式获取用户路径:
HOUSE=`getent passwd ${SUDO_UID:-$(id -u)} | cut -d: -f 6`
MacOS没有/etc/passwd
,也不支持getent passwd <UID>
方式获取用户信息,但是sudo下也能保持$USER和$HOME变量内容不变。
所以更改为下:
HOUSE=${$(`getent passwd ${SUDO_UID:-$(id -u)} | cut -d: -f 6`):-$HOME}
即如果getent
方式无法获取内容,则直接取$HOME的值。
因为bash不支持以上嵌套的三元运算表达式,所以要拆开:
HOUSE="`cat /etc/passwd |grep ${SUDO_UID:-$(id -u)} | cut -d: -f 6`"
HOUSE=${HOUSE:-$HOME}
如果是root的话,grep uid的时候会匹配到passwd中所有含0的行,所以要改进为以下:
HOUSE="`cat /etc/passwd |grep ^${SUDO_USER:-$(id -un)}: | cut -d: -f 6`"
HOUSE=${HOUSE:-$HOME}
经常配合find
使用,find根本不支持pipline |
,太不方便了。
xargs做到的是,把|
前面传过来的stdin信息,分拆成一系列的args,然后再把这些args组成参数传给下一个命令。这样就解决了很多pipline管道做不到的事情了,因为管道不能传参数。
常用操作参考:
# 查找文本文件,并显示每个文件的行树
$ find . -name "*.txt" | xargs wc -l
# 顺便再排个序
$ find . -name "*.txt" | xargs wc -l | sort
# 把参数传给指定的命令
$ echo '--help' | xargs cat
>>> cat --help
注意:xargs不同的版本会不兼容。Mac上默认版本非常低所以不兼容,需要更新Mac的gnu utils等。
-d 即delimiter,分隔符。
xargs
既然是处理args的命令,自然少不了args分割。比如"--abc,--bcd,--def",我们可以把用,
把字符串分割为三个参数,一起传给下一个命令:
$ echo "-abc,-bcd,-def" | xargs -d "," echo
>>> echo --abc --bcd --def
-p 即print。
xargs可以把组合成功的整个命令打印出来,然后交互让你选择是否执行:
$ echo "--help" | xargs -p cat
>>> cat --help ?...
xargs可以把拆分后的多个参数,分组传递给下个命令,比如--restart always --name ubuntu
,我们可以把他们用空格分开,然后两个一组传给下个命令:
$ cat "--restart always --name ubuntu" | xargs -n 2 echo
>>> echo --restart always --name ubuntu
-E 同--eof-str 即End Of File - String.
xargs可以根据一定的条件在多个参数里面搜索,如果搜索到了这个词,就把这个词及以后的全删掉,只保留其之前的。
注意:-E只有在xargs不指定-d的时候有效,如果指定了-d则不起作用,而不管-d指定的是什么字符,空格也不行。
$ echo '11@22@33@44@55' | xargs -E '33' echo
>>> 11 22
Cmake是一个编译程序。
目前官方推荐的安装方法只有编译安装,Mac/Linux都是一样。
# Download
cd /tmp
wget https://github.com/Kitware/CMake/releases/download/v3.13.1/cmake-3.13.1.tar.gz
tar -xzvf cmake-3.13.1.tar.gz
cd cmake-3.13.1.tar.gz
# Build
./bootstrap
make
make install
当然,已经有很多包管理器可以一键安装。
Mac:
brew install cmake
注意:Autotools 目前只对C/C++系的语言支持。
configure
脚本用来检测系统环境,最终目的是生成Makefile和config.h。
make
命令通过读取Makefile
文件,开始构建软件。
make install
命令最后将软件安装到需要安装的位置。
configure
文件和Makefile
编译文件动不动就几千行代码,一般是不会手动写的。都是用GNU出品的Autotools
完成。
这套Autotools
的大概逻辑就是:只需要自己写configure.ac
和Makefile.am
两个文件就够了。
"Make is the new Python!"
如果是bash,则:
if (( a > b )); then
...
fi
如果是POSIX shell那么可能会不支持((...))
,那么就要用-gt
:
if [ "$a" -gt "$b" ]; then
...
fi
Bash原生不支持浮点运算,只支持整数。如果运算中输入的数字不是整数,它会报错告诉你需要整数输入。 所以我们要用第三方工具,还好*nix都配了计算工具。
(推荐)使用bc命令,即basic calculator
。
$ num1=3.17648E-22
$ num2=1.5
$ echo $num1'>'$num2 | bc -l
>>> 0
$echo $num2'>'$num1 | bc -l
>>> 1
"Makefile is the new python" - Forget who wrote that in a tutorial
That is the point where I was intrigued the most to decide to learn Makefile
.
Don't get it wrong,
the Makefile
is NOT only for compiling the C/C++ programmes, but to "anything".
Even if it's just a normal folder contains some markdown files, you can create a Makefile
to do some automation stuff to it.
In essence, a Makefile
is just to run some bash commands for you.
e.g., you want to do some cp
, rm
& mv
in a folder, you can line them up in a Makefile
and give it a simple name like orgainize
, and run the command make organize
to run all the commands you have defined, in which it looks like this:
# Makefile
organize:
cp test.py test2.py
rm *.pyc
mv test.py test_bak.py
Refer to: What is a Makefile and how does it work?
The simplest one:
TAG1:
command
command
Notice that before each command there're 4 spaces or a tab.
But this will display each command entirely, which sometimes we don't want that. To hide display of commands but only coomand outputs, we simply add @
to each command:
say_hello:
@echo "Hello, world!"
Tag with "prerequisites" (other tags):
init:
cp 01.txt 02.txt
clean: init
rm 02.txt
When you run make clean
, the command rm 02.txt
only works when the init
works
At the top of Makefile
, you can add some useful headers
like constant variables or options to make the "script" cleaner.
Refer to StackOverflow: make : rule call rule Refer to GNU: How the MAKE Variable Works
./Makefile
in root directory:
tosub:
@cd sub && $(MAKE) sub
./sub/Makefile
:
sub:
$(MAKE) subcommand
subcommand:
@echo This recipe is in sub folder.
Run:
$ cd /path/to/project
$ make sub
/usr/bin/make subcommand
This recipe is in sub folder.
Linux:
# Encrypt
cat mask.png info.zip > pic.png
# Decrypt
mv pic.png info.zip
mask.png
will be a cover to be open a normal picture.
本专题涉及计算机基础理论。
涉及领域: