mayihetu

mayihetu 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

mayihetu 发布了文章 · 1月7日

ROM 种类

ROM(Read-Only Memory),只读存储器。是一种只能读取所存数据的 固态半导体存储器。通常用在不需经常变更资料的电子或电脑系统中,并且因为数据通过各种手段固化在设备上,所以资料不会因为电源关闭而消失。

分类

1.ROM,只读存储器(Read-Only Memory)
将资料以一特制光罩(mask)烧录于线路中,其资料内容在写入后就不能更改,所以有时又称为“光罩式只读内存”(mask ROM)。此内存的制造成本较低,常用于电脑中的开机启动如启动光盘。

2.PROM ,可编程程序只读存储器(Programmable ROM,PROM)
之内部有行列式的熔丝,是需要利用电流将其烧断,写入所需的资料,但仅能写录一次。 PROM在出厂时,存储的内容全为1,用户可以根据需要将其中的某些单元写入数据0(部分的PROM在出厂时数据全为0,则用 户可以将其中的部分单元写入1), 以实现对其“编程”的目的。PROM的典型产品是“双极性熔丝结构”,如果我们想改写某些单元,则可以给这些单元通以足够大的电流,并维持一定的时间,原 先的熔丝即可熔断,这样就达到了改写某些位的效果。另外一类经典的PROM为使用“肖特基二极管”的PROM,出厂时,其中的二极管处于反向截止状态,还 是用大电流的方法将反相电压加在“肖特基二极管”,造成其永久性击穿即可。

3.EPROM ,可抹除可编程只读存储器(Erasable Programmable Read Only Memory,EPROM)
可利用高电压将资料编程写入,抹除时将线路曝光于紫外线下,则资料可被清空,并且可重复使用。通常在封装外壳上会预留一个石英透明窗以方便曝光。

4.OTPROM ,一次编程只读存储器(One Time Programmable Read Only Memory,OTPROM)
写入原理同EPROM,但是为了节省成本,编程写入之后就不再抹除,因此不设置透明窗。

5.EEPROM ,电子式可抹除可编程只读存储器(Electrically Erasable Programmable Read Only Memory,EEPROM)
写入原理同EPROM,但是抹除的方式是使用高电场来完成,因此不需要透明窗。

6.快闪存储器(Flash memory)
每一个记忆胞都具有一个“控制闸”与“浮动闸”,利用高电场改变浮动闸的临限电压即可进行编程动作。

详见百度

查看原文

赞 0 收藏 0 评论 0

mayihetu 发布了文章 · 1月3日

Shell 语法

变量

声明、使用、只读、删除

myUrl="runoob.com"

echo $myUrl ${myUrl}

readonly myUrl

unset myUrl

1、定义变量时,不加 $ ,变量名和等号之间 不能有空格
2、使用变量,变量名前面加 $
3、{ }可选,加不加都行,加花括号是为了帮助解释器识别变量的边界
4、unset命令,不能删除只读变量

字符串(拼接、长度、截取、查找)

字符串可以用单引号,也可以用双引号,也可以不用引号。

单双引号的区别

单引号字符串的限制:
①单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;②单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。

双引号的优点:
①双引号里可以有变量;
②双引号里可以出现转义字符

拼接字符串

your_name="runoob"

# 使用双引号拼接
greeting="hello, "$your_name" !"

greeting_1="hello, ${your_name} !"

# 使用单引号拼接
greeting_2='hello, '$your_name' !'

# hello, runoob !

错误拼接

greeting_3='hello, ${your_name} !'

# hello, ${your_name}  !

获取字符串长度

echo ${#your_name} # 输出 6

提取/截取子字符串 :从字符串第 2 个字符开始截取 4 个字符:

echo ${your_name:1:4}  # 输出 unoo

查找子字符串 :查找字符 i 或 o 的位置(哪个字母先出现就计算哪个):

echo `expr index "$your_name" io`  # 输出 4

数组(定义、读取、长度)

bash支持一维数组(不支持多维数组),并且没有限定数组的大小

定义数组

在 Shell 中,用 括号 来表示数组,数组元素用 "空格" 符号分割开。定义数组的一般形式为:

arrays=(value0 value1 value2 value3)

array=(
value0
value1
value2
value3
)

单独定义,可以不使用连续的下标,而且下标的范围没有限制

array[5]=value

读取数组

${array[0]}

使用 @ 符号可以获取数组中的所有元素

echo ${array[@]}

获取数组的长度

length=${#array[@]}

length=${#array[*]}

取得数组单个元素的长度

lengthn=${#array[n]}

注释

单行注释

# 开头,会被解释器忽略。

多行注释

:<<EOF
注释内容...
EOF

EOF 也可以使用其他符号:

:<<'
注释内容...
'
:<<!
注释内容...
!

非常规方法:可以把这一段要注释的代码用一对花括号括起来,定义成一个函数,没有地方调用这个函数,这块代码就不会执行,达到了和注释一样的效果。

运算符

原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。
awk 是一个优良的文本处理工具,Linux及Unix环境中现有的功能最强大的数据处理引擎之一。awk 是三位创始人的首字母。
expr 是一款表达式计算工具,用于在UNIX/LINUX下求表达式变量的值,一般用于整数值,也可用于字符串。

表达式和运算符之间要有空格,完整的表达式要被 `` 包含。

val=`expr 2 + 2`

算术运算符

+-*/%===!=

1、条件表达式要放在方括号之间,并且要有空格,例如: [ $a == $b ]

2、乘号(*)前边必须加反斜杠(\)才能实现乘法运算;val=`expr \$a \* $b`

3、在 MAC 中 shell 的 expr 语法是:$((表达式)),此处表达式中的 "*" 不需要转义符号 "\" 。

关系运算符

命令说明
-eq相等
-ne不等
-gt大于
-lt小于
-ge大于等于
-le小于等于

布尔运算符

! 非 、-o 或 、-a

逻辑运算符

&&||

字符串运算符

命令说明
=相等
!=不等
-z长度为0
-n长度不为0
$字符串不为空
[ $a = $b ]

[ -z $a ] 

文件测试运算符

命令说明
-b file检测文件是否是块设备文件,如果是,则返回 true。 返回 false。
-c file检测文件是否是字符设备文件
-d file检测文件是否是目录
-f file检测文件是否是普通文件(既不是目录,也不是设备文件)
-g file检测文件是否设置了 SGID 位
-k file检测文件是否设置了粘着位(Sticky Bit)
-p file检测文件是否是有名管道
-u file检测文件是否设置了 SUID 位
-r file检测文件是否可读。
-w file检测文件是否可写
-x file检测文件是否可执行
-s file检测文件是否为空(文件大小是否大于0)
-e file检测文件(包括目录)是否存在

其他检查符:

-S file 判断某文件是否 socket
-L file 检测文件是否存在并且是一个符号链接
[ -b $file ]
...

echo、printf、test

echo

echo "It is a test"

echo It is a tests

echo "It is a test" > myfile    //显示结果定向至文件

echo '$name\"'    //原样输出字符串,不进行转义或取变量(用单引号)

echo `date`    //显示命令执行结果

echo -e "OK! \n"  //显示换行

echo -e "OK! \c"  //显示不换行

显示变量(从命令行里获取值)

read 命令从标准输入中读取一行,并把输入行的每个字段的值指定给 shell 变量

#!/bin/sh

read name 

echo "$name It is a test"

以上代码保存为 test.sh,name 接收标准输入的变量,结果将是:

sh test.sh

OK                     #标准输入

OK It is a test        #输出

printf

printf 由 POSIX 标准所定义,因此使用 printf 的脚本比使用 echo 移植性好。
printf 使用引用文本或空格分隔的参数,外面可以在 printf 中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。
默认 printf 不会像 echo 自动添加换行符,我们可以手动添加 \n。

printf "Hello, Shell\n"
printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234 
# 郭靖     男      66.12

%s、%c、%d、%f 都是格式替代符

%-10s 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。

%-4.2f 指格式化为小数,其中.2指保留2位小数。

#!/bin/bash
 
# format-string为双引号
printf "%d %s\n" 1 "abc"
#1 abc

# 单引号与双引号效果一样 
printf '%d %s\n' 1 "abc"
#1 abc

# 没有引号也可以输出
printf %s abcdef
#abcdef

# 格式只指定了一个参数,但多出的参数仍然会按照该格式输出,format-string 被重用
printf %s abc def
#abcdef

printf "%s\n" abc def
#abc
#def

printf "%s %s %s\n" a b c d e f g h i j    

#a b c
#d e f
#g h i
#j  

# 如果没有 arguments,那么 %s 用NULL代替,%d 用 0 代替
printf "%s and %d \n"
# and 0

printf的转义序列

命令说明
\a警告字符,通常为ASCII的BEL字符
\b后退
\c抑制(不显示)输出结果中任何结尾的换行字符(只在%b格式指示符控制下的参数字符串中有效),而且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略
\f换页(formfeed)
\n换行
\r回车(Carriage return)
\t水平制表符
\v垂直制表符
\\一个字面上的反斜杠字符
\ddd表示1到3位数八进制值的字符。仅在格式字符串中有效
\0ddd表示1到3位的八进制值字符

test

数值测试

if test $[num1] -eq $[num2]
...
a=5
b=6

result=$[a+b] # 代码中的 [] 执行基本的`算数运算`, 等号两边不能有空格

字符串测试

if test $num1 = $num2
...

文件测试

if test -e ./bash
...

逻辑操作符

Shell提供了与( -a )、或( -o )、非( ! )三个逻辑操作符用于将测试条件连接起来,其优先级为:!最高,-a次之,-o最低

if test -e ./notFile -o -e ./bash
...

流程控制

和Java、PHP等语言不一样,sh的流程控制不可为空。在sh/bash里,如果else分支没有语句执行,就不要写else

if else

if condition1
then
    command1
elif condition2 
then 
    command2
else
    commandN
fi

写成一行(适用于终端命令提示符):

if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi

if else语句经常与test命令结合使用,如下所示:

if test $[num1] -eq $[num2]
then
    echo '两个数字相等!'
else
    echo '两个数字不相等!'
fi

for 循环

for var in item1 item2 ... itemN
do
    command1
    command2
    ...
    commandN
done

写成一行

for var in item1 item2 ... itemN; do command1; command2… done;
for loop in 1 2 3 4 5
do
    echo "The value is: $loop"
done

for 其他用法

对于习惯其他语言 for 循环的朋友来说可能有点别扭。

for((assignment;condition:next));
do
    command_1;
    commond_..;
done

这里的 for 循环与 C 中的相似,但并不完全相同。

通常情况下 shell 变量调用需要加 $,但是 for 的 (()) 中不需要

#!/bin/bash
for((i=1;i<=5;i++));do
    echo "这是第 $i 次调用";
done;

while 语句

#!/bin/bash
int=1
while(( $int<=5 ))
do
    echo $int
    let "int++"
done

以上实例使用了 Bash let 命令,它用于执行一个或多个表达式,变量计算中不需要加上 $ 来表示变量。

while循环可用于读取键盘信息。下面的例子中,输入信息被设置为变量FILM,按<Ctrl-D>结束循环。

echo '按下 <CTRL-D> 退出'
echo -n '输入你最喜欢的网站名: '

while read FILM
do
    echo "是的!$FILM 是一个好网站"
done

无限循环

while :
do
    command
done
while true
do
    command
done
for (( ; ; ))

until 循环

一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用。

语法:

until condition
do
    command
done

实例:

#!/bin/bash
a=0
until [ ! $a -lt 10 ]
do
   echo $a
   a=`expr $a + 1`
done

case

case 值 in
模式1)
    command1
    command2
    ...
    commandN
    ;;
模式2)
    command1
    command2
    ...
    commandN
    ;;
esac
echo '输入 1 到 4 之间的数字:'
echo '你输入的数字为:'
read aNum
case $aNum in
    1)  echo '你选择了 1'
    ;;
    2)  echo '你选择了 2'
    ;;
    3)  echo '你选择了 3'
    ;;
    4)  echo '你选择了 4'
    ;;
    *)  echo '你没有输入 1 到 4 之间的数字'
    ;;
