很好的 C语言入门教程(中文)

豆瓣上备受好评的 《Linux C 编程一站式学习》 网上几大书店都无货了,淘宝有,但是贵,然后竟然在 Github 上找到它的在线版本 http://akaedu.github.io/book/,真心要点赞啊。

linux-c-on-github

这个书还有个新版本,叫《一站式学习 C 编程》,也是无货的。具体内容我没有比较过,但是基础知识应该是差不多的。新版书名中把 Linux 去掉了,也是可以理解的,毕竟 C 不局限于 Linux,特别是现在多种平台,还有逐步升温的各式嵌入式设备,在可以 预见的未来 C 语言还将继续扛大旗

program-language-trends

这样看趋势还不明显,不过相信很快大家都会感受到的了。

Javascript hashCode 函数

网上找了好一轮,找到个比较像样而且足够短的 hashCode 实现,是从 Java 的 hashCode 中借鉴而得的。原理见 Java hashCode() ,也可以跟这里的 Java String 的源码 参照对比一下。

为了使用的方便,稍稍再改良了一下

function hashcode(str) {
  var hash = 0, i, chr, len;
  if (str.length === 0) return hash;
  for (i = 0, len = str.length; i < len; i++) {
    chr   = str.charCodeAt(i);
    hash  = ((hash << 5) - hash) + chr;
    hash |= 0; // Convert to 32bit integer
  }
  return hash;
}

hashcode("this is a string")
//-1853110172

这里接受的参数是一个 String,其它类型怎么办?可以先做一个统一的处理,比如

hashcode(JSON.stringify(obj))

序列化之后再使用 hashCode 函数,基本所有类型数据都通吃,除了含有循环嵌套的对象。

PS:
函数实现中有一行使用了 “|” 运算符,只是利用 Bitwise 运算符转换参数为 32bit,用来确保结果是个 32位整数。

如何生成唯一且不可预测的 ID

通常数据库可以生成唯一的 ID,最多的就是数字序列,也有像 MongoDB 这样产生组合序列的,不过这种形式的 ID 由于是序列,是可以预测的。如果想得到不可预测且唯一的 ID,方法还是有的。

下面主要以 Node.js 的环境为例。

Node-uuid

Github 上有个 node-uuid 项目,它可以快速地生成符合 RFC4122 规范 version 1 或者 version 4 的 UUID。

安装,既可以通过 npm install node-uuid ,也可以在 package.json 中定义。使用方式:

var uuid = require('node-uuid');

// 产生一个 v1 (基于时间的) id
uuid.v1(); // -> '6c84fb90-12c4-11e1-840d-7b25c5ee775a'

// 产生一个 v4 (随机) id
uuid.v4(); // -> '110ec58a-a0f2-4ac4-8393-c866d813b8d1’

听起来很有保证,只是……有点太长了。

坊间也有一些在 UUID 基础上随机截取几位的办法来组成一个新的短一点的字符串,只不过唯一性就又重新成为一个问号了。

Hashids

另外一个方法,来自 hashids,它的原理就是从数字经过一个加盐(salted)算法产生一个哈希(hash)字符串。这样算法就是通过混淆使结果具有不可预测性,而唯一性依然由数字本身来达成,从而得到(类似 youtube 里的)足够短,不可预测且唯一的 ID。

安装方式类似,只是 hashids 建议使用一个固定的版本。比如在 package.json 中

"dependencies": {
  "hashids": "0.3.3"
}

使用方法也很简单。先设定一个字符串作为 salt。

