内容简介:最近工作比较繁忙,每天能用于学习知识的时间越来越少,深感这样不利于自己的技术提升。恰好想起狼叔 所说的 “迷茫时学习 Node.js 最好的方法 -希望通过这一系列的文章,一方面提醒自己在工作中牢记技术的初心,另一方面鞭策自己在 Node.js 的路上不断前行。第一个 npm 模块我选择的是username,用于获取当前用户的用户名,当前版本为 3.0.0,周下载量 6万+。
最近工作比较繁忙,每天能用于学习知识的时间越来越少,深感这样不利于自己的技术提升。恰好想起狼叔 所说的 “迷茫时学习 Node.js 最好的方法 - 每天看十个 npm 模块 “,虽然每天没有那么多时间看十个模块,但时间就像海绵一样,挤一挤,每天阅读一个模块还是能做到的。
希望通过这一系列的文章,一方面提醒自己在工作中牢记技术的初心,另一方面鞭策自己在 Node.js 的路上不断前行。
一句话介绍
第一个 npm 模块我选择的是username,用于获取当前用户的用户名,当前版本为 3.0.0,周下载量 6万+。
用法
username 支持同步和 Promise 异步的写法:
const username = require('username'); // 同步 console.log(username.sync()); // => 'elvin' // 异步 username().then(username => { console.log(username); // => 'elvin' }); 复制代码
源码学习
核心代码一共二十多行,主体逻辑为:
-
首先通过
process.env
变量中的值获得用户名,若存在,直接返回; -
接着若存在
os.userinfo
函数,则通过os.userinfo().username
获得用户名并返回; -
若上述方法均失败,则在 OS X/Linux 下通过执行
id -un
命令,Windows 下通过whoami
命令获得用户名并返回。
接下来将结合源码对这三步进行探究。
process.env
// 源代码 1-1 function getEnvVar() { const env = process.env; return env.SUDO_USER || env.C9_USER /* Cloud9 */ || env.LOGNAME || env.USER || env.LNAME || env.USERNAME; } const envVar = getEnvVar(); if (envVar) { return Promise.resolve(envVar); } 复制代码
process.env
返回的是一个包含用户当前环境变量(environment variable)的对象,可以在命令行执行 printenv
命令查看所有的环境变量,也可以 printenv v_name
命令获取某一个环境变量的值:
$ printenv // => LANG=zh_CN.UTF-8 // => PWD=/Users/elvin/ // => SHELL=/bin/zsh // => USER=elvin // => ... $ printenv USER // => elvin 复制代码
Shell 变量(shell variables)容易与环境变量(environment variable)弄混,通过 set
命令可以查看所有的 Shell 变量。关于这两者的区别和使用建议参考这篇英文资料 How To Read and Set Environmental and Shell Variables on a Linux VPS
。
在 Node.js 中,关于 process.env
还有三个地方需要了解:
-
可以通过
process.env.foo = "bar"
的方式设置环境变量,目前所有类型的值都允许且会被转化为 string 类型,根据官方文档,在将来的版本中将只允许 string、number 和 boolean 类型的值,设置其他类型的值将会抛出异常。process.env.foo = undefined; console.log(process.env.foo, typeof process.env.foo); // => 'undefined', 'string' process.env.foo = {}; console.log(process.env.foo, typeof process.env.foo); // => '[object Object]', 'string' 复制代码
-
可以通过
delete
方法删除环境变量。process.env.foo = undefined; delete process.env.foo console.log(process.env.foo) // => undefined 复制代码
-
在 Node.js 中对
process.env
的修改并不会反映在 node 进程之外,不过可以在外部设置环境变量然后通过 Node.js 代码去获取,实践中经常通过这种方式设置NODE_ENV
变量,然后在 webpack 配置代码中读取它的值来判断环境进行不同的构建。$ node -e 'process.env.foo = "bar"' && echo $foo // => 空 $ NODE_ENV=production node -e 'console.log(process.env.NODE_ENV)' // => 'production' 复制代码
Windows 下不支持直接
NODE_ENV=production
这种方式,需要安装cross-env 包进行兼容。
回到源代码 1-1 中的 getEnvVar
函数,可以看到依次尝试从 process.env
的 SUDO_USER、C9_USER、LOGNAME、USER、LNAME 和 USERNAME 这些环境变量获得用户名,这里着重介绍一下 SUDO_USER
和 C9_USER
这两个变量:
-
当用户身份是 root 时,此时的
USER
变量会返回 root,而SUDO_USER
变量返回的是登陆为 root 的账户名,例如:当我以 elvin 账户通过sudo su
变为 root 用户后,USER
会返回 root,SUDO_USER
会返回 elvin。$ sudo su // => input password $ printenv USER // => root $ printenv SUDO_USER // => elvin 复制代码
-
C9_USER
从注释来看是针对Cloud9 的适配,它是亚马逊推出的用于编写、运行和调试代码的云 IDE,感兴趣的同学可以试一试~
os.userInfo
源码中从 process.env
无法获取用户名时,会尝试通过 os.userInfo()
函数获取:
// 源代码 1-2 if (os.userInfo) { return Promise.resolve(os.userInfo().username); } 复制代码
os.userinfo()
返回的是当前用户的一些信息,相较于 process.env
而言信息少很多,而且 Node.js V6.0 及以上版本才支持:
const os = require('os'); console.log(os.userInfo()); // => { // => uid: 501, // => gid: 20, // => username: 'elvin', // => homedir: '/Users/elvin', // => shell: '/bin/zsh' // => } 复制代码
上述各字段的意思是:
/etc/passwd /etc/group
执行命令行命令
当前两种方式都无法获取用户名时,在 OS X/Linux 下会通过 id -un
命令获取用户名,在 Windows 下会通过 whoami
命令获取用户名。
// 源代码 1-3 function cleanWinCmd(x) { return x.replace(/^.*\\/, ''); } function noop() {} if (process.platform === 'darwin' || process.platform === 'linux') { return execa('id', ['-un']).then(x => x.stdout).catch(noop); } else if (process.platform === 'win32') { return execa('whoami').then(x => cleanWinCmd(x.stdout)).catch(noop); } 复制代码
上述代码首先通过 process.platform
判断操作系统,若是 OS X(即 darwin)或是 Linux,则执行 id -un
获取用户名;若是 Windows(即 win32),则执行 whoami
获取平台 & 用户名,再通过 cleanWinCmd
函数利用正则提取用户名。其实在 OS X/Linux 上也能通过 whoami
获取用户名,但其已经在文档中声明被 id
命令淘汰(obsoleted)。
根据Node.js 文档, process.platform
会返回当前的平台,包括 aix | darwin | freebsd | linux | openbsd | sunos | win32 | android,所以其实可以看出上述代码只考虑了其中的三种情况,个人觉得可以适当做一些如下修改:
if (process.platform === 'win32') { return execa('whoami').then(x => cleanWinCmd(x.stdout)).catch(noop); } else { return execa('id', ['-un']).then(x => x.stdout).catch(noop); } 复制代码
这里提出的改进已经通过 PR #20 被 merge 到最新的代码中 :blush:
在源码 1-3 中, noop
空函数的使用也值得学习:当命令执行异常时,通过 noop
函数吞掉报错,并返回 undefined
。一开始我会好奇这里为什么不将详细的异常信息返回便于出错时定位,但后来站在包使用者的角度来看,我认为直接返回 undefined
有两个好处:
undefined
另外需要注意的是,这里使用的是第三方包execa 而不是 Node.js 内置的 child_process.exec
模块来执行命令行命令,execa 在原生模块的基础上进行了提升,目前每周下载量约为 600 万,这里主要是利用了其提供 Promise 的接口。
写在最后
今天通过username 六十行的代码:
-
了解系统环境变量,懂得了
SUDO_USER
与USER
变量的区别; -
学会 Node.js 中
process.env
增删查改; -
了解 Node.js 中
os.userInfo()
返回的信息; -
知道
process.platform
返回的值不止 darwin | win32 | Linux,也许username
这里能有更好的处理; -
noop
空函数在 Promise 出错时吞掉异常的优点。
其实username 还通过mem 包对结果进行缓冲提升了效率,明天将会阅读mem 包进行学习。
关于我:毕业于华科,工作在腾讯,elvin 的博客 欢迎来访 ^_^
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Nginx源码阅读笔记-事件处理模块
- 每天阅读一个 npm 模块(2)- mem
- 每天阅读一个 npm 模块(6)- pify
- # 每天阅读一个 npm 模块(7)- delegates
- 《WebKit技术内幕》阅读摘要 —— WebKit 架构和模块
- 每天阅读一个 npm 模块(3)- mimic-fn
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。