esac

跳出循环

在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell使用两个命令来实现该功能:break和continue

break命令

break命令允许跳出所有循环(终止执行后面的所有循环)。

#!/bin/bash
while :
do
    echo -n "输入 1 到 5 之间的数字:"
    read aNum
    case $aNum in
        1|2|3|4|5) echo "你输入的数字为 $aNum!"
        ;;
        *) echo "你输入的数字不是 1 到 5 之间的! 游戏结束"
            break
        ;;
    esac
done

continue

continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。

#!/bin/bash
while :
do
    echo -n "输入 1 到 5 之间的数字: "
    read aNum
    case $aNum in
        1|2|3|4|5) echo "你输入的数字为 $aNum!"
        ;;
        *) echo "你输入的数字不是 1 到 5 之间的!"
            continue
            echo "游戏结束"
        ;;
    esac
done

运行代码发现,当输入大于5的数字时,该例中的循环不会结束,语句 echo "游戏结束" 永远不会被执行。

case ... esac

case ... esac 与其他语言中的 switch ... case 语句类似,是一种多分枝选择结构,每个 case 分支用右圆括号开始,用两个分号 ;; 表示 break,即执行结束,跳出整个 case ... esac 语句,esac(就是 case 反过来)作为结束标记。

case 值 in
模式1)
    command1
    ...
    ;;
模式2)
    command1
    ...
    ;;
*)
    command1
    ...
    ;;
esac

case 后为取值,值可以为变量或常数。

值后为关键字 in,接下来是匹配的各种模式,每一模式最后必须以右括号结束,模式支持正则表达式。

#!/bin/sh

site="runoob"

case "$site" in
   "runoob") echo "菜鸟教程"
   ;;
   "google") echo "Google 搜索"
   ;;
   "taobao") echo "淘宝网"
   ;;
esac

函数

[ function ] funname [()]

{

    action;

    [return int;]

}

1、可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
2、参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。return后跟数值n(0-255),还可以通过echo 直接返回。
3、注意,shell中通过return返回是有限制的,最大返回255,超过255,则从0开始计算

#!/bin/bash

demoFun(){
    echo "这是我的第一个 shell 函数!"
}
echo "-----函数开始执行-----"
demoFun
echo "-----函数执行完毕-----"

下面定义一个带有return语句的函数:

#!/bin/bash

funWithReturn(){
    echo "这个函数会对输入的两个数字进行相加运算..."
    echo "输入第一个数字: "
    read aNum
    echo "输入第二个数字: "
    read anotherNum
    echo "两个数字分别为 $aNum 和 $anotherNum !"
    return $(($aNum+$anotherNum))
}
funWithReturn
echo "输入的两个数字之和为 $? !"
函数返回值在调用该函数后通过 $? 来获得。

注意:所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至shell解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。

函数参数

在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,$1表示第一个参数,$2表示第二个参数...

带参数的函数示例:

#!/bin/bash

