Python中格式化字符串更酷的方式

Python中格式化字符串更酷的方式
Python中格式化字符串更酷的方式
在 Python 中,大家都习惯使用 %s 或 format 来格式化字符串,在 Python 3.6 中,有了一个新的选择 f-string。
使用对比
我们先来看下 Python 中已经存在的这几种格式化字符串的使用比较。
# %s
username = ‘tom’
action = ‘payment’
message = ‘User %s has logged in and did an action %s.’ % (username, action)
print(message)
# format
username = ‘tom’
action = ‘payment’
message = ‘User {} has logged in and did an action {}.’.format(username, action)
print(message)
# f-string
username = ‘tom’
action = ‘payment’
message = f’User {user} has logged in and did an action {action}.’
print(message)
f”{2 * 3}”
# 6
comedian = {‘name’: ‘Tom’, ‘age’: 20}
f”The comedian is {comedian[‘name’]}, aged {comedian[‘age’]}.”
# ‘The comedian is Tom, aged 20.’
相比于常见的字符串格式符 %s 或 format 方法,f-strings 直接在占位符中插入变量显得更加方便,也更好理解。
方便的转换器
f-string 是当前*佳的拼接字符串的形式,拥有更强大的功能,我们再来看一下 f-string 的结构。
f ‘ <text> { <expression> <optional !s, !r, or !a> <optional : format specifier> } <text> … ‘
其中 ‘!s’ 调用表达式上的 str(),’!r’ 调用表达式上的 repr(),’!a’ 调用表达式上的 ascii()。大家可以看看下面的例子。
class Person:
    def __init__(self, name, nickname):
        self.name = name
        self.nickname = nickame
    def __str__(self):
        return self.name
    def __repr__(self):
        return self.nickname
