solomonxie / blog-in-the-issues

A personalised tech-blog, notebook, diary, presentation and introduction.
https://solomonxie.github.io
68 stars 12 forks source link

Unix & Computer Science #36

Open solomonxie opened 6 years ago

solomonxie commented 6 years ago

本专题涉及计算机基础理论。

涉及领域:

solomonxie commented 6 years ago
solomonxie commented 6 years ago
solomonxie commented 5 years ago

❖ Linux 标准输出(stdout)和标准错误(stderr)的重定向

以前经常会听到这些词,还有标准输入之类,完全不明所以。直到最近需要让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。 实际上,重定向有很多种设置和配合,让你可以分别重定向标准输出和标准错误,或者一起重定向,然后还可以选择是只输出到文件里还是同时输出大显示屏上和文件里。 这里我们就要了解一下设置重定向的基本语法了,如下:

所以,cmd > file实际上是缩略了的写法,理解起来,应该是cmd &1> file,也就是只把标准输出转出去。 那么同理,只把标准错误转出去,就应该是cmd &2> file。 其中,&符号没任何实际意义,只是以至区分,代表后面的符号是要设置重定向用的,而不是某个文件的名字。

关于"2>&1"

每次查重定向问题时,我们总会看到这句话,一般人很难理解这到底是在干嘛。我一开始以为是2要大于1什么的,真是笑话。 其实这是个重定向的设置,设置让2重定向到1,也就是让stderr标准错误重定向到stdout标准输出,然后两个并在一起再重定向。其中&没什么意思只是区分开来1是代表stdout而不是代表一个文件名。 用起来的格式是:cmd > file 2>&1。 为什么设置要放在后面呢? 具体暂时还不知道,只知道是这么用,放在前面还不行只能放在后面。

比如:

$ git push > log.txt 2>&1

那么这时候,屏幕上就真的不会显示任何东西了,标准输出、标准错误,全部都会存到log.txt文件里了。

关于"1>&2"

这个是比较好用的方法:即程序输出到屏幕,也输出到文件。

常用重定向及解释

参考文章:stackoverflow回答

image

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.

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.

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.

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.

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.

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..

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.

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.

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.

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.

solomonxie commented 5 years ago

Bash脚本常用变量

参考:终端打印、算术运算、常用变量

常用环境变量

set 命令可以查看所有的环境变量,echo $xx可以打印某一个环境变量。

$USER 查看账户信息
$logname 登录相关信息
$UID
$Shell
$HOME 家目录
$pwd
$PATH 用户所输入的命令是在哪些目录中查找
$PS1
$PS2
$RANDOM 随机数
solomonxie commented 5 years ago

❖ epoll方式 (从HTTP Server角度看)[DRAFT]

epoll是Linux内核采用的处理多任务高并发的一种方式。不同于多线程、多进程、协程、非阻塞等方式,epoll只通过单进程、单线程即可完成多任务同时处理。

selectpoll是Unix的内核处理多任务方式,而epoll是在前两者基础上衍生出来的Linux多任务处理方式。

理解epoll

但是epoll和其它那些有本质上的区别:多进程、多线程、协程无论怎么运用,都逃不出Application的内存空间,之后还是要一个一个的复制到Kernel内核中去运行,而且还不是一口气,需要和其它application应用程序互相穿插着、排队去kernel运行。 但是由于我们要处理的是高并发socket问题,那就必然遇到某一些socket等待对方传数据的过程,这就造成了对kernel的极大浪费。

epoll的作用就是,让我们“跳出”Application的盒子,达到Kernel内核级的运算。

现今所有流行的HTTP Servers都是采用的epoll方式来加速处理多任务,包括Apache、Nginx等。包括协程的Python库gevent也是采用epoll底层处理。所有这些多进程、多线程、协程、非堵塞的方式,在和epoll高效率比起来,都是一个天上一个地下。这也就是为什么epoll这么流行的原因。

epoll两大特点:

Socket Reminder: 回想HTTP Server的服务,其中TCP三次握手,每次握手、每次挥手、每次上传、每次下载,都是一次向对方电脑的一个socket文本文件写入的过程。 但是从socket文件建立,到写入数据,到完成写入,是有不确定的时间间隔的。 只有当这个socket写入完成时,作为应用程序的HTTP Server才会去处理。

epoll要消除的,就是socket建立 -> socket写入完成这之间的堵塞时间。

实现epoll

epoll的实现是需要用C语言等最底层的语言去与Linux Kernel沟通才能实现。一般来讲没有kernel级别的编程能力,就只要理解实现原理就够了。

IO多路复用 就是select, poll, epoll,即Event driven I/O的IO方式。

基本工作方式: 采用select -> poll -> epoll模型,不断循环,不断轮询所有socket,只有当某个socket的数据变化了(接收到数据了),才通知用户进程来处理。

solomonxie commented 5 years ago

❖ Bash脚本中的「闭包」

高级语言中的闭包或装饰器,是个非常好用的功能。Bash没有直接的闭包,但是Bash可以将函数作为参数传递给另一个函数,这样我们就可以达到闭包功能了。

参考:Bash: 将函数作为参数传递

function inner() {
    echo "Hello, world!"
}

function outter() {
    echo "before"
    eval $1
    echo "after"
}

outter inner

然后就会输出:

before
Hello world
after
solomonxie commented 5 years ago

❖ Bash脚本判别使用者的身份 (硬核)

经常要在bash脚本里面或者直接对脚本本身加上sudo运行命令,但是这引发了一系列的问题。

比如用sudo的时候,脚本里的~$HOME指代用户文件夹的这个变量,到底是应该指向我真正的用户文件夹如/home/pi呢,还是指向了超级管理员的用户文件夹/root/呢?

实际上它指向了/root/文件夹,这是我们绝对不想要的。但是很多命令如安装个程序,都不得不用sudo,那怎么办?

首先要说下经验:命令行的权限执行,从表现上来看,可以分为以下5种情况:

很多变量、环境变量在这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}
solomonxie commented 5 years ago

❖ xargs和pipline管道 [DRAFT]

pipline管道

xargs

经常配合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选项

-d 即delimiter,分隔符。

xargs既然是处理args的命令,自然少不了args分割。比如"--abc,--bcd,--def",我们可以把用,把字符串分割为三个参数,一起传给下一个命令:

$ echo "-abc,-bcd,-def" | xargs -d "," echo
>>> echo --abc --bcd --def

命令打印 -p选项

-p 即print。

xargs可以把组合成功的整个命令打印出来,然后交互让你选择是否执行:

$ echo "--help" | xargs -p cat
>>> cat --help ?...

参数组合 -n选项

xargs可以把拆分后的多个参数,分组传递给下个命令,比如--restart always --name ubuntu,我们可以把他们用空格分开,然后两个一组传给下个命令:

$ cat "--restart always --name ubuntu" | xargs -n 2 echo
>>> echo --restart always --name ubuntu

参数截取 -E选项

-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
solomonxie commented 5 years ago

Cmake入门 [DRAFT]

Cmake是一个编译程序。

目前官方推荐的安装方法只有编译安装,Mac/Linux都是一样。

参考官方:Installing CMake

# 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
solomonxie commented 5 years ago

❖ Linux GNU Autotools 自动编译工具集 [DRAFT]

注意:Autotools 目前只对C/C++系的语言支持。

参考:绝世秘籍之GNU构建系统与Autotool概念分析

configure脚本用来检测系统环境,最终目的是生成Makefile和config.h。 make命令通过读取Makefile文件,开始构建软件。 make install命令最后将软件安装到需要安装的位置。

configure文件和Makefile编译文件动不动就几千行代码,一般是不会手动写的。都是用GNU出品的Autotools完成。

这套Autotools的大概逻辑就是:只需要自己写configure.acMakefile.am两个文件就够了。

image

"Make is the new Python!"

Mac 安装Autotools

solomonxie commented 5 years ago

Bash的数值运算

整数运算

如果是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
solomonxie commented 5 years ago

Makefile Is The New Python [DRAFT]

"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

Syntax

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

Headers

At the top of Makefile, you can add some useful headers like constant variables or options to make the "script" cleaner.

Sub-make

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.
solomonxie commented 4 years ago

Steganography - The Easiest way to hide information in a picture

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.