funWithParam(){
    echo "第一个参数为 $1 !"
    echo "第十个参数为 $10 !"        //会输出:第十个参数为 10 ! 而非第十个
    echo "第十个参数为 ${10} !"
    echo "第十一个参数为 ${11} !"
    echo "参数总数有 $# 个!"
    echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73

注意,$10 不能获取第十个参数,获取第十个参数需要 ${10}。当n>=10时,需要使用${n}来获取参数。

另外,还有几个特殊字符用来处理参数:

命令说明
$#传递到脚本的参数个数
$*以一个单字符串显示所有向脚本传递的参数
$$脚本运行的当前进程ID号
$!后台运行的最后一个进程的ID号
$@与$*相同,但是使用时加引号,并在引号中返回每个参数。
$-显示Shell使用的当前选项,与set命令功能相同。
$?显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。

补充:

1、$? 仅对其上一条指令负责,一旦函数返回后其返回值没有立即保存入参数,那么其返回值将不再能通过 $? 获得。

2、函数与命令的执行结果可以作为条件语句使用。要注意的是,和 C 语言不同,shell 语言中 0 代表 true,0 以外的值代表 false

更多

shell和shell脚本

查看原文

赞 0 收藏 0 评论 0

mayihetu 发布了文章 · 1月3日

Shell 和 Shell 脚本

Shell

Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。
Shell 既是一种命令语言,又是一种程序设计语言。
Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。
Ken Thompson 的 sh 是第一种 Unix Shell,Windows Explorer (文件资源管理器)是一个典型的图形界面 Shell。

Linux 的 Shell 种类众多,常见的有:

  • Bourne Shell(/usr/bin/sh或/bin/sh)
  • Bourne Again Shell(/bin/bash)
  • C Shell(/usr/bin/csh)
  • K Shell(/usr/bin/ksh)
  • Shell for Root(/sbin/sh)
  • ……

Bash,也就是 Bourne Again Shell,由于易用和免费,在日常工作中被广泛使用。同时,Bash 也是大多数Linux 系统默认的 Shell。

Shell 脚本

一般书写

新建一个文件 test.sh,扩展名为 sh(sh代表shell),扩展名并不影响脚本执行。输入一些代码,一般是这样:

#!/bin/bash

echo "Hello World !"

1、#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。
2、一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh ,它同样也可以改为 #!/bin/bash

运行的两种方法

1、作为可执行程序

将上面的代码保存为 test.sh,并 cd 到相应目录:

chmod +x ./test.sh #使脚本具有执行权限

./test.sh #执行脚本

注意,一定要写成./test.sh,而不是test.sh,运行其它二进制的程序也一样
直接写 test.sh,linux 系统会去 PATH 里寻找有没有 test.sh ,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 test.sh 是会找不到命令的,要用 ./test.sh 告诉系统说,就在当前目录找。

2、作为解释器参数

直接运行解释器,其参数就是 shell 脚本的文件名,如:

/bin/sh test.sh

/bin/php test.php

这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用

脚本文件参数

在文件内使用 $ 获取。 如下:test.sh

#!/bin/sh

echo "shell脚本本身的名字: $0"

echo "传给shell的第一个参数: $1"

echo "传给shell的第二个参数: $2"

运行:

bash test.sh 1 2

文件包含(文件引入)

Shell 也可以包含(引入)外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。

Shell 文件包含的语法格式如下( .source ):

. filename 
source filename

实例

test1.sh

url="https://segmentfault.com"

test2.sh

. ./test1.sh

# source ./test1.sh

echo "思否:$url"

接下来,我们为 test2.sh 添加可执行权限并执行:

chmod +x test2.sh

./test2.sh 

输出

思否:https://segmentfault.com

注:被包含的文件 test1.sh 不需要可执行权限

变量类型

运行shell时,会同时存在三种变量:局部变量、环境变量、shell变量

局部变量: 在脚本或命令中定义,仅在当前shell实例中有效

环境变量: 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。

shell变量: shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行

输入/输出重定向

大多数 UNIX 系统命令从你的终端接受输入并将所产生的输出发送回​​到您的终端。

一个命令通常从一个叫标准输入的地方读取输入,默认情况下,这恰好是你的终端。同样,一个命令通常将其输出写入到标准输出,默认情况下,这也是你的终端。

重定向命令列表如下:

命令说明
command > file将输出重定向到 file
command < file将输入重定向到 file
command >> file将输出以追加的方式重定向到 file
n > file将文件描述符为 n 的文件重定向到 file
n >> file将文件描述符为 n 的文件以追加的方式重定向到 file
n >& m将输出文件 m 和 n 合并
n <& m将输入文件 m 和 n 合并
<< tag将开始标记 tag 和结束标记 tag 之间的内容作为输入

需要注意的是文件描述符 0 通常是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。

输出重定向

重定向一般通过在命令间插入特定的符号来实现。特别的,这些符号的语法如下所示:

command > file

1.执行 command 然后将输出的内容存入 file。
2.file内的内容将被替代。如果要将新内容 添加 在文件末尾,请使用>>操作符。

实例 :执行 who 命令,将命令的完整的输出重定向在用户文件中(users):

who > users

输入重定向

和输出重定向一样,Unix 命令也可以从文件获取输入,语法为:

command < file

这样,本来需要从键盘获取输入的命令会转移到文件读取内容。

实例 :统计 users 文件的行数:

一般方法:

wc -l users

# 2 users

重定向方法:将输入重定向到 users 文件:

wc -l < users

# 2

注意,上面两个例子的结果不同:第一个,会输出文件名;第二个不会,因为它仅仅知道从标准输入读取内容。

同时替换输入和输出

command < infile > outfile

执行command,从文件infile读取内容,然后将输出写入到outfile中。

重定向深入讲解

一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:

  • 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
  • 标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
  • 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。

默认情况下,command > file 将 stdout 重定向到 file,command < file 将stdin 重定向到 file。

如果希望 stderr 重定向到 file,可以这样写:

command 2 > file

如果希望 stderr 追加到 file 文件末尾,可以这样写:

command 2 >> file

2 表示标准错误文件(stderr)。

如果希望将 stdout 和 stderr 合并后重定向到 file,可以这样写:

command > file 2>&1

或者

command >> file 2>&1

Here Document(内容重定向)

Here Document 是 Shell 中的一种特殊的重定向方式,用来将输入重定向到一个交互式 Shell 脚本或程序。

它的基本的形式如下,作用是将两个 delimiter (分隔符,EOF等) 之间的内容(document) 作为输入传递给 command。

command << delimiter
    document
delimiter

注意:
1、结尾的delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。
2、开始的delimiter前后的空格会被忽略掉。

实例:在命令行中通过 wc -l 命令计算 Here Document 的行数:

$ wc -l << EOF
    hello,
    world!
EOF
2          # 输出结果为 2 行

/dev/null 文件

如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出 重定向/dev/null

command > /dev/null

1、/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;
2、如果尝试从该文件读取内容,那么什么也读不到。
3、但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。

如果希望屏蔽 stdout 和 stderr,可以这样写:

command > /dev/null 2>&1

shell 语法

详见 shell 语法学习菜鸟教程

查看原文

赞 0 收藏 0 评论 0

mayihetu 发布了文章 · 2018-09-30

ES6 ...操作符

在ES6语法中,...操作符有两种意义:rest(剩余语法,rest参数) 和 spread(展开语法,展开数组/对象),作为函数、数组、对象的扩展运算符。

从某种意义上说,剩余语法与展开语法是相反的:
剩余语法将多个元素收集起来并“凝聚”为单个元素,而展开语法则是将数组/对象展开为其中的各个元素。

剩余语法

rest参数

形式为(...变量名),将一个不定数量的参数表示为一个数组。用于获取函数实参中的多余参数,组成一个数组,这样就不需要使用arguments对象了。

语法

function(a, b, ...theArgs) {
  // ...
}
注意:
  • rest参数之后不能再有其他参数(只能是最后一个参数)否则会报错。
  • 函数的length属性不包括rest参数。

    (function(a,b,...c){}).length //2

一个实例

function sumOnlyNumbers() {  
  var args = arguments;
  var numbers = filterNumbers();
  return numbers.reduce((sum, element) => sum + element);


  function filterNumbers() {
    return Array.prototype.filter.call(args, 
        element => typeof element === 'number' 
    );
  }
}
sumOnlyNumbers(1, 'Hello', 5, false); // => 6  

缺点: 首先我们要将arguments分配给给一个临时新变量args,这样才能在内部函数filterNumbers中可以访问args新变量,因为 filterNumbers()定义了它自己的arguments 会覆盖外部的arguments 。这种做法太冗余了。
优化: 使用rest操作符可以灵活解决这个问题,允许在函数中定义一个rest参数 ...args:

function sumOnlyNumbers(...args) {  
  var numbers = filterNumbers();
  return numbers.reduce((sum, element) => sum + element);
  function filterNumbers() {
    return args.filter(element => typeof element === 'number');
  }
}
sumOnlyNumbers(1, 'Hello', 5, false); // => 6
剩余参数和 arguments 对象的区别摘自
  • 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参。
  • arguments 对象不是一个真实的数组,而剩余参数是真实的 Array实例。
    所以,能够在剩余参数上面直接使用所有的数组方法,比如 sort,map,forEach,pop。
    而如果想在arguments对象上使用数组方法,你首先得将它转换为真实的数组。

    [].slice.call(arguments)
  • arguments 对象对象还有一些附加的属性 (比如callee属性)。

解构赋值

数组
let [first,...rest]=[1,2,3,4,5];
first //1
rest  //[2,3,4,5]
// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list
const [first, ...rest] = [];
first // undefined
rest  // []
对象
let { x, ...y } = { x: 1, a: 2, b: 3 };
x // 1
y // { a: 2, b: 3 }
// 浅拷贝

let obj = { a: { b: 1 } };
let { ...x } = obj;

obj.a.b = 2;
x.a.b // 2
// ...运算符的解构赋值不能继承自原型对象的属性

let o1 = { a: 1 };
let o2 = { b: 2 };

o2.__proto__ = o1;
let { ...o3 } = o2;

o3 // { b: 2 }
o3.a // undefined
const o = Object.create({ x: 1, y: 2 });
o.z = 3;

let { x, ...newObj } = o;
let { y, z } = newObj;
x // 1
y // undefined
z // 3

上面代码中,变量x是单纯的解构赋值,所以可以读取对象o继承的属性;变量y和z是扩展运算符的解构赋值,只能读取对象o自身的属性,所以变量z可以赋值成功,变量y取不到值。
ES6 规定,变量声明语句之中,如果使用解构赋值,扩展运算符后面必须是一个变量名,而不能是一个解构赋值表达式,所以上面代码引入了中间变量newObj,如果写成下面这样会报错。

let { x, ...{ y, z } } = o;
// SyntaxError: ... must be followed by an identifier in declaration contexts

注意:

  • 解构赋值只能放在参数最后一位,否则会报错。
  • 解构赋值要求等号右边是一个对象,如果等号右边是undefined或null,就会报错,因为它们无法转为对象。
  • 解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本
  • ...运算符的解构赋值,不能复制继承自原型对象的属性
  • 变量声明语句之中,如果使用解构赋值,扩展运算符后面必须是一个变量名,而不能是一个解构赋值表达式

rest参数&解构赋值

扩展某个函数的参数,引入其他操作。

function baseFunction({ a, b }) {
  // ...
}
function wrapperFunction({ x, y, ...restConfig }) {
  // 使用 x 和 y 参数进行操作
  // 其余参数传给原始函数
  return baseFunction(restConfig);
}

展开语法

spread运算则可以看作是rest参数的逆运算。

数组

展开数组作为参数序列、复制数组、合并数组、代替apply、...+表达式

1.展开数组作为函数参数(...arr), 可以将数组转化为逗号分隔的参数序列

console.log(1,...arr)

arr1.push(...arr); 

注意:push方法的参数不能是数组

函数应用实例
JavaScript 的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个问题的一种变通方法。

var dateFields = readDateFields(database);
var d = new Date(...dateFields);

上面代码从数据库取出一行数据,通过扩展运算符,直接将其传入构造函数Date。

2.复制数组

const a2 = a1.concat();    // ES5

const itemsCopy = [...items];    // ES6

注意:这两种方法都是浅拷贝,使用的时候需要注意。

3.合并数组

// 合并生成一个新的数组,不影响原来的两个数组
arr = [4].concat(list)    // ES5

arr = [4, ...list]    // ES6
// 扩展arr变量,追加arr2
Array.prototype.push.apply(arr, list);    // ES5

arr.push(...list);    // ES6

如果扩展运算符后面是一个空数组,则不产生任何效果

[...[], 1]
// [1]

4.可以替代apply方法,apply要求将参数合并为数组,作为参数传入

Function.apply(obj,args)方法能接收两个参数

  • obj:这个对象将代替Function类里this对象
  • args:这个是数组,它将作为参数传给Function(args-->arguments)

数组没有max方法。Math.max.apply(null,[]);

Math.max.apply(null,[14,3,7])    // ES5写法
Math.max(...[14,3,7])    // ES6写法

// 等同于
Math.max(14, 3, 77);

5.扩展运算符后面还可以放置表达式。

const arr = [
  ...(x > 0 ? ['a'] : []),
  'b',
];

对象

复制对象、完整克隆、合并对象、...+表达式、取值函数get

1.拷贝

let z = { a: 3, b: 4 };
let n = { ...z };    // { a: 3, b: 4 }

这等同于使用Object.assign方法。

let aClone = { ...a };    // 等同于,
let aClone = Object.assign({}, a);

上面的例子只是拷贝了对象实例的属性。

2.完整克隆一个对象,拷贝实例属性+对象原型的属性

// 写法一
const clone1 = {
  __proto__: Object.getPrototypeOf(obj),
  ...obj
};

// 写法二
const clone2 = Object.assign(
  Object.create(Object.getPrototypeOf(obj)),
  obj
);

// 写法三
const clone3 = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
)

上面代码中,写法一的__proto__属性在非浏览器的环境不一定部署,因此推荐使用写法二和写法三。

3.合并对象

// 合并
let ab = { ...a, ...b };    // 等同于,
let ab = Object.assign({}, a, b);
如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉
let aWithOverrides = { ...a, x: 1, y: 2 };    // 等同于

let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };    // 等同于

let x = 1, y = 2, aWithOverrides = { ...a, x, y };    // 等同于

let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });

上面代码中,a对象的x属性和y属性,拷贝到新对象后会被覆盖掉。这用来修改现有对象部分的属性就很方便了

let newVersion = {
  ...previousVersion,
  name: 'New Name' // Override the name property
};

上面代码中,newVersion对象自定义了name属性,其他属性全部复制自previousVersion对象。

如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。
let aWithDefaults = { x: 1, y: 2, ...a };
// 等同于
let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
// 等同于
let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);

如果扩展运算符后面是一个空对象,则没有任何效果。
如果扩展运算符的参数是null或undefined,这两个值会被忽略,不会报错

{...{}, a: 1}    // { a: 1 }

let emptyObject = { ...null, ...undefined }; // 不报错

4.与数组的扩展运算符一样,对象的扩展运算符后面可以跟表达式。

const obj = {
  ...(x > 1 ? {a: 1} : {}),
  b: 2,
};

5.对象中取值函数get问题

扩展运算符的参数对象之中,如果有取值函数get,这个函数是会执行的
// 并不会抛出错误,因为 x 属性只是被定义,但没执行
let aWithXGetter = {
  ...a,
  get x() {
    throw new Error('not throw yet');
  }
};

// 会抛出错误,因为 x 属性被执行了
let runtimeError = {
  ...a,
  ...{
    get x() {
      throw new Error('throw now');
    }
  }
};

字符串

[...'hello']    // [ "h", "e", "l", "l", "o" ]

Unicode 是有两个字节、四字节之区分。上面的写法,有一个重要的好处,那就是能够正确识别四个字节的 Unicode 字符。

'x\uD83D\uDE80y'    //'x🚀y'

'x\uD83D\uDE80y'.length // 4
[...'x\uD83D\uDE80y'].length // 3

上面代码的第一种写法,JavaScript 会将四个字节的 Unicode 字符,识别为 2 个字符,采用扩展运算符就没有这个问题。因此,正确返回字符串长度的函数,可以像下面这样写。

function length(str) {
  return [...str].length;
}

length('x\uD83D\uDE80y') // 3

凡是涉及到操作四个字节的 Unicode 字符的函数,都有这个问题。因此,最好都用扩展运算符改写。

let str = 'x\uD83D\uDE80y';

str.split('').reverse().join('')    // 'y\uDE80\uD83Dx'

[...str].reverse().join('')    // 'y\uD83D\uDE80x'

上面代码中,如果不用扩展运算符,字符串的reverse操作就不正确。reverse,颠倒数组中元素的顺序,会改变原来的数组,而不会创建新的数组。

实现了 Iterator 接口的对象

任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组。

let nodeList = document.querySelectorAll('div'); //nodeList 类数组对象
let array = [...nodeList];

这时,...运算符可以将其转为真正的数组,原因就在于NodeList对象实现了 Iterator 。
对于那些没有部署 Iterator 接口的类似数组的对象,...运算符就无法将其转为真正的数组。

let arrayLike = {
  '0': 'a',
  '1': 'b',
  '2': 'c',
  length: 3
};

// TypeError: Cannot spread non-iterable object.
let arr = [...arrayLike];

上面代码中,arrayLike是一个类似数组的对象,但是没有部署 Iterator 接口,扩展运算符就会报错。这时,可以改为使用Array.from方法将arrayLike转为真正的数组。

Map 和 Set 结构,Generator 函数

扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。

let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

let arr = [...map.keys()]; // [1, 2, 3]

Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。

const go = function*(){
  yield 1;
  yield 2;
  yield 3;
};

[...go()] // [1, 2, 3]

上面代码中,变量go是一个 Generator 函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。

如果对没有 Iterator 接口的对象,使用扩展运算符,将会报错。

const obj = {a: 1, b: 2};
let arr = [...obj]; // TypeError: Cannot spread non-iterable object

阮老师的文章:数组中的扩展运算
阮老师的文章:对象中的扩展运算
可算有个跟我想的一样的了

查看原文

赞 4 收藏 2 评论 0

mayihetu 发布了文章 · 2018-09-30

ES6 解构赋值

解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。查看阮老师的原文

解构赋值的要点:前后结构一致

“模式匹配”,等号两边的模式相同,按照对应关系(位置或名称),左边的变量被赋予对应的值。

一、数组的解构赋值

数组:数组的元素是按次序排列的,变量的取值由它的位置决定;

let [a, b, c] = [1, 2, 3];
1. 解构不成功, foo等于undefined
let [foo] = [];
let [bar, foo] = [1];
2. 不完全解构
let [a, [b], d] = [1, [2, 3], 4];    //a b d - 1 2 4
3. 默认值
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
4. 一些规则:

ES6 内部使用严格相等运算符(===)

let [x = 1] = [undefined];    //x= 1
let [x = 1] = [null];    //x= null

如果默认值是一个表达式,那么这个表达式是惰性求值的, 以下f不会执行

function f() {
    console.log('aaa');
}

let [x = f()] = [1];

二、对象的解构赋值

对象:对象的属性没有次序,变量必须与属性同名,才能取到正确的值;
let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };

简写为,

let { foo, bar } = { foo: "aaa", bar: "bbb" };
1.对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者
let { foo: baz } = { foo: "aaa", bar: "bbb" };

上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。
可以理解为左侧表达式中':'前的都是匹配模式吗?(暗自这么理解)

2.用于嵌套结构的对象
let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p: [x, { y }] } = obj;

x // "Hello"
y // "World"