person = Person(‘王大锤’, ‘Wang Gangdan’)
print(f'{person!s}’)
print(f'{person!r}’)
print(f'{person.name!a}’)
print(f'{person.nickname!a}’)
性能
f-string 除了提供强大的格式化功能之外,还是这三种格式化方式中性能*高的实现。
>>> import timeit
>>> timeit.timeit(“””name = “Eric”
… age = 74
… ‘%s is %s.’ % (name, age)”””, number = 10000)
0.003324444866599663
>>> timeit.timeit(“””name = “Eric”
… age = 74
… ‘{} is {}.’.format(name, age)”””, number = 10000)
0.004242089427570761
>>> timeit.timeit(“””name = “Eric”
… age = 74
… f'{name} is {age}.'”””, number = 10000)
0.0024820892040722242
坦白的说,f-string 就是字符串 format 方法一个语法糖,但它进一步简化了格式化字符串的操作并带来了性能上的提升。使用 Python 3.6+ 的同学,使用 f-string 来代替你的 format 函数,以获得更强大的功能和更高的性能。

爬虫到底违法吗?你离违法还有多远?

爬虫到底违法吗?你离违法还有多远?
*近,国家依法查处了部分编写爬虫程序,盗取其他公司数据的不良企业。一时间风声鹤唳,关于爬虫程序是否违法的讨论遍布程序员圈子。那么到底编写爬虫程序是否违法呢?
其爬虫下载数据,一般而言都不违法,因为爬虫爬取的数据同行也是网站上用户打开页面能够看到的数据,但是如果符合下列条件的网站进行强行数据采集时,会具有法律风险。
采集的站点有声明禁止爬虫采集时。
2. 网站通过Robots协议拒*采集时。
Robots协议(也称为爬虫协议、机器人协议等)的全称是“网络爬虫排除标准”(Robots Exclusion Protocol)。网站通过Robots协议告诉爬虫哪些页面可以抓取,哪些页面不能抓取。如果想查看一个网站的Robots协议,可以打开位于网站根目录下的robots.txt文件即可,例如:https://www.jd.com/robots.txt。
如果因为爬虫的问题产生官司,通常如果对方能够举证你的爬虫有破坏动产(如服务器)的行为,那么基本上打官司你会败诉并要求做出赔偿。
爬虫二十问
以下是业界某位大神关于爬虫相关问题的回答。
非爬虫方向的技术转行做爬虫是否可行?
答:可行,而且有一定的基础会很容易上手,至于深入的部分就看自己了。
2. 非技术转行做爬虫是否可行?
答:可行,但我认为较难,因为爬虫做深了以后是需要你了解各种相关领域知识的,而你现在对这些领域的东西一无所知,甚至可能连编程都还不知道怎么开始,起点会比有基础的人低很多。
3. 爬虫工作日常如何?加班多不多?
答:这个得看公司的,有些公司搞得都是些天天更新反爬的平台(比如工商信息相关的),那基本就是得一直盯着看会不会出问题,一不小心就会要加班。
4. 爬虫对于学生党的用处体现在哪些地方?
答:这个问题看个人,因为爬虫技术可用的地方太多了,没法一个一个地都拿出来说。比如你想搞个自动签到的工具,这其实本质上就是爬虫;比如你想搞个自动回复设定内容的机器人,这其实本质上也是爬虫。
5. 学到什么程度才能入职爬虫工程师?
答:我觉得首先发请求不用说了吧?抓包工具的使用也不用说了吧?熟练掌握XPath、正则表达式这种解析工具也是基本的,然后JSON之类的传输格式至少要了解过长啥样吧,再就是JS逆向总得会一点吧(从只改变量名函数名混淆级别的代码中找出加密参数生成部分的程度)。差不多会这些以后,再自己做几个项目,应聘个初级爬虫工程师没啥问题。
6. 如何成为一名优秀的爬虫工程师?
答:垂直爬虫做到后面本质上就是逆向,你需要有良好的逆向思维方式,并且对一些安全领域的骚东西也有一定的了解,这样你才能游刃有余地处理高难度的反爬。
7. 学爬虫的学习路线?
答:有一些Python基础就可以做爬虫了,主要是数据获取、数据解析、数据预处理、数据持久化这方面的东西,然后是一些三方库和框架,如Scrapy、Selenium WebDriver等。
8. 大约学习并从事爬虫几年才可以达到一个不错的高度?
答:这个问题也很看个人,我觉得主要看有没有需求逼迫成长吧。之前招人的时候,很多三年经验的也就比入门水平稍微好一点,他们在工作时遇到的难点几乎全是依靠自动化测试工具解决的,对逆向水平毫无增长。所以建议还是多依靠逆向手段去解决问题,成长速度会很快。
9. 薪资方面如何,在几年内可以达到15K?
答:同上,标15K及以上的招聘还是挺多的,看看招聘需求就知道大概到什么程度了。
10. 面试爬虫哪些技能点是加分项?
答:丰富且有深度的逆向经验、熟悉通信协议底层实现、有过哪些骚操作经历等,但主要还是逆向经验和反爬方面的经验。
11. 作为一名爬虫工程师,对该岗位的前景如何看待?
答:未来主要内容在App上的平台应该会越来越多,难度也会越来越高,所以对于爬虫工程师的逆向水平要求会越来越高,只会简单逆向甚至不会逆向的人找工作会越来越难。
12. 爬虫和数据挖掘是一样的吗?
答:不一样,爬虫只是将数据取回来,具体怎么分析才是数据挖掘的事情。
13. 爬虫是否和黑客差不多?
答:差很多,与上个问题类似,只不过“黑客”这个词太宽泛了,黑客也是有具体方向的。
14.千奇百怪的验证码只能对接打码平台吗?有啥其他办法?
答:自己破呗,逆向+机器学习。
15. 如何爬x平台?
答:涉及法律问题,这种针对某个平台的东西是不能细说的。
16. 爬虫违法吗?如何避免过线导致的违法?怎么规避法律风险?
答:算是擦边球吧,其实你即使遵守规则去爬别人的网站,只要人家想搞你,还是可以让你做的事情变成违法的。所以建议不要做太过分的事情,毕竟狗急了也会跳墙。
还有就是不要为一些明显是做灰黑产的人/公司写代码,一旦他们出事了,你也会被牵连。
知乎上之前那个很火的被抓了的人,从回答内容中来看其实就是做打码平台的那个微凉,他这一个平台据说赚了至少千万,主要应该是提供给做黑产的人使用了,这种情况下被抓是迟早的事。*好的避免违法的办法就是明显觉得不太好的事情就不要去碰,基本就不会有啥问题。
17. 如何有目的地爬取到真正想要的数据?
答:让需要数据的人提需求,如果你自己就是那个需要数据的人,那就去做市场调研,看看你需要的数据在哪里能找到。
18. 反爬虫*先进的技术是什么?*有效的技术是什么?
答:*先进的技术其实就是使用在PC平台上已经玩烂的各种反破解技术将行为监测点(设备指纹、用户操作等)隐藏起来,然后传给服务端做行为识别,如果操作非人类或者缺少某些东西就触发风控。
*有效的技术其实不是技术而是方法,这个方法就是账号收费,将你的数据变成需要花多少钱才能看到这样子的,就能做到啥高端技术都不用上、轻松提高爬虫方的获取数据成本的效果,当然这也需要结合良好的产品设计,否则普通用户的体验会很差。
19. 请问爬虫在x领域有哪些应用?
答:这个应该是对应领域的人自己思考一下自己拿到那些公开数据究竟可以做什么。
20. 需要大量账号的平台成本过高该怎么办?
答:人家就是依靠这种方式来提高你成本的,你如果觉得成本过高要么放弃要么换一条路线获取数据。

用 pprint 代替 print 更友好的打印调试信息

用 pprint 代替 print 更友好的打印调试信息
pprint 是 “pretty printer” 的简写,“pretty” 的含义是 “漂亮的、美观的”,因此 pprint 的含义便是:漂亮的打印。
这是个相当简单却有用的模块,主要用于打印复杂的数据结构对象,例如多层嵌套的列表、元组和字典等。
先看看 print() 打印的一个例子:
mylist = [“Beautiful is better than ugly.”, “Explicit is better than implicit.”, “Simple is better than complex.”, “Complex is better than complicated.”]
print(mylist)
[‘Beautiful is better than ugly.’, ‘Explicit is better than implicit.’, ‘Simple is better than complex.’, ‘Complex is better than complicated.’]
这是一个简单的例子,全部打印在一行里。如果对象中的元素是多层嵌套的内容(例如复杂的 字典 数据),再打印出那肯定是一团糟的,不好阅读。
使用 pprint 模块的 pprint() 替代 print(),可以解决如下痛点:
设置合适的行宽度,作适当的换行
设置打印的缩进、层级,进行格式化打印
判断对象中是否有无限循环,并优化打印内容
基本使用
pprint(object, stream=None, indent=1, width=80, depth=None, *,compact=False)
默认的行宽度参数为 80,当打印的字符小于 80 时,pprint() 基本上等同于内置函数 print(),当字符超出时,它会作美化,进行格式化输出。
import pprint
mylist = [“Beautiful is better than ugly.”, “Explicit is better than implicit.”, “Simple is better than complex.”, “Complex is better than complicated.”]
pprint.pprint(mylist)
# 超出80字符,打印的元素是换行的
[‘Beautiful is better than ugly.’,
 ‘Explicit is better than implicit.’,
 ‘Simple is better than complex.’,
 ‘Complex is better than complicated.’]
设置缩进
pprint.pprint(mylist, indent=4)
[   ‘Beautiful is better than ugly.’,
    ‘Explicit is better than implicit.’,
    ‘Simple is better than complex.’,
    ‘Complex is better than complicated.’]
设置打印行宽
mydict = {‘students’: [{‘name’:’Tom’, ‘age’: 18},{‘name’:’Jerry’, ‘age’: 19}]}
pprint.pprint(mydict)
# 正常打印
{‘students’: [{‘age’: 18, ‘name’: ‘Tom’}, {‘age’: 19, ‘name’: ‘Jerry’}]}
pprint.pprint(mydict, width=20)
# 行宽为 20
{‘students’: [{‘age’: 18,
 ‘name’: ‘Tom’},
              {‘age’: 19,
 ‘name’: ‘Jerry’}]}
pprint.pprint(mydict, width=70)
# 行宽为 70
{‘students’: [{‘age’: 18, ‘name’: ‘Tom’},
              {‘age’: 19, ‘name’: ‘Jerry’}]}
设置打印层级
newlist = [1, [2, [3, [4, [5]]]]]
pprint.pprint(newlist, depth=3)
# 超出的层级会用 … 表示
[1, [2, [3, […]]]]
用 pprint 替换 print
import pprint
print = pprint.pprint
mylist = [“Beautiful is better than ugly.”, “Explicit is better than implicit.”, “Simple is better than complex.”, “Complex is better than complicated.”]
print(mylist)
[‘Beautiful is better than ugly.’,
 ‘Explicit is better than implicit.’,
 ‘Simple is better than complex.’,
 ‘Complex is better than complicated.’]

Linux系统下如何运行.sh文件

在Linux系统下运行.sh文件有两种方法,比如我在root目录下有个datelog.sh文件

*种(这种办法需要用chmod使得文件具备执行条件(x): chmod u+x datelog.sh):

1、在任何路径下,输入该文件的*对路径/root/datelog.sh就可执行该文件(当然要在权限允许情况下)

%title插图%num

2、cd到datelog.sh文件的目录下,然后执行./datelog.sh

%title插图%num

第二种(这种办法不需要文件具备可执行的权限也可运行):

1、在该文件路径下sh加上文件名字即可,sh datelog.sh

%title插图%num

2、在任意路径下,sh 加上文件路径及文件名称:sh /root/ datelog.sh

%title插图%num

adb shell 执行sh脚本_Bash技巧:一个可以通过命令简写执行对应命令的Shell脚本

本篇文章介绍一个在 Linux 系统上可以通过命令简写执行对应命令的 shell 脚本。

假设这个 shell 脚本的名称为 tinyshell.sh。

在 Linux 下进行项目开发,经常会用到一些调试开发命令。

这些命令可能比较长,需要输入多个字符。

例如,Android 系统抓取全部 log 并包含 log 时间的命令是 adb logcat -b all -v threadtime。

抓取 log 是调试开发非常常见的操作,这个命令又很长,输入起来不方便。

为了简化输入,可以配置一些命令简写来对应比较长命令。

例如,配置 ala 对应 adb logcat -b all -v threadtime。

把 als 作为参数传递给当前的 tinyshell.sh 脚本,会执行该命令简写对应的命令。

这样只需要输入比较少的字符,就能执行比较长的命令。

实际上,这个功能类似于 bash 的 alias 别名,只是将这些别名统一放到该脚本来处理。

可以把 tinyshell.sh 脚本作为学习 shell 脚本的参考例子,独立维护更新,根据需要扩充更多的功能。

配置命令简写
如之前说明,可以用 ala 表示 adb logcat -b all -v threadtime 这个命令。

这个 ala 称之为 “命令简写”。

命令简写使用一些简单的字符来表示特定的命令。

可以在命令简写后面动态提供命令的参数。

为了方便动态添加、删除、查询命令简写,可以把这些命令简写保存在一个配置文件里面。

在执行 tinyshell.sh 脚本时,会读取配置文件内容,获取到各个配置项的值。

配置项的基本格式是:命令简写|命令内容

每个配置项占据一行。每一行默认以*个竖线 ‘|’ 隔开命令简写和命令内容。

一个参考的配置文件内容如下所示:

ll|ls –color=auto -lala|adb logcat -b all -v threadtimegl|git loggp|git pull –stat –no-tags $(git remote) $(git rev-parse –abbrev-ref HEAD)
这里配置的命令内容可以是系统支持的任意命令。

解析配置文件时,需要用到之前文章介绍的 parsecfg.sh 脚本。

要获取 parsecfg.sh 脚本的代码,可以查看之前的文章。

后面会提供具体测试的例子,可供参考。

脚本代码
列出 tinyshell.sh 脚本的具体代码如下所示。

在这个代码中,对大部分关键代码都提供了详细的注释,方便阅读。

这篇文章的后面也会对一些关键点进行说明,有助理解。

#!/bin/bash -i# 使用 bash 的 -i 选项,让该脚本在交互模式下运行.# 实现一个小型的 shell. 支持内置命令、命令简写. 如果提供这两种命令之外# 的其他命令,会尝试在 bash 中直接执行所给命令,可以执行系统支持的命令.# 命令简写指的是一些简单的字符,会对应一串实际要执行的命令.只要输入命令# 简写就可以执行对应的命令,减少需要输入的字符.命令简写在配置文件中配置.# 下面变量指定默认解析的配置文件名.该文件配置了命令简写、以及对应的命令.# 这个 tinyshellcmds.txt 文件需要预先配置好,放到指定路径的目录底下.# 直接修改这个配置文件,就可以动态添加或删除命令简写.不需要修改脚本代码.SHORT_COMMANDS=”${HOME}/.liconfig/tinyshellcmds.txt”# PARSECFG_filepath 是 parsecfg.sh 脚本里面的变量. 如果这个变量为空,# 说明还没有打开过配置文件,进入下面的分支打开默认的配置文件.if [ -z “$PARSECFG_filepath” ]; then # 导入解析配置文件的脚本,以便调用该脚本的函数来解析配置文件. source parsecfg.sh # 调用 parsecfg.sh 里面的 open_config_file() 函数解析配置文件. # 如果配置文件不存在,会返回 1,经过’!’操作符取反为 0,会退出执行. if ! open_config_file “$SHORT_COMMANDS”; then exit 2 fifi# 下面变量指定 tiny shell 的提示字符串.PROMPT=”TinySh>>> “# 下面使用 basename 命令来提取出脚本的文件名,去掉目录路径部分.show_help(){printf “USAGE $(basename $0) [option] [shortcmd [argument1 … [argumentn]]]OPTIONS option: 可选的选项参数. 支持的选项参数描述如下: -h: 打印这个帮助信息. -l: 打印配置文件本身的内容,会列出配置的命令简写和对应的命令. -v: 以键值对的方式列出命令简写和对应的命令. -i: 在配置文件中查找指定内容.后面跟着一个参数,指定要查找的内容. -e: 使用 vim 打开脚本的配置文件,以供编辑. -a: 新增或修改一个命令简写和对应的命令.后面跟着一个参数,用 单引号括起来,以指定命令简写和命令. 格式为: 命令简写|命令. 例如 -a ‘p|git pull’,如果p简写不存在则新增它,否则修改它. -d: 从脚本配置文件中删除一个命令简写和对应的命令.后面跟着一个 参数,指定要删除的命令简写.例如 -d s,会删除命令简写为 s 的行. shortcmd: 可选选项. 指定要直接执行的命令简写. 提供命令简写参数,不会进入 tiny shell. argument1 … argumentn: 可选选项. 指定该命令简写的参数. 命令简写对应一个命令,支持动态提供参数.NOTE 如果没有提供任何参数,默认会进入 tiny shell 解释器. 在 tiny shell 中 接收用户输入并执行对应的命令.直到读取到EOF、或者执行quit命令才会退出.”}# tiny shell 的内置命令数组. 这是一个关联数组. 数组元素的# 键名是内置命令名. 数组元素的键值是响应内置命令的函数名.declare -A BUILTIN_COMMAND=( [help]=”builtin_command_help” [quit]=”builtin_command_quit” [debug]=”builtin_command_debug” )# bash 的 help 命令默认会打印内置命令列表. 这里仿照这个行为,# 让 help 内置命令打印内置命令列表、以及配置文件包含的命令简写.builtin_command_help(){printf “下面列出 Tiny Shell 支持的内置命令列表和配置的命令简写列表.输入内置命令名或命令简写,会执行对应的命令.也可以输入系统自身支持的命令,会在 bash 中执行所给命令.内置命令列表: debug: 所给*个参数指定打开、或关闭调试功能. 其参数说明如下: on: 打开调试功能,会执行 bash 的 set -x 命令 off: 关闭调试功能,会执行 bash 的 set +x 命令 help: 打印当前帮助信息. quit: 退出当前 Tiny Shell.命令简写列表:” # 调用 parsecfg.sh 的 handle_config_option -v 打印命令简写列表 handle_config_option -v}# quit 内置命令. 执行该命令会退出整个脚本,从而退出当前 tiny shell.builtin_command_quit(){ exit}# debug 内置命令. 所给*个参数指定打开、或关闭调试功能.# debug on: 打开调试功能,会执行 bash 的 set -x 命令# debug off: 关闭调试功能,会执行 bash 的 set +x 命令builtin_command_debug(){ if [ $# -ne 1 ]; then echo “Usage: debug on/off” return 1 fi if [ “$1” == “on” ]; then set -x elif [ “$1” == “off” ]; then set +x else echo -e “Unknown argument: $1Usage: debug on/off” fi return}# 处理 tiny shell 内置命令.对于内置命令,会调用对应函数进行处理.# 该函数的返回值表示所给命令名是否内置命令.# 返回 0, 表示是内置命令. 返回 1, 表示不是内置命令.execute_builtin_command(){ # 在传递过来的参数中,*个参数是命令名,剩余的参数是该命令的参数. local cmdname=”$1″ # 从 BUILTIN_COMMAND 数组中获取所给命令对应的处理函数. # 如果所给命令不是内置命令,会获取为空. local cmdfunc=”${BUILTIN_COMMAND[“${cmdname}”]}” if [ -n “${cmdfunc}” ]; then # 将位置参数左移一位,移除命令名,剩下的就是该命令的参数. shift 1 ${cmdfunc} “$@” # 无论执行内置命令是否报错,都会返回 0,表示该命令是内置命令. return 0 else return 1 fi}# 处理 tiny shell 的命令简写.在所解析的配置文件中包含了支持的命令简写.# 该函数的返回值表示所给命令名是否命令简写.# 返回 0, 表示是命令简写. 返回 1, 表示不是命令简写.execute_short_command(){ # 判断所给的参数是否对应配置文件中的某个键名.如果是,将取出键值. local key=”$1″ # 从配置文件中获取所给命令简写对应要执行的命令 local cmd_value=$(get_value_by_key “${key}”) if test -n “${cmd_value}”; then # 将位置参数左移一位,移除命令简写,剩下的就是命令的参数. shift 1 # 下面要用 “$*” 来把所有参数组合成一个参数,再跟命令内容一起传入 # bach -c,确保 bash -c 把命令内容和所有参数都当成要执行的命令 bash -c “$cmd_value $*” # 打印命令简写,以及该简写对应的命令,以便查看具体执行了什么命令. # 先执行命令,再打印命令内容. 由于有些命令的输出很多,先打印命令 # 内容的话,需要拉动终端滚动条,才能找到打印的命令内容,不便于查看. echo -e “e[33m命令简写: ${key}. 命令: ${cmd_value} $*e[0m” return 0 else # 如果获取到的键值为空,表示所给键名不是有效的命令简写,返回 1 return 1 fi}# 处理所给的内容.这个内容可能是内置命令,命令简写,或者命令本身.handle_input_command(){ # 所给参数是要执行的命令名、以及命令参数. 如果命令名是配置的 # 命令简写,会把该命令简写替换成对应的命令,再进行对应的命令. local inputcmd=”$@” # if 语句可以直接判断命令返回值是否为 0,并不是只能搭配 [ 命令使用. # 注意: 由于有的 tiny shell 内置命令接收参数,下面的 ${cmd_line} # 不能用双引号括起来,否则多个参数会被当成一个参数. if execute_builtin_command ${inputcmd}; then # 先调用 execute_builtin_command 函数处理内置命令.如果所给 # 命令是内置命令,则调用对应的函数进行处理,且不再往下执行. return 0 elif execute_short_command ${inputcmd}; then # 调用 execute_short_command 函数处理命令简写. return 0 else # 对于 tiny shell 不能执行的命令,尝试用 bash -c 在 bash 中执行. bash -c “${inputcmd}” # 当 return 命令不加具体状态码时,它会返回上一条执行命令的状态码. return fi}# SIGINT 信号的处理函数.目前不做特殊处理,只是想在输入CTRL-C后,不会终止# 当前 tiny shell. 输入 CTRL-C 还是可以终止 tiny shell 启动的子 shell.sigint_handler(){ # 当输入 CTRL-C 后,终端只显示”^C”,但是不会自动换行,需要输入回车才会 # 换行,并重新输出提示字符串. 而在交互式Bash中,输入”^C”后,就会自动回 # 车,并输出提示字符串.这里模仿这个行为,先输出一个回车,再输出提示符. printf “${PROMPT}”}# 启动 tiny shell 解释器. 从标准输入不停读取、并执行所给命令.直到# 使用 CTRL-D 输入 EOF 为止, 或者输入 quit 命令退出当前解释器.start_tinyshell(){ # 执行 python 命令,默认会打印 python 版本号和一句帮助提示. # 这里仿照这个行为,打印 tiny shel 版本号和一句帮助提示. echo -e “Tiny shell 1.0.0Type ‘help’ for more information.” # 捕获SIGINT信号,以便输入 CTRL-C 后,不会退出当前的 tiny shell. # 注意: 由于子shell会继承父shell所忽略的信号,所以不能将 SIGINT 信号 # 设成忽略,而是要指定一个处理函数. 当前 shell 所捕获的信号不会被 # 子 shell 继承. 所以子 shell 还是可以被 CTRL-C 终止. 即,指定信号处理 # 函数后,当前 tiny shell 不会被CTRL-C终止.但是当前 tiny shell 执行的 # 命令会运行在子 shell 下,可以用 CTRL-C 终止运行在子 shell 下的命令. # 查看 man bash 对子 shell 的信号继承关系说明如下: # traps caught by the shell are reset to the values inherited from # the shell’s parent, and traps ignored by the shell are ignored trap “sigint_handler” SIGINT # 如果不使用 -e 选项,输入上光标键, read 会读取到 “^[[A”;输入下光标键, # read 会读取到 “^[[B”.而使用 -e 选项后,输入上下光标键,不会读取到乱码, # 但是在子shell中,也不会返回历史命令.因为shell脚本是在非交互模式下执行. # 可以使用 bash 的 -i 选项让脚本在交互模式下运行,例如: “#/bin/bash -i” while read -ep “${PROMPT}” input; do # 传递参数给函数时,参数要用双引号括起来,避免参数带有空格时,会拆分 # 成多个参数. 当输入CTRL-C时, tiny shell 捕获了这个信号,不会退出 # 当前的 tiny shell.但是read命令会被中断,此时读取到的 input 为空. # 不需要对空行做处理,所以下面先判断 input 变量值是否为空. if [ -n “${input}” ]; then handle_input_command “${input}” # 执行 history -s 命令把所给的参数添加到当前历史记录中.后续 # 通过上下光标键获取历史命令,就可以获取到新添加的命令.这个只 # 影响当前 tiny shell 的历史记录,不会写入外部shell的历史记录. history -s “${input}” fi done # 输出一个换行.当用户输入CTRL-D结束执行后,需要换行显示原先的终端提示符. echo}# 循环调用 getopts 命令处理选项参数.while getopts “hlvi:ea:d:” opt; do # 调用parsecfg.sh脚本处理选项的函数来处理 “lvi:ea:d:” 这几个选项. # 如果处理成功,就直接继续读取下一个选项,不再往下处理. # handle_config_option()函数要求传入的选项以’-‘开头,而getopts命令 # 返回的选项不带有’-‘,所以下面在 ${opt} 前面加上一个 ‘-‘. handle_config_option “-${opt}” “${OPTARG}” if [ $? -ne 127 ]; then continue fi case “$opt” in h) show_help ;; ?) echo “出错: 异常选项,请使用 -h 选项查看脚本的帮助说明.” ;; esacdone# $# 大于0,说明提供了命令参数. $# 等于OPTIND减去1,说明传入的参数都# 是以 ‘-‘ 开头的选项参数. 此时,直接结束执行,不需要再往下处理.# 下面的 -a 表示两个表达式都为真时才为真.表达式之间不要加小括号.# Shell里面的小括号有特殊含义,跟C语言的小括号有些区别,加上会有问题.if [ $# -gt 0 -a $# -eq $((OPTIND-1)) ]; then exit 0fiif [ $# -eq 0 ]; then # 当不带任何参数时,默认启用 tiny shell. start_tinyshellelse # 左移所给的命令参数,去掉已处理过的选项参数,只剩下非选项参数. shift $((OPTIND-1)) # 执行脚本时,如果提供了非选项参数,那么*个参数认为是命令简写, # 需要执行该命令简写对应的命令. *个参数之后的所有参数认为是 # 命令的参数. 即,可以在命令简写之后提供参数来动态指定一些操作. execute_short_command “$@”fiexit
代码关键点说明
使用 trap 命令捕获信号
在 bash 中,可以使用 trap 命令捕获信号,并指定信号处理函数。

捕获信号后,可以避免收到某个信号终止脚本执行。

当前 tinyshell.sh 脚本使用 trap 命令捕获 SIGINT 信号。

也就是 CTRL-C 键所发送的信号,避免按 CTRL-C 键会退出当前 tiny shell。

要注意的是,不能设置成忽略 SIGINT 信号。

在 bash 中,父 shell 所忽略的信号,也会被子 shell 所忽略。

除了内置命令之外,当前 tiny shell 所执行的命令运行在子 shell 下。

如果设置成忽略 SIGINT 信号,那么子 shell 也会忽略这个信号。

那么就不能用 CTRL-C 来终止子 shell 命令的执行。

例如,Android 系统的 adb logcat 命令会不停打印 log,需要按 CTRL-C 来终止。

此时,在 tiny shell 里面按 CTRL-C 就不能终止 adb logcat 的执行。

父 shell 所捕获的信号,子 shell 不会继承父 shell 所捕获的信号。

子 shell 会继承父 shell 的父进程的信号状态。

父 shell 的父进程一般是外部 bash shell 进程。

而 bash shell 进程默认捕获SIGINT并终止前台进程。

即,虽然当前 tiny shell 捕获了 SIGINT 信号,但是子 shell 并没有捕获该信号。

可以在 tiny shell 使用 CTRL-C 来终止子 shell 命令的执行。

使用 history -s 命令添加历史记录
在 tiny shell 执行命令后,默认不能用上下光标键查找到 tiny shell 自身执行的历史命令。

为了可以查找到 tiny shell 自身执行的历史命令,使用 history -s 命令添加命令到当前 shell 的历史记录。

这个命令只会影响当前 shell 的历史记录。

退出当前 shell 后,在外部 shell 还是看不到 tiny shell 所执行的命令。

由于这个 tiny shell 主要是为了执行命令简写。

这些命令简写只有 tiny shell 自身支持,不需要添加到 bash shell 的历史记录。

如果想要命令历史信息添加到外部 shell 的历史记录,可以在退出 tinyshell.sh 脚本之前,执行 history -w ~/.bash_history 命令把历史记录写入到 bash 自身的历史记录文件。

测试例子
把 tinyshell.sh 脚本放到 PATH 变量指定的可寻址目录下。

查看 tinyshell.sh 脚本代码,可知要解析的配置文件名是 tinyshellcmds.txt。

把前面贴出的命令简写配置信息写入 tinyshellcmds.txt 文件。

把这个文件放到 HOME 目录的 .liconfig 目录下。

之后,就可以开始执行 tinyshell.sh 脚本。

当前的 tinyshell.sh 脚本可以执行内置命令、命令简写对应的命令、系统自身支持的命令。

当不提供任何命令参数时,会进入 tiny shell。

在 tiny shell 中,会不停接收用户输入并执行对应命令。

直到读取到 EOF 、或者执行 quit 命令才会退出 tiny shell。

处理选项参数和直接处理命令简写的例子
下面是不进入 tiny shell,只处理选项参数和命令简写的例子:

$ tinyshell.sh -vkey=’gl’ value=’git log’key=’gp’ value=’git pull –stat –no-tags $(git remote) $(git rev-parse –abbrev-ref HEAD)’key=’ll’ value=’ls –color=auto -l’key=’ala’ value=’adb logcat -b all -v threadtime’$ tinyshell.sh ll-rwxrwxr-x 1 xxx xxx 964 11月 14 17:37 tinyshell.sh命令简写: ll. 命令: ls –color=auto -l
这里先执行 tinyshell.sh -v 命令,用键值对的形式列出支持的命令简写。

此时,只处理所给的选项参数,不会进入 tiny shell 里面。

tinyshell.sh ll 命令,提供了一个 ll 参数(两个小写字母 l)。

这个参数会被当成命令简写,然后执行该命令简写对应的命令。

执行结束后,不会进入 tiny shell 里面。

基于刚才列出的命令简写,可知 ll 对应 ls –color=auto -l 命令。

实际执行的也是这个命令。

进入 tiny shell 循环处理命令的例子
当不提供任何命令参数时,会进入 tiny shell 里面,循环处理命令。

具体例子如下所示:

$ tinyshell.shTiny shell 1.0.0Type ‘help’ for more information.TinySh>>> help下面列出 Tiny Shell 支持的内置命令列表和配置的命令简写列表.输入内置命令名或命令简写,会执行对应的命令.也可以输入系统自身支持的命令,会在 bash 中执行所给命令.内置命令列表: debug: 所给*个参数指定打开、或关闭调试功能. 其参数说明如下: on: 打开调试功能,会执行 bash 的 set -x 命令 off: 关闭调试功能,会执行 bash 的 set +x 命令 help: 打印当前帮助信息. quit: 退出当前 Tiny Shell.命令简写列表:key=’gl’ value=’git log’key=’gp’ value=’git pull –stat –no-tags $(git remote) $(git rev-parse –abbrev-ref HEAD)’key=’ll’ value=’ls –color=auto -l’key=’ala’ value=’adb logcat -b all -v threadtime’TinySh>>> date2019年 12月 31日 星期二 17:46:41 CSTTinySh>>> ll -Ctinyshell.sh命令简写: ll. 命令: ls –color=auto -l -C
当执行 tinyshell.sh 命令会进入 tiny shell 时,会打印一个 “TinySh>>>” 提示符。

在 tiny shell 中执行 help 命令可以查看支持的内置命令和命令简写。

在 tiny shell 中执行 date 命令打印当前的日期和时间。

当前的 tiny shell 自身不支持 date 命令。

这里执行了系统自身的 date 命令。

*后执行 ll -C 命令。

这里的 ll 是命令简写。后面的 -C 是对应命令的参数。

具体执行的命令是 ls –color=auto -l -C。

ls 命令的 -C 选项会多列显示文件名,覆盖了 -l 选项的效果。

由于 -l 选项的效果被覆盖,输出结果没有打印文件的详细信息,只列出文件名。

可以看到,在命令简写之后,可以再提供其他的命令参数。

即,可以只配置比较长的命令前缀部分,一些简单的参数可以动态提供。

不需要在配置文件中添加很多内容相似、只有细微差异的配置项。

shell脚本名称直接执行sh脚本

在实际的操作中,不会有人真的把所有的脚本都加一遍到path中,而且,也不能每一次在别的目录中新增脚本,就增加一次到path路径中。所以,此时应该csh
修改/etc/passwd 把对应用户修改成/bin/csh
如果没有csh,需要安装
切到用户根目录,新增.cshrc文件,内容如下(以下是我的配置,可以根据实际情况修改):

# @(#)cshrc 1.11 89/11/29 SMI
##################################
# for general stuff
##################################

umask 022
set filec

setenv USER `whoami`
set prompt=`hostname`-$USER%

set path=(. /usr/local/bin /usr/ccs/bin /usr/bin /usr/ucb /usr/sbin /etc /usr/atria/bin $path)
if ( $?prompt ) then
set history=100
endif
setenv TERM vt100
setenv EDITOR vi
setenv MANPATH /usr/man:/usr/local/man:
setenv PATH ${HOME}/java/bin:$PATH
setenv JAVA_HOME ${HOME}/java

##################################
# for chinese input
##################################
setenv LANG zh_CN
##################################

##################################
# for aliases
##################################
alias h history
alias ls ls -F
alias sc source ~/.cshrc

##################################
# for java heapdump and javacore
##################################

执行shell脚本三种方法的区别:(sh、exec、source)

一、概念对比
sh 方式
使用$ sh script.sh执行脚本时,当前shell是父进程,生成一个子shell进程,在子shell中执行脚本。脚本执行完毕,退出子shell,回到当前shell。

./script.sh与 sh script.sh等效。

source方式
使用$ source script.sh方式,在当前上下文中执行脚本,不会生成新的进程。脚本执行完毕,回到当前shell。
source方式也叫点命令。

. script.sh与 source script.sh等效。

exec方式
使用exec command方式,会用command进程替换当前shell进程,并且保持PID不变。执行完毕,直接退出,不回到之前的shell环境。

二、测试验证
loop.sh脚本
vi loop.sh
#!/bin/sh
while [ 1 = 1 ]; do
echo $$
sleep 1
done

#显示当前进程
[root@izuf61vzhmpllsrk4narjxz /]# echo $$
6770
# sh的方式:执行loop.sh打印执行进程
[root@izuf61vzhmpllsrk4narjxz /]# sh loop.sh
13736
13736
# source方式:执行loop.sh打印执行进程
[root@izuf61vzhmpllsrk4narjxz /]# source loop.sh
6770
6770
# source方式:执行loop.sh打印执行进程
[root@izuf61vzhmpllsrk4narjxz /]# exec ./loop.sh
6770
6770
# 按下ctrl+C
^CConnection closing…Socket close.

Connection closed by foreign host.

Disconnected from remote host(测试实例) at 09:57:54.

Type `help’ to learn how to use Xshell prompt.
[E:\~]$

结论:

sh方式:父进程是6770,执行loop.sh时的子进程是13736。执行完毕后回到父进程shell。
source方式:父进程和子进程都是6770(执行时没有新的进程),执行完毕会回到父进程shell。
exec方式:进程PID没有改变都是6770,执行完毕(ctrl+C强制关闭)时直接退出了shell。脚本执行时替换了父进程的shell,执行完毕后直接退出,没有回到之前的shell。
实际经验说明
工作中我曾用脚本中 执行下面命令,用来关闭tomcat。但catalina.sh报错后,我的脚本直接中断。后面其他代码都不再执行了。

exec “$Tomcat_Home”/bin/catalina.sh” stop。
1
所以修改为sh执行,无论exec是否报错,catalina.sh执行完毕后会回到原来shell。我脚本中的后续代码是继续执行的。这个非常有用。

sh”$Tomcat_Home”/bin/catalina.sh” stop。
1
三、使用sh和source方式对上下文的影响
在sh和source方式下,脚本执行完毕,都会回到之前的shell中。但是两种方式对上下文的影响不同。

此例中,jump.sh脚本执行如下操作:1)跳到/,2)打印当前工作目录,3)打印Hello。
jump.sh脚本

$ vi jump.sh
#!/bin/sh
cd /
pwd
echo Hello

# sh 方式执行完毕后,目录还是原来的目录
[root@izuf61vzhmpllsrk4narjxz test]# sh jump.sh
/
Hello
# source执行完毕后 目录变成了/
[root@izuf61vzhmpllsrk4narjxz test]# source jump.sh
/
Hello

结果分析:

执行结果输出一致,不同在于source执行完毕后目录变成了 /

linux–exec命令

shell的内建命令exec将并不启动新的shell,而是用要被执行命令替换当前的shell进程,并且将老进程的环境清理掉,而且exec命令后的其它命令将不再执行。

因此,如果你在一个shell里面,执行exec ls那么,当列出了当前目录后,这个shell就自己退出了,因为这个shell进程已被替换为仅仅执行ls命令的一个进程,执行结束自然也就退出了。为了避免这个影响我们的使用,一般将exec命令放到一个shell脚本里面,用主脚本调用这个脚本,调用点处可以用bash a.sh,(a.sh就是存放该命令的脚本),这样会为a.sh建立一个sub shell去执行,当执行到exec后,该子脚本进程就被替换成了相应的exec的命令。
source命令或者”.”,不会为脚本新建shell,而只是将脚本包含的命令在当前shell执行。
不过,要注意一个例外,当exec命令来对文件描述符操作的时候,就不会替换shell,而且操作完成后,还会继续执行接下来的命令。
exec 3<&0:这个命令就是将操作符3也指向标准输入。
别处,这个命令还可以作为find命令的一个选项,如下所示:
(1)在当前目录下(包含子目录),查找所有txt文件并找出含有字符串”bin”的行
find ./ -name “*.txt” -exec grep “bin” {}
(2)在当前目录下(包含子目录),删除所有txt文件
find ./ -name “*.txt” -exec rm {}

IPv6 测速节点搭建教程

本教程是搭建 IPv6.stream 测速节点的经验记录,对其他应用可能也有参考价值

本文使用 Apache 作为网络服务器,Nginx 版见 这里

如使用 Docker 可能需要更高配置,本教程暂不涉及

VPS 准备
*低硬件要求:
128MB RAM 、1 GB 剩余空间、IPv6 支持

操作系统以 Debian 10 为例

网络准备
部分服务商可能配置有系统或者服务商级防火墙,应使其允许 80 、443 入站

由于存在 OpenVZ 机型,为保证测速结果的可比性,本站测速点默认不开启 BBR

测试 IPv4 连通性
在 VPS 上执行:

ping -4 ip.sb
带 IPv4 的机型通常可以连通(含 NAT )
仅 IPv6 的机型通常不通

如果不通,不支持 IPv6 的在线安装可能会出现问题,可配置 DNS64 解决:

echo -e “nameserver 2001:67c:2b0::4\nnameserver 2001:67c:2b0::6\nnameserver 127.0.0.1” > /etc/resolv.conf
此命令会覆盖原有的配置

测试 IPv6 连通性
在 VPS 上执行:

ping -6 ip.sb
应当可以连通,否则会影响测速程序正常运行

个别服务商的 DHCPv6 配置存在问题,会获得意外的 IPv6 地址,导致服务器无法出站
如遇到此情况,设网卡为 eth0,则可在 /etc/sysctl.conf 中增加:

net.ipv6.conf.eth0.autoconf=0
net.ipv6.conf.eth0.accept_ra=0
net.ipv6.conf.eth0.use_tempaddr=0
从而禁止意外的自动地址配置

域名
以下是本文示例使用的域名和地址,操作时请记得替换:

示例域名 example.ipv6.stream
示例 IP 地址 2001:db8::1

如果是自己持有域名,在 DNS 提供商处设置 example.ipv6.stream 的 AAAA 记录为 2001:db8::1

如果想使用本站域名,但还未解析,可先在本地计算机的 hosts 增加一条记录用于后续配置:

2001:db8::1 example.ipv6.stream
提出申请、解析完成后建议删除此记录

HTTP 配置
安装教程所需的软件

apt update
apt install php libapache2-mod-php unzip curl vim
speedtest-x
下载 speedtest-x 代码并解压缩

cd /var/www/
wget https://github.com/BadApple9/speedtest-x/archive/refs/heads/master.zip
unzip master.zip
mv speedtest-x-master speedtest
rm master.zip
授予 Apache ( www-data )测速记录所在目录的读写权限

chown www-data:www-data /var/www/speedtest/backend/
个性化
本站的配置文件 /var/www/speedtest/backend/config.php 如下:

<?php

/**
* *多保存多少条测试记录
*/
const MAX_LOG_COUNT = 150;

/**
* IP 运营商解析服务:(1) ip.sb | (2) ipinfo.io (如果 1 解析 ip 异常,请切换成 2 )
*/
const IP_SERVICE = ‘ip.sb’;

/**
* 是否允许同一 IP 记录多条测速结果
*/
const SAME_IP_MULTI_LOGS = true;
Apache
新建文件 /etc/apache2/sites-available/speedtest.conf
写入:

<VirtualHost *:80>
ServerName example.ipv6.stream
DocumentRoot /var/www/speedtest
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
启用网站

a2ensite speedtest.conf
如果希望记录的时间与北京时间保持一致,可在 /etc/php/7.3/apache2/php.ini 中配置时区:

date.timezone = “PRC”
(大约位于第 956 行,去掉注释)

重启 Apache

systemctl restart apache2
如无报错,打开域名 http://example.ipv6.stream 应能显示出测速页面
检查上下行测速、记录是否正常工作

至此 HTTP 部分已经配置完成。

HTTPS 配置
该部分需要域名已经完成解析

本教程使用 acme.sh 申请,cerbot 可参考 Nginx 教程

acme.sh
使用前可先参考 官方文档

获取并安装

curl https://get.acme.sh | sh
退出并重新登陆终端

签署证书

acme.sh –issue -d example.ipv6.stream –apache
新建证书目录并安装

mkdir /var/ssl/
acme.sh –install-cert -d example.ipv6.stream \
–cert-file /var/ssl/cert.pem \
–key-file /var/ssl/key.pem \
–fullchain-file /var/ssl/fullchain.pem \
–reloadcmd “service apache2 force-reload”
Apache
启用 SSL

a2enmod ssl
新建文件 /etc/apache2/sites-available/speedtest-ssl.conf

写入:

<VirtualHost *:443>
ServerName example.ipv6.stream
DocumentRoot /var/www/speedtest
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLEngine on
SSLCertificateFile /var/ssl/cert.pem
SSLCertificateKeyFile /var/ssl/key.pem
SSLCertificateChainFile /var/ssl/fullchain.pem
</VirtualHost>
启用新配置

a2ensite speedtest-ssl.conf
重启 Apache

systemctl restart apache2
如无报错,打开域名 https://example.ipv6.stream 应能显示出测速页面

至此 HTTPS 部分已经配置完成。

配置完成后
可提交 Issue 加入 IPv6.stream 列表。

var SSL ream Apache2 条回复 • 2021-05-15 12:40:19 +08:00
datou 1
datou 39 天前
推荐使用 golang 版的 librespeed-speedtest

直接二进制部署简单粗暴
TulvL 2
TulvL 39 天前
@datou 如果只做测速这一件事情的话那是不错。我这里是要在 80 、443 提供服务,而且还有 Looking Glass,所以难免网络服务器和 php 配置。相信这样也更有借鉴意义。

Oracle Cloud free Tier 可以创建*大 24GB 内存的 ARM VM – Ampere A1 Compute

Infrastructure
2 AMD based Compute VMs with 1/8 OCPU and 1 GB memory each.
4 Arm-based Ampere A1 cores and 24 GB of memory usable as one VM or up to 4 VMs.
2 Block Volumes Storage, 200 GB total.
10 GB Object Storage.
10 GB Archive Storage.
Resource Manager: managed Terraform.
5 OCI Bastions.
https://www.oracle.com/cloud/free/ https://www.oracle.com/cloud/compute/arm/

似乎是新产品,不知道跟 AMD 的免费 VM 比,实际性能怎么样。

Storage GB vms ampere19 条回复 • 2021-06-14 11:20:15 +08:00
geekvcn 1
geekvcn 11 天前 via iPhone
火星可好,都被薅羊毛大军薅爆炸了
mikeven 2
mikeven 11 天前
前两天试了一下,好像国内卡申请不了 free 了吧
irytu 3
irytu 11 天前 via iPhone
搞了两台 很香
ericls 4
ericls 11 天前
ARM 很有可能是云的未来

如果开发工具也运行在 ARM 上的话 ARM 一定是云的未来
Tink 5
Tink 11 天前 via Android
火星
Tink 6
Tink 11 天前 via Android
@mikeven 可以
darknoll 7
darknoll 11 天前
中国申奥成功了
mikeven 8
mikeven 11 天前
@Tink #6 求个教程呗,我填信用卡账单地址不会填
Yien 9
Yien 11 天前
已經創建了一台 AMD 的機器,刪除 AMD 機器後是否能免費使用 ARM 機器?
xinJang 10
xinJang 11 天前
两个甲骨文云账号转一个试试

wdy3334 11
wdy3334 11 天前 via iPhone
早就撸炸了,首尔已经开不出来机器了
chenliang0571 12
chenliang0571 10 天前 ❤️ 1
@Yien 不用删除。2 AMD 以及 4 ARM,总共*多 6 台机器可以同时享有。
churchmice 13
churchmice 10 天前
我撸了两个 amd 的和一个 arm 的
但是速度那是真不稳定,速度一会 200K,一会 5M
还是用我 300 买的 GIA 路线香,速度稳定在 5M
chenliang0571 14
chenliang0571 10 天前
@wdy3334 大阪有。
keylock 15
keylock 10 天前
ARM 这个实例后面没有 Always Free 标志,只是 Boot Volume 显示永久免费,不会后面收钱吧
beyondex 16
beyondex 9 天前
注册一直报错。。。明明信息是对的。
AkideLiu 17
AkideLiu 9 天前 via iPhone
开了太 4c24g 的…不知道装点啥。
本来想 gitlab,好像没有 arm 的 docker image,arm 生态支持确实还不行。
感觉 oracle 这 4c arm 不如 gcp 1c x86…
labulaka521 18
labulaka521 9 天前
@beyondex 注册一直有问题 这个看命
labulaka521 19
labulaka521 9 天前
开了又销毁了 用不太到