var Hashids = require("hashids"),
    hashids = new Hashids("this is my salt”);

然后对数字(准确来说是数字数组)进行编码

var hash = hashids.encrypt(12345);
// Nkk9

var hash = hashids.encrypt(683, 94108, 123, 5);
// aBMswoO2UB3Sj

对数组编码可以有很有趣的用法,比如需要在一个分布式的环境里使用,可以使用机器编号 + 序列数字组合数组然后再编码。

有编码就有解码

var numbers = hashids.decrypt("NkK9”);
// [12345]

编码的输出会随着数字的增大而变长,为了达到足够混淆,它可以指定最少长度。

hashids = new Hashids("this is my salt", 8);

var hash = hashids.encrypt(1);
// gB0NV05e

还有其它选项,比如控制输出所使用的字符,编码 MongoDB 的 ObjectId 等等。

上面介绍的两个项目,各有各优缺点,不过目的是一致的,可以根据实际情况选用。两个项目在其它语言上也有类似的实现。

如何在命令行中使用 proxy

国内的网络形势你懂的,要翻墙,无法是 VPN 或者 proxy。我个人还是用 proxy 比较多,浏览器里装个插件就能自动适应。但是那只是针对浏览器,命令行也很常用,遇墙就会卡着不动了。

有一个软件可以帮助你在 Command Line 里使用 proxy,叫 ProxyChains-NG (new generation)

在 Mac 上安装超简单(只要你机器上装好了 brew

brew install proxychains-ng

==> Downloading https://downloads.sourceforge.net/project/proxychains-ng/proxychains-4.7.tar.bz2
 ######################################################################## 100.0%
==> ./configure --prefix=/usr/local/Cellar/proxychains-ng/4.7 --sysconfdir=/usr/local/Cellar/proxychains-ng/4.7/etc
==> make
==> make install
==> make install-config
==> /usr/local/Cellar/proxychains-ng/4.7: 9 files, 92K, built in 10 seconds

其它平台的安装大同小异,先找找有没有一个命令能搞掂的,没有的话参考一下其文档的 Installation 部分。

然后,做一个简单配置,打开配置文件

vi /usr/local/etc/proxychains.conf

视乎你的安装方式不同,配置文件的地方略有不同,但会遵从平台的一般规范,例如在 Linux 上安装,配置文件的位置可能在 /etc/proxychains.conf

打开配置文件之后,略过前面所有,直奔最后一行,默认配置是使用 tor 的,根据你机器上 proxy 的种类配置好就可以了。常见的配置

http    127.0.0.1  8080
socks5  127.0.0.1  1080

其实上面几行就有 example ,找到合适的照抄就是了。

最后,使用也非常简单,只要在命令前面加个 proxychains4,比如

proxychains4 telnet targethost.com

PS. 还有个名字接近但更加老牌的同类软件, ProxyChains ,使用方法几乎一样。

Javascript void(0)

JavaScript-logo常有人疑惑一个看上去很简单的问题,<a href=”javascript:void(0)”>some button</a> 里面的 void(0) 是什么意思?又或者不明白为什么好像很多人要这么写而不是其它更加简洁的写法,比如 <a href=”#”>some button</a>

href=”javascript:void(0)” 实际上是为了在 link 被点击的时候,返回一个空值,告诉浏览器什么都不用做。因为浏览器会把当前页面里的内容替换为返回的结果。(这个效果反而让我感到奇怪,因为真的很少这么用)

而 href=”#” 看上去简洁,但是它其实有个默认的行为,会把页面重新定位到页面顶部,这就是为什么页面会跳一下的一个原因。因为更多的情况,点击后页面保持在原位才是我们想要的结果。使用 # 甚至可能出现一些更加奇葩的问题

除了这个,还有没有其它更加简洁的写法呢?会不会是 <a href=””>some button</a> 呢?这个点击后会刷新当前页面,显然不符合要求。应该是 <a href=”javascript:;”>some button</a> ,什么都不执行。但有时过分简洁也容易造成“不清晰”的印象从而引起不必要争拗。如果从代码可读性考虑,也为了团队内沟通少点分歧,我会建议使用 href=”javascript:void(0)”。

void 操作符最原始就是用来“制造” undefined ,特别是某段代码只需要其副作用(side effect)又不希望有返回值产生。void 本身没有副作用,也没有正作用(返回值是空),但却能包含一切,犹如黑洞一般神秘。

== 参考 ==
Void Operator in MDN

Zeco CX6 投影仪刷固件

我一直以为刷固件离我很远,昨晚 CX6 开机之后就一直停在了开机画面,重启数遍无效,找到客服,说要刷固件,当时哪个场合太囧了!第一次拿到别人家里放,N 双眼睛等着看新玩意,结果告诉我要!刷!固!件!

好吧,那就现场刷一下吧,又遇到一件囧事,CX6 背后的 micro USB 插口深不见底啊,普通 5mm 长的嘴插进去就掉了,好吧,那就用手按着吧,根据说明一步一步,结果还是不停显示出错了,出错了…… 打电话问技术支持,当时是晚上 10:10,得到的消息是插头不够长,建议我削掉一点胶。

明显是又一个很不合理的设计,我找了 N 根 micro USB 的线,全部都是最近一两年的机型配的原装数据线,嘴都是 5mm 的,CX6 的要至少 6mm 长的才行,问题是都知道自己的特殊一点,就配一条 6mm 的嘛。虽然技术支持说可以邮一根过来,但是时间就太长了。

好,扯远了。准备好工具之后,就开始动手削!可能要牺牲一根华为的线了。

削线需要的工具

先把头部的胶削掉 0.5mm ,试了一下,还真不行,再削 0.5mm,都见到里面的“骨头”了,已经不能削更多了。上天保佑终于可以了。

露出 6mm

刷固件就难在,要先拿个牙签桶住 RESET 按钮不妨再开机,如果你习惯用右手的话,最好用左手去桶,不解释了。然后就没有什么难度,打开软件,选择一下固件来源,跟着都自动完成。

左手桶 RESET 键

烧录完成

至于为什么 CX6 会变砖,跟马航事件一样目前没有定论,不过最大的嫌疑是 App 更新造成的。App 自己都提示更新是修复已知问题,架构上是应该不会影响到系统无法启动的,关于这一点,得到的建议是不要点 App 自己的更新,呃…… 有骗小白的嫌疑。

这不说软件在稳定性上完全跟不上了,说说外观设计。同样是插头,HDMI / VGA 的就比较浅,至少外面就能看到,OTG 接口就不能再伸出来一点点么?同样的 TF-CARD 的,卡已经够小了,手指粗的人能塞卡进去,但是如果想取出来,你得有长指甲,注意是要长指甲,刚剪过的会够不着。底部的进风口距离固定点太近,三脚架的快装板多少会遮住一点。我认为,接口少一点其实更加好,可以更加专注提升软件质量,做接口其实还要考虑接口做工的可用性,至少用来急救刷固件的接口要确保场合适应性。

思维的陷阱

前阵子遇到个问题,是在 Joomla 里升级组件的时候出现 “Failed to copy file” 的错误。我已经习惯了先 google 解决方案,跟随头几个方案,都说是文件或文件夹权限的问题,我满心欢喜地以为已经接近真相了。

几小时过去,哪几个文件夹,包括父文件夹都已经变成 777 了,还是一样报错。然后,开始想是不是姿势不对,还有几个可疑的地方,包括是不是应该先删除旧的组件,然后新安装;不要用上传 zip 包的方式,而改用直接用服务器上的文件进行安装;改用 ftp 的方式进行安装等等。多得有 git ,每次尝试完一种办法不可行之后,所有文件都可以恢复到初始状态。不过多次尝试未果之后,隐约觉得有点浪费生命了。

几天过去了,中间也重复过之前的尝试,每次都有一点点变化,希望找到突破口,也向身边的同行咨询,尝试过各种跟权限有关的方法,依然是未有进展。

事情开始变得吊诡起来。

又几天过去,浪费生命也得有个谱,思维才开始跳出这是一个权限问题的框框,立刻动手写了个小脚本,放在同一位置,用最简单的 API,对同样的文件进行操作,结果是令人震惊的,毫无权限问题。然后开始跟踪代码,系统比较大,对源码结构不是很熟悉,花了点时间才最终确认,由于一个程序配置的问题,导致文件复制的步骤总是返回失败。触摸真相的感觉依然是让人兴奋的。

回头再想,如果早一点开始翻查源码,会不会就能早点发现事实呢?

但问题是,为什么不早一点。

是因为这个陷阱太完美了么?还是因为自己的思维出现固定模式,感觉到同样的问题一定已经有不少人遇到了,而且很可能都已经解决了,只是还没有搜索到而已。源代码与搜索引擎这两个工具,我潜意识会更多地使用的是搜索引擎而不是源代码。也许是因为搜索引擎很多时候能更快的给出答案,不需要太多的思考,而阅读源代码注定要花比较多时间,要记忆流程思考其中的逻辑。问题不在于工具本身,而在于人在什么位置出发思考问题。

还有另外一个因素是时间,急于解决问题,于是求助于通常能快速解决问题的搜索引擎,越是急,越会优先选择快速方案而不是思考正确的方案。

Google Webmaster 索引历史丢失 (已解决)

刚刚发现一个奇怪的事情,下午的时候,Google Webmaster 里一个站点的索引历史好好的,还给别人看索引趋势,刚才再去观察的时候就全都没了。

被吓得赶紧用 site: 指令搜索一下,还好索引还在。只是历史丢了,也没收到什么警告邮件之类的。难道只是一时的数据丢失?

希望这些数据认得路回家。

===== 华丽的分割线 =====

Update: 睡了一觉之后,重新进入 Webmaster 里查看,站点地图里的编入索引数量和历史已经回来了,但是索引状态那里却依然是一条直直的线。

如下图↯
Google 索引显示为 0

Update: 经过一周之后,终于看见索引历史的曲线了。这个数据的更新周期大约是一周至二周,所以频繁的去观察这个数据是没有多大意义的。只要 sitemap 里索引数量正常就可以了。

jQuery SelectAll Plugin

这是一个处理 全部选中/不选 的插件。

这个插件节省不了你太多代码,但是能帮助你分离处理逻辑。通过在改变选择项时,向 controller 发送一个 “change:select” 事件(还附带 selected 和 total)来实现。这样你就可以灵活地选择在什么地方处理这个事件。

controller 不一定是 jQuery 对象,也可以是 Backbone.Events 的派生对象。

jquery-selectAll.js

Gitlab-shell 1.1.0 的 SSH 访问异常

GitLab 4.2 升级到 5.0,虽然 步骤 有点儿繁琐,但还算是一路绿灯,但最终却遇到了不能以 ssh 方式访问库的问题。搞了几个小时,找了所有能想到的地方,都搞不清楚究竟哪里错了,于是在 github 上给提了个 issue 3578

问题的情况简单来说就是,ssh -T 成功,但是 git clone/pull/push 却意外失败,另外 http 的方式是成功的,说明应该还是连接的问题。

唯一有可能存在疑问的地方,应该是 gitlab-shell 的版本。检查的时候会报告说需要 1.1.0,而事实根据升级指引中的操作结果是 1.2.0 的。但从 另一个 issue 的说明 看来,可以通过修改 check.rake 来实现“瞒天过海”。

暂停了 gitlab service,然后切换 gitlab-shell 的版本到 1.1.0

> cd /home/git/gitlab-shell
> git checkout v1.1.0
> git checkout -b v1.1.0

# 配置文件不需要改
> ./bin/install

重启 gitlab 后再试试 ssh -T git@mygitlab.com

结果却出错了!

/usr/local/lib/ruby/1.9.1/json/common.rb:148:in `parse': 743: unexpected token at '<html> (JSON::ParserError)
<html>
<head>
<title>Welcome to nginx!</title>
</head>
<body bgcolor="white" text="black">
<center><h1>Welcome to nginx!</h1></center>
</body>
</html>
'
     from /usr/local/lib/ruby/1.9.1/json/common.rb:148:in `parse'
     from /home/git/gitlab-shell/lib/gitlab_net.rb:24:in `discover'
     from /home/git/gitlab-shell/lib/gitlab_shell.rb:28:in `exec'
     from /home/git/gitlab-shell/bin/gitlab-shell:16:in `<main>'

为什么会出现这个错误,我没什么头绪,google 了一轮也没什么对症的答案。

要出现默认的 Nginx 页面,应该还是跟 Nginx 的配置有关,而哪个配置文件是从网上下载的,唯一就是指定了 IP 以及域名。就算直接用 IP 访问,也是可以看见 GitLab 的页面的,但是不会看见 Nginx 的默认页面。

这时,我想起之前曾经改过 /etc/hosts

127.0.0.1   mygitlab.com

如果在服务器端访问 http://mygitlab.com 的时候才会看见 Nginx 的默认页面。果断去掉这个 host 设定,然后再试,一切都正常了。

嗯嗯,具体什么原因要加这个 host 设定,不太清楚了,装 4.2 的时候的事情了。反正 gitlab-shell 1.1.0 跟它有冲突。不过应该算是 gitlab-shell 的问题,它不应该解析出域名的 IP 后直接用 IP 去访问,因为有可能一个 IP 上会挂好多个站点,默认的不一定就是 gitlab。

有时候指定 hosts 能解决一些问题,也有像这种帮倒忙的情况,能不加还是不加吧。

PS: mygitlab.com 不是一个真实的域名。

Update 17 Apr: Issue 3384: Gitlab can’t clone or push