注意:这时p是模式,不是变量,因此不会被赋值。如果p也要作为变量赋值,可以写成下面这样。

let { p, p: [x, { y }] } = obj;

再一个例子

const node = {
  loc: {
    start: {
      line: 1,
      column: 5
    }
  }
};

let { loc, loc: { start }, loc: { start: { line }} } = node;
3.嵌套赋值的例子
let obj = {};
let arr = [];

({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });
4.对象的解构也可以指定默认值,默认值生效的条件是,对象的属性值严格等于undefined
5.如果解构失败,变量的值等于undefined
6.如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错
// 报错
let {foo: {bar}} = {baz: 'baz'};
7.将一个已经声明的变量用于解构赋值,必须非常小心
// 错误的写法, SyntaxError: syntax error
let x;
{x} = {x: 1};

// 正确的写法
let x;
({x} = {x: 1});

上面代码的写法会报错,因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。

8.解构赋值允许等号左边的模式之中,不放置任何变量名
({} = [true, false]);
({} = 'abc');
({} = []);
9.由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;

三、Set

对于 Set 结构,也可以使用数组的解构赋值
let [x, y, z] = new Set(['a', 'b', 'b']); //重复的会先被去掉,z等于undefined

四、Iterator

事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值
function* fibs() {
  let a = 0;
  let b = 1;
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

let [first, second, third, fourth, fifth, sixth] = fibs();
sixth // 5
如果等号的右边不是可遍历的结构,那么将会报错
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

五、字符串的解构赋值

const [a, b, c, d, e] = 'hello';
let {length : len} = 'hello'; //len=5

六、数值和布尔值的解构赋值

let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true

let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。

七、函数参数的解构赋值

function add([x, y]){
  return x + y;
}

add([1, 2]); // 3
[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]

区别以下两组

function move({x = 0, y = 0} = {}) {
  return [x, y];
}
move({}); // [0, 0]


function move({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}
move({}); // [undefined, undefined],  为函数move的参数指定默认值,而不是为变量x和y指定默认值

八、圆括号问题

ES6 的规则是,只要有可能导致解构的歧义,就不得使用圆括号
不能使用圆括号的情况:
(1)变量声明语句
(2)函数参数
(3)赋值语句的模式

可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号。

九、用法总结

提取 JSON 数据

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;    // id, status, number

交换变量的值

let x = 1;
let y = 2;

[x, y] = [y, x];

从函数返回多个值

// 返回一个数组

function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

// 返回一个对象

function example() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example();

函数参数的定义

// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);

// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});

函数参数的默认值

jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
  // ... more config
} = {}) {
  // ... do stuff
};

就避免了在函数体内部再写var foo = config.foo || 'default foo';这样的语句

遍历 Map 结构

// 获取键名
for (let [key] of map) {
  // ...
}

// 获取键值
for (let [,value] of map) {
  // ...
}

输入模块的指定方法

const { SourceMapConsumer, SourceNode } = require("source-map");

与ES6...语法一起用

查看原文

赞 0 收藏 0 评论 0

mayihetu 发布了文章 · 2018-04-11

webpack-基本使用

webpack 是一个现JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会分析你的项目结构,递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块按照指定的规则打包成一个或多个 bundle,过程中可以通过配置把一些浏览器不能直接运行的拓展语言(scss,typescript等)转换为合适的格式。

webpack处理模块化js

webpack 用于编译 JavaScript 模块。一旦完成安装,你就可以通过 webpack 的 CLI 或 API 与其配合交互。

准备工作

新建demo文件夹,npm init初始化项目, 局部安装webpack。

项目的基本结构

通常会在项目的根目录建立两个文件夹,分别为src文件夹和dist(或build)文件夹

  • src文件夹:用来存放我们编写的javascript代码,可以简单的理解为用JavaScript编写的模块。
  • dist文件夹:用来存放供浏览器读取的文件,这个是webpack打包成的文件。
使用webpack的基本文件
<!-- dist/index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Webpack Sample Project</title>
  </head>
  <body>
    <div id='root'>
    </div>
    <script data-original="bundle.js"></script>
  </body>
</html>
//  src/Greeter.js
module.exports = function() {
  var greet = document.createElement('div');
  greet.textContent = "Hi there and greetings!";
  return greet;
};
//  src/main.js 
const greeter = require('./Greeter.js');
document.querySelector("#root").appendChild(greeter());
使用webpack的方式

1.终端中使用

# {extry file}处填写入口文件的路径
# {destination for bundled file}处填写打包文件的存放路径
webpack {entry file} {destination for bundled file}

如果webpack不是全局安装的,那么当你在终端中使用此命令时,需要额外指定其在node_modules中的地址

# webpack非全局安装的情况
node_modules/.bin/webpack src/main.js dist/bundle.js

命令执行成功后,会在dist目录下出现bundle.js文件,index.html就可以在浏览器中预览结果了。

2.通过配置文件来使用Webpack

根目录下创建webpack.config.js, 配置entry、output,终端中使用时可以不用每次都手动添加。

const path = require('path');

module.exports = {
  entry:  __dirname + "/src/main.js", //已多次提及的唯一入口文件
  output: {
    path: __dirname + "/dist", //打包后的文件存放的地方
    filename: "bundle.js" //打包后输出文件的文件名
  }
}
webpack 或

webpack --config webpack.config.js

注:

  • “__dirname”是node.js中的一个全局变量,它指向当前执行脚本所在的目录。
  • 文件中path也可以这样写path: path.resolve(__dirname,'dist'), 注意需要引入‘path’, path.resolve(__dirname,'dist')获取了项目的绝对路径
  • 非全局需使用node_modules/.bin/webpack
  • 如果 webpack.config.js 存在,则 webpack 命令将默认选择使用它,终端命令可以不明确指出,如果命名为其他名字,需要--config xxx指明希望使用的配置文件。

3.npm 脚本

通常webpack推荐局部安装,因此在命令行中输入命令类似于node_modules/.bin/webpack这样的路径比较麻烦,可以在package.json中对scripts对象进行相关设置即可,设置方法如下

{
    ...
    "scripts": {
        "start": "webpack" // 修改的是这里,JSON文件不支持注释,引用时请清除
    },
    ...
}
为什么可以直接写webpack,而不需要写路径,详见webpack安装踩坑中关于npm 脚本原理的内容。

运行

npm run start 或

npm start

//除了几个默认的命令,如start等,run不可以省略。

webpack的基本使用就完成了……

其他方式处理相互依赖的js

1.使用古老的js引入方式去管理 JavaScript 项目
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Webpack Sample Project</title>
  </head>
  <body>
    <div id='root'>
    </div>
    <script data-original="greet.js"></script>
    <script data-original="main.js"></script>
  </body>
</html>
// greet.js
function greeter() {
  var greet = document.createElement('div');
  greet.textContent = "Hi there and greetings!";
  return greet;
};
// main.js
document.querySelector("#root").appendChild(greeter());

会有一些问题:

  • 无法立即体现,脚本的执行依赖于外部扩展库(external library)。
  • 如果依赖不存在,或者引入顺序错误,应用程序将无法正常运行。
  • 如果依赖被引入但是并没有使用,浏览器将被迫下载无用代码。
2.模块化方式
  • CMD规范的seaJS
  • AMD规范的requireJS
  • browserify
  • ES2015 中的 import 和 export 语句已经被标准化(大多数浏览器还无法支持它们)
查看原文

赞 0 收藏 1 评论 0

mayihetu 发布了文章 · 2018-04-03

webpack-安装踩坑

前提:安装了 Node.js

Tips:

  • webpack可以全局安装或者本地安装。官网上不推荐全局安装,因为这会将你项目中的 webpack 锁定到指定版本,并且在使用不同的 webpack 版本的项目中,可能会导致构建失败。
  • 全局和局部都安装webpack,这样命令行内直接使用webpack命令,使用的是全局的,npm运行的是局部的webpack
  • 全局安装是为了可以在命令行中使用webpack,项目安装是为了让项目发布后,其他人可以在直接使用npm命令时使用与你相同版本的webpack。

全局安装webpack

$ npm install -g webpack
全局安装的包都在 /usr/local/lib/node_modules 文件夹下(OS X)。安装后可执行文件默认放在/usr/local/bin,库文件默认放在/usr/local/lib,配置文件默认放在/usr/local/etc。bin目录一般存编译好的dll库文件和可执行文件,bin是二进制binrary的英文缩写。(查看bin目录下是各种软链接,git,npm等)

安装失败

一般有三种可能:

  • 检查你node的版本号,如果版本号过低,升级为最新版本。
  • 网络问题,可以考虑使用cnpm来安装(淘宝实时更新的镜像),具体可以登录cnpm的官方网站学习。
  • 权限问题,在Linux、Mac安装是需要权限,如果你是Windows系统,主要要使用以管理员方式安装。

安装成功,运行失败

可能是环境变量配置的原因,尝试的解决方式和结果:

  • 直接在终端下,设置环境变量export NODE_PATH="/usr/local/lib/node_modules", 后在项目根目录运行webpack,仍然报错。在项目根目录下输入指令 echo $NODE_PATH 输出结果为空。
  • 在项目根目录下设置环境变量export NODE_PATH="/usr/local/lib/node_modules" 后在项目根目录运行webpack,成功!在项目根目录下输入指令 echo $NODE_PATH 能输出结果。
    但是当新开一个终端进入项目,并在项目根目录下运行webpack指令,仍然报错,输入指令 echo $NODE_PATH 输出结果为空。说明之前设置的环境变量只是一个临时的值!
  • 在~/.bash_profile中添加如下设置:export NODE_PATH="/usr/local/lib/node_modules",保存退出。
    运行webpack,成功!输入指令 echo $NODE_PATH 能输出结果!

局部安装webpack


$ npm install webpack --save-dev

Tips

  1. 局部 npm install 安装包之前,需要在当前目录下执行初始化。也就是说当前目录必须有package.json文件,或者你在当前的目录下人为的建立好node_modules目录。

    • 项目中只有package.json,文件内容不能为空,不能为null,至少应该包含一个{},会如期望安装,安装完成后会生成node_modules文件夹,package.json中新增devDependencies。
    • 项目中只有node_modules文件夹,可以正常下载到文件夹中,不会生成package.json,可能会生成package-lock.json(取决于npm版本)。再初始化的时候,webpack及版本号默认添加在dependencies,而不是devDependencies。
    • 两者都没有,npm会一直向上寻找package.json或者node_modules文件夹所在目录,最终终止在用户根目录。
  2. 如果你使用的是 npm 5,你可能还会在目录中看到一个 package-lock.json 文件(可以理解为npm5以上,有包下载到node_modules,就会生成package-lock.json)。
  3. package.json中的name属性不能为“webpack”
  4. 本地安装 webpack 后,我们也并不能在命令行中使用 webpack 命令。因为环境变量中没有对应的路径,提示:webpack command is not found.
  5. 如果不带参数或者带--save,会在dependencies这里。卸载的时候清空dependencies。--save-dev在"devDependencies",卸载清空devDependencies。卸载不用带参数。

那么,我们应该如何使用局部webpack命令呢?

1. 利用package.json设置中的scripts属性。定义在package.json里面的脚本,称为 npm 脚本
在 npm scripts 中我们可以通过包名直接引用本地安装的 npm 包的二进制版本,而无需编写包的整个路径。

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "lwebpack": "webpack"
}
在命令行下,$ npm run,然后回车,就会显示所有可以使用的命令。npm run 是npm run-script的缩略。
start、test、stop和restart这样的特殊脚本可以直接执行,npm [xx], 其它的脚本任务需要用npm run xx来执行。
通过向 npm run [命令]和参数之间添加两组两个中横线,可以将自定义参数传递给 npm 脚本,例如:npm run lwebpack -- --v。

npm 脚本原理

1.每当执行npm run,就会自动新建一个 Shell,在这个 Shell 里面执行指定的scripts脚本命令。因此,只要是 Shell(一般是 Bash)可以运行的命令,就可以写在 npm 脚本里面。

2.比较特别的是,npm run新建的这个 Shell,执行scripts的时候,会将当前目录的node_modules/.bin子目录加入PATH变量,执行结束后,再将PATH变量恢复原样。

3.这意味着,当前目录的node_modules/.bin子目录里面的所有脚本,在scripts中都可以直接用脚本名,而不必加上路径。比如,当前项目的依赖里面有 Mocha,只要直接写mocha test就可以了。

"test": "./node_modules/.bin/mocha test"
//可以简写为
"test": "mocha test"

由于 npm 脚本的唯一要求就是可以在 Shell 执行,因此它不一定是 Node 脚本,任何可执行文件都可以写在里面。

2. 自定义shell命令来操作

$ alias lwebpack="node_modules/.bin/webpack" 或,
$ alias lwebpack =PATH=$(npm bin):$PATH

3. 可以运行在初始安装的 webpack 包中的 webpack 二进制文件(./node_modules/.bin/webpack)。直接在cli里运行:

$ node_modules/.bin/webpack -v
$ node_modules/webpack/bin/webpack.js -v

本地安装 webpack 后,node_modules中会生成一个.bin文件夹,可以发现里面有webpack相关的几个文件。这里的 webpack 是 node_modules/webpack/bin 这个包里面 webpack.js可执行文件 的软链接 。直接在.bin 目录下使用./webpack -v,或者在bin目录下使用./webpack.js -v,需要加[ . / ], 外层则可加,可不加。

注意:当在 windows 中通过调用路径去调用 webpack 时,必须使用反斜线\

4.$ node ./node_modules/.bin/webpack -v 同理,执行 webpack 的可执行 js ,这样执行的好处是可以加一些 js 的标记变量 flag ,例如调大 old memory size 或者进行 chrome debug (暂时还没懂什么意思,不过这也是一种应用方法)
5. 还可以:

$ `npm bin`/webpack -v

6. npm 5.2.0以上,会安装一个新的包npx。npx是一个npm包执行器,旨在提高从npm注册表使用软件包的体验。

$ npx webpack -v

7. 新建一个index.js,内容如下:

const webpack = require('webpack');
webpack();

然后在cli里运行$ node index.js即可


8. 利用package.json的bin属性npm link
网上查的时候有人提到了bin这个属性,这里,花了一些时间,终于好像弄清楚了。

package.json中bin:
很多包都有一个或多个可执行模块,希望用户安装包的同时,把这些可执行模块配置到PATH中,npm让这个工作变得十分简单(实际上npm本身也是通过bin属性安装为一个可执行命令的)
bin,是一个命令名和本地文件名的映射。在npm安装某个包(模块)时,会查找这个包中package.json文件是否包含bin属性,如果有,会为bin中配置的可执行文件创建一个软链接,名字是bin中的key值,安装方式不同,链接位置不同。如果是全局安装,链接会放到prefix/bin(对于windows系统,默认会在C:UsersusernameAppDataRoamingnpm目录下,OS X系统中在usr/local/bin,链接到usr/local/lib/node_modules/包下的对应文件),这使你可以直接在命令行执行key对应命令。如果是本地(局部)安装,则会在项目内的./node_modules/.bin/目录下创建一个软链接,指向node_modules/包下对应可执行文件。
如果你只有一个可执行文件,那么它的名字应该和包名相同,此时只需要提供这个文件路径(字符串),如:"bin": "./path/to/program"
(实验:在demo中package.json中添加bin,执行npm install,不行,什么软链接都没有;内容都删除只留下package.json然后npm intall,也不行。如果想实验这个功能,可以实验性的安装自己本地的包,使用相对路径。如npm install ../project)
npm link ,  create a global symbolic link to the current folder.
如果package.json中没有bin,单独使用npm link:只在/usr/local/lib/node_modules/下生成了一个链接指向项目目录,链接的名字package.json中的name.
结合bin使用npm link:在/usr/local/lib/node_modules中新生成了一个软链接,(名字是package.json的name?还是项目名?经实验是name)指向项目文件夹。并且,在/usr/local/bin中新生成了一个软链接(bin中的key),指向/usr/local/lib/node_modules/(name)/下bin指定的文件。
(两个不同版本webpack项目中,设置link同一个名字,(如果link4.0版本,哪里都4.0,如果link3.0,则有4.0的4.0,其他3.0. 设置的覆盖先设置的)

安装指定版本

$ npm install webpack@version

最新体验版本

如果你热衷于使用最新版本的 webpack,你可以使用以下命令,直接从 webpack 的仓库中安装:

$ npm install webpack@beta
$ npm install webpack/webpack#<tagname/branchname>

安装这些最新体验版本时要小心!它们可能仍然包含 bug,因此不应该用于生产环境。

卸载

$ npm uninstall webpack -g ,//没试
$ npm uninstall webpack
查看原文

赞 9 收藏 10 评论 0

mayihetu 发布了文章 · 2018-03-20

Mac、Linux 安装zsh & ohmyzsh

shell 俗称壳,c语言编写的命令解析器程序,是用户使用linux的桥梁。Linux/Unix提供了很多种Shell。常用的Shell有这么几种,sh、bash、csh等。可以通过以下命令,查看系统有几种shell

    $ cat /etc/shells 

目前常用的 Linux 系统和 OS X 系统的默认 Shell 都是 bash。但是真正强大的 Shell 是深藏不露的 zsh,史称『终极 Shell』,由于与bash相似,功能又有所加强,zsh在Linux社区获得了关注。但因配置过于复杂,所以初期无人问津。直到国外有个程序员开发出了一个能够快速上手的zsh项目,叫做「oh my zsh」,Github 网址是:https://github.com/robbyrusse...

安装使用zsh&ohmyzsh 的方法如下:

第一步:查看系统中有无zsh,以及版本
$ cat /etc/shells  或
$ zsh --version  //--zsh 5.2 (x86_64-apple-darwin16.0)

$ echo $ZSH_VERSION     //--5.2
第二步:若系统中没有zsh,则需要安装: ( 更多系统的安装方式 )
// Linux
$ sodu yum install zsh    (Fedora和RedHat以及SUSE中)或
$ sodu apt-get install zsh    (Debian系列,Ubuntu )

// macOS 系统自带了zsh, 一般不是最新版,如果需要最新版可通过Homebrew来安装(确认安装了Homebrew)
$ brew install zsh zsh-completions

// 或者也可以使用MacPorts(包管理工具)
$ sudo port install zsh zsh-completions

rpm包和deb包是两种Linux系统下最常见的安装包格式。rpm包主要应用在RedHat系列包括 Fedora等发行版的Linux系统上,deb包主要应用于Debian系列包括现在比较流行的Ubuntu等发行版上。

yum命令是在Fedora和RedHat以及SUSE中基于rpm的软件包管理器,它可以使系统管理人员交互和自动化地更细与管理RPM软件包,能够从指定的服务器自动下载RPM包并且安装,可以自动处理依赖性关系,并且一次安装所有依赖的软体包,无须繁琐地一次次下载、安装。
apt-get命令是Debian Linux发行版中的APT软件包管理工具。所有基于Debian的发行都使用这个包管理系统。deb包可以把一个应用的文件包在一起,大体就如同Windows上的安装文件。(更多关于apt和apt-get )

第三步:查看当前默认shell,→ usr/bin/zsh or similar
$ echo $SHELL    //把zsh设为默认shell,如果shell列表中没有zsh或者你没有使用chsh权限的时候,不起作用
       
$ [sudo] chsh -s $(which zsh) 或,
$ chsh -s /bin/zsh

注销重新登录后生效

第四步:安装 oh my zsh,主题样式介绍

安装 oh my zsh 之前必须安装zsh,否则会收到如下提示:Zsh is not installed! Please install zsh first!

#方法一:wget方式自动化安装oh my zsh:
$ wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | sh

#方法二:
$ curl -L https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh | sh 

#官网上的另外一种写法 
$ sh -c "$(wget https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"
$ sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

#方法三:当然也可以通过git下载 
$ git clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh                       

wget,Linux命令,用来从指定的URL下载文件。mac使用这个命令,需要安装。可以参考这里或者这里

$ wget(选项)(参数)
$ wget url  下载一个文件到当前目录
$ wget url -O - 在终端展示文件内容

-O -在终端展示文件内容
Saving to: “STDOUT”。stdout,标准输出,默认将信息输出到终端,在默认情况下,stdout是行缓冲的,他的输出会放在一个buffer里面,只有到换行的时候,才会输出到屏幕。

curl,linux命令,是一种命令行工具,作用是发出网络请求,然后得到和提取数据,显示在"标准输出"(stdout)上面。它被广泛应用在Unix、多种Linux发行版中,并且有DOS和Win32、Win64下的移植版本,已经是苹果机上内置的命令行工具之一了。window上的安装和使用参考这里。更多curl可以学习阮一峰curl网站开发指南

sh命令是shell命令语言解释器,执行命令从标准输入读取或从一个文件中读取。通过用户输入命令,和内核进行沟通!

$ sh [options] [file]  -c string    //命令从-c后的字符串读取。

在执行脚本的时候是用sh + 脚本名的方式来执行,其实,大部分的时候,简单脚本只要权限设置正确,可以直接执行,不需要sh命令的

| ,Linux管道符,利用管道符将两个命令隔开,管道符左边命令的输出就会作为管道符右边命令的输入。

第五步:配置。
1.查看什么Theme可以用
   $ ls ~/.oh-my-zsh/themes
2.查看是否有 ~/.zshrc文件,如果想要备份系统的zsh配置
   $ cp ~/.zshrc ~/.zshrc.orig
3.创建配置文件(cp 源文件 目标文件 把源文件复制到目标文件并改名,如果不存在,新建,如果已存在,内容覆盖,也可以手动)
   $ cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc
4.Oh-My-Zsh的默认配置文件在:~/.zshrc。编辑~/.zshrc修改主题,默认情况下,使用的是robbyrussell主题:(在line 10,重启终端后有效或者使用source ~/.zshrc更新配置)
   ZSH_THEME="amuse"
进一步优化:

精简 user@hostname:添加export DEFAULT_USER="username"到~/.zshrc中,可以隐藏固定的 user@hostname 信息。

可以到这里下载一些zsh的主题,放置在 ~/.oh-my-zsh/themes 目录目录下,并在配置文件.zshrc中进行适当的配置。其实,默认情况下,themes目录下已有很多样式。

还可以安装一些插件

配置内置的插件(在line 54,https://www.linuxidc.com/Linu...

查看原文

赞 6 收藏 4 评论 1

mayihetu 发布了文章 · 2018-03-20

SSH-远程登录

SSH是一种网络协议,用于计算机之间的加密登录。

一、客户端登录基本用法
$ ssh user@host (可以在config中配置,使用 ssh 别名,不需要用户名和主机ip. config配置见五)
$ ssh host    如果本地用户名与远程用户名一致,登录时可以省略用户名

SSH的默认端口是22,登录请求会送进远程主机的22端口,使用p参数,可以修改这个端口。

$ ssh -p 2222 user@host
二、口令登录(密码登录)--使用远程主机公钥加密密码,远程私钥解密
过程:
(1)远程主机收到用户的登录请求,把自己的公钥发给用户。
(2)用户使用这个公钥,将登录密码加密后,发送回来。
(3)远程主机用自己的私钥,解密登录密码,如果密码正确,就同意用户登录。

首次登录时,系统提示:无法确认主机真实性,只知道它的公钥指纹(公钥1024位,通过MD5计算,变成128位的指纹),是否连接?
当远程主机的公钥被接受以后,它就会和ip一起被保存在文件$HOME/.ssh/known_hosts之中。再次登录时,主机发来的公钥直接与known_hosts文件中的对应iP的公钥作对比,从而跳过警告部分,直接提示输入密码。

风险: 如果攻击者插在用户与远程主机之间(比如在公共的wifi区域),用伪造的公钥,获取用户的登录密码。再用这个密码登录远程主机,那么SSH的安全机制就荡然无存了。这种风险就是著名的"中间人攻击"(Man-in-the-middle attack)

每个SSH用户都有自己的独立的known_hosts文件,此外系统也有一个这样的文件,通常在/etc/ssh/ssh_known_hosts,保存一些对所有用户都可信赖的远程主机的公钥。

三、公钥登录(密钥登录)--本地使用私钥加密远程主机发送的随机字符

SSH还提供了公钥登录,可以省去输入密码的步骤。

过程:
(1)用户本地生成密钥对,一个私钥一个公钥。 (也可以远程生成一对,把私钥下载到本地)
(2)登录前,上传自己的公钥到服务器 .ssh/authorized_keys中,注意文件夹和文件的权限。
(3)客户端向服务器发出请求,请求用你的密匙进行安全验证;
(4)服务器收到请求之后,先在该服务器上寻找你的公钥,然后把它和你发送过来的公用密匙进行比较。如果两个密匙一致,服务器就用这个公钥加密一个随机字符串并把它发送给客户端;
(5)本地收到后,使用私钥进行解密,如果生成密钥时,加了密码,此时需要密码,然后将解密后的字符串发送给服务端;
(6)远程主机得到的解密字符串与最初发送的一致,就证明用户是可信的,直接允许登录shell,不再要求密码。

这种方式的好处是其他机器无法仿冒真正的服务器,因为必须拿到客户端的公钥。这就要求用户必须提供自己的公钥。使用这种方法的步骤

1.如果没有,可以直接用ssh-keygen生成一个,ssh-keygen是用于生产密钥的工具:
$ ssh-keygen
$ ssh-keygen -b 1024 -t rsa -P '' -f ~/.ssh/id_rsa

参数说明:

-b:采用长度为1024字节的公钥/私钥对,最长4096字节,一般1024或2048,rsa方式最短不能小于768字节长度。
-t:指定生成密钥类型(rsa、dsa、ecdsa等),默认为SSH-2 的rsa类型;
-P:指定passphrase,用于确保私钥的安全
-f:指定存放密钥的文件

运行上面的命令以后,系统会出现一系列提示,可以一路回车。其中有一个问题是,要不要对私钥设置口令(passphrase),如果担心私钥的安全,这里可以设置一个。运行结束以后,在$HOME/.ssh/目录下,会新生成两个文件:id_rsa.pub和id_rsa。前者是你的公钥,后者是你的私钥。

2.最关键的是要留意远程服务器上的文件和目录的权限问题

再一次强调用户自己的目录(~/.ssh)必须不能有其他人可写的权限,否则ssh服务器会拒绝登录。

//~/.ssh 目录的必须是700权限
$ chmod 700 ~/.ssh 
// ~/.ssh/authorized_keys 必须是600权限,, 否则ssh服务器会拒绝用户登陆
$ chmod 600 ~/.ssh/authorized_keys
3.将公钥传送到远程主机host上面的3种方法:
$ scp -P 22 ~/.ssh/id_rsa.pub user@host:~/authorized_keys #可选参数-P

$ ssh-copy-id user@host  #此种方式简单,不需追加改文件名,但不能指定端口号,默认以22端口

$ cat ~/.ssh/id_rsa.pub | ssh -p 22 user@host 'cat >> ~/.ssh/authorized_keys'
4.如果连接不成功,打开远程主机的/etc/ssh/sshd_config这个文件,打开密钥登录功能。检查下面几行前面"#"注释是否取掉。
RSAAuthentication yes

PubkeyAuthentication yes

AuthorizedKeysFile .ssh/authorized_keys

另外,请留意 root 用户能否通过 SSH 登录:

PermitRootLogin yes

当你完成全部设置,并以密钥方式登录成功后,再禁用密码登录:

PasswordAuthentication no

然后,重启远程主机的ssh服务。

// ubuntu系统
service ssh restart

// debian系统
/etc/init.d/ssh restart
四、known_hosts、authorized_keys文件
known_hosts, 首次登录远程主机时,确认后,远程主机发来的公钥(是固定的,不是随机改变的,因为是公钥,可公开),之后登录就不再需要确认了。

authorized_keys, 远程主机$HOME/.ssh/authorized_keys中,保存登录用户的公钥。用于密钥登录(免密码登录)。公钥就是一段字符串,只要把它追加在authorized_keys文件的末尾就行了。

这里不使用上面的ssh-copy-id命令,改用下面的命令,解释公钥的保存过程:

$ ssh user@host 'mkdir -p .ssh && cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub

这条命令由多个语句组成,依次分解开来看:

  1. "$ ssh user@host",表示登录远程主机;
  2. 单引号中的mkdir .ssh && cat >> .ssh/authorized_keys,表示登录后在远程shell上执行的命令:
  3. "$ mkdir -p .ssh"的作用是,如果用户主目录中的.ssh目录不存在,就创建一个;
  4. 'cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub的作用是,将本地的公钥文件~/.ssh/id_rsa.pub,重定向追加到远程文件authorized_keys的末尾。

写入authorized_keys文件后,公钥登录的设置就完成了。

五、config文件

ssh client有两个配置文件,/etc/ssh/ssh_config和~/.ssh/config,前者是对所有用户,后者是针对某个用户,不存在可以创建一个,两个文件的格式是一样的。

ssh默认使用~/.ssh/id_rsa这个密钥,想使用指定密钥的两种方法:

1.ssh -i path/to/id_rsa username@server.com
2.可以在~/.ssh/config中配置IdentifyFile,同时也可以配置用户名、服务器地址
Host server1
        HostName server.com
        User username
        IdentifyFile path/to/id_rsa

使用 ssh server1。server1 就是个别名,可以任意取名,不取也可以

Host *github.com*
        User git
        IdentityFile ~/.ssh/id_rsa_github         

示例: 有两个github账号,一台电脑上都要访问这两个账号,都使用ssh key认证方式

Host github-user1
        HostName github.com
        User git
        IdentityFile ~/.ssh/id_rsa1
Host github-user2
        HostName github.com
        User git
        IdentityFile ~/.ssh/id_rsa2

使用git clone时就用如下命令:

$ git clone github-user1:user1/repo.git
$ git clone github-user2:user2/repo.git

指定哪个用户登录哪个主机,使用哪个私钥,权限 rw--r--r。 (ly:指定的是私钥,所以用于公钥/密钥/免密码登录时。密码登录不需要私钥。)

六、ssh配置文件ssh_config和sshd_config区别(参考:www.cnblogs.com/xiaochina/p/5802008.html

ssh_config和sshd_config都是ssh服务器的配置文件,二者区别在于,前者是针对客户端的配置文件,后者则是针对服务端的配置文件。两个配置文件都允许你通过设置不同的选项来改变客户端程序的运行方式。

查看原文

赞 2 收藏 1 评论 0

mayihetu 发布了文章 · 2018-03-16

git-本地文件命令

1. 初始化本地库
$ git init

文件夹中生成.git版本库,包括stage(index),还有git自动创建的分支master,指向master分支的指针HEAD指针。

2. 工作区→缓存区→版本库 (工作区:电脑中可以看到的目录)

拆分作业

$ git add [file1] [file2] 
$ git add [dir1] [dir2]
$ git add .
$ git commit -m "a readme file"

添加、提交一步到位

$ git commit -am "once"
$ git commit -a -m "once"
$ git commit [file] [folder] -m ""
3. 提交时,漏掉了某文件
$ git add xxx
$ git commit --amend 编辑工具会显示最近一次提交的提交message,可以修改
$ git commit --amend -m 
$ git commit --amend --no-edit (加入--no-edit参数,会修复提交但不修改提交信息)
4. 撤销修改

a. 撤销工作区的修改,不影响缓存区

$ git checkout -- [file1] [file2]
$ git checkout -- . 

b. 撤销缓存区的修改,将修改内容放回工作区,与工作区后期修改合并(clean 或者合并2处修改)

$ git reset HEAD xxx(文件名)
$ git reset HEAD .
5. 查看
$ git status
$ git diff xxx                        工作区和缓存区
$ git diff --cached/staged      已经暂存起来的文件和上次提交时的快照之间的差异
$ git diff HEAD -- xxx             工作区和版本库  

注:修改并add一个版本,工作区再改回来,工作区与版本库无差别。有待add的文件, add 后无待commit的文件了
$ git log   显示从最近到远的提交日志
$ git log --stat        显示commit历史,以及每次commit发生变更的文件
$ git log -p [files]        显示指定文件的每次commit的diff
$ git log -p -2        常用 -p 选项展开显示每次提交的内容差异,用 -2 则仅显示最近的两次更新, 还有其他参数可选
$ git log -[number] --pretty --oneline        显示最近number次的log
$ git log --pretty=oneline        漂亮地展示
$ git log --graph        分支合并图
$ git log --graph --pretty=oneline --abbrev-commit        分支合并情况
6.回退版本

HEAD表示当前版本,即最新的提交,HEAD^上一个版本, HEAD^^, HEAD~100, clean。(--soft不会clean,modified)

$ git reset --hard HEAD^
$ git reset --hard HEAD            等价于 clean掉工作区和缓存区
$ git reset --hard [commit_id]
$ git reset [commit_id] (非clean,有待添加的文件)
7.回到未来版本

当回退到某个版本x后,想回来,发现git log中,看不到x之后的commit记录(x+1)。此时,

a. 你的命令行还没关,你就可以顺着往上找,找到对应的commit id
b. $ git reflog,找到对应的commit id
$ git reset --hard 3628164
8.“储藏”和“恢复”工作现场
$ git stash
$ git stash list 查看
$ git stash apply    stash内容并不删除
$ git stash pop        恢复的同时把stash内容也删了
$ git stash drop  (.......)
9.文件重命名
$ git mv README.txt README

其实,运行 git mv 就相当于运行了下面三条命令:

$ mv README.txt README
$ git rm README.txt
$ git add README
10.删除文件,手动删除之后,2个选择:
a.从版本库中删除 $ git rm xxx / add commit 
b.误删了,恢复 $ git checkout -- xxx (撤销工作区修改)

如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f(译注:即 force 的首字母),以防误删除文件后丢失修改的内容。
另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。用 --cached 选项即可:

$ git rm --cached readme.txt

后面可以列出文件或者目录的名字,也可以使用 glob 模式。比方说:

$ git rm log/\*.log

注意到星号 * 之前的反斜杠 ,因为 Git 有它自己的文件模式扩展匹配方式,所以我们不用 shell 来帮忙展开(译注:实际上不加反斜杠也可以运行,只不过按照 shell 扩展的话,仅仅删除指定目录下的文件而不会递归匹配。上面的例子本来就指定了目录,所以效果等同,但下面的例子就会用递归方式匹配,所以必须加反斜杠。)。此命令删除所有 log/ 目录下扩展名为 .log 的文件。类似的比如:

$ git rm \*~

会递归删除当前目录及其子目录中所有 ~ 结尾的文件。

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 23 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-01-29
个人主页被 810 人浏览