Little H title

this is subtitle


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 公益404

pacman源添加及yaourt安装

发表于 2018-06-10 | 分类于 linux

pacman的介绍

在linux系统中安装一个新应用,主要有三部分,软件包文件、库、依赖关系。
软件包是程序本身的数据文件。库是各种函数封装存放的地方。依赖关系是各个程序之间共用的数据和函数库形成的联系。
所以,要安装一个软件不光要安装软件本身,还要依据依赖关系安装其他用到的库和软件。

1.添加源

源,是软件源的简称,是互联网上存放软件包和库的服务器,这些服务器一般都是由官方维护,不少高校、互联网公司等权威机构有自己的镜像源,也有开发者自己的社区软件源。

软件包工具,是使用这些源的工具,多是终端里的一种命令,如apt-get 、yum、dpkg、pacman等,在这些工具中,分为高级和低级,低级工具(dpkg、rpm)执行安装删除等任务,高级工具(apt-get、yum、pacman)提供依赖关系解决等功能。每种工具都有相对应的软件包格式、相对应的源。

pacman源的设置在/etc/pacman.conf和/etc/pacman.d/mirrorlist里

1)在/etc/pacman.conf类似这样

1
2
3
4
5
6
7
8
9
10
11
[core]
SigLevel = PackageRequired
Include = /etc/pacman.d/mirrorlist

[extra]
SigLevel = PackageRequired
Include = /etc/pacman.d/mirrorlist

[community]
SigLevel = PackageRequired
Include = /etc/pacman.d/mirrorlist

2)在/etc/pacman.d/mirrorlist类似这样
mirrorlist文件默认的源都在国外, 速度慢,需要添加国内镜像地址作为源,添加内容如下:

1
2
3
4
Server = https://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch  
Server = https://mirrors.aliyun.com/archlinux/$repo/os/$arch
Server = https://mirrors.tuna.tsinghua.edu.cn/archlinux/$repo/os/$arch
Server = https://mirrors.163.com/archlinux/$repo/os/$arch

上面是大概的,下面是要做的
1.根据软件源的速度排列源(在终端输入)也就不用在上面手动添加了

1
sudo pacman-mirrors -g

一步到位

1
sudo pacman-mirrors -c China

2.Wiki上一个优化机械硬盘的命令,类似于磁盘整理(固态硬盘跳过这步,固态不要用,虚拟机也不要用)

1
sudo pacman-optimize && sync

3.更新系统(后面添加完USTC源之后也要一次更新)

1
sudo pacman -Syyu

4.完成以上步骤后,可添加archlinuxCN源,方便我们安装等软件(比如google-chrome)

1
sudo vim /etc/pacman.conf

在打开的文件最后黏贴上以下几行:(这里用的中科大的源,用了可以搜到chrome的哦)

1
2
3
4
# USTC
[archlinuxcn]
SigLevel = Optional TrustedOnly
Server = https://mirrors.ustc.edu.cn/archlinuxcn/$arch

5.导入 GPG key(这个必须要有哦,会自动签名的)

1
sudo pacman -S archlinuxcn-keyring

//这块暂时不用
(2)运行sudopacman -Syyu提示Keys错误,GPG啥的,依次运行以下命令:
sudo rm -r /etc/pacman.d/gnupg(移除旧的keys)
sudo pacman -Sy gnupg archlinux-keyring manjaro-keyring(重新安装最新keys)
sudo pacman-key —init(初始化pacman的keys)
sudo pacman-key —populate archlinux manjaro(加载签名的keys)
sudo pacman-key –refresh-keys(刷新升级已签名keys)
sudo pacman -Sc(清空并下载新数据)
最后运行:

sudo pacman -Syyu

pacman的使用

pacman)软件包管理器是 Arch Linux 的一大亮点。它将一个简单的二进制包格式和易用的构建系统结合了起来(参见makepkg和ABS)。不管软件包是来自官方的 Arch 库还是用户自己创建,pacman 都能方便地管理。

pacman 通过和主服务器同步软件包列表来进行系统更新。这种服务器/客户端模式可以使用一条命令就下载或安装软件包,同时安装必需的依赖包。

pacman 用 C 语言编写,使用tar打包格式。

我们可以在机子上使用pacman -h来看基本参数或者man pacman来查看具体使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[abc@manjaro ~]$ pacman -h  
usage: pacman <operation> [...]
operations:
pacman {-h --help} //帮助
pacman {-V --version} //查看版本
pacman {-D --database} <options> <package(s)> //数据库选项
pacman {-F --files} [options] [package(s)]
pacman {-Q --query} [options] [package(s)] //列出本机中安装的所有的包
pacman {-R --remove} [options] <package(s)> //删除包
pacman {-S --sync} [options] [package(s)] //安装包
pacman {-T --deptest} [options] [package(s)] //
pacman {-U --upgrade} [options] <file(s)> //更新

use 'pacman {-h --help}' with an operation for available options

主要看 SRUD 这几个参数的意思

  同步与升级
  安装和升级软件包前,先让本地的包数据库和远程的软件仓库同步是个好习惯。
  pacman -Syy
  也可以使用一句命令同时进行同步软件库并更新系统到最新状态
  pacman -Syu
  安装软件包
  安装或者升级单个软件包,或者一列软件包(包含依赖包),使用如下命令:
  pacman -S package_name1 package_name2
  有时候在不同的软件仓库中,一个软件包有多个版本(比如extra和testing)。你可以选择一个来安装:
  pacman -S extra/package_name
  pacman -S testing/package_name
  你也可以在一个命令里同步包数据库并且安装一个软件包:
  pacman -Sy package_name
  卸载软件包
  删除单个软件包,保留其全部已经安装的依赖关系
  pacman -R package_name
  删除指定软件包,及其所有没有被其他已安装软件包使用的依赖关系:
  pacman -Rs package_name
  包数据库查询
  可以使用 -Q 标志搜索和查询本地包数据库。详情参见
  pacman -Q —help
  可以使用-S 标志搜索和查询远程同步的包数据库。详情参见
  pacman -S —help
  其它
  下载包而不安装它:
  pacman -Sw package_name
  安装一个本地包(不从源里):
  pacman -U /path/to/package/package_name-version.pkg.tar.gz
  完全清理包缓存(/var/cache/pacman/pkg):
  pacman -Scc 

pacman:

pacman -S :安装

pacman -Syu :升级系统的包

pacman -Ss :查询

pacman -R :删除

pacman -Rs :删除包和其依赖

pacman -Qs :查询已安装包

pacman -Qi :显示查找的包的信息

pacman -Ql:显示包的文件安装位置

pacman -Sw :下载包但不安装

pacman -U path/。。。 : 安装本地的包

pacman -Scc : 清除缓存

pacman -Sy abc 和源同步后安装名为abc的包
pacman -S abc 从本地数据库中得到abc的信息,下载安装abc包
pacman -Sf abc 强制安装包abc
pacman -Ss abc 搜索有关abc信息的包
pacman -Si abc 从数据库中搜索包abc的信息
pacman -Syu 同步源,并更新系统
pacman -Sy 仅同步源
pacman -R abc 删除abc包
pacman -Rc abc 删除abc包和依赖abc的包
pacman -Rsn abc 移除包所有不需要的依赖包并删除其配置文件
pacman -Sc 清理/var/cache/pacman/pkg目录下的旧包
pacman -Scc 清除所有下载的包和数据库
pacman -Sd abc 忽略依赖性问题,安装包abc
pacman -Su —ignore foo 升级时不升级包foo
pacman -Sg abc 查询abc这个包组包含的软件包
pacman -Q 列出系统中所有的包
pacman -Q package 在本地包数据库搜索(查询)指定软件包
pacman -Qi package 在本地包数据库搜索(查询)指定软件包并列出相关信息
pacman -Q | wc -l 统计当前系统中的包数量
pacman -Qdt 找出孤立包
pacman -Rs $(pacman -Qtdq) 删除孤立软件包(递归的,小心用)

不要开启AUR

yaourt安装暂时不用

ubuntu重装后要做的事

发表于 2018-06-04 | 分类于 linux

ubuntu重装后要做的事(manjaro看其他)

如何安装看ubuntu上安装软件方法3种方式安装

重点说下

1
2
3
sudo apt-get update  更新源

sudo apt-get upgrade 更新已安装的包

当然是先安装玩ubuntu18.04咯,当然也有别的版本如voyagerlive和haiku OS
还是用ukylin18.04(我的天 这界面xp时代的吧)
Manjaro吧,不是Arch,不过这个gnome不好用吧
下个debian吧

web专业人员工具

计算机

Windows MAC Linux 这里说Linux了

文本编辑器

vim sublime vscode
atom brackets webstorm

浏览器

chrome Firefox Safari opera edge ie

图片编辑器

photoshop gimp

版本控制

git

自动化构建工具

grunt glup

优化步骤

首先删除一些不用的东西

1
sudo apt-get remove packageName
  • unity-webapps-common 亚马逊链接 18.04中不是这个名字了,用右键查看细节去删除
    • Amazon1
  • rhythmbox 音乐,换网易云16.04,18.04的暂时用不了
  • libreoffice-common libreoffice换wps.deb
  • empathy 聊天软件, 18.04也没这个 没用(系统没这个) 有微信就可以了 github搜 Electronic WeChat 也有wechat这个版本
  • deja-dup 备份,虚拟机中用不到
  • thunderbird 自带邮件
  • transmission-common 自带的bt下载客户端

下面这些随意了

  • gnome-mahjongg 对对碰游戏,麻将
  • simple-scan(扫描器删了,又不是打印,也不是用不到)
  • aisleriot 纸牌
  • gnome-mines 扫雷
  • cheese 相机
  • gnome-orca 屏幕阅读
  • webbrowser-app 自带浏览器
  • gnome-sudoku 数独
  • onboard 屏幕键盘(虚拟键盘)
  • landscape-client-ui-install 管理服务

换下源(建议先删除不要的东西后再换源,防止不要的软件更新)

software&updates中选择切换,虽然大家都选aliyun, 我选了ustc的,当然你可以自动选.
yuan1

然后更新下源和包

1
2
3
sudo apt-get update  更新源,换源后要执行

sudo apt-get upgrade 更新已安装的包,旧的软件升级

然后就是安装你想要的东西了.

解释下上面为什么
首先第一条更新源,其实我们的源,这些地址都保存在
sudo cat /etc/apt/sources.list
当然你可以直接修改这里, 也可以按上面说的在software&updates修改.只不过修改完后要执行
sudo apt-get update来更新, 就这么回事, 不然还是旧的,有缓存到本地软件列表.
而第二条sudo apt-get upgrade
这个命令,会把本地已安装的软件,与刚下载的软件列表里对应软件进行对比,如果发现已安装的软件版本太低,就会提示你更新。
总结下 update是更新本地软件列表,upgrade是升级本地软件

桌面整理

Dock

dock换到下面,并且设置自动隐藏
dock1

Ubuntu Dock 启用最小化操作:

1
gsettings set org.gnome.shell.extensions.dash-to-dock click-action 'minimize'

时间

时区修改下,为上海.在setting > detail中
time1

背景及锁屏和头像

背景和锁屏在setting中修改
background1

头像在detail中
avatar1

vm上的工具

1
sudo apt install open-vm-tools open-vm-tools-desktop

解锁’Tweaks’隐藏的设置

安装gnome-tweak-tool

1
sudo apt-get install gnome-tweak-tool

1.打开软件,extensions
2.启动两个插件 appindicators dock
3.打开软件商城,附加组件
4安装 ??

user themes
dash to dock
Hide Top Bar
weather in the clock

开始安装web专业人员工具

先vim git 用apt-get安装好,剩下的用.deb双击好了,要么tar.gz解压好了

编程工具

都只要解压后 export就行.export PATH=$PATH:/usr/local/go/bin
前端

  • chrome
  • vscode
  • nvm nodejs nrm
  • mongodb+robo3T/nosqlbooster4mongo-4.5.2.AppImage

其他

  • gcc
  • IDEA+jdk10
  • go
  • jq 处理JSON格式的工具
  • GIMP

终端工具

  • guake
  • terminator
  • tmux

shell

  • zsh
1
2
3
4
5
6
7
sudo apt-get install zsh
// 然后安装oh-my-zsh, 要先有git 下面二选一
sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
sudo wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | sh
//在.zshrc中修改自己喜欢的主题 ys
//切换shell为, chsh 命令是改变登陆shell,需要重启才能看到效果。
chsh -s /bin/zsh
1
2
3
4
//查看本机有哪些shell
cat /etc/shells
// 查看本机用了那个shell用env
env | grep SHELL

配置zsh
此时已经看到shell已经改变(关了shell重开),默认主题为robbyrussell,接下来我们还要再安装一些插件,更改外观让他变得更强大更好看。
zsh的配置文件在用户目录下的.zshrc里面
主题配置
更改主题只需在~/.zshrc 文件中 修改ZSH_THEME=”设置为你的主题”
而在~/.oh-my-zsh/themes下是各种的自带的主题(博主的是ys)。
设置方法如下如更改ZSH_THEME为自己的主题名即可
ZSH_THEME=”ys”

当然要更新下配置文件
source ~/.zshrc

查看主题截图请戳这里
插件配置
添加插件只需在~/.zshrc 文件中
~/.oh-my-zsh/plugins里面有默认自带的近百的插件

powerline的配置

zsh: corrupt history file /home/floodlight/.zsh_history

mv .zsh_history .zsh_history_bad
strings .zsh_history_bad > .zsh_history
fc -R .zsh_history

文字工具

  • vim
  • sublime
  • wps
  • typora

输入法

  • 搜狗

系统工具

  • htop
  • shutter

触摸屏

  • 自带synaptics
  • Touchégg
  • Fusuma

Indicator-Multiload,(这货有时候你开的多了 gnome桌面就崩了,所以卸载它就好了)
是一个很不错的系统指示器,可以显示CPU、内存、网络状态、SWAP交换空间、加载程序、硬盘使用等情况。

1
2
3
sudo add-apt-repository ppa:indicator-multiload/stable-daily
sudo apt-get update
sudo apt-get install indicator-multiload

Psensor 温度传感器 ??

1
2
3
4
5
sudo apt-get install lm-sensors
sudo sensors-detect
sudo apt-add-repository ppa:jfi/ppa
sudo apt-get update
sudo apt-get install psensor

lm-sensors是一款软件工具,可以借助嵌入在硬件的传感器,监测温度、电压、湿度和风扇运行状况。hddtemp这款工具可以通过S.M.A.R.T.数值,测量硬盘的温度。psensor是一款用于监测温度的图形化前端程序

Linux下Albert 相当于launchy

网络工具

  • curl
  • tsocks 代理

一些linux上遇到的问题

vm中

一旦在任务管理器中终止了VMware导致打不开虚拟机或电脑了,直接把所有的vmware的进程都给结束,然后打开虚拟机就可以了。
吓死我了,那个文件只有3K了
vm1.png
以后删除快照都要暂停机子搞
但这里出现问题,虚拟机中的机子都上不了网了,处理办法:
(”我的电脑”)右键—管理—服务,然后启动
VMware Workstation虚拟机不能联网的解决办法
vm2

左键失效

zuojian.png
虚拟机中的ubuntu菜单栏 这个都点不开,特么这是触摸屏不能按 直接按触摸屏的左键就行

拖动图标,只要左键按住1秒就行
zuojian2.png

在18.04中如果点击左键还是失效,显示一个小手,那就按下win键.这个和16.04中按alt拖动不同. 这其实就是老是meta键触发,气死.

无法对下载下来的sublime固定dock上

就是右键没有add to favorites

小瑕疵

sublime没有pacman的版本.chrome也是. 当我用tar.gz的用之后怎么把他们都加到all applications中去?

怎么查看我的pacman中有这个软件包,用pacman -Q 不怎么好用

直接下来的原码包怎么用

牛客JS能力评测

发表于 2018-05-30 | 分类于 前端

牛客JS能力评测

查找数组元素位置

题目描述
找出元素 item 在给定数组 arr 中的位置
输出描述:
如果数组中存在 item,则返回元素在数组中的位置,否则返回 -1
示例1
输入
[ 1, 2, 3, 4 ], 3
输出
2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
function indexOf(arr, item) {
return arr.indexOf(item) //indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置,若未找到,则返回-1。
}

function indexOf(arr, item) {
for(var i = 0; i < arr.length; ++i) {
if(arr[i] == item) {
return i;
}
}
return -1;
}

结合两种,
第二种:如果浏览器不支持indexOf怎么办呢?

function indexOf(arr, item) {
  if (Array.prototype.indexOf){   //判断当前浏览器是否支持
      return arr.indexOf(item);
  } else {
      for (var i = 0; i < arr.length; i++){
          if (arr[i] === item){
              return i;
          }
      }
  }
  return -1;     //总是把return -1暴漏在最外层
}

//使用forEach的话用到return break continue是不会退出的
function indexOf(arr, item) {
        arr.forEach(function(ele,index){
            if (ele === item){
                return index;
            }
console.log(3333,ele);
        });
        return -1;
}


function indexOf(arr, item){
    var index = -1;
    arr.forEach(function(res,i){
        if(res === item && index === -1){
            index = i;
        }
    });
    return index;
};

提供另外一种解法,支持数组arr中的数据类型为对象, 数组, 等。
eg: var arr = [{age: 1}, '1', 2, true, [1,2]], 依然可以测试。
/** 获取元素位置 */
function indexOf(arr, item) {
    if (!arr || !arr.length) {
        return -1;
    }
  
    for (var i = 0, len = arr.length; i < len; i++) {
        // 支持 arr[i] 为对象,数组等
        if (JSON.stringify(arr[i]) === JSON.stringify(item)) {
            return i
        }
    }
    return -1;
}

数组求和

题目描述
计算给定数组 arr 中所有元素的总和
输入描述:
数组中的元素均为 Number 类型
示例1
输入
[ 1, 2, 3, 4 ]
输出
10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//5种
//常规循环:
function sum(arr) {
    var s = 0;
    for (var i=0; i<arr.length; i--) {
        s += arr[i];
    }
//     for (var i=arr.length-1; i>=0; i--) {
//         s += arr[i];
//     }
    return s;
}

//函数式编程 map-reduce:
function sum(arr) {
    return arr.reduce(function(prev, curr, idx, arr){ //reduce也有个0咯 同forEach
        return prev + curr;
    });
}

//forEach遍历: 也可以map,用法同forEach 就是返回一个新数组
function sum(arr) {
    var s = 0;
    arr.forEach(function(val, idx, arr) { //array.forEach(function(currentValue, index, arr), thisValue)
        s += val;
//s+=arr[idx]
    }, 0);
    return s;
};

//不考虑算法复杂度,用递归做:
function sum(arr) {
    var len = arr.length;
    if(len == 0){
        return 0;
    } else if (len == 1){
        return arr[0];
    } else {
        return arr[0] + sum(arr.slice(1));
//return arr[0] + arguments.callee(arr.slice(1));
    }
}

//eval:
function sum(arr) {
    return eval(arr.join("+"));
//eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
//语法:eval(string)
//参数string:必需。要计算的字符串,其中含有要计算的 JavaScript 表达式或要执行的语句。

//join() 方法用于把数组中的所有元素放入一个字符串。
//语法:arrayObject.join(separator)
//参数separator:可选。指定要使用的分隔符。如果省略该参数,则使用逗号作为分隔符。

//看得懂上面的中文的前提下你的例子就可以这么解释:
//join方法在arr数组的每一个元素之间插入一个 + 号,变成 "1+2+3+4+5+6",然后eval()函数执行"1+2+3+4+5+6",最后将结果复制给value。
};

移除数组中的元素

移除数组 arr 中的所有值与 item 相等的元素。不要直接修改数组 arr,结果返回新的数组
示例1
输入
[1, 2, 3, 4, 2], 2
输出
[1, 3, 4]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//用filter
function remove(arr, item) {
// return arr.filter(function(value, idx, arr) {
// if(arr[idx]!=item){
// return value
// }
// })
return arr.filter(function(value, idx, arr) {
if(value!=item){
return value
}
})

// return arr.filter(function(value, idx, arr) {
// return value!=item
// })
}

// 用push
function remove(arr, item) {
var newArr = []
for(var i = 0; i< arr.length; ++i) {
if(arr[i] != item) {
newArr.push(arr[i])
}
}
return newArr;
}


function remove(arr,item){
    var newArr = [];
    for(var i = 0; i < arr.length; i++){
        if(arr[i] == item){
continue
};
       newArr.push(arr[i]);
    }
    return newArr;
}

// 用splice
function remove(arr, item) {
var newArr = arr.slice(0); //复制一份原数组
for (var i = 0; i < newArr.length; ++i) {
if(newArr[i] == item){
newArr.splice(i,1)
i--; //这很重要,记得要去掉一个长度哦,返回一步i
}
}
return newArr;
}

// 倒着检测到是不用考虑位置i--
function remove(arr, item) {
var newArr = arr.slice(0);
for (var i = newArr.length-1; i >= 0; --i) {
if(newArr[i] == item){
newArr.splice(i,1)
}
}
return newArr;
}

移除数组中的元素

移除数组 arr 中的所有值与 item 相等的元素,直接在给定的 arr 数组上进行操作,并将结果返回
示例1
输入
[1, 2, 2, 3, 4, 2, 2], 2
输出
[1, 3, 4]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//用splice
function removeWithoutCopy(arr, item) {
for(var i = 0; i < arr.length; ++i){
if(arr[i] == item) {
arr.splice(i, 1)
i--; //缺了这个就变成forEach的了,forEach也不是for in
}
}
return arr;
}
// 倒着检测;不用考虑;位置影响
function removeWithoutCopy(arr, item) {
    for(i=arr.length-1;i>=0;i--){
       if(arr[i]==item){
            arr.splice(i,1);
       }
    }
    return arr;
}

// 用for in就不用考虑i--了
function removeWithoutCopy(arr, item) {
    for(var i in arr){
        while(arr[i]==item){
            arr.splice(i,1);
        }
    }
    return arr;
}

//使用forEach有点不对,forEach遍历每一项, 只不过不能控制i--
function removeWithoutCopy(arr, item) {
return arr.forEach(function(value, index, arr) {
if(value == item) {
return continue; //没return continue break这种
}
})
}

// 一个while循环就出来了,判断是否存在,存在了就删掉,不存在return
function removeWithoutCopy(arr, item) {
    while(arr.indexOf(item) != -1){ //一直到找不到item 就不删了
        arr.splice(arr.indexOf(item),1);
    }
    return arr;
}
//当然也可以这么循环下去写
function removeWithoutCopy(arr, item) {
    for(var i=0;i<arr.length;i++){
      var a=arr.indexOf(item);
      arr.splice(a,1);
    }
    return arr;
}
//用es6的set去重,然后删一次好了
function removeWithoutCopy(arr, item) {
    arr=Array.from(new Set(arr));
    var a=arr.indexOf(item);
    arr.splice(a,1);
    return arr;
}

//这个就厉害了,将每次判断第一个是不是和item同,不是就插入末尾,然后删除第一个,是的话直接删除.
function removeWithoutCopy(arr, item) {
    var n = arr.length;
    for (var i = 0; i<n;i++) {
        if(arr[0] !== item){
            arr.push(arr[0]);
        }
        arr.splice(0,1);
    }
    return arr;
}

添加元素

在数组 arr 末尾添加元素 item。不要直接修改数组 arr,结果返回新的数组
示例1
输入
[1, 2, 3, 4], 10
输出
[1, 2, 3, 4, 10]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
 * 使用concat将传入的数组或非数组值与原数组合并,组成一个新的数组并返回
 * @param arr
 * @param item
 * @returns {Array.<T>|string}
 */
function(arr, item) {
    return arr.concat(item);
};

/**
* 普通的迭代拷贝
* @param arr
* @param item
* @returns {Array}
*/
function append(arr, item) {
var newArr = [];
for(var i = 0; i< arr.length; ++i) {
newArr.push(arr[i])
}
newArr.push(item);
return newArr;
}

/**
 * 使用slice浅拷贝+push组合
 * @param arr
 * @param item
 * @returns {Blob|ArrayBuffer|Array.<T>|string}
 */
var append2 = function(arr, item) {
    var newArr = arr.slice(0);  // slice(start, end)浅拷贝数组
    newArr.push(item);
    return newArr;
}

/**
 * 使用join+split+push组合
 * @param arr
 * @param item
 * @returns {Array}
 */
function append(arr, item) {
    var newArr=arr.join().split(','); //就是转了一圈
    newArr.push(item);
    return newArr;
}

https://blog.csdn.net/u013005050/article/details/78565636
/**
 * 使用unshift.apply
 * @param arr
 * @param item
 * @returns {Array}
 */
 
function append(arr, item) {
    var newArr=[item];
    [].unshift.apply(newArr, arr);
    return newArr;
}

//用JSON的parse和stringify
function append(arr, item) {
    var result = JSON.parse(JSON.stringify(arr))
    result.push(item);
    return result;
}

// 将数组转化成字符串,然后再使用+号连接item
// 最后分割字符串形成新数组
function append(arr, item) {
var str = arr.join() + "," + item;
return str.split(",");
}

删除数组最后一个元素

删除数组 arr 最后一个元素。不要直接修改数组 arr,结果返回新的数组
示例1
输入
[1, 2, 3, 4]
输出
[1, 2, 3]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//使用slice
function truncate(arr) {
return arr.slice(0, arr.length-1)
//return arr.slice(0, -1) //-1会自动加上length
}

//普通的迭代拷贝for+push
function truncate(arr, item) {
    var newArr=[];
    for(var i=0;i<=arr.length-2;i++){
        newArr.push(arr[i]);
    }
    return newArr;
}

//复制一个新数组(slice或for或concat或join+split或push.apply或JSON.parse(JSON.stringify(arr))),然后pop
function truncate(arr) {
var newArr = arr.slice(0)
newArr.pop()
return newArr;
}

//利用concat+pop
function truncate(arr) {
    var newArr = arr.concat();
    newArr.pop();
    return newArr;
}

//利用join+split+pop    注意!!!:数据类型会变成字符型
function truncate(arr) {
    var newArr = arr.join().split(',');
    newArr.pop();
    return newArr;
}

//利用push.apply+pop
function truncate(arr) {
    var newArr=[];
    [].push.apply(newArr, arr);
    newArr.pop();
    return newArr;
}
function truncate(arr) {
var newArr = JSON.parse(JSON.stringify(arr))
    newArr.pop();
    return newArr;
}


//复制一个,然后用splice
function truncate(arr) {
var newArr = arr.slice(0)
newArr.splice(newArr.length-1, 1)
return newArr;
}
//用length改大小
function truncate(arr, item){
     var newArr = arr.slice(0);
     newArr.length --;
     return newArr;
}

总结 复制一个新数组(slice或for或concat或join+split或push.apply或JSON.parse(JSON.stringify(arr)))
出来用pop或splice或length改

添加元素

在数组 arr 开头添加元素 item。不要直接修改数组 arr,结果返回新的数组
示例1
输入
[1, 2, 3, 4], 10
输出
[10, 1, 2, 3, 4]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//复制一个新数组(slice或for或concat或join+split或push.apply或JSON.parse(JSON.stringify(arr)),filter和map都可以复制数组)
//用unshift进,splice
function prepend(arr, item) {
var newArr = arr.slice(0);
newArr.unshift(item);
return newArr;
}

function prepend(arr, item) {
var newArr = arr.slice(0);
newArr.splice(0, 0, item);
return newArr;
}

//看顺序
function prepend(arr, item) {
return [item].concat(arr);
//return [].concat(item, arr)
}

//使用push.apply
function prepend(arr, item) {
    var newArr=[item];
    [].push.apply(newArr, arr);
    return newArr;
}

删除数组第一个元素

删除数组 arr 第一个元素。不要直接修改数组 arr,结果返回新的数组
示例1
输入
[1, 2, 3, 4]
输出
[2, 3, 4]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//直接从1开始复制 slice
function curtail(arr) {
var newArr = arr.slice(1)
return newArr;
}

//普通的迭代拷贝
function curtail(arr) {
    var newArr=[];
    for(var i=1;i<arr.length;i++){
        newArr.push(arr[i]);
    }
    return newArr;
}

//复制后用shift()出
function curtail(arr) {
var newArr = arr.slice(0)
newArr.shift();
return newArr;
}

//filter也可以复制新数组
function curtail(arr) {
var newArr = arr.filter(function(value,index, arr) {
// return arr
return value;
});
newArr.shift();
return newArr;
}
//map也可以
function curtail(arr) {
var newArr = arr.map(function(value,index, arr) {
return value;
});
newArr.shift();
return newArr;
}

//利用filter
function curtail(arr) {
return arr.filter(function(value,index) {
return index!==0;
});
}

数组合并

合并数组 arr1 和数组 arr2。不要直接修改数组 arr,结果返回新的数组
示例1
输入
[1, 2, 3, 4], [‘a’, ‘b’, ‘c’, 1]
输出
[1, 2, 3, 4, ‘a’, ‘b’, ‘c’, 1]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//直接concat,   主要就是理解了apply的用法  es6的...
// [].concat.apply([],[[1,2,3],[4,5,6],[7,8,9]]),然后你得到的是[1 2 3 4 5 6 7 8 9]
function concat(arr1, arr2) {
return arr1.concat(arr2)
}

//都用for循环
function concat(arr1, arr2) {
var newArr = []
for (var i = 0; i< arr1.length; ++i) {
newArr.push(arr1[i])
}
for (var j = 0; j< arr2.length; ++j) {
newArr.push(arr2[j])
}
return newArr;
}

//利用slice+push.apply
function concat(arr1, arr2) {
    var newArr=arr1.slice(0);
    [].push.apply(newArr, arr2);
    return newArr;
}
//利用slice+push
function concat(arr1, arr2) {
    var newArr=arr1.slice(0);
    for(var i=0;i<arr2.length;i++){
        newArr.push(arr2[i]);
    }
    return newArr;
}

//用字符串转,也就是join也可以啊
function concat(arr1, arr2) {
    var str=arr1.toString()+","+arr2.toString();
    return str.split(",");
}

//es6的...
function concat(arr1, arr2) {
return [...arr1, ...arr2];
}

添加元素

在数组 arr 的 index 处添加元素 item。不要直接修改数组 arr,结果返回新的数组
示例1
输入
[1, 2, 3, 4], ‘z’, 2
输出
[1, 2, ‘z’, 3, 4]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//新数组 复制的话有for slice concat join+split push.apply JSON filter map
//插入用splice,
//或者用slice+concat+slice连上来
function insert(arr, item, index) {
var newArr = arr.slice(0)
newArr.splice(index, 0, item)
return newArr;
}
//return arr.slice(0).splice(index,0,item);不行 因为splice的方法当插入使用时,不返回值,只有当删除功能使用时,才有返回值,并且返回值为其删除的数值


//利用slice+concat,这是分段连接
function insert(arr, item, index) {
    return arr.slice(0,index).concat(item,arr.slice(index));
}

//有丶意思 到了index处就push两个
function insert(arr, item, index) {
    var m = [];
    for (var i = 0; i < arr.length; i++) {
        if(i === index){
            var n = [item,arr[i]];
            [].push.apply(m,n);
            continue;
        }
        m.push(arr[i]);
    }
    return m;
}

计数

统计数组 arr 中值等于 item 的元素出现的次数
示例1
输入
[1, 2, 4, 4, 3, 4, 3], 4
输出
3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//用filter,map也对啊,forEach,要么for
function count(arr, item) {
var num = 0;
arr.filter(function(value, index, arr) {
if(value == item) {
num ++;
}
})
return num;
}

function count(arr, item) {
var num = 0;
for( var i = 0; i < arr.length; ++i) {
if (arr[i] == item) {
num++;
}
}
return num;
}

//有丶意思 用长度
function count(arr, item) {
    return arr.filter(function(i){
        return (i === item);
    }).length;
}
//也是长度,存在就一直push进去
function count(arr, item) {
    var count = [];
    var pos = arr.indexOf(item);
    while(pos > -1){
        count.push(pos);
        pos= arr.indexOf(item, pos+1);
    }
    return count.length
}

//reduce()-->从数组的第一项开始,逐个遍历到最后;
function count(arr, item) {
var count = arr.reduce(function(prev, curr, index, arr) {
return curr === item ? prev+1 : prev;
}, 0);
return count;
}

//正则
function count(arr, item) {
return arr.toString().match(new RegExp(item,"g")).length;
}

查找重复元素

找出数组 arr 中重复出现过的元素
示例1
输入
[1, 2, 4, 4, 3, 3, 1, 5, 3]
输出
[1, 3, 4]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Set数据结构,它类似于数组,其成员的值都是唯一的。
// 利用Array.from将Set结构转换成数组
function dedupe(array){
return Array.from(new Set(array));
}

//直接用...
let arr = [1,2,3,3];
let resultarr = [...new Set(arr)];
console.log(resultarr); //[1,2,3]

//先用原数组的indexOf+lastIndexOf来判断读取重复的(这里还没用到去重,而是用到读重复),
//在对新数组用indexOf == -1来去重,只不过顺序不一样了
function duplicates(arr) {
var newArr = [];
arr.map(function(value, index, arr) {
if(arr.indexOf(value) != arr.lastIndexOf(value) && newArr.indexOf(value) == -1) {
newArr.push(value)
}
})
return newArr;
}

//重点在数组去重了
//数组下标判断法, 遍历数组,利用indexOf判断元素的值是否与当前索引相等,如相等则加入
function unique(arr) {
result = []
arr.forEach(function (value, index, arr) {
if(arr.indexOf(value) == index) { //第2中用indexOf了,
result.push(value)
}
})
return result;
}


//典型的循环判断
function unique(arr) {
var res = [];
for (var i = 0; i < arr.length; i++) {
var repeat = false;
for (var j = 0; j < res.length; j++) {
if (arr[i] == res[j]) {
repeat = true;
break;
}
}
//每次按顺序判断原数组的第1个,第2个与新数组数组中的所有元素有没有相同的,没有相同就把原数组的当前加入新数组.
if (!repeat) {
res.push(arr[i]);
}
}
return res;
}

// 双层循环,外层循环元素,内层循环时比较值
// 如果有相同的值则跳过,不相同则push进数组
function distinct(arr) {
var result = []
for( var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
j = ++i;
}
}
result.push(arr[i]);
}
return result;
}

// 双层循环,外层循环元素,内层循环时比较值
// 值相同时,则删去这个值
// 注意点:删除元素之后,需要将数组的长度也减1.
function distinct(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1);
arr.length--;
j--;
}
}
}
return arr;
};



//还有就是利用sort,再次判断原数组中的第i个元素与新数组中的最后一个元素,因为已经排好序了,所以,重复的都是在相邻,容易比出来
//就是一头一尾进行去重
function unique(arr) {
arr.sort(); //先排序,不需要从大到小 只需要相同的在一起
// arr.sort(function(a,b){ //对数组进行排序才能方便比较
// return a - b;
// })
var result = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] !== result[result.length - 1]) {
result.push(arr[i]);
}
}
return result;
}

//去重,用hash对象,
function unique(arr) {
var result = [], hash = {};
for (var i = 0, elem; i < arr.length; i++) {
elem = arr[i]; // hash[arr[i]] != null
if (!hash[elem]) {
result.push(elem);
hash[elem] = true;
}
}
// for (var i = 0, elem; (elem = arr[i]) != null; i++) {
// if (!hash[elem]) { //判断hasn对象中有没有这个elem属性,没有就加入result
// result.push(elem);
// hash[elem] = true;
// }
// }
return result;
}



//还有利用下标的 因为number,只不过最后都变成string了,因为对象的属性名就是string
function unique(arr) {
var result = [], hash = [];
for(var i = 0; i < arr.length; ++i) {
hash[arr[i]] = null
}
//用for in得 key
for(var key in hash) {
result.push(key)
}
return result;
}

求二次方

为数组 arr 中的每个元素求二次方。不要直接修改数组 arr,结果返回新的数组
示例1
输入
[1, 2, 3, 4]
输出
[1, 4, 9, 16]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//直接使用map
function square(arr) {
return arr.map(function(value, index, arr) {
return value = value*value
//return Math.pow(value,2)
})
}

//复制一个shuzu
//在for
function square(arr) {
var newArr = arr.slice(0)
for(var i in newArr) {
newArr[i] = newArr[i]*newArr[i]
}
return newArr
}

查找元素位置

在数组 arr 中,查找值与 item 相等的元素出现的所有位置
示例1
输入
‘abcdefabc’
输出
[0, 6]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//这题没给出target 所以自己假设有这么个target而且要返回是一个数组 新的.
//filter和map都可以
function findAllOccurrences(arr, target) {
var newArr = []
arr.filter(function(value, index, arr) {
if(value == target) {
return newArr.push(index)
}
})
return newArr
}

//for 遍历
function findAllOccurrences(arr, target) {
var newArr = []
for(var i in arr) {
if(arr[i] == target) {
newArr.push(i)
}
}
return newArr
}

//lastIndexOf+slice/splice
function findAllOccurrences(arr, target) {
    var result=[],index=arr.lastIndexOf(target);
    while(index>-1){
        result.push(index);
        arr.splice(index,1);//arr=arr.slice(0,index);
        index=arr.lastIndexOf(target);
    }
    return result;
}
//indexOf
function findAllOccurrences(arr, target) {
    var result=[],index=arr.indexOf(target);
    while(index>-1){ //!=-1
        result.push(index);
        index=arr.indexOf(target,index+1);
    }
    return result;
}

避免全局变量

给定的 js 代码中存在全局变量,请修复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function globals() {
myObject = {
name : 'Jory'
};

return myObject;
}

改为
function globals() {
var myObject = {
name : 'Jory'
};

return myObject;
}

正确的函数定义

请修复给定的 js 代码中,函数定义存在的问题
示例1
输入
true
输出
a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function functions(flag) {
if (flag) {
function getValue() { return 'a'; }
} else {
function getValue() { return 'b'; }
}

return getValue();
}

改为
function functions(flag) {
var getValue
if (flag) {
getValue = function () { return 'a'; }
} else {
getValue = function () { return 'b'; }
}

return getValue();
}

正确的使用 parseInt

修改 js 代码中 parseInt 的调用方式,使之通过全部测试用例
示例1
输入
‘12’
输出
12
示例2
输入
‘12px’
输出
12
示例3
输入
‘0x12’
输出
0

1
2
3
4
5
6
7
8
function parse2Int(num) {
return parseInt(num);
}

改为
function parse2Int(num) {
return parseInt(num, 10);
}

完全等同

判断 val1 和 val2 是否完全等同

1
2
3
function identity(val1, val2) {
return val1===val2;
}

计时器

实现一个打点计时器,要求
1、从 start 到 end(包含 start 和 end),每隔 100 毫秒 console.log 一个数字,每次数字增幅为 1
2、返回的对象中需要包含一个 cancel 方法,用于停止定时操作
3、第一个数需要立即输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//用setInterval clearInterval
function count(start, end) {
//立即输出第一个值
console.log(start++);
var timer = setInterval(function () {
if (start <= end) {
console.log(start++);
} else {
clearInterval(timer);
}
}, 100);
//返回一个对象
return {
cancel: function () {
clearInterval(timer);
}
};
}


//用setTimeout clearTimeout
function count(start, end) {
if (start <= end) {
console.log(start);
start++;
st = setTimeout(function () {
count(start, end)
}, 100);
}
return {
cancel: function () {
clearTimeout(st);
}
}
}

流程控制

实现 fizzBuzz 函数,参数 num 与返回值的关系如下:
1、如果 num 能同时被 3 和 5 整除,返回字符串 fizzbuzz
2、如果 num 能被 3 整除,返回字符串 fizz
3、如果 num 能被 5 整除,返回字符串 buzz
4、如果参数为空或者不是 Number 类型,返回 false
5、其余情况,返回参数 num

示例1
输入
15
输出
fizzbuzz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
function fizzBuzz(num) {
if(num % 3 ==0 && num % 5 == 0 ){
return 'fizzbuzz'
}else if(num % 3 == 0) {
return 'fizz'
}else if(num % 5 == 0) {
return 'buzz'
}else if(typeof num == undefined || typeof num != 'number') {
return false
}
return num
}

function fizzBuzz(num) {
if(num % 3 ==0 && num % 5 == 0 ){
return 'fizzbuzz'
}
if(num % 3 == 0) {
return 'fizz'
}
if(num % 5 == 0) {
return 'buzz'
}
if(num == null || typeof num != 'number') {
return false
}
return num
}


function fizzBuzz(num) {
    switch(true){
        case num == null || typeof(num) != "number" :return false;break;
        case num%3==0&&num%5==0 :return "fizzbuzz";break;
        case num%3==0 :return "fizz";break;
        case num%5==0 :return "buzz";break;      
        default:return num;           
    } 
}

// 高程明确定义 Number类型下两种表示: var num = 120 or var num1 = new Number(120)
// 高票第一回答下需要考虑一个问题new Number(120)也是Number类型。
// 前者typeof num === 'number', 后者typeof num1 = 'object';
// 最精确的判断方法向来是Object.prototype.toString.call(args) === '[object ' + type + ]';
// 这里type可取[ 'Array', 'Number', 'Object', 'String', 'Undefined', 'null' ]
function fizzBuzz(num) {
if (num === undefined || Object.prototype.toString.call(num) !== '[object Number]') {
return false;
}
if (num % 3 === 0 && num % 5 === 0) {
return 'fizzbuzz';
} else if (num % 3 === 0) {
return 'fizz';
} else if (num % 5 === 0) {
return 'buzz';
}
return num;
}

函数传参

将数组 arr 中的元素作为调用函数 fn 的参数
示例1
输入
function (greeting, name, punctuation) {return greeting + ‘, ‘ + name + (punctuation || ‘!’);}, [‘Hello’, ‘Ellie’, ‘!’]
输出
Hello, Ellie!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//调用函数可以使用call或者apply这两个方法,区别在于call需要将传递给函数的参数明确写出来,是多少参数就需要写多少参数。而apply则将传递给函数的参数放入一个数组中,传入参数数组即可。
// 调用函数有3种方式:
// obj.func();
// func.call(obj,args);//参数列出
// func.apply(obj,[m,n......]);//参数数组
function argsAsArray(fn, arr) {
return fn.apply(this, arr)
}


// 笨办法
function argsAsArray(fn, arr) {
  return fn(arr[0],arr[1],arr[2]);
}

// 用apply
function argsAsArray(fn, arr) {
  return fn.apply(fn, arr);
}

//或者
function argsAsArray(fn, arr) {
  return fn.apply(this, arr);
}
//或者不要随便绑定this
function argsAsArray(fn, arr) {
return fn.apply(null, arr);
}

// 用call
function argsAsArray(fn, arr) {
  return fn.call(fn, arr[0],arr[1],arr[2]);
}
//或者
function argsAsArray(fn, arr) {
  return fn.call(this, arr[0],arr[1],arr[2]);
}

//es6
function argsAsArray(fn, arr) {
return fn(...arr)
}

函数的上下文

将函数 fn 的执行上下文改为 obj 对象
示例1
输入
function () {return this.greeting + ‘, ‘ + this.name + ‘!!!’;}, {greeting: ‘Hello’, name: ‘Rebecca’}
输出
Hello, Rebecca!!!

1
2
3
4
5
6
7
8
9
10
11
//https://www.cnblogs.com/libin-1/p/6069031.html
//用apply或call
//还有bind
//bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用
function speak(fn, obj) {
return fn.call(obj)
}

function speak(fn, obj) {
return fn.bind(obj)();
}

返回函数

实现函数 functionFunction,调用之后满足如下条件:
1、返回值为一个函数 f
2、调用返回的函数 f,返回值为按照调用顺序的参数拼接,拼接字符为英文逗号加一个空格,即 ‘, ‘
3、所有函数的参数数量为 1,且均为 String 类型
示例1
输入
functionFunction(‘Hello’)(‘world’)
输出
Hello, world

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//先传入str,然后返回函数执行,再传入s
function functionFunction(str) {
return f = function(s){
return str+', '+s
}
}

function functionFunction(str) {
return function(newStr) {
return [str, newStr].join(', ');
};
}

function functionFunction(str) {
    var args = Array.prototype.slice.call(arguments);
    return function() {
        return args.concat(Array.prototype.slice.call(arguments)).join(', ')
    }
}

使用闭包

实现函数 makeClosures,调用之后满足如下条件:
1、返回一个函数数组 result,长度与 arr 相同
2、运行 result 中第 i 个函数,即 resulti,结果与 fn(arr[i]) 相同
示例1
输入
[1, 2, 3], function (x) {
return x * x;
}
输出
4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
简单的描述闭包:如果在函数func内部声明函数inner,然后在函数外部调用inner,这个过程即产生了一个闭包。
题目要求的是返回一个函数数组,如果在循环中直接写result[i] = function(){return fn(arr[i]);}或者result.push(function(){return fn(arr[i]);}),最终的结果是不正确的,因为在每次迭代的时候,那样的语句后面的方法并没有执行,只是创建了一个函数体为“return fn(arr[i]);”的函数对象而已,当迭代停止时,i为最终迭代停止的值,在函数被调用时,i依旧为最终迭代停止的值,因此无法返回正确的结果。
为了解决这个问题,需要声明一个匿名函数,并立即执行它。
function(num){return function(){return fn(arr[num]); }; }(i),函数执行后,i立即传入并被内部函数访问到,因此就能得到正确的结果。闭包允许你引用存在于外部函数中的变量。

function makeClosures(arr, fn) {
var result = [];
arr.forEach(function(value, index, arr){
result.push(function(num){
return function(){
return fn(num);
};
}(value)); //只要给fn传递的是arr中的项就行了。我上面用的forEach,迭代的参数就是arr中的每一项,value就是每一项
});
return result;
}

function makeClosures(arr, fn) {
var result = [];
arr.forEach(function(value, index, arr){
result.push(function(i){
return function(){
return fn(arr[i]) //只要包上一个函数,传入相应参数就好
}
}(index));
});
return result;
}

//直接用es6的let就不用闭包了
function makeClosures(arr, fn) {   
    var result = new Array();
    for(let i=0;i<arr.length;i++){
        result[i] = function(){
            return fn(arr[i]); //let声明的变量只在let所在代码块内有效,因此每次循环的i都是一个新的变量
        };
    }
    return result;
}

//var 这种是错误的写法会导致result中每个函数的参数都是arr[arr.length]
function makeClosures(arr, fn) {
    var result = new Array();
     for(var i=0;i<arr.length;i++){
        result[i] = function(){
            return fn(arr[i]);
        };
    }
    return result;
}

//参考《JavaScript高级程序设计》的典型方法
function makeClosures(arr, fn) {
    var result = new Array();
    for(var i=0;i<arr.length;i++){
        result[i] = function(num){
            return function(){
                return fn(num);
            }
        }(arr[i]);
    }
    return result;
}

// 此外ES5提供了bind方法,apply(),call(),bind()方法在使用时如果已经对参数进行了定义
//使用ES5的bind()方法
function makeClosures(arr, fn) {
    var result = new Array();
    for(var i=0;i<arr.length;i++){
        result[i] = fn.bind(null,arr[i]);
    }
    return result;
}


// 还是喜欢直接匿名函数
function makeClosures(arr, fn) {
    var result = [];
    for(var i=0; i<arr.length; ++i){
        (function(v){
            result[v] = function(){
                return fn.call(null,arr[v]);
            }
        })(i);
    }
    return result;
}

二次封装函数

已知函数 fn 执行需要 3 个参数。请实现函数 partial,调用之后满足如下条件:
1、返回一个函数 result,该函数接受一个参数
2、执行 result(str3) ,返回的结果与 fn(str1, str2, str3) 一致
示例1
输入
var sayIt = function(greeting, name, punctuation) { return greeting + ‘, ‘ + name + (punctuation || ‘!’); }; partial(sayIt, ‘Hello’, ‘Ellie’)(‘!!!’);
输出
Hello, Ellie!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// call和apply必须显式地调用str3,立即执行
// bind不是立即执行,未传入str3时,并未执行,只是返回一个函数,等待参数传入
// this用于上下文不确定的情况

因为这题传入的函数没有用到传入的this,所以不管传的是什么,都没有影响。this可以改成任何东西
// call
function partial(fn, str1, str2) {
    function result(str3) {
        return fn.call(this, str1, str2, str3);
    }
 
     return result;
}
 
// apply(这里只是为了对照)
function partial(fn, str1, str2) {
    function result(str3) {
        return fn.apply(this, [str1, str2, str3]);
    }
 
    return result;
}
 
// 这个bind会生成一个新函数(对象), 它的str1, str2参数都定死了, str3未传入, 一旦传入就会执行
function partial(fn, str1, str2) {
    return fn.bind(this, str1, str2); // 或 return fn.bind(null, str1, str2);
}
 
// bind同上, 多了一步, 把str3传入的过程写在另一个函数里面,
// 而另一个函数也有str1, str2参数
// 此法有种多次一举的感觉,但是表示出了后续的调用。
function partial(fn, str1, str2) {
    function result(str3) {
        return fn.bind(this, str1, str2)(str3);
    }
 
    return result;
}
 
// 匿名函数,默认this绑定global,与bind的第一个参数为this时效果一样。
function partial(fn, str1, str2) {
    return function(str3) {
        return fn(str1, str2, str3);
    }
}
 
// ES6。this指向undefined.
const partial = (fn, str1, str2) => str3 => fn(str1, str2, str3);

使用 arguments

函数 useArguments 可以接收 1 个及以上的参数。请实现函数 useArguments,返回所有调用参数相加后的结果。本题的测试参数全部为 Number 类型,不需考虑参数转换。
示例1
输入
1, 2, 3, 4
输出
10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 这里重点就是类数组对象:arguments -- 接收所有传入函数的参数值的类数组对象,它有两个特点跟数组很像,1.可以用下标访问每个元素2.具有length属性。
// 这里最好先通过Array.prototype.slice.call(我们的类数组对象) 将其转换成一个真正的数组对象,然后再遍历求和即可。
function useArguments() {
var sum = 0
for(var i = 0; i < arguments.length; ++i) {
sum += arguments[i]
}
return sum
}

//这里从类数组转成真的数组,可以用使用数组的迭代方法,不然只要length属性和[]下标可以用
function useArguments() {
    return Array.prototype.slice.call(arguments).reduce(function(x, y) {
      return x + y;
    });
}

//这里也是一样,只不过求和方式不同,3种求和方式
function useArguments() {
    var arr=Array.prototype.slice.call(arguments)//把arguments类数组转化为数组
    return eval(arr.join("+"));//求和
}

使用 apply 调用函数

实现函数 callIt,调用之后满足如下条件
1、返回的结果为调用 fn 之后的结果
2、fn 的调用参数为 callIt 的第一个参数之后的全部参数
示例1
输入
无
输出
无

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
要注意arguments本身不存在slice方法,需要借用Array.prototype.slice进行类数组到数组的转换
function callIt(fn) {
    //将arguments转化为数组后,截取第一个元素之后的所有元素
    var args = Array.prototype.slice.call(arguments,1);
    //调用fn
    var result = fn.apply(null,args);
    return result;
}

// slice (不改变数组)
function callIt(fn) {
    return fn.apply(this, [].slice.call(arguments, 1));
}
 
// shift (会改变数组)
function callIt(fn) {
    return [].shift.call(arguments).apply(null, arguments);
}

//反正就是复制一份arguments
function callIt(fn) {
    var args=[];
    for(var i=1;i<arguments.length;i++){
        args.push(arguments[i]);
    }
    var result=fn.apply(null,args);
    return result;
}

二次封装函数

实现函数 partialUsingArguments,调用之后满足如下条件:
1、返回一个函数 result
2、调用 result 之后,返回的结果与调用函数 fn 的结果一致
3、fn 的调用参数为 partialUsingArguments 的第一个参数之后的全部参数以及 result 的调用参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function partialUsingArguments(fn) {
     //先获取p函数第一个参数之后的全部参数
     var args = Array.prototype.slice.call(arguments,1);
     //声明result函数
     var result = function(){
         //使用concat合并两个或多个数组中的元素
         return fn.apply(null, args.concat([].slice.call(arguments)));
     }
     return result;
 }


function partialUsingArguments(fn){
    //得到partialUsingArguments方法第一个参数后面的参数组成的数组
    var args=Array.prototype.slice.call(arguments,1); 
    var result=function(){
        //将上面的args和result的参数组合成一个数组argss
        var argss=args.concat(Array.prototype.slice.call(arguments));
        //fn调用这个参数数组
        return fn.apply(null,argss);
    }
    return result;
}

柯里化

已知 fn 为一个预定义函数,实现函数 curryIt,调用之后满足如下条件:
1、返回一个函数 a,a 的 length 属性值为 1(即显式声明 a 接收一个参数)
2、调用 a 之后,返回一个函数 b, b 的 length 属性值为 1
3、调用 b 之后,返回一个函数 c, c 的 length 属性值为 1
4、调用 c 之后,返回的结果与调用 fn 的返回值一致
5、fn 的参数依次为函数 a, b, c 的调用参数
示例1
输入
var fn = function (a, b, c) {return a + b + c}; curryIt(fn)(1)(2)(3);
输出
6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。简单理解题目意思,就是指,我们将预定义的函数的参数逐一传入到curryIt中,当参数全部传入之后,就执行预定义函数。于是,我们首先要获得预定义函数的参数个数fn.length,然后声明一个空数组去存放这些参数。返回一个匿名函数接收参数并执行,当参数个数小于fn.length,则再次返回该匿名函数,继续接收参数并执行,直至参数个数等于fn.length。最后,调用apply执行预定义函数。

function curryIt(fn) {
     //获取fn参数的数量,总的实参参数个数
     var n = fn.length;
     //声明一个数组args
     var args = [];
     //返回一个匿名函数
     return function(arg){
         //将curryIt后面括号中的参数放入数组,这类事返回的,少了一个参数的总参数
         args.push(arg);
         //如果args中的参数个数小于fn函数的参数个数,
         //则执行arguments.callee(其作用是引用当前正在执行的函数,这里是返回的当前匿名函数)。
         //否则,返回fn的调用结果
         if(args.length < n){
            return arguments.callee;
         }else {
return fn.apply("",args);
}
     }
 }


function curryIt(fn) {
    var length = fn.length,
        args = [];
    var result =  function (arg){
        args.push(arg);
        length --;
        if(length <= 0 ){
            return fn.apply(this, args);
        } else {
            return result;
        }
    }
     
    return result;
}

var currying = function (fn) {
var _args = [];
return function () {
if (arguments.length === 0) {
return fn.apply(this, _args);
}
Array.prototype.push.apply(_args, [].slice.call(arguments));
return arguments.callee;
}
};

或运算

返回参数 a 和 b 的逻辑或运算结果
示例1
输入
false, true
输出
true

1
2
3
function or(a, b) {
return a||b;
}

且运算

返回参数 a 和 b 的逻辑且运算结果
示例1
输入
false, true
输出
false

1
2
3
function and(a, b) {
return a && b
}

模块

完成函数 createModule,调用之后满足如下要求:
1、返回一个对象
2、对象的 greeting 属性值等于 str1, name 属性值等于 str2
3、对象存在一个 sayIt 方法,该方法返回的字符串为 greeting属性值 + ‘, ‘ + name属性值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 字面量模式:
function createModule(str1, str2) {
     var obj = {
         greeting : str1,
         name     : str2,
         sayIt    : function(){
             return this.greeting+", "+this.name
         }
     }
     return obj
 }


// 原型模式:
function createModule(str1, str2) {
    function Obj()
    {
        this.greeting = str1;
        this.name = str2;
    }
    Obj.prototype.sayIt = function(){
return this.greeting + ", " + this.name;
}
    return new Obj();
}

// 构造函数模式:
function createModule(str1, str2) {
    function Obj()
    {
        this.greeting = str1;
        this.name = str2;
        this.sayIt = function(){
return this.greeting + ", " + this.name;
}
    }
    return new Obj();
}

// 创建对象模式:
function createModule(str1, str2) {
    function CreateObj()
    {
        obj = new Object;
        obj.greeting = str1;
        obj.name = str2;
        obj.sayIt = function(){
return this.greeting + ", " + this.name;
}
        return obj;
    }
    return CreateObj();
}

二进制转换

获取数字 num 二进制形式第 bit 位的值。注意:
1、bit 从 1 开始
2、返回 0 或 1
3、举例:2 的二进制为 10,第 1 位为 0,第 2 位为 1
示例1
输入
128, 8
输出
1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//才不是parseInt(num, 2) 这个是把num按基数为2当做, 表示为10进制是多少,而不是转成2进制
function valueAtBit(num, bit) {
return (num >> (bit -1)) & 1;
}

function valueAtBit(num, bit) {
var s = num.toString(2);
return s[s.length - bit];
}


function valueAtBit(num, bit) {
    //toString转化为二进制,split将二进制转化为数组,reverse()将数组颠倒顺序
    var arr = num.toString(2).split("").reverse();
    return arr[bit-1];
}

// 方法多多 就是不知道每个方法的优点与缺点
// 我的是按位与运算 看结果是不是0
function valueAtBit(num, bit) {
    return (num&Math.pow(2,bit-1))==0?0:1;
}

二进制转换

给定二进制字符串,将其换算成对应的十进制数字
示例1
输入
‘11000000’
输出
192

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

function base10(str) {
    /**
        其它进制转十进制
        parseInt(str,2)
        parseInt(str,8)
        parseInt(str,16)
    */
    return parseInt(str,2);
}


function base10(str) {

    var res=str.split('');
    var sum=0;
    for(var i=0;i<res.length;i++)
    {
        sum+=res[i]*Math.pow(2,res.length-i-1);
    }
    return sum;
}

二进制转换

将给定数字转换成二进制字符串。如果字符串长度不足 8 位,则在前面补 0 到满8位。
示例1
输入
65
输出
01000001

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// // 首先通过toString方法将num转为2进制数形式,然后判断其长度是否足够8位。如不足8位,则声明一个“0000000”字符串用于补0,因为目标的2进制数形式最少为一位,因此最多只需要7个0;通过slice方法对“0000000”进行截取,然后将其结果加在目标前面即可。
function convertToBinary(num) {
     //转换为2进制格式
     var s = num.toString(2);
     //获得2进制数长度
     var l = s.length;
     if(l<8){
         //声明一个字符串用于补满0
         var s1 = "0000000";
         var s2 = s1.slice(0,8-l);
         s = s2+s; 
     }
     return s;
 }

function convertToBinary(num) {
    var str = num.toString(2);
    while(str.length < 8) {
        str = "0" + str;
    }
    return str;
}


function convertToBinary(num) {
    var res = num.toString(2);
    return res.length > 8 ? res : ('00000000' + res).slice(-8);
}

乘法

求 a 和 b 相乘的值,a 和 b 可能是小数,需要注意结果的精度问题
示例1
输入
3, 0.0001
输出
0.0003

1
2
3
function multiply(a, b) {
return a*b;
}

改变上下文

将函数 fn 的执行上下文改为 obj,返回 fn 执行后的值
示例1
输入
alterContext(function() {return this.greeting + ‘, ‘ + this.name + ‘!’; }, {name: ‘Rebecca’, greeting: ‘Yo’ })
输出
Yo, Rebecca!

1
2
3
4
5
6
//apply  call  bind
function alterContext(fn, obj) {
return fn.apply(obj)
// return fn.call(obj)
// return fn.bind(obj)()
}

批量改变对象的属性

给定一个构造函数 constructor,请完成 alterObjects 方法,将 constructor 的所有实例的 greeting 属性指向给定的 greeting 变量。
示例1
输入
var C = function(name) {this.name = name; return this;};
var obj1 = new C(‘Rebecca’);
alterObjects(C, ‘What\’s up’); obj1.greeting;
输出
What’s up

1
2
3
function alterObjects(constructor, greeting) {
constructor.prototype.greeting = greeting;
}

属性遍历

找出对象 obj 不在原型链上的属性(注意这题测试例子的冒号后面也有一个空格~)
1、返回数组,格式为 key: value
2、结果数组不要求顺序
示例1
输入
var C = function() {this.foo = ‘bar’; this.baz = ‘bim’;};
C.prototype.bop = ‘bip’;
iterate(new C());
输出
[“foo: bar”, “baz: bim”]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function iterate(obj) {
     var arr = [];
     //使用for-in遍历对象属性
     for(var key in obj){
         //判断key是否为对象本身的属性
         if(obj.hasOwnProperty(key)){
             //将属性和值按格式存入数组
             arr.push(key+": "+obj[key]);
         }
     }
     return arr;
}

function iterate(obj) {
    return Object.getOwnPropertyNames(obj).map(function(key){
        return key+": "+obj[key];
    });
}


// Object.keys 只收集自身属性名,不继承自原型链上的属性,所以可以直接这么写
function iterate(obj) {
    var arr=Object.keys(obj);
    var arrs=[];
    arr.forEach(function(item){
        arrs.push(item+': '+obj[item])
    })
    
    return arrs
     
}

vim和shell命令脚本

发表于 2018-04-23 | 分类于 linux

vim 和 shell 命令脚本

本章首先讲解如何使用Vim编辑器来编写、修改文档,然后通过逐个配置主机名称、系统网卡以及 Yum 软件仓库参数文件等实验,帮助读者加深 Vim 编辑器中诸多命令、快捷键、模式切换方法的理解。然后把前面章节中讲解的 Linux 命令、命令语法与 Shell 脚本中的各种流程控制语句通过 Vim 编辑器写到 Shell 脚本中结合到一起,实现最终能够自动化工作的脚本文件。本章最后演示了怎样通过 at 命令与 crond 计划任务服务来分别实现一次性的系统任务设置和长期性的系统任务设置,从而让日常的工作更加高效,更自动化。

Vim 文本编辑器

“在 Linux 系统中一切都是文件,而配置一个服务就是在修改其配置文件的参数”。而且在日常工作中大家也肯定免不了要编写文档,这些工作都是通过文本编辑器来完成的。
Vim 之所以能得到广大厂商与用户的认可,原因在于 Vim 编辑器中设置了三种模式—命令模式、末行模式和编辑模式,每种模式分别又支持多种不同的命令快捷键,这大大提高了工作效率,而且用户在习惯之后也会觉得相当顺手。要想高效率地操作文本,就必须先搞清这三种模式的操作区别以及模式之间的切换方法

命令模式:控制光标移动,可对文本进行复制、粘贴、删除和查找等工作。

输入模式:正常的文本录入。

末行模式:保存或退出文档,以及设置编辑环境。

vim不同模式间的切换.png

在每次运行 Vim 编辑器时,默认进入命令模式,此时需要先切换到输入模式后再进行文档编写工作,而每次在编写完文档后需要先返回命令模式,然后再进入末行模式,执行文档的保存或退出操作。在 Vim 中,无法直接从输入模式切换到末行模式。Vim 编辑器中内置的命令有成百上千种用法,为了能够帮助读者更快地掌握 Vim 编辑器,

命令 作用
dd 删除(剪切)光标所在整行
5dd 删除(剪切)从光标处开始的 5 行 往下删除 5 行
yy 复制光标所在整行
5yy 复制从光标处开始的 5 行
n 显示搜索命令定位到的下一个字符串
N 显示搜索命令定位到的上一个字符串
u 撤销上一步的操作 那么回去呢?
p 将之前删除(dd)或复制(yy)过的数据粘贴到光标后面

末行模式主要用于保存或退出文件,以及设置 Vim 编辑器的工作环境,还可以让用户执行外部的 Linux 命令或跳转到所编写文档的特定行数。要想切换到末行模式,在命令模式中输入一个冒号就可以了。

命令 作用
:w 保存
:q 退出
:q! 强制退出(放弃对文档的修改内容)
:wq! 强制保存退出
:set nu 显示行号
:set nonu 不显示行号
:命令 执行该命令
:整数 跳转到该行
:s/one/two 将当前光标所在行的第一个 one 替换成 two
:s/one/two/g 将当前光标所在行的所有 one 替换成 two
:%s/one/two/g 将全文中的所有 one 替换成 two
?字符串 在文本中从下至上搜索该字符串
/字符串 在文本中从上至下搜索该字符串

编写简单文档

编写脚本文档的第 1 步就是给文档取个名字,这里将其命名为 practice.txt。如果存在该文档,则是打开它。如果不存在,则是创建一个临时的输入文件,如图所示。

第1步:创建文档.png

打开 practice.txt 文档后,默认进入的是 Vim 编辑器的命令模式。此时只能执行该模式下的命令,而不能随意输入文本内容,我们需要切换到输入模式才可以编写文档。

在图 4-1 中提到,可以分别使用a、i、o三个键从命令模式切换到输入模式。其中,a 键与 i 键分别是在光标后面一位和光标当前位置切换到输入模式,而 o 键则是在光标的下面再创建一个空行,此时可敲击 a 键进入到编辑器的输入模式.
进入输入模式后,可以随意输入文本内容,Vim 编辑器不会把您输入的文本内容当作命令而执行
在编写完之后,想要保存并退出,必须先敲击键盘 Esc 键从输入模式返回命令模式,如图所示。然后再输入:wq!切换到末行模式才能完成保存退出操作

第5步:敲击“:wq”,保存并退出.png

当在末行模式中输入:wq!命令时,就意味着强制保存并退出文档。然后便可以用 cat 命令查看保存后的文档内容了.

是不是很简单?!继续编辑这个文档。因为要在原有文本内容的下面追加内容,所以在命令模式中敲击 o 键进入输入模式更会高效.

因为此时已经修改了文本内容,所以 Vim 编辑器在我们尝试直接退出文档而不保存的时候就会拒绝我们的操作了。此时只能强制退出才可以结束本次输入操作,

大家在学完了理论知识之后又自己动手编写了一个文本,现在是否感觉成就满满呢?接下来将会由浅入深为读者安排三个小任务。为了彻底掌握 Vim 编辑器的使用,大家一定要逐个完成不许偷懒,如果在完成这三个任务期间忘记了相关命令,可返回前文进一步复习掌握。

配置主机名称

为了便于在局域网中查找某台特定的主机,或者对主机进行区分,除了要有 IP 地址外,还要为主机配置一个主机名,主机之间可以通过这个类似于域名的名称来相互访问。在 Linux 系统中,主机名大多保存在/etc/hostname 文件中,接下来将/etc/hostname 文件的内容修改为“linuxprobe.com”

第 1 步:使用 Vim 编辑器修改“/etc/hostname”主机名称文件。

第 2 步:把原始主机名称删除后追加“linuxprobe.com”。注意,使用 Vim 编辑器修改主机名称文件后,要在末行模式下执行:wq!命令才能保存并退出文档。

第 3 步:保存并退出文档,然后使用hostname命令检查是否修改成功。

配置网卡信息

网卡 IP 地址配置的是否正确是两台服务器是否可以相互通信的前提。在 Linux 系统中,一切都是文件,因此配置网络服务的工作其实就是在编辑网卡配置文件,因此这个小任务不仅可以帮助您练习使用 Vim 编辑器,而且也为您后面学习 Linux 中的各种服务配置打下了坚实的基础。当您认真学习完本书后,一定会特别有成就感,因为本书前面的基础部分非常扎实,而后面内容则具有几乎一致的网卡 IP 地址和运行环境,从而确保您全身心地投入到各类服务程序的学习上,而不用操心系统环境的问题。

现在有一个名称为 ifcfg-eno16777736 的网卡设备,我们将其配置为开机自启动,并且 IP 地址、子网、网关等信息由人工指定,其步骤应该如下所示。

第 1 步:首先切换到/etc/sysconfig/network-scripts目录中(存放着网卡的配置文件)。

第 2 步:使用 Vim 编辑器修改网卡文件 ifcfg-eno16777736,逐项写入下面的配置参数并保存退出。由于每台设备的硬件及架构是不一样的,因此请读者使用 ifconfig 命令自行确认各自网卡的默认名称。

设备类型:TYPE=Ethernet

地址分配模式:BOOTPROTO=static

网卡名称:NAME=eno16777736

是否启动:ONBOOT=yes

IP 地址:IPADDR=192.168.10.10

子网掩码:NETMASK=255.255.255.0

网关地址:GATEWAY=192.168.10.1

DNS 地址:DNS1=192.168.10.1

第 3 步:重启网络服务并测试网络是否联通。

进入到网卡配置文件所在的目录,然后编辑网卡配置文件,在其中填入下面的信息:

1
2
3
4
5
6
7
8
9
10
[root@linuxprobe ~]# cd /etc/sysconfig/network-scripts/
[root@linuxprobe network-scripts]# vim ifcfg-eno16777736
TYPE=Ethernet
BOOTPROTO=static
NAME=eno16777736
ONBOOT=yes
IPADDR=192.168.10.10
NETMASK=255.255.255.0
GATEWAY=192.168.10.1
DNS1=192.168.10.1

执行重启网卡设备的命令(在正常情况下不会有提示信息),然后通过 ping 命令测试网络能否联通。由于在 Linux 系统中 ping 命令不会自动终止,因此需要手动按下 Ctrl-c 键来强行结束进程。

1
2
3
4
5
6
7
8
9
10
11
[root@linuxprobe network-scripts]# systemctl restart network
[root@linuxprobe network-scripts]# ping 192.168.10.10
PING 192.168.10.10 (192.168.10.10) 56(84) bytes of data.
64 bytes from 192.168.10.10: icmp_seq=1 ttl=64 time=0.081 ms
64 bytes from 192.168.10.10: icmp_seq=2 ttl=64 time=0.083 ms
64 bytes from 192.168.10.10: icmp_seq=3 ttl=64 time=0.059 ms
64 bytes from 192.168.10.10: icmp_seq=4 ttl=64 time=0.097 ms
^C
--- 192.168.10.10 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 2999ms
rtt min/avg/max/mdev = 0.059/0.080/0.097/0.013 ms

配置 Yum 仓库

本书前面讲到,Yum 软件仓库的作用是为了进一步简化 RPM 管理软件的难度以及自动分析所需软件包及其依赖关系的技术。可以把 Yum 想象成是一个硕大的软件仓库,里面保存有几乎所有常用的工具,而且只需要说出所需的软件包名称,系统就会自动为您搞定一切。

既然要使用 Yum 软件仓库,就要先把它搭建起来,然后将其配置规则确定好才行。鉴于第 6 章才会讲解 Linux 的存储结构和设备挂载操作,所以我们当前还是将重心放到 Vim 编辑器的学习上。如果遇到看不懂的参数也不要紧,后面章节会单独讲解。搭建并配置 Yum 软件仓库的大致步骤如下所示

编写 Shell 脚本

Linux shell 编程基础,一看就能入门

可以将 Shell 终端解释器当作人与计算机硬件之间的“翻译官”,它作为用户与 Linux 系统内部的通信媒介,除了能够支持各种变量与参数外,还提供了诸如循环、分支等高级编程语言才有的控制结构特性。要想正确使用 Shell 中的这些功能特性,准确下达命令尤为重要。Shell 脚本命令的工作方式有两种:交互式和批处理。

交互式(Interactive):用户每输入一条命令就立即执行。

批处理(Batch):由用户事先编写好一个完整的 Shell 脚本,Shell 会一次性执行脚本中诸多的命令。

在 Shell 脚本中不仅会用到前面学习过的很多 Linux 命令以及正则表达式、管道符、数据流重定向等语法规则,还需要把内部功能模块化后通过逻辑语句进行处理,最终形成日常所见的 Shell 脚本。

查看 SHELL 变量可以发现当前系统已经默认使用 Bash 作为命令行终端解释器了:

1
2
$ echo $SHELL
/bin/bash

编写简单的脚本

估计读者在看完上文中有关 Shell 脚本的复杂描述后,会累觉不爱吧。但是,上文指的是一个高级 Shell 脚本的编写原则,其实使用 Vim 编辑器把 Linux 命令按照顺序依次写入到一个文件中,这就是一个简单的脚本了。

例如,如果想查看当前所在工作路径并列出当前目录下所有的文件及属性信息,实现这个功能的脚本应该类似于下面这样

1
2
3
4
5
$ vim example.sh
#!/bin/bash
#For Example BY linuxprobe.com
pwd
ls -al

Shell 脚本文件的名称可以任意,但为了避免被误以为是普通文件,建议将.sh后缀加上,以表示是一个脚本文件。在上面的这个 example.sh 脚本中实际上出现了三种不同的元素:
第一行的脚本声明(#!)用来告诉系统使用哪种 Shell 解释器来执行该脚本;
第二行的注释信息(#)是对脚本功能和某些命令的介绍信息,使得自己或他人在日后看到这个脚本内容时,可以快速知道该脚本的作用或一些警告信息;
第三、四行的可执行语句也就是我们平时执行的 Linux 命令了。什么?!你们不相信这么简单就编写出来了一个脚本程序,那我们来执行一下看看结果:

1
2
3
4
5
6
$ bash example.sh
/root/Desktop
total 8
drwxr-xr-x. 2 root root 23 Jul 23 17:31 .
dr-xr-x---. 14 root root 4096 Jul 23 17:31 ..
-rwxr--r--. 1 root root 55 Jul 23 17:31 example.sh

除了上面用 bash 解释器命令直接运行 Shell 脚本文件外,第二种运行脚本程序的方法是通过输入完整路径的方式来执行。但默认会因为权限不足而提示报错信息,此时只需要为脚本文件增加执行权限即可(详见第 5 章)。初次学习 Linux 系统的读者不用心急,等下一章学完用户身份和权限后再来做这个实验也不迟:

1
2
3
4
5
6
7
8
9
[root@linuxprobe ~]# ./example.sh
bash: ./Example.sh: Permission denied
[root@linuxprobe ~]# chmod u+x example.sh
[root@linuxprobe ~]# ./example.sh
/root/Desktop
total 8
drwxr-xr-x. 2 root root 23 Jul 23 17:31 .
dr-xr-x---. 14 root root 4096 Jul 23 17:31 ..
-rwxr--r--. 1 root root 55 Jul 23 17:31 example.sh

接收用户的参数

但是,像上面这样的脚本程序只能执行一些预先定义好的功能,未免太过死板了。为了让 Shell 脚本程序更好地满足用户的一些实时需求,以便灵活完成工作,必须要让脚本程序能够像之前执行命令时那样,接收用户输入的参数。
其实,Linux 系统中的 Shell 脚本语言早就考虑到了这些,已经内设了用于接收参数的变量,变量之间可以使用空格间隔。例如$0 对应的是当前 Shell 脚本程序的名称,$#对应的是总共有几个参数,$*对应的是所有位置的参数值,$?对应的是显示上一次命令的执行返回值,而$1、$2、$3……则分别对应着第 N 个位置的参数值,如图 4-15 所示。

Shell脚本程序中的参数位置变量.png

1
2
3
4
5
6
7
8
9
[root@linuxprobe ~]# vim example.sh
#!/bin/bash
echo "当前脚本名称为$0"
echo "总共有$#个参数,分别是$*。"
echo "第1个参数为$1,第5个为$5。"
[root@linuxprobe ~]# sh example.sh one two three four five six
当前脚本名称为example.sh
总共有6个参数,分别是one two three four five six。
第1个参数为one,第5个为five。

判断用户的参数

学习是一个登堂入室、由浅入深的过程。在学习完 Linux 命令、掌握 Shell 脚本语法变量和接收用户输入的信息之后,就要踏上新的高度—能够进一步处理接收到的用户参数。

在本书前面章节中讲到,系统在执行 mkdir 命令时会判断用户输入的信息,即判断用户指定的文件夹名称是否已经存在,如果存在则提示报错;反之则自动创建。Shell 脚本中的条件测试语法可以判断表达式是否成立,若条件成立则返回数字 0,否则便返回其他随机数值。条件测试语法的执行格式如图 4-16 所示。切记,条件表达式两边均应有一个空格。

测试语句格式.png

按照测试对象来划分,条件测试语句可以分为 4 种:

文件测试语句;

逻辑测试语句;

整数值比较语句;

字符串比较语句。

文件测试即使用指定条件来判断文件是否存在或权限是否满足等情况的运算符,具体的参数如表

操作符 作用
-e 测试文件是否存在
-f 判断是否为一般文件
-d 测试文件是否为目录类型
-r 测试当前用户是否有权限读取
-w 测试当前用户是否有权限写入
-x 测试当前用户是否有权限执行

下面使用文件测试语句来判断/etc/fstab 是否为一个目录类型的文件,然后通过 Shell 解释器的内设$?变量显示上一条命令执行后的返回值。如果返回值为 0,则目录存在;如果返回值为非零的值,则意味着目录不存在:(2 步)

1
2
3
[root@linuxprobe ~]# [ -d /etc/fstab ]
[root@linuxprobe ~]# echo $?
1

再使用文件测试语句来判断/etc/fstab 是否为一般文件,如果返回值为 0,则代表文件存在,且为一般文件:

1
2
3
[root@linuxprobe ~]# [ -f /etc/fstab ]
[root@linuxprobe ~]# echo $?
0

逻辑语句用于对测试结果进行逻辑分析,根据测试结果可实现不同的效果。例如在 Shell 终端中逻辑“与”的运算符号是&&,它表示当前面的命令执行成功后才会执行它后面的命令,因此可以用来判断/dev/cdrom 文件是否存在,若存在则输出 Exist 字样。

1
2
[root@linuxprobe ~]# [ -e /dev/cdrom ] && echo "Exist"
Exist

除了逻辑“与”外,还有逻辑“或”,它在 Linux 系统中的运算符号为||,表示当前面的命令执行失败后才会执行它后面的命令,因此可以用来结合系统环境变量 USER 来判断当前登录的用户是否为非管理员身份

1
2
3
4
5
6
[root@linuxprobe ~]# echo $USER
root
[root@linuxprobe ~]# [ $USER = root ] || echo "user"
[root@linuxprobe ~]# su - linuxprobe
[linuxprobe@linuxprobe ~]$ [ $USER = root ] || echo "user"
user

第三种逻辑语句是“非”,在 Linux 系统中的运算符号是一个叹号(!),它表示把条件测试中的判断结果取相反值。也就是说,如果原本测试的结果是正确的,则将其变成错误的;原本测试错误的结果则将其变成正确的。

我们现在切换回到 root 管理员身份,再判断当前用户是否为一个非管理员的用户。由于判断结果因为两次否定而变成正确,因此会正常地输出预设信息:

1
2
3
4
[linuxprobe@linuxprobe ~]$ exit
logout
[root@linuxprobe root]# [ $USER != root ] || echo "administrator"
administrator

当前我们正在登录的即为管理员用户—root。下面这个示例的执行顺序是,先判断当前登录用户的 USER 变量名称是否等于 root,然后用逻辑运算符“非”进行取反操作,效果就变成了判断当前登录的用户是否为非管理员用户了。最后若条件成立则会根据逻辑“与”运算符输出 user 字样;或条件不满足则会通过逻辑“或”运算符输出 root 字样,而如果前面的&&不成立才会执行后面的||符号。

1
2
[root@linuxprobe ~]# [ $USER != root ] && echo "user" || echo "root"
root

整数比较运算符仅是对数字的操作,不能将数字与字符串、文件等内容一起操作,而且不能想当然地使用日常生活中的等号、大于号、小于号等来判断。因为等号与赋值命令符冲突,大于号和小于号分别与输出重定向命令符和输入重定向命令符冲突。因此一定要使用规范的整数比较运算符来进行操作。可用的整数比较运算符如表

操作符 作用
-eq 是否等于
-ne 是否不等于
-gt 是否大于
-lt 是否小于
-le 是否等于或小于
-ge 是否大于或等于

接下来小试牛刀。我们先测试一下 10 是否大于 10 以及 10 是否等于 10(通过输出的返回值内容来判断):

1
2
3
4
5
6
[root@linuxprobe ~]# [ 10 -gt 10 ]
[root@linuxprobe ~]# echo $?
1
[root@linuxprobe ~]# [ 10 -eq 10 ]
[root@linuxprobe ~]# echo $?
0

在 2.4 节曾经讲过free命令,它可以用来获取当前系统正在使用及可用的内存量信息。接下来先使用free -m命令查看内存使用量情况(单位为 MB),然后通过grep Mem:命令过滤出剩余内存量的行,再用awk '{print $4}'命令只保留第四列,最后用FreeMem=\语句``的方式把语句内执行的结果赋值给变量。

这个演示确实有些难度,但看懂后会觉得很有意思,没准在运维工作中也会用得上。

1
2
3
4
5
6
7
8
9
10
11
12
[root@linuxprobe ~]# free -m
total used free shared buffers cached
Mem: 1826 1244 582 9 1 413
-/+ buffers/cache: 830 996
Swap: 2047 0 2047
[root@linuxprobe ~]# free -m | grep Mem:
Mem: 1826 1244 582 9
[root@linuxprobe ~]# free -m | grep Mem: | awk '{print $4}'
582
[root@linuxprobe ~]# FreeMem=`free -m | grep Mem: | awk '{print $4}'`
[root@linuxprobe ~]# echo $FreeMem
582

上面用于获取内存可用量的命令以及步骤可能有些“超纲”了,如果不能理解领会也不用担心,接下来才是重点。我们使用整数运算符来判断内存可用量的值是否小于 1024,若小于则会提示“Insufficient Memory”(内存不足)的字样:

1
2
[root@linuxprobe ~]# [ $FreeMem -lt 1024 ] && echo "Insufficient Memory"
Insufficient Memory

字符串比较语句用于判断测试字符串是否为空值,或两个字符串是否相同。它经常用来判断某个变量是否未被定义(即内容为空值),理解起来也比较简单。字符串比较中常见的运算符如表

操作符 作用
= 比较字符串内容是否相同
!= 比较字符串内容是否不同
-z 判断字符串内容是否为空

接下来通过判断 String 变量是否为空值,进而判断是否定义了这个变量:

1
2
3
[root@linuxprobe ~]# [ -z $String ]
[root@linuxprobe ~]# echo $?
0

再尝试引入逻辑运算符来试一下。当用于保存当前语系的环境变量值 LANG 不是英语(en.US)时,则会满足逻辑测试条件并输出“Not en.US”(非英语)的字样

1
2
3
4
[root@linuxprobe ~]# echo $LANG
en_US.UTF-8
[root@linuxprobe ~]# [ $LANG != "en.US" ] && echo "Not en.US"
Not en.US

流程控制语句

尽管此时可以通过使用 Linux 命令、管道符、重定向以及条件测试语句来编写最基本的 Shell 脚本,但是这种脚本并不适用于生产环境。原因是它不能根据真实的工作需求来调整具体的执行命令,也不能根据某些条件实现自动循环执行。例如,我们需要批量创建 1000 位用户,首先要判断这些用户是否已经存在;若不存在,则通过循环语句让脚本自动且依次创建他们。

接下来我们通过if、for、while、case这 4 种流程控制语句来学习编写难度更大、功能更强的 Shell 脚本。为了保证下文的实用性和趣味性,做到寓教于乐,我会尽可能多地讲解各种不同功能的 Shell 脚本示例,而不是逮住一个脚本不放,在它原有内容的基础上修修补补。尽管这种修补式的示例教学也可以让读者明白理论知识,但是却无法开放思路,不利于日后的工作

if 条件测试语句

if 条件测试语句可以让脚本根据实际情况自动执行相应的命令。从技术角度来讲,if 语句分为单分支结构、双分支结构、多分支结构;其复杂度随着灵活度一起逐级上升。

if 条件语句的单分支结构由if、then、fi关键词组成,而且只在条件成立后才执行预设的命令,相当于口语的“如果……那么……”。单分支的 if 语句属于最简单的一种条件判断结构,语法格式如图

单分支结构-2.png

下面使用单分支的 if 条件语句来判断/media/cdrom 文件是否存在,若存在就结束条件判断和整个 Shell 脚本,反之则去创建这个目录:

1
2
3
4
5
6
7
[root@linuxprobe ~]# vim mkcdrom.sh
#!/bin/bash
DIR="/media/cdrom"
if [ ! -e $DIR ]
then
mkdir -p $DIR
fi

由于第 5 章才讲解用户身份与权限,因此这里继续用“bash 脚本名称”的方式来执行脚本。在正常情况下,顺利执行完脚本文件后没有任何输出信息,但是可以使用ls命令验证/media/cdrom目录是否已经成功创建:

1
2
3
[root@linuxprobe ~]# bash mkcdrom.sh
[root@linuxprobe ~]# ls -d /media/cdrom
/media/cdrom

if 条件语句的双分支结构由if、then、else、fi关键词组成,它进行一次条件匹配判断,如果与条件匹配,则去执行相应的预设命令;反之则去执行不匹配时的预设命令,相当于口语的“如果……那么……或者……那么……”。if 条件语句的双分支结构也是一种很简单的判断结构,语法格式如图

双分支结构-1.png

下面使用双分支的 if 条件语句来验证某台主机是否在线,然后根据返回值的结果,要么显示主机在线信息,要么显示主机不在线信息。这里的脚本主要使用ping命令来测试与对方主机的网络联通性,而 Linux 系统中的 ping 命令不像 Windows 一样尝试 4 次就结束,因此为了避免用户等待时间过长,需要通过-c参数来规定尝试的次数,并使用-i参数定义每个数据包的发送间隔,以及使用-W参数定义等待超时时间。

1
2
3
4
5
6
7
8
9
[root@linuxprobe ~]# vim chkhost.sh
#!/bin/bash
ping -c 3 -i 0.2 -W 3 $1 &> /dev/null
if [ $? -eq 0 ]
then
echo "Host $1 is On-line."
else
echo "Host $1 is Off-line."
fi

我们在 4.2.3 小节中用过$?变量,作用是显示上一次命令的执行返回值。若前面的那条语句成功执行,则$?变量会显示数字0,反之则显示一个非零的数字(可能为1,也可能为2,取决于系统版本)。因此可以使用整数比较运算符来判断$?变量是否为 0,从而获知那条语句的最终判断情况。这里的服务器 IP 地址为 192.168.10.10,我们来验证一下脚本的效果:

1
2
3
4
[root@linuxprobe ~]# bash chkhost.sh 192.168.10.10
Host 192.168.10.10 is On-line.
[root@linuxprobe ~]# bash chkhost.sh 192.168.10.20
Host 192.168.10.20 is Off-line.

if 条件语句的多分支结构由if、then、else、elif、fi关键词组成,它进行多次条件匹配判断,这多次判断中的任何一项在匹配成功后都会执行相应的预设命令,相当于口语的“如果……那么……如果……那么……”。if 条件语句的多分支结构是工作中最常使用的一种条件判断结构,尽管相对复杂但是更加灵活,语法格式如图

多分支结构-2.png

下面使用多分支的 if 条件语句来判断用户输入的分数在哪个成绩区间内,然后输出如 Excellent、Pass、Fail 等提示信息。在 Linux 系统中,read是用来读取用户输入信息的命令,能够把接收到的用户输入信息赋值给后面的指定变量,-p参数用于向用户显示一定的提示信息。在下面的脚本示例中,只有当用户输入的分数大于等于 85 分且小于等于 100 分,才输出 Excellent 字样;若分数不满足该条件(即匹配不成功),则继续判断分数是否大于等于 70 分且小于等于 84 分,如果是,则输出 Pass 字样;若两次都落空(即两次的匹配操作都失败了),则输出 Fail 字样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@linuxprobe ~]# vim chkscore.sh
#!/bin/bash
read -p "Enter your score(0-100):" GRADE
if [ $GRADE -ge 85 ] && [ $GRADE -le 100 ] ; then
echo "$GRADE is Excellent"
elif [ $GRADE -ge 70 ] && [ $GRADE -le 84 ] ; then
echo "$GRADE is Pass"
else
echo "$GRADE is Fail"
fi
[root@linuxprobe ~]# bash chkscore.sh
Enter your score(0-100):88
88 is Excellent
[root@linuxprobe ~]# bash chkscore.sh
Enter your score(0-100):80
80 is Pass

下面执行该脚本。当用户输入的分数分别为 30 和 200 时,其结果如下:

1
2
3
4
5
6
[root@linuxprobe ~]# bash chkscore.sh
Enter your score(0-100):30
30 is Fail
[root@linuxprobe ~]# bash chkscore.sh
Enter your score(0-100):200
200 is Fail

为什么输入的分数为 200 时,依然显示 Fail 呢?原因很简单—没有成功匹配脚本中的两个条件判断语句,因此自动执行了最终的兜底策略。可见,这个脚本还不是很完美,建议读者自行完善这个脚本,使得用户在输入大于 100 或小于 0 的分数时,给予 Error 报错字样的提示。

for 条件循环语句

for 循环语句允许脚本一次性读取多个信息,然后逐一对信息进行操作处理,当要处理的数据有范围时,使用 for 循环语句再适合不过了。for 循环语句的语法格式如图

for条件语句-1.png

下面使用 for 循环语句从列表文件中读取多个用户名,然后为其逐一创建用户账户并设置密码。首先创建用户名称的列表文件 users.txt,每个用户名称单独一行。读者可以自行决定具体的用户名称和个数

1
2
3
4
5
6
7
[root@linuxprobe ~]# vim users.txt
andy
barry
carl
duke
eric
george

接下来编写 Shell 脚本Example.sh。在脚本中使用read命令读取用户输入的密码值,然后赋值给 PASSWD 变量,并通过-p参数向用户显示一段提示信息,告诉用户正在输入的内容即将作为账户密码。在执行该脚本后,会自动使用从列表文件 users.txt 中获取到所有的用户名称,然后逐一使用“id 用户名”命令查看用户的信息,并使用$?判断这条命令是否执行成功,也就是判断该用户是否已经存在。

需要多说一句,/dev/null 是一个被称作 Linux 黑洞的文件,把输出信息重定向到这个文件等同于删除数据(类似于没有回收功能的垃圾箱),可以让用户的屏幕窗口保持简洁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@linuxprobe ~]# vim Example.sh
#!/bin/bash
read -p "Enter The Users Password : " PASSWD
for UNAME in `cat users.txt`
do
id $UNAME &> /dev/null
if [ $? -eq 0 ]
then
echo "Already exists"
else
useradd $UNAME &> /dev/null
echo "$PASSWD" | passwd --stdin $UNAME &> /dev/null
if [ $? -eq 0 ]
then
echo "$UNAME , Create success"
else
echo "$UNAME , Create failure"
fi
fi
done

执行批量创建用户的 Shell 脚本 Example.sh,在输入为账户设定的密码后将由脚本自动检查并创建这些账户。由于已经将多余的信息通过输出重定向符转移到了/dev/null 黑洞文件中,因此在正常情况下屏幕窗口除了“用户账户创建成功”(Create success)的提示后不会有其他内容。

在 Linux 系统中,/etc/passwd 是用来保存用户账户信息的文件。如果想确认这个脚本是否成功创建了用户账户,可以打开这个文件,看其中是否有这些新创建的用户信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@linuxprobe ~]# bash Example.sh
Enter The Users Password : linuxprobe
andy , Create success
barry , Create success
carl , Create success
duke , Create success
eric , Create success
george , Create success
[root@linuxprobe ~]# tail -6 /etc/passwd
andy:x:1001:1001::/home/andy:/bin/bash
barry:x:1002:1002::/home/barry:/bin/bash
carl:x:1003:1003::/home/carl:/bin/bash
duke:x:1004:1004::/home/duke:/bin/bash
eric:x:1005:1005::/home/eric:/bin/bash
george:x:1006:1006::/home/george:/bin/bash

您还记得在学习双分支 if 条件语句时,用到的那个测试主机是否在线的脚本么?既然我们现在已经掌握了 for 循环语句,不妨做些更酷的事情,比如尝试让脚本从文本中自动读取主机列表,然后自动逐个测试这些主机是否在线。

首先创建一个主机列表文件 ipadds.txt:

1
2
3
4
[root@linuxprobe ~]# vim ipadds.txt
192.168.10.10
192.168.10.11
192.168.10.12

然后前面的双分支 if 条件语句与 for 循环语句相结合,让脚本从主机列表文件 ipadds.txt 中自动读取 IP 地址(用来表示主机)并将其赋值给 HLIST 变量,从而通过判断 ping 命令执行后的返回值来逐个测试主机是否在线。脚本中出现的$(命令)是一种完全类似于第 3 章的转义字符中反引号命令的 Shell 操作符,效果同样是执行括号或双引号括起来的字符串中的命令。大家在编写脚本时,多学习几种类似的新方法,可在工作中大显身手:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@linuxprobe ~]# vim CheckHosts.sh
#!/bin/bash
HLIST=$(cat ~/ipadds.txt)
for IP in $HLIST
do
ping -c 3 -i 0.2 -W 3 $IP &> /dev/null
if [ $? -eq 0 ] ; then
echo "Host $IP is On-line."
else
echo "Host $IP is Off-line."
fi
done
[root@linuxprobe ~]# ./CheckHosts.sh
Host 192.168.10.10 is On-line.
Host 192.168.10.11 is Off-line.
Host 192.168.10.12 is Off-line.

while 条件循环语句

while 条件循环语句是一种让脚本根据某些条件来重复执行命令的语句,它的循环结构往往在执行前并不确定最终执行的次数,完全不同于 for 循环语句中有目标、有范围的使用场景。while 循环语句通过判断条件测试的真假来决定是否继续执行命令,若条件为真就继续执行,为假就结束循环。while 语句的语法格式如图 4-21 所示。

while条件语句-1.png

接下来结合使用多分支的 if 条件测试语句与 while 条件循环语句,编写一个用来猜测数值大小的脚本 Guess.sh。该脚本使用$RANDOM 变量来调取出一个随机的数值(范围为 0 ~ 32767),将这个随机数对 1000 进行取余操作,并使用 expr 命令取得其结果,再用这个数值与用户通过 read 命令输入的数值进行比较判断。这个判断语句分为三种情况,分别是判断用户输入的数值是等于、大于还是小于使用 expr 命令取得的数值。当前,现在这些内容不是重点,我们当前要关注的是 while 条件循环语句中的条件测试始终为 true,因此判断语句会无限执行下去,直到用户输入的数值等于 expr 命令取得的数值后,这两者相等之后才运行 exit 0 命令,终止脚本的执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@linuxprobe ~]# vim Guess.sh
#!/bin/bash
PRICE=$(expr $RANDOM % 1000)
TIMES=0
echo "商品实际价格为0-999之间,猜猜看是多少?"
while true
do
read -p "请输入您猜测的价格数目:" INT
let TIMES++
if [ $INT -eq $PRICE ] ; then
echo "恭喜您答对了,实际价格是 $PRICE"
echo "您总共猜测了 $TIMES 次"
exit 0
elif [ $INT -gt $PRICE ] ; then
echo "太高了!"
else
echo "太低了!"
fi
done

在这个 Guess.sh 脚本中,我们添加了一些交互式的信息,从而使得用户与系统的互动性得以增强。而且每当循环到 let TIMES++命令时都会让 TIMES 变量内的数值加 1,用来统计循环总计执行了多少次。这可以让用户得知总共猜测了多少次之后,才猜对价格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@linuxprobe ~]# bash Guess.sh
商品实际价格为0-999之间,猜猜看是多少?
请输入您猜测的价格数目:500
太低了!
请输入您猜测的价格数目:800
太高了!
请输入您猜测的价格数目:650
太低了!
请输入您猜测的价格数目:720
太高了!
请输入您猜测的价格数目:690
太低了!
请输入您猜测的价格数目:700
太高了!
请输入您猜测的价格数目:695
太高了!
请输入您猜测的价格数目:692
太高了!
请输入您猜测的价格数目:691
恭喜您答对了,实际价格是 691
您总共猜测了 9 次

case 条件测试语句

如果您之前学习过 C 语言,看到这一小节的标题肯定会会心一笑“这不就是 switch 语句嘛!”是的,case 条件测试语句和 switch 语句的功能非常相似!case 语句是在多个范围内匹配数据,若匹配成功则执行相关命令并结束整个条件测试;而如果数据不在所列出的范围内,则会去执行星号(*)中所定义的默认命令。case 语句的语法结构如图

case条件语句-1.png

在前文介绍的Guess.sh 脚本中有一个致命的弱点—只能接受数字!您可以尝试输入一个字母,会发现脚本立即就崩溃了。原因是字母无法与数字进行大小比较,例如,“a 是否大于等于 3”这样的命题是完全错误的。我们必须有一定的措施来判断用户的输入内容,当用户输入的内容不是数字时,脚本能予以提示,从而免于崩溃。

通过在脚本中组合使用 case 条件测试语句和通配符(详见第 3 章),完全可以满足这里的需求。接下来我们编写脚本 Checkkeys.sh,提示用户输入一个字符并将其赋值给变量 KEY,然后根据变量 KEY 的值向用户显示其值是字母、数字还是其他字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@linuxprobe ~]# vim Checkkeys.sh
#!/bin/bash
read -p "请输入一个字符,并按Enter键确认:" KEY
case "$KEY" in
[a-z]|[A-Z])
echo "您输入的是 字母。"
;;
[0-9])
echo "您输入的是 数字。"
;;
*)
echo "您输入的是 空格、功能键或其他控制字符。"
esac
[root@linuxprobe ~]# bash Checkkeys.sh
请输入一个字符,并按Enter键确认:6
您输入的是 数字。
[root@linuxprobe ~]# bash Checkkeys.sh
请输入一个字符,并按Enter键确认:p
您输入的是 字母。
[root@linuxprobe ~]# bash Checkkeys.sh
请输入一个字符,并按Enter键确认:^[[15~
您输入的是 空格、功能键或其他控制字符。

计划任务服务程序

经验丰富的系统运维工程师可以使得 Linux 在无需人为介入的情况下,在指定的时间段自动启用或停止某些服务或命令,从而实现运维的自动化。尽管我们现在已经有了功能彪悍的脚本程序来执行一些批处理工作,但是,如果仍然需要在每天凌晨两点敲击键盘回车键来执行这个脚本程序,这简直太痛苦了(当然,也可以训练您的小猫在半夜按下回车键)。接下来,刘遄老师将向大家讲解如何设置服务器的计划任务服务,把周期性、规律性的工作交给系统自动完成。

计划任务分为一次性计划任务与长期性计划任务,大家可以按照如下方式理解。

一次性计划任务:今晚 11 点 30 分开启网站服务。

长期性计划任务:每周一的凌晨 3 点 25 分把/home/wwwroot 目录打包备份为 backup.tar.gz。

顾名思义,一次性计划任务只执行一次,一般用于满足临时的工作需求。我们可以用at命令实现这种功能,只需要写成“at 时间”的形式就可以。如果想要查看已设置好但还未执行的一次性计划任务,可以使用“at -l”命令;要想将其删除,可以用“atrm 任务序号”。在使用 at 命令来设置一次性计划任务时,默认采用的是交互式方法。例如,使用下述命令将系统设置为在今晚 23:30 分自动重启网站服务

1
2
3
4
5
6
[root@linuxprobe ~]# at 23:30
at > systemctl restart httpd
at > 此处请同时按下Ctrl+d来结束编写计划任务
job 3 at Mon Apr 27 23:30:00 2015
[root@linuxprobe ~]# at -l
3 Mon Apr 27 23:30:00 2016 a root

如果读者想挑战一下难度更大但简捷性更高的方式,可以把前面学习的管道符(任意门)放到两条命令之间,让 at 命令接收前面 echo 命令的输出信息,以达到通过非交互式的方式创建计划一次性任务的目的。

1
2
3
4
5
[root@linuxprobe ~]# echo "systemctl restart httpd" | at 23:30
job 4 at Mon Apr 27 23:30:00 2015
[root@linuxprobe ~]# at -l
3 Mon Apr 27 23:30:00 2016 a root
4 Mon Apr 27 23:30:00 2016 a root

如果我们不小心设置了两个一次性计划任务,可以使用下面的命令轻松删除其中一个:

1
2
3
[root@linuxprobe ~]# atrm 3
[root@linuxprobe ~]# at -l
4 Mon Apr 27 23:30:00 2016 a root

如果我们希望 Linux 系统能够周期性地、有规律地执行某些具体的任务,那么 Linux 系统中默认启用的crond服务简直再适合不过了。创建、编辑计划任务的命令为“crontab -e”,查看当前计划任务的命令为“crontab -l”,删除某条计划任务的命令为“crontab -r”。另外,如果您是以管理员的身份登录的系统,还可以在 crontab 命令中加上-u参数来编辑他人的计划任务。

在正式部署计划任务前,请先跟刘遄老师念一下口诀“分、时、日、月、星期 命令”。这是使用 crond 服务设置任务的参数格式(其格式见表 4-6)。需要注意的是,如果有些字段没有设置,则需要使用星号(*)占位,如图

cron计划任务的参数.png

字段 说明
分钟 取值为 0 ~ 59 的整数
小时 取值为 0 ~ 23 的任意整数
日期 取值为 1 ~ 31 的任意整数
月份 取值为 1 ~ 12 的任意整数
星期 取值为 0 ~ 7 的任意整数,其中 0 与 7 均为星期日
命令 要执行的命令或程序脚本

假设在每周一、三、五的凌晨 3 点 25 分,都需要使用 tar 命令把某个网站的数据目录进行打包处理,使其作为一个备份文件。我们可以使用 crontab -e 命令来创建计划任务。为自己创建计划任务无需使用-u 参数,具体的实现效果的参数如 crontab -l 命令结果所示:

1
2
3
4
5
[root@linuxprobe ~]# crontab -e
no crontab for root - using an empty one
crontab: installing new crontab
[root@linuxprobe ~]# crontab -l
25 3 * * 1,3,5 /usr/bin/tar -czvf backup.tar.gz /home/wwwroot

需要说明的是,除了用逗号(,)来分别表示多个时间段,例如“8,9,12”表示 8 月、9 月和 12 月。还可以用减号(-)来表示一段连续的时间周期(例如字段“日”的取值为“12-15”,则表示每月的 12 ~ 15 日)。以及用除号(/)表示执行任务的间隔时间(例如“*/2”表示每隔 2 分钟执行一次任务)。

如果在 crond 服务中需要同时包含多条计划任务的命令语句,应每行仅写一条。例如我们再添加一条计划任务,它的功能是每周一至周五的凌晨 1 点钟自动清空/tmp 目录内的所有文件。尤其需要注意的是,在 crond 服务的计划任务参数中,所有命令一定要用绝对路径的方式来写,如果不知道绝对路径,请用 whereis 命令进行查询,rm 命令路径为下面输出信息中加粗部分。

1
2
3
4
5
6
7
[root@linuxprobe ~]# whereis rm
rm: /usr/bin/rm /usr/share/man/man1/rm.1.gz /usr/share/man/man1p/rm.1p.gz
[root@linuxprobe ~]# crontab -e
crontab: installing new crontab
[root@linuxprobe ~]# crontab -l
25 3 * * 1,3,5 /usr/bin/tar -czvf backup.tar.gz /home/wwwroot
0 1 * * 1-5 /usr/bin/rm -rf /tmp/*

在 crond 服务的配置参数中,可以像 Shell 脚本那样以#号开头写上注释信息,这样在日后回顾这段命令代码时可以快速了解其功能、需求以及编写人员等重要信息。

计划任务中的“分”字段必须有数值,绝对不能为空或是*号,而“日”和“星期”字段不能同时使用,否则就会发生冲突。

最后再啰嗦一句,想必读者也已经发现了,诸如 crond 在内的很多服务默认调用的是 Vim 编辑器,相信大家现在能进一步体会到在 Linux 系统中掌握 Vim 文本编辑器的好处了吧。所以请大家一定要在彻底掌握 Vim 编码器之后再学习下一章

参考

Linux shell 编程基础,一看就能入门

管道符,重定向与环境变量

发表于 2018-04-22 | 分类于 linux
  • 输入输出重定向
    1. < << Delimiter
    2. > >>追加
    3. 2> 2>>
    4. $>>
  • 管道命令符
    1. |
  • 命令行的通配符
    1. *
    2. ?
    3. [0-9]
  • 常用哦转义字符
    1. \
    2. ‘’
    3. “”
    4. ``
  • 重要的环境变量
    1. 判断用户是否以绝对路径或相对路径的方式输入命令
    2. Linux系统检查用户输入的命令是否为“别名命令”
    3. Bash解释器判断用户输入的是内部命令还是外部命令
    4. 系统在多个路径中查找用户输入的命令文件,而定义这些路径的变量叫作PATH

管道符,重定向与环境变量

目前为止,我们已经学习了数十个常用的Linux系统命令,如果不能把这些命令进行组合使用,则无法提升工作效率。本章首先讲解与文件读写操作有关的重定向技术的5种模式:

  1. 标准覆盖输出重定向
  2. 标准追加输出重定向
  3. 错误覆盖输出重定向
  4. 错误追加输出重定向
  5. 输入重定向,

让读者通过实验切实理解每个重定向模式的作用,解决输出信息的保存问题。然后深入讲解管道命令符,帮助读者掌握命令之间的搭配使用方法,进一步提高命令输出值的处理效率。
随后通过讲解Linux系统命令行中的通配符和常见转义符,让您输入的Linux命令具有更准确的意义,为下一章学习编写Shell脚本打好功底。
最后,本章深度剖析了Bash解释器执行Linux命令的内部原理,为读者掌握PATH变量及Linux系统中的重要环境变量打下了基础。

输入输出重定向

既然我们已经在上一章学完了几乎所有基础且常用的Linux命令,那么接下来的任务就是把多个Linux命令适当地组合到一起,使其协同工作,以便我们更加高效地处理数据。要做到这一点,就必须搞明白命令的输入重定向和输出重定向的原理。

简而言之,输入重定向是指把文件导入到命令中,而输出重定向则是指把原本要输出到屏幕的数据信息写入到指定文件中。在日常的学习和工作中,相较于输入重定向,我们使用输出重定向的频率更高,所以又将输出重定向分为了标准输出重定向和错误输出重定向两种不同的技术,以及清空写入与追加写入两种模式。

标准输入重定向(STDIN,文件描述符为0):默认从键盘输入,也可从其他文件或命令中输入。

标准输出重定向(STDOUT,文件描述符为1):默认输出到屏幕。

错误输出重定向(STDERR,文件描述符为2):默认输出到屏幕。

比如我们分别查看两个文件的属性信息,其中第二个文件是不存在的,虽然针对这两个文件的操作都分别会在屏幕上输出一些数据信息,但这两个操作的差异其实很大:

1
2
3
4
5
$ touch linuxprobe
$ ls -l linuxprobe
-rw-r--r--. 1 root root 0 Aug 5 05:35 linuxprobe
$ ls -l xxxxxx
ls: cannot access xxxxxx: No such file or directory

在上述命令中,名为linuxprobe的文件是存在的,输出信息是该文件的一些相关权限、所有者、所属组、文件大小及修改时间等信息,这也是该命令的标准输出信息。而名为xxxxxx的第二个文件是不存在的,因此在执行完ls命令之后显示的报错提示信息也是该命令的错误输出信息。那么,要想把原本输出到屏幕上的数据转而写入到文件当中,就要区别对待这两种输出信息。

对于输入重定向来讲,用到的符号及其作用如表

符号 作用
命令 < 文件 将文件作为命令的标准输入
命令 << 分界符 从标准输入中读入,直到遇见分界符才停止
命令 < 文件1 > 文件2 将文件1作为命令的标准输入并将标准输出到文件2

对于输出重定向来讲,用到的符号及其作用如表

符号 作用
命令 > 文件 将标准输出重定向到一个文件中(清空原有文件的数据)
命令 2> 文件 将错误输出重定向到一个文件中(清空原有文件的数据)
命令 >> 文件 将标准输出重定向到一个文件中(追加到原有内容的后面)
命令 2>> 文件 将错误输出重定向到一个文件中(追加到原有内容的后面)
命令 >> 文件 2>&1 或 命令 &>> 文件 将标准输出与错误输出共同写入到文件中(追加到原有内容的后面)

对于重定向中的标准输出模式,可以省略文件描述符1不写,而错误输出模式的文件描述符2是必须要写的。我们先来小试牛刀。通过标准输出重定向将man bash命令原本要输出到屏幕的信息写入到文件readme.txt中,然后显示readme.txt文件中的内容。具体命令如下:

1
2
$ man bash > readme.txt
$ cat readme.txt

我们接下来尝试输出重定向技术中的覆盖写入与追加写入这两种不同模式带来的变化。首先通过覆盖写入模式向readme.txt文件写入一行数据(该文件中包含上一个实验的man命令信息),然后再通过追加写入模式向文件再写入一次数据,其命令如下:

1
2
$ echo "Welcome to LinuxProbe.Com" > readme.txt
$ echo "Quality linux learning materials" >> readme.txt

虽然都是输出重定向技术,但是不同命令的标准输出和错误输出还是有区别的。例如查看当前目录中某个文件的信息,这里以linuxprobe文件为例。因为这个文件是真实存在的,因此使用标准输出即可将原本要输出到屏幕的信息写入到文件中,而错误的输出重定向则依然把信息输出到了屏幕上。

1
2
3
4
5
$ ls -l linuxprobe 
-rw-r--r--. 1 root root 0 Mar 1 13:30 linuxprobe
$ ls -l linuxprobe > /root/stderr.txt
$ ls -l linuxprobe 2> /root/stderr.txt
-rw-r--r--. 1 root root 0 Mar 1 13:30 linuxprobe //输出到屏幕

如果想把命令的报错信息写入到文件,该怎么操作呢?当用户在执行一个自动化的Shell脚本时,这个操作会特别有用,而且特别实用,因为它可以把整个脚本执行过程中的报错信息都记录到文件中,便于安装后的排错工作。接下来我们以一个不存在的文件进行实验演示:

1
2
3
4
5
6
7
$ ls -l xxxxxx 
cannot access xxxxxx: No such file or directory
$ ls -l xxxxxx > /root/stderr.txt
cannot access xxxxxx: No such file or directory
$ ls -l xxxxxx 2> /root/stderr.txt //与上面区别,这个如果不报错的情况下,当然输出到屏幕,相当于不执行.但是如果报错,那么用2> 将错误信息写入文件.本来错误信息是输出到屏幕的.
$ cat /root/stderr.txt
ls: cannot access xxxxxx: No such file or directory

输入重定向相对来说有些冷门,在工作中遇到的概率会小一点。输入重定向的作用是把文件直接导入到命令中。接下来使用输入重定向把readme.txt文件导入给wc -l命令,统计一下文件中的内容行数。

1
$ wc -l < readme.txt

上述命令实际上等同于接下来要学习的cat readme.txt | wc -l的管道符命令组合。

管道命令符

细心的读者肯定还记得在2.6节学习tr命令时曾经见到过一个名为管道符的东西。同时按下键盘上的Shift+\键即可输入管道符|,其执行格式为“命令A | 命令B”。管道命令符的作用也可以用一句话来概括“把前一个命令原本要输出到屏幕的标准正常数据当作是后一个命令的标准输入”。在2.8节讲解grep文本搜索命令时,我们通过匹配关键词/sbin/nologin找出了所有被限制登录系统的用户。在学完本节内容后,完全可以把下面这两条命令合并为一条:

找出被限制登录用户的命令是grep “/sbin/nologin” /etc/passwd;

统计文本行数的命令则是wc -l。

现在要做的就是把搜索命令的输出值传递给统计命令,即把原本要输出到屏幕的用户信息列表再交给wc命令作进一步的加工,因此只需要把管道符放到两条命令之间即可,具体如下。这简直是太方便了!

1
$ grep "/sbin/nologin" /etc/passwd | wc -l

这个管道符就像一个法宝,我们可以将它套用到其他不同的命令上,比如用翻页的形式查看/etc目录中的文件列表及属性信息(这些内容默认会一股脑儿地显示到屏幕上,根本看不清楚):

1
2
3
4
5
6
7
$ ls -l /etc/ | more
total 1400
drwxr-xr-x. 3 root root 97 Jul 10 17:26 abrt
-rw-r--r--. 1 root root 16 Jul 10 17:36 adjtime
-rw-r--r--. 1 root root 1518 Jun 7 2013 aliases
-rw-r--r--. 1 root root 12288 Jul 10 09:38 aliases.db
--More--

在修改用户密码时,通常都需要输入两次密码以进行确认,这在编写自动化脚本时将成为一个非常致命的缺陷。通过把管道符和passwd命令的—stdin参数相结合,我们可以用一条命令来完成密码重置操作:

1
2
3
$ echo "linuxprobe" | passwd --stdin root
Changing password for user root.
passwd: all authentication tokens updated successfully.

大家是不是觉得管道符命令有些相见恨晚?管道符的玩法还有很多,比如,在发送电子邮件时,默认采用交互式的方式来进行,我们完全可以利用一条结合了管道符的命令语句,把编辑好的内容与标题一起“打包”,最终用这一条命令实现邮件的发送。

1
2
3
4
5
6
7
$ echo "Content" | mail -s "Subject" linuxprobe
$ su - linuxprobe
Last login: Fri Jul 10 09:44:07 CST 2017 on :0
$ mail
Heirloom Mail version 12.5 7/5/10. Type ? for help.
"/var/spool/mail/linuxprobe": 1 message 1 new
>N 1 root Sun Aug 30 17:33 18/578 "Subject"

如果读者是一名Linux新手,可能会觉得上面的命令组合已经十分复杂了,但是有过运维经验的读者又会感觉如隔靴挠痒般不过瘾,他们希望能将这样方便的命令写得更高级一些,功能更强大一些。比如通过重定向技术能够一次性地把多行信息打包输入或输出,让日常工作更有效率。

下面这条自造的命令就结合使用了mail邮件命令与输入重定向的分界符,其目的是让用户一直输入内容,直到用户输入了其自定义的分界符时,才结束输入。

1
2
3
4
5
6
$ mail -s "Readme" root@linuxprobe.com << over
> I think linux is very practical
> I hope to learn more
> can you teach me ?
> over
$

当然,大家千万不要误以为管道命令符只能在一个命令组合中使用一次,我们完全可以这样使用:“命令A | 命令B | 命令C”。

命令行的通配符

大家可能都遇到过提笔忘字的尴尬,作为Linux运维人员,我们有时候也会遇到明明一个文件的名称就在嘴边但就是想不起来的情况。如果就记得一个文件的开头几个字母,想遍历查找出所有以这个关键词开头的文件,该怎么操作呢?又比如,假设想要批量查看所有硬盘文件的相关权限属性,一种方式是这样的:

1
2
3
4
5
6
7
8
$ ls -l /dev/sda
brw-rw----. 1 root disk 8, 0 May 4 15:55 /dev/sda
$ ls -l /dev/sda1
brw-rw----. 1 root disk 8, 1 May 4 15:55 /dev/sda1
$ ls -l /dev/sda2
brw-rw----. 1 root disk 8, 2 May 4 15:55 /dev/sda2
$ ls -l /dev/sda3
ls: cannot access /dev/sda3: No such file or directory

幸亏我的硬盘文件和分区只有3个,要是有几百个,估计需要花费一天的时间来忙这个事情了。由此可见,这种方式的效率确实很低。虽然我们在第6章才会讲解Linux系统的存储结构和FHS,但现在我们应该能看出一些简单规律了。比如,这些硬盘设备文件都是以sda开头并且存放到了/dev目录中,这样一来,即使我们不知道硬盘的分区编号和具体分区的个数,也可以使用通配符来搞定。
顾名思义,通配符就是通用的匹配信息的符号,比如星号(*)代表匹配零个或多个字符,问号(?)代表匹配单个字符,中括号内加上数字[0-9]代表匹配0~9之间的单个数字的字符,而中括号内加上字母[abc]则是代表匹配a、b、c三个字符中的任意一个字符。俗话讲“百闻不如一见,看书不如实验”,下面我们就来匹配所有在/dev目录中且以sda开头的文件:

1
2
3
4
$ ls -l /dev/sda*
brw-rw----. 1 root disk 8, 0 May 4 15:55 /dev/sda
brw-rw----. 1 root disk 8, 1 May 4 15:55 /dev/sda1
brw-rw----. 1 root disk 8, 2 May 4 15:55 /dev/sda2

如果只想查看文件名为sda开头,但是后面还紧跟其他某一个字符的文件的相关信息,该怎么操作呢?这时就需要用到问号来进行通配了。

1
2
3
$ ls -l /dev/sda?
brw-rw----. 1 root disk 8, 1 May 4 15:55 /dev/sda1
brw-rw----. 1 root disk 8, 2 May 4 15:55 /dev/sda2

除了使用[0-9]来匹配0~9之间的单个数字,也可以用[135]这样的方式仅匹配这三个指定数字中的一个,若没有匹配到,则不会显示出来:

1
2
3
4
5
$ ls -l /dev/sda[0-9]
brw-rw----. 1 root disk 8, 1 May 4 15:55 /dev/sda1
brw-rw----. 1 root disk 8, 2 May 4 15:55 /dev/sda2
$ ls -l /dev/sda[135]
brw-rw----. 1 root disk 8, 1 May 4 15:55 /dev/sda1

常用的转义字符

为了能够更好地理解用户的表达,Shell解释器还提供了特别丰富的转义字符来处理输入的特殊数据。从数十个转义字符中提炼出了4个最常用的转义字符!

4个最常用的转义字符如下所示。

反斜杠(\):使反斜杠后面的一个变量变为单纯的字符串。

单引号(’’):转义其中所有的变量为单纯的字符串。

双引号(””):保留其中的变量属性,不进行转义处理。

反引号(``):把其中的命令执行后返回结果。

我们先定义一个名为PRICE的变量并赋值为5,然后输出以双引号括起来的字符串与变量信息:

1
2
3
$ PRICE=5
$ echo "Price is $PRICE"
Price is 5

接下来,我们希望能够输出“Price is $5”,即价格是5美元的字符串内容,但碰巧美元符号与变量提取符号合并后的$$作用是显示当前程序的进程ID号码,于是命令执行后输出的内容并不是我们所预期的:

1
2
$ echo "Price is $$PRICE" 
Price is 3767PRICE

要想让第一个“$”乖乖地作为美元符号,那么就需要使用反斜杠(\)来进行转义,将这个命令提取符转义成单纯的文本,去除其特殊功能。

1
2
$ echo "Price is \$$PRICE"
Price is $5

而如果只需要某个命令的输出值时,可以像命令这样,将命令用反引号括起来,达到预期的效果。例如,将反引号与uname -a命令结合,然后使用echo命令来查看本机的Linux版本和内核信息:

1
2
$ echo `uname -a`  //很像es6,毕竟es6就是这么发展过来的
Linux linuxprobe.com 3.10.0-123.el7.x86_64 #1 SMP Mon May 5 11:16:57 EDT 2017 x86_64 x86_64 x86_64 GNU/Linux

重要的环境变量

变量是计算机系统用于保存可变值的数据类型。在Linux系统中,变量名称一般都是大写的,这是一种约定俗成的规范。我们可以直接通过变量名称来提取到对应的变量值。Linux系统中的环境变量是用来定义系统运行环境的一些参数,比如每个用户不同的家目录、邮件存放位置等。

前文中曾经讲到,在Linux系统中一切都是文件,Linux命令也不例外。那么,在用户执行了一条命令之后,Linux系统中到底发生了什么事情呢?简单来说,命令在Linux中的执行分为4个步骤。

第1步:判断用户是否以绝对路径或相对路径的方式输入命令(如/bin/ls),如果是的话则直接执行。

第2步:Linux系统检查用户输入的命令是否为“别名命令”,即用一个自定义的命令名称来替换原本的命令名称。可以用alias命令来创建一个属于自己的命令别名,格式为“alias 别名=命令”。若要取消一个命令别名,则是用unalias命令,格式为“unalias 别名”。我们之前在使用rm命令删除文件时,Linux系统都会要求我们再确认是否执行删除操作,其实这就是Linux系统为了防止用户误删除文件而特意设置的rm别名命令,接下来我们把它取消掉:

1
2
3
4
5
6
7
8
9
10
$ ls
anaconda-ks.cfg Documents initial-setup-ks.cfg Pictures Templates
Desktop Downloads Music Public Videos
$ rm anaconda-ks.cfg
rm: remove regular file ‘anaconda-ks.cfg’? y
[root@linuxprobe~]# alias rm
alias rm='rm -i'
$ unalias rm
$ rm initial-setup-ks.cfg
$

第3步:Bash解释器判断用户输入的是内部命令还是外部命令。内部命令是解释器内部的指令,会被直接执行;而用户在绝大部分时间输入的是外部命令,这些命令交由步骤4继续处理。可以使用“type命令名称”来判断用户输入的命令是内部命令还是外部命令。

第4步:系统在多个路径中查找用户输入的命令文件,而定义这些路径的变量叫作PATH,可以简单地把它理解成是“解释器的小助手”,作用是告诉Bash解释器待执行的命令可能存放的位置,然后Bash解释器就会乖乖地在这些位置中逐个查找。PATH是由多个路径值组成的变量,每个路径值之间用冒号间隔,对这些路径的增加和删除操作将影响到Bash解释器对Linux命令的查找。

1
2
3
4
5
$ echo $PATH
/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
$ PATH=$PATH:/root/bin
$ echo $PATH
/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/root/bin

这里有比较经典的问题:“为什么不能将当前目录(.)添加到PATH中呢? ” 原因是,尽管可以将当前目录(.)添加到PATH变量中,从而在某些情况下可以让用户免去输入命令所在路径的麻烦。但是,如果黑客在比较常用的公共目录/tmp中存放了一个与ls或cd命令同名的木马文件,而用户又恰巧在公共目录中执行了这些命令,那么就极有可能中招了。

所以,作为一名态度谨慎、有经验的运维人员,在接手了一台Linux系统后一定会在执行命令前先检查PATH变量中是否有可疑的目录,另外读者从前面的PATH变量示例中是否也感觉到环境变量特别有用呢。我们可以使用env命令来查看到Linux系统中所有的环境变量,而刘遄老师为您精挑细选出了最重要的10个环境变量,

变量名称 作用
HOME 用户的主目录(即家目录)
SHELL 用户在使用的Shell解释器名称
HISTSIZE 输出的历史命令记录条数
HISTFILESIZE 保存的历史命令记录条数
MAIL 邮件保存路径
LANG 系统语言、语系名称
RANDOM 生成一个随机数字
PS1 Bash解释器的提示符
PATH 定义解释器搜索用户执行命令的路径
EDITOR 用户默认的文本编辑器

Linux作为一个多用户多任务的操作系统,能够为每个用户提供独立的、合适的工作运行环境,因此,一个相同的变量会因为用户身份的不同而具有不同的值。例如,我们使用下述命令来查看HOME变量在不同用户身份下都有哪些值(su是用于切换用户身份的命令,将在第5章跟大家见面):

1
2
3
4
5
6
$ echo $HOME
/root
$ su - linuxprobe
Last login: Fri Feb 27 19:49:57 CST 2017 on pts/0
$ echo $HOME
/home/linuxprobe

其实变量是由固定的变量名与用户或系统设置的变量值两部分组成的,我们完全可以自行创建变量,来满足工作需求。例如设置一个名称为WORKDIR的变量,方便用户更轻松地进入一个层次较深的目录:

1
2
3
4
5
$ mkdir /home/workdir
$ WORKDIR=/home/workdir
$ cd $WORKDIR
$ pwd
/home/workdir

但是,这样的变量不具有全局性,作用范围也有限,默认情况下不能被其他用户使用。如果工作需要,可以使用export命令将其提升为全局变量,这样其他用户也就可以使用它了:

1
2
3
4
5
6
7
8
9
10
11
[root@linuxprobe workdir]# su linuxprobe
Last login: Fri Mar 20 20:52:10 CST 2017 on pts/0
[linuxprobe@linuxprobe ~]$ cd $WORKDIR
[linuxprobe@linuxprobe ~]$ echo $WORKDIR
[linuxprobe@linuxprobe ~]$ exit
[root@linuxprobe ~]# export WORKDIR
[root@linuxprobe ~]# su linuxprobe
Last login: Fri Mar 20 21:52:10 CST 2017 on pts/0
[linuxprobe@linuxprobe ~]$ cd $WORKDIR
[linuxprobe@linuxprobe workdir]$ pwd
/home/workdir

linux基本命令使用

发表于 2018-04-19 | 分类于 linux

linux基本命令使用

打开终端

  1. guake 按F12

各种配置见preferences

  1. terminal 按ctrl+alt+t
  • 常用系统工作命令
    1. echo
    2. date
    3. reboot,poweroff,shutdown
    4. wget
    5. ps
    6. top(htop)
    7. pidof
    8. kill
    9. killall
  • 系统状态检测命令
    1. ifconfig
    2. uname
    3. uptime(top)
    4. free(top)
    5. who
    6. last
    7. history
    8. sosreport
  • 工作目录切换命令
    1. pwd
    2. cd
    3. ls
  • 文本文件编辑命令
    1. cat
    2. more
    3. head
    4. tail
    5. tr
    6. wc word count
    7. stat
    8. cut
    9. diff
  • 文件目录管理命令
    1. touch -a -m -d
    2. mkdir
    3. cp -p
    4. mv
    5. dd if= of= count= bs=
    6. file
  • 打包压缩与搜索命令
    1. tar -cx/zj/vf
    2. grep -n -v
    3. find

man命令中常用按键以及用途

按键 用处
空格键 向下翻一页
PaGe down 向下翻一页
PaGe up 向上翻一页
home 直接前往首页
end 直接前往尾页
/ 从上至下搜索某个关键词,如“/linux”
? 从下至上搜索某个关键词,如“?linux”
n 定位到下一个搜索到的关键词
N 定位到上一个搜索到的关键词
q 退出帮助文档

一般来讲,使用man命令查看到的帮助内容信息都会很长,然而并没有什么用.

结构名称 代表意义
NAME 命令的名称
SYNOPSIS 参数的大致使用方法
DESCRIPTION 介绍说明
EXAMPLES 演示(附带简单说明)
OVERVIEW 概述
DEFAULTS 默认的功能
OPTIONS 具体的可用选项(带介绍)
ENVIRONMENT 环境变量
FILES 用到的文件
SEE ALSO 相关的资料
HISTORY 维护历史与联系方式

常用系统工作命令

  1. echo命令
    echo命令用于在终端输出字符串或变量提取后的值,格式为“echo [字符串 | $变量]”。
1
2
echo hello 
eche $SHELL
  1. date命令
    date命令用于显示及设置系统的时间或日期,格式为“date [选项] [+指定的格式]”。
    只需在强大的date命令中输入以“+”号开头的参数,即可按照指定格式来输出系统的时间或日期,这样在日常工作时便可以把备份数据的命令与指定格式输出的时间信息结合到一起。例如,把打包后的文件自动按照“年-月-日”的格式打包成“backup-2017-9-1.tar.gz”,用户只需要看一眼文件名称就能大概了解到每个文件的备份时间了
参数 作用
%t 跳格[Tab键]
%Y 年 %y是2位
%m 月 %M是分钟了
%d 日
%D 04/19/18 %d/%m/%y
%H / %I 小时(00~23)/小时(00~12)
%M 分钟(00~59)
%S 秒(00~59)
%j 今年中的第几天
1
2
date +%Y-%m-%d
date "+%Y-%m-%d %H:%M:%S" //有空格的一定要加引号
  1. reboot poweroff halt

一般我用shutdown诶

  1. wget
    wget命令用于在终端中下载网络文件,格式为“wget [参数] 下载地址”。

  2. ps命令
    ps命令用于查看系统中的进程状态,格式为“ps [参数]”。

执行这个命令时通常会将ps命令与第3章的管道符技术搭配使用,用来抓取与某个指定服务进程相对应的PID号码。ps命令的常见参数以及作用如表。

参数 作用
-a 显示所有进程(包括其他用户的进程)
-u 用户以及其他详细信息
-x 显示没有控制终端的进程
1
2
ps -aux
ps aux

如前面所提到的,在Linux系统中的命令参数有长短格式之分,长格式和长格式之间不能合并,长格式和短格式之间也不能合并,只有短格式和短格式之间是可以合并的,合并后仅保留一个-(减号)即可。另外ps命令可允许参数不加减号(-),因此可直接写成ps aux的样子。

Linux系统中时刻运行着许多进程,如果能够合理地管理它们,则可以优化系统的性能。在Linux系统中,有5种常见的进程状态,分别为运行、中断、不可中断、僵死与停止,其各自含义如下所示。

R(运行running):进程正在运行或在运行队列中等待。

S(中断interruptible sleep):进程处于休眠中,当某个条件形成后或者接收到信号时,则脱离该 状态。

D(不可中断uninterruptible sleep):进程不响应系统异步信号,即便用kill命令也不能将其中断。

T(停止stopped by job control signal):进程收到停止信号后停止运行。

Z(僵死zombie):进程已经终止,但进程描述符依然存在, 直到父进程调用wait4()系统函数后将进程释放。

还有一些加在后面的

< 高优先级 (not nice to other users)
N 低优先级 (nice to other users)
L 内存中有pages locked(for real-time and custom IO)
s 是一个session leader
l 是多线程的multi-threaded (using CLONE_THREAD, like NPTL pthreads do)
+ 在前台进程组中

  1. top命令(还有htop)

top命令用于动态地监视进程活动与系统负载等信息,其格式为top。

top命令相当强大,能够动态地查看系统运维状态,完全将它看作Linux中的“强化版的Windows任务管理器”

第1行top:系统时间、up运行时间、user登录终端数、load average系统负载(三个数值分别为1分钟、5分钟、15分钟内的平均值,数值越小意味着负载越低)。

第2行Tasks:total进程总数、running运行中的进程数、sleeping睡眠中的进程数、stopped停止的进程数、zombie僵死的进程数。

第3行Cpu:%user用户占用资源百分比、%system系统内核占用资源百分比、%ni改变过优先级的进程资源百分比、%idle空闲的资源百分比等。其中数据均为CPU数据并以百分> 比格式显示,例如“97.1 id”意味着有97.1%的CPU处理器资源处于空闲。

1
2
3
4
5
6
7
8
us: is meaning of "user CPU time"
sy: is meaning of "system CPU time"
ni: is meaning of" nice CPU time"
id: is meaning of "idle"
wa: is meaning of "iowait"
hi:is meaning of "hardware irq" 硬中断
si : is meaning of "software irq" 软中断
st : is meaning of "steal time"

第4行Mem:物理内存总量、内存使用量、内存空闲量、作为内核缓存的内存量。

第5行Swap:虚拟内存总量、虚拟内存使用量、虚拟内存空闲量、已被提前加载的内存量。

  1. pidof命令
    pidof命令用于查询某个指定服务进程的PID值,格式为“pidof [参数] [服务名称]”。

每个进程的进程号码值(PID)是唯一的,因此可以通过PID来区分不同的进程。例如,可以使用如下命令来查询本机上sshd服务程序的PID

1
2
pidof zsh
90436 90108 89864 89585 80262 79890 75011 2211
  1. kill命令

kill命令用于终止某个指定PID的服务进程,格式为“kill [参数] [进程PID]”。

接下来,我们使用kill命令把上面用pidof命令查询到的PID所代表的进程终止掉.

1
kill 90436
  1. killall命令

killall命令用于终止某个指定名称的服务所对应的全部进程,格式为:“killall [参数] [进程名称]”。

通常来讲,复杂软件的服务程序会有多个进程协同为用户提供服务,如果逐个去结束这些进程会比较麻烦,此时可以使用killall命令来批量结束某个服务程序带有的全部进程。下面以httpd服务程序为例,来结束其全部进程。由于RHEL7系统默认没有安装httpd服务程序,因此大家此时只需看操作过程和输出结果即可,等学习了相关内容之后再来实践。

1
2
3
4
5
$ pidof httpd
13581 13580 13579 13578 13577 13576
$ killall httpd
$ pidof httpd
$

如果我们在系统终端中执行一个命令后想立即停止它,可以同时按下Ctrl + C组合键(生产环境中比较常用的一个快捷键),这样将立即终止该命令的进程。或者,如果有些命令在执行时不断地在屏幕上输出信息,影响到后续命令的输入,则可以在执行命令时在末尾添加上一个&符号,这样命令将进入系统后台来执行。

系统状态检测命令

网卡网络、系统内核、系统负载、内存使用情况、当前启用终端数量、历史登录记录、命令执行记录以及救援诊断等相关命令的使用方法.

  1. ifconfig命令

ifconfig命令用于获取网卡配置与网络状态等信息,格式为“ifconfig [网络设备] [参数]”。

使用ifconfig命令来查看本机当前的网卡配置与网络状态等信息时,其实主要查看的就是网卡名称、inet参数后面的IP地址、ether参数后面的网卡物理地址(又称为MAC地址),以及RX、TX的接收数据包与发送数据包的个数及累计流量(即下面加粗的信息内容):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# abc123 @ ubuntu in ~ [8:48:15] 
$ ifconfig
ens33 Link encap:Ethernet HWaddr 00:0c:29:99:ee:76
inet addr:192.168.138.142 Bcast:192.168.138.255 Mask:255.255.255.0
inet6 addr: fe80::9016:a587:cb6c:8e22/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:358492 errors:0 dropped:0 overruns:0 frame:0
TX packets:194286 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:411586597 (411.5 MB) TX bytes:19353703 (19.3 MB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:16667 errors:0 dropped:0 overruns:0 frame:0
TX packets:16667 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:1591176 (1.5 MB) TX bytes:1591176 (1.5 MB)
  1. uname命令

uname命令用于查看系统内核与系统版本等信息,格式为“uname [-a]”。

在使用uname命令时,一般会固定搭配上-a参数来完整地查看当前系统的内核名称、主机名、内核发行版本、节点名、系统时间、硬件名称、硬件平台、处理器类型以及操作系统名称等信息。

1
2
3
# abc123 @ ubuntu in ~ [8:48:18] 
$ uname -a
Linux ubuntu 4.13.0-38-generic #43~16.04.1-Ubuntu SMP Wed Mar 14 17:48:43 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

顺带一提,如果要查看当前系统版本的详细信息,则需要查看redhat-release文件,其命令以及相应的结果如下

1
2
3
4
5
$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.3 LTS"
  1. uptime命令(这不就是top命令的第一行么)

uptime用于查看系统的负载信息,格式为uptime。

uptime命令真的很棒,它可以显示当前系统时间、系统已运行时间、启用终端数量以及平均负载值等信息。平均负载值指的是系统在最近1分钟、5分钟、15分钟内的压力情况(下面加粗的信息部分);负载值越低越好,尽量不要长期超过1,在生产环境中不要超过5。

1
2
$ uptime 
08:53:54 up 9:13, 10 users, load average: 1.64, 0.96, 0.70
  1. free命令(top中)

free用于显示当前系统中内存的使用量信息,格式为“free [-h]”。

为了保证Linux系统不会因资源耗尽而突然宕机,运维人员需要时刻关注内存的使用量。在使用free命令时,可以结合使用-h参数以更人性化的方式输出当前内存的实时使用量信息

1
2
3
4
$ free -h
total used free shared buff/cache available
Mem: 1.9G 1.5G 74M 66M 366M 139M
Swap: 1.0G 1.0G 0B
  1. who命令

who用于查看当前登入主机的用户终端信息,格式为“who [参数]”。

这三个简单的字母可以快速显示出所有正在登录本机的用户的名称以及他们正在开启的终端信息

1
2
3
4
5
6
7
8
9
10
11
$ who
abc123 tty7 2018-04-18 17:59 (:0)
abc123 pts/18 2018-04-18 17:59 (ubuntu)
abc123 pts/23 2018-04-19 20:22 (ubuntu)
abc123 pts/24 2018-04-19 20:22 (ubuntu)
abc123 pts/25 2018-04-19 20:22 (ubuntu)
abc123 pts/26 2018-04-19 20:22 (ubuntu)
abc123 pts/27 2018-04-19 20:22 (ubuntu)
abc123 pts/28 2018-04-19 20:22 (ubuntu)
abc123 pts/25 2018-04-19 20:25 (:0)
abc123 pts/19 2018-04-19 21:30 (ubuntu)
  1. last命令

last命令用于查看所有系统的登录记录,格式为“last [参数]”。

使用last命令可以查看本机的登录记录。但是,由于这些信息都是以日志文件的形式保存在系统中,因此黑客可以很容易地对内容进行篡改。千万不要单纯以该命令的输出信息而判断系统有无被恶意入侵!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$ last
abc123 pts/19 ubuntu Thu Apr 19 21:30 gone - no logout
abc123 pts/26 :0 Thu Apr 19 20:26 - 20:26 (00:00)
abc123 pts/25 :0 Thu Apr 19 20:25 gone - no logout
abc123 pts/24 ubuntu Thu Apr 19 20:22 gone - no logout
abc123 pts/23 ubuntu Thu Apr 19 20:22 gone - no logout
abc123 pts/28 ubuntu Thu Apr 19 20:22 gone - no logout
abc123 pts/27 ubuntu Thu Apr 19 20:22 gone - no logout
abc123 pts/26 ubuntu Thu Apr 19 20:22 - 20:26 (00:03)
abc123 pts/25 ubuntu Thu Apr 19 20:22 - 20:25 (00:03)
abc123 pts/24 ubuntu Thu Apr 19 20:22 - 20:22 (00:00)
abc123 pts/23 ubuntu Thu Apr 19 20:22 - 20:22 (00:00)
abc123 pts/18 ubuntu Wed Apr 18 17:59 gone - no logout
abc123 tty7 :0 Wed Apr 18 17:59 gone - no logout
reboot system boot 4.13.0-38-generi Wed Apr 18 17:57 still running
abc123 pts/18 ubuntu Wed Apr 18 16:03 - crash (01:54)
abc123 tty7 :0 Wed Apr 18 16:03 - crash (01:54)
reboot system boot 4.13.0-38-generi Wed Apr 18 16:03 still running
abc123 pts/18 ubuntu Sat Apr 14 21:21 - crash (3+18:41)
abc123 tty7 :0 Sat Apr 14 21:21 - crash (3+18:41)
reboot system boot 4.13.0-38-generi Sat Apr 14 21:21 still running
abc123 pts/1 :0 Fri Apr 13 17:51 - 17:51 (00:00)
abc123 pts/18 ubuntu Fri Apr 13 17:20 - down (02:24)
abc123 tty7 :0 Fri Apr 13 17:20 - down (02:24)
reboot system boot 4.13.0-38-generi Fri Apr 13 17:20 - 19:44 (02:24)
abc123 pts/18 ubuntu Fri Apr 13 10:17 - crash (07:02)
abc123 tty7 :0 Fri Apr 13 10:17 - crash (07:02)
reboot system boot 4.13.0-38-generi Fri Apr 13 10:17 - 19:44 (09:27)

wtmp begins Thu Apr 12 21:08:41 2018
  1. history命令

history命令用于显示历史执行过的命令,格式为“history [-c]”。

history命令应该是作者最喜欢的命令。执行history命令能显示出当前用户在本地计算机中执行过的最近1000条命令记录。如果觉得1000不够用,还可以自定义/etc/profile文件中的HISTSIZE变量值。在使用history命令时,如果使用-c参数则会清空所有的命令历史记录。还可以使用“!编码数字”的方式来重复执行某一次的命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
586  ifconfig
587 top
588 ifconfig
589 uname -a
590 cat /etc/lsb-release
591 cat /etc/debian_version
592 uptime
593 free -h
594 free
595 top
596 top -h
597 top -hv
598 free -h
599 who
600 last

历史命令会被保存到用户家目录中的.bash_history文件中。Linux系统中以点(.)开头的文件均代表隐藏文件,这些文件大多数为系统服务文件,可以用cat命令查看其文件内容

1
cat ~/.bash_history

要清空当前用户在本机上执行的Linux命令历史记录信息,可执行如下命令:

1
history -c
  1. sosreport命令(ubuntu上要先安装)

sosreport命令用于收集系统配置及架构信息并输出诊断文档,格式为sosreport。

当Linux系统出现故障需要联系技术支持人员时,大多数时候都要先使用这个命令来简单收集系统的运行状态和服务配置信息,以便让技术支持人员能够远程解决一些小问题,亦或让他们能提前了解某些复杂问题。在下面的输出信息中,加粗的部分是收集好的资料压缩文件以及校验码,将其发送给技术支持人员即可:

工作目录切换命令

  1. pwd命令

pwd命令用于显示用户当前所处的工作目录,格式为“pwd [选项]”。

1
2
$ pwd
/home/abc123/Downloads
  1. cd命令

cd命令用于切换工作路径,格式为“cd [目录名称]”。

这个命令应该是最常用的一个Linux命令了。可以通过cd命令迅速、灵活地切换到不同的工作目录。除了常见的切换目录方式,还可以使用“cd -”命令返回到上一次所处的目录,使用“cd..”命令进入上级目录,以及使用“cd ~”命令切换到当前用户的家目录,亦或使用“cd ~username”切换到其他用户的家目录。例如,可以使用“cd 路径”的方式切换进/etc目录中:

1
2
3
4
cd /etc
cd -
cd ..
cd ~
  1. ls命令

ls命令用于显示目录中的文件信息,格式为“ls [选项] [文件] ”。

所处的工作目录不同,当前工作目录下的文件肯定也不同。使用ls命令的“-a”参数看到全部文件(包括隐藏文件),使用“-l”参数可以查看文件的属性、大小等详细信息。将这两个参数整合之后,再执行ls命令即可查看当前目录中的所有文件并输出这些文件的属性信息

如果想要查看目录属性信息,则需要额外添加一个-d参数。例如,可使用如下命令查看/etc目录的权限与属性信息:

1
2
$ ls -ld /usr
drwxr-xr-x 11 root root 4096 Aug 1 2017 /usr

文本文件编辑命令

  1. cat命令

cat命令用于查看纯文本文件(内容较少的),格式为“cat [选项] [文件]”。

Linux系统中有多个用于查看文本内容的命令,每个命令都有自己的特点,比如这个cat命令就是用于查看内容较少的纯文本文件的。cat这个命令也很好记,因为cat在英语中是“猫”的意思,小猫咪是不是给您一种娇小、可爱的感觉呢?

如果在查看文本内容时还想顺便显示行号的话,不妨在cat命令后面追加一个-n参数:

1
cat -n 1.txt
  1. more命令

more命令用于查看纯文本文件(内容较多的),格式为“more [选项]文件”。

如果需要阅读长篇小说或者非常长的配置文件,那么“小猫咪”可就真的不适合了。因为一旦使用cat命令阅读长篇的文本内容,信息就会在屏幕上快速翻滚,导致自己还没有来得及看到,内容就已经翻篇了。因此对于长篇的文本内容,推荐使用more命令来查看。more命令会在最下面使用百分比的形式来提示您已经阅读了多少内容。您还可以使用空格键或回车键向下翻页:

1
more 1.txt
  1. head命令

head命令用于查看纯文本文档的前N行,格式为“head [选项] [文件]”。

在阅读文本内容时,谁也难以保证会按照从头到尾的顺序往下看完整个文件。如果只想查看文本中前20行的内容,该怎么办呢?head命令可以派上用场了:

1
head -n 20 1.txt
  1. tail命令

tail命令用于查看纯文本文档的后N行或持续刷新内容,格式为“tail [选项] [文件]”。

我们可能还会遇到另外一种情况,比如需要查看文本内容的最后20行,这时就需要用到tail命令了。tail命令的操作方法与head命令非常相似,只需要执行“tail -n 20 文件名”命令就可以达到这样的效果。tail命令最强悍的功能是可以持续刷新一个文件的内容,当想要实时查看最新日志文件时,这特别有用,此时的命令格式为“tail -f 文件名”:

1
tail -f /var/log/messages
  1. tr命令

tr命令用于替换文本文件中的字符,格式为“tr [原始字符] [目标字符]”。

在很多时候,我们想要快速地替换文本中的一些词汇,又或者把整个文本内容都进行替换,如果进行手工替换,难免工作量太大,尤其是需要处理大批量的内容时,进行手工替换更是不现实。这时,就可以先使用cat命令读取待处理的文本,然后通过管道符(详见第3章)把这些文本内容传递给tr命令进行替换操作即可。例如,把某个文本内容中的英文全部替换为大写:

1
cat anaconda-ks.cfg | tr [a-z] [A-Z]
  1. wc命令(word count)

wc命令用于统计指定文本的行数、字数、字节数,格式为“wc [参数] 文本”。

每次我在课堂上讲到这个命令时,总有同学会联想到一种公共设施,其实这两者毫无关联。Linux系统中的wc命令用于统计文本的行数、字数、字节数等。如果为了方便自己记住这个命令的作用,也可以联想到上厕所时好无聊,无聊到数完了手中的如厕读物上有多少行字。wc的参数以及相应的作用如表2-10所示。

1
2
$ wc zoobar\ setup.txt 
88 206 2562 zoobar setup.txt
参数 作用
-l 只显示行数
-w 只显示单词数
-c 只显示字节数

在Linux系统中,passwd是用于保存系统账户信息的文件,要统计当前系统中有多少个用户,可以使用下面的命令来进行查询,是不是很神奇:

1
2
$ wc -l /etc/passwd
38 /etc/passwd
  1. stat命令

stat命令用于查看文件的具体存储信息和时间等信息,格式为“stat 文件名称”。

stat命令可以用于查看文件的存储信息和时间等信息,命令stat anaconda-ks.cfg会显示出文件的三种时间状态(已加粗):Access、Modify、Change。这三种时间的区别将在下面的touch命令中详细详解:

1
2
3
4
5
6
7
8
9
$ stat zoobar\ setup.txt 
File: 'zoobar setup.txt'
Size: 2562 Blocks: 8 IO Block: 4096 regular file
Device: 801h/2049d Inode: 941078 Links: 1
Access: (0766/-rwxrw-rw-) Uid: ( 1000/ abc123) Gid: ( 1000/ abc123)
Access: 2018-04-20 13:56:20.364043512 +0800
Modify: 2017-11-22 21:38:13.280060000 +0800
Change: 2017-11-23 13:56:28.538010046 +0800
Birth: -
  1. cut命令

cut命令用于按“列”提取文本字符,格式为“cut [参数] 文本”。

在Linux系统中,如何准确地提取出最想要的数据,这也是我们应该重点学习的内容。一般而言,按基于“行”的方式来提取数据是比较简单的,只需要设置好要搜索的关键词即可。但是如果按列搜索,不仅要使用-f参数来设置需要看的列数,还需要使用-d参数来设置间隔符号。passwd在保存用户数据信息时,用户信息的每一项值之间是采用冒号来间隔的,接下来我们使用下述命令尝试提取出passwd文件中的用户名信息,即提取以冒号(:)为间隔符号的第一列内容:

1
2
3
$ head -n 2 /etc/passwd
root:x:0:0:root:/root:/bin/bash
$ cut -d: -f1 /etc/passwd //f1第一列
  1. diff命令

diff命令用于比较多个文本文件的差异,格式为“diff [参数] 文件”。

在使用diff命令时,不仅可以使用—brief参数来确认两个文件是否不同,还可以使用-c参数来详细比较出多个文件的差异之处,这绝对是判断文件是否被篡改的有力神器。例如,先使用cat命令分别查看diff_A.txt和diff_B.txt文件的内容,然后进行比较:

1
2
3
$ cat diff_A.txt

$ cat diff_A.txt

接下来使用diff —brief命令显示比较后的结果,判断文件是否相同:

1
2
$ diff --brief diff_A.txt diff_B.txt
Files diff_A.txt and diff_B.txt differ

最后使用带有-c参数的diff命令来描述文件内容具体的不同:

1
diff -c diff_A.txt diff_B.txt

文件目录管理命令

  1. touch命令

touch命令用于创建空白文件或设置文件的时间,格式为“touch [选项] [文件]”。

在创建空白的文本文件方面,这个touch命令相当简捷,简捷到没有必要铺开去讲。比如,touch linuxprobe命令可以创建出一个名为linuxprobe的空白文本文件。对touch命令来讲,有难度的操作主要是体现在设置文件内容的修改时间(mtime)、文件权限或属性的更改时间(ctime)与文件的读取时间(atime)上面。touch命令的参数及其作用如表.

参数 作用
-a 仅修改“读取时间”(atime)
-m 仅修改“修改时间”(mtime)
-d 同时修改atime与mtime
1
2
3
4
5
6
7
8
$ ls -l anaconda-ks.cfg 
-rw-------. 1 root root 1213 May 4 15:44 anaconda-ks.cfg
$ echo "hello nihao" >> anaconda-ks.cfg
$ ls -l anaconda-ks.cfg
-rw-------. 1 root root 1260 Aug 2 01:26 anaconda-ks.cfg
$ touch -d "2017-05-04 15:44" anaconda-ks.cfg
$ ls -l anaconda-ks.cfg
-rw-------. 1 root root 1260 May 4 15:44 anaconda-ks.cfg
  1. mkdir命令

mkdir命令用于创建空白的目录,格式为“mkdir [选项] 目录”。

在Linux系统中,文件夹是最常见的文件类型之一。除了能创建单个空白目录外,mkdir命令还可以结合-p参数来递归创建出具有嵌套叠层关系的文件目录。

  1. cp命令

cp命令用于复制文件或目录,格式为“cp [选项] 源文件 目标文件”。大家对文件复制操作应该不陌生,在Linux系统中,复制操作具体分为3种情况:

如果目标文件是目录,则会把源文件复制到该目录中;

如果目标文件也是普通文件,则会询问是否要覆盖它;

如果目标文件不存在,则执行正常的复制操作。

参数 作用
-p 保留原始文件的属性
-d 若对象为“链接文件”,则保留该“链接文件”的属性
-r 递归持续复制(用于目录)
-i 若目标文件存在则询问是否覆盖
-a 相当于-pdr(p、d、r为上述参数)
  1. mv命令

mv命令用于剪切文件或将文件重命名,格式为“mv [选项] 源文件 [目标路径|目标文件名]”。

剪切操作不同于复制操作,因为它会默认把源文件删除掉,只保留剪切后的文件。如果在同一个目录中对一个文件进行剪切操作,其实也就是对其进行重命名:

  1. rm命令

rm命令用于删除文件或目录,格式为“rm [选项] 文件”。

在Linux系统中删除文件时,系统会默认向您询问是否要执行删除操作,如果不想总是看到这种反复的确认信息,可在rm命令后跟上-f参数来强制删除。另外,想要删除一个目录,需要在rm命令后面一个-r参数才可以,否则删除不掉。

  1. dd命令

dd命令用于按照指定大小和个数的数据块来复制文件或转换文件,格式为“dd [参数]”。

dd命令是一个比较重要而且比较有特色的一个命令,它能够让用户按照指定大小和个数的数据块来复制文件的内容。当然如果愿意的话,还可以在复制过程中转换其中的数据。Linux系统中有一个名为/dev/zero的设备文件,每次在课堂上解释它时都充满哲学理论的色彩。因为这个文件不会占用系统存储空间,但却可以提供无穷无尽的数据,因此可以使用它作为dd命令的输入文件,来生成一个指定大小的文件

参数 作用
if 输入的文件名称 input file
of 输出的文件名称 output file
bs 设置每个“块”的大小 block size
count 设置要复制“块”的个数

例如我们可以用dd命令从/dev/zero设备文件中取出一个大小为560MB的数据块,然后保存成名为560_file的文件。在理解了这个命令后,以后就能随意创建任意大小的文件了:

1
$ dd if=/dev/zero of=560_file count=1 bs=560M

dd命令的功能也绝不仅限于复制文件这么简单。如果您想把光驱设备中的光盘制作成iso格式的镜像文件,在Windows系统中需要借助于第三方软件才能做到,但在Linux系统中可以直接使用dd命令来压制出光盘镜像文件,将它变成一个可立即使用的iso镜像

1
$ dd if=/dev/cdrom of=RHEL-server-7.0-x86_64-LinuxProbe.Com.iso

考虑到有些读者会纠结bs块大小与count块个数的关系,只要能满足需求,可随意组合搭配方式

  1. file命令

file命令用于查看文件的类型,格式为“file 文件名”。

在Linux系统中,由于文本、目录、设备等所有这些一切都统称为文件,而我们又不能单凭后缀就知道具体的文件类型,这时就需要使用file命令来查看文件类型了。

1
2
3
4
$ file anaconda-ks.cfg 
anaconda-ks.cfg: ASCII text
$ file /dev/sda
/dev/sda: block special

打包压缩与搜索命令

  1. tar命令

tar命令用于对文件进行打包压缩或解压,格式为“tar [选项] [文件]”。

在Linux系统中,常见的文件格式比较多,其中主要使用的是.tar或.tar.gz或.tar.bz2格式,我们不用担心格式太多而记不住,其实这些格式大部分都是由tar命令来生成的

参数 作用
-c 创建压缩文件
-x 解开压缩文件
-t 查看压缩包内有哪些文件
-z 用Gzip压缩或解压
-j 用bzip2压缩或解压
-v 显示压缩或解压的过程
-f 目标文件名
-p 保留原始的权限与属性
-P 使用绝对路径来压缩
-C 指定解压到的目录

首先,-c参数用于创建压缩文件,-x参数用于解压文件,因此这两个参数不能同时使用。
其次,-z参数指定使用Gzip格式来压缩或解压文件,-j参数指定使用bzip2格式来压缩或解压文件。用户使用时则是根据文件的后缀来决定应使用何种格式参数进行解压。
在执行某些压缩或解压操作时,可能需要花费数个小时,如果屏幕一直没有输出,您一方面不好判断打包的进度情况,另一方面也会怀疑电脑死机了,因此非常推荐使用-v参数向用户不断显示压缩或解压的过程。
-C参数用于指定要解压到哪个指定的目录。
-f参数特别重要,它必须放到参数的最后一位,代表要压缩或解压的软件包名称。刘遄老师一般使用“tar -czvf 压缩包名称.tar.gz 要打包的目录”命令把指定的文件进行打包压缩;相应的解压命令为“tar -xzvf 压缩包名称.tar.gz”。

  1. grep命令

grep命令用于在文本中执行关键词搜索,并显示匹配的结果,格式为“grep [选项] [文件]”。

参数 作用
-b 将可执行文件(binary)当作文本文件(text)来搜索
-c 仅显示找到的行数
-i 忽略大小写
-n 显示行号
-v 反向选择——仅列出没有“关键词”的行。

grep命令是用途最广泛的文本搜索匹配工具,虽然有很多参数,但是大多数基本上都用不到。我们在这里只讲两个最最常用的参数:-n参数用来显示搜索到信息的行号;-v参数用于反选信息(即没有包含关键词的所有信息行)。这两个参数几乎能完成您日后80%的工作需要,至于其他上百个参数,即使以后在工作期间遇到了,再使用man grep命令查询也来得及。

1
2
3
4
5
6
7
$ grep /sbin/nologin /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
  1. find命令

find命令用于按照指定条件来查找文件,格式为“find [查找路径] 寻找条件 操作”。

本书中曾经多次提到“Linux系统中的一切都是文件”,接下来就要见证这句话的分量了。在Linux系统中,搜索工作一般都是通过find命令来完成的,它可以使用不同的文件特性作为寻找条件(如文件名、大小、修改时间、权限等信息),一旦匹配成功则默认将信息显示到屏幕上。

参数 作用
-name 匹配名称
-perm 匹配权限(mode为完全匹配,-mode为包含即可)
-user 匹配所有者
-group 匹配所有组
-mtime -n +n 匹配修改内容的时间(-n指n天以内,+n指n天以前)
-atime -n +n 匹配访问文件的时间(-n指n天以内,+n指n天以前)
-ctime -n +n 匹配修改文件权限的时间(-n指n天以内,+n指n天以前)
-nouser 匹配无所有者的文件
-nogroup 匹配无所有组的文件
-newer f1 !f2 匹配比文件f1新但比f2旧的文件
—type b/d/c/p/l/f 匹配文件类型(后面的字幕字母依次表示块设备、目录、字符设备、管道、链接文件、文本文件)
-size 匹配文件的大小(+50KB为查找超过50KB的文件,而-50KB为查找小于50KB的文件)
-prune 忽略某个目录
-exec …… {}\; 后面可跟用于进一步处理搜索结果的命令(下文会有演示)

这里需要重点讲解一下-exec参数重要的作用。这个参数用于把find命令搜索到的结果交由紧随其后的命令作进一步处理,它十分类似于第3章将要讲解的管道符技术,并且由于find命令对参数的特殊要求,因此虽然exec是长格式形式,但依然只需要一个减号(-)。

根据文件系统层次标准(Filesystem Hierarchy Standard)协议,Linux系统中的配置文件会保存到/etc目录中(详见第6章)。如果要想获取到该目录中所有以host开头的文件列表,可以执行如下命令:

1
$ find /etc -name "host*" -print

如果要在整个系统中搜索权限中包括SUID权限的所有文件(详见第5章),只需使用-4000即可:

1
$ find / -perm -4000 -print

进阶实验:在整个文件系统中找出所有归属于linuxprobe用户的文件并复制到/root/findresults目录。

该实验的重点是“-exec {} \;”参数,其中的{}表示find命令搜索出的每一个文件,并且命令的结尾必须是“\;”。完成该实验的具体命令如下:

1
$ find / -user linuxprobe -exec cp -a {} /root/findresults/ \;

ubuntu上安装软件方法

发表于 2018-04-19 | 分类于 linux

ubuntu上安装软件方法

总结下来就是有3种

  1. 使用sudo apt-get
1
2
3
4
5
apt-cache search package 搜索包
apt-cache show package 获取包的相关信息,如说明、大小、版本等

sudo apt-get install package 安装包
sudo apt-get remove package 删除包
  1. 使用dpkg
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. dpkg -i <package.deb>
安装一个 Debian 软件包,如你手动下载的文件,(其中-i等价于--install)

2. dpkg -c <package.deb>
列出<package.deb> 的内容中包含的文件结构(其中-c等价于--contents)

3. dpkg - I<package.deb>
从<package.deb> 中提取包裹信息的详细信息,包括软件名称. 版本以及大小等(其中-I等价于--info)

4. dpkg -r <package>
移除一个已安装的包裹(软件名称可通过dpkg -I命令查看,其中-r等价于--remove)

5. dpkg -P <package>
完全清除一个已安装的包裹。和 remove 不同的是,remove 只是删掉数据和可执行文件,purge 另外还删除所有的配制文件。
  1. 解压缩.tar.gz的直接运行
1
2
3
4
5
6
7
8
解压:$ tar -xzvf <FileName.tar.gz>
压缩:$ tar -czvf <FileName.tar.gz DirName>
-c: 建立压缩档案
-x:解压

-z:有gzip属性的
-v:显示所有过程
-f: 使用档案名字,切记,这个参数是最后一个参数,后面只能接档案名。

apt-get

常用的APT命令参数
  apt-cache search package 搜索包
  apt-cache show package 获取包的相关信息,如说明、大小、版本等

  sudo apt-get install package 安装包
  sudo apt-get install package — reinstall 重新安装包
  sudo apt-get -f install 修复安装”-f = —fix-missing”
  sudo apt-get remove package 删除包
  sudo apt-get remove package — purge 删除包,包括删除配置文件等
  sudo apt-get update 更新源
  sudo apt-get upgrade 更新已安装的包
  sudo apt-get dist-upgrade 升级系统
  sudo apt-get dselect-upgrade 使用 dselect 升级

  apt-cache depends package 了解使用依赖
  apt-cache rdepends package 是查看该包被哪些包依赖

  sudo apt-get build-dep package 安装相关的编译环境
  apt-get source package 下载该包的源代码
  sudo apt-get clean && sudo apt-get autoclean 清理无用的包
  sudo apt-get check 检查是否有损坏的依赖
  其中:
  1 有sudo的表示需要管理员特权!
  2 在UBUNTU中命令后面参数为短参数是用“-”引出,长参数用“—”引出
  3 命令帮助信息可用man 命令的方式查看或者
  命令 -H(—help)方式查看
  4 在man命令中需要退出命令帮助请按“q”键!!
  选项 含义 作用
  sudo -h Help 列出使用方法,退出。
  sudo -V Version 显示版本信息,并退出。
  sudo -l List 列出当前用户可以执行的命令。只有在sudoers里的用户才能使用该选项。
  sudo -u username|#uid User 以指定用户的身份执行命令。后面的用户是除root以外的,可以是用户名,也可以是#uid。
  sudo -k Kill 清除“入场卷”上的时间,下次再使用sudo时要再输入密码。
  sudo -K Sure kill 与-k类似,但是它还要撕毁“入场卷”,也就是删除时间戳文件。
  sudo -b command Background 在后台执行指定的命令。
  sudo -p prompt command Prompt 可以更改询问密码的提示语,其中%u会代换为使用者帐号名称,%h会显示主机名称。非常人性化的设计
  sudo -e file Edit 不是执行命令,而是修改文件,相当于命令sudoedit。

dpkg

1. dpkg -i <package.deb>
安装一个 Debian 软件包,如你手动下载的文件,(其中-i等价于--install)

2. dpkg -c <package.deb>
列出<package.deb> 的内容中包含的文件结构(其中-c等价于--contents)

3. dpkg - I<package.deb>
从<package.deb> 中提取包裹信息的详细信息,包括软件名称. 版本以及大小等(其中-I等价于--info)

4. dpkg -r <package>
移除一个已安装的包裹(软件名称可通过dpkg -I命令查看,其中-r等价于--remove)

5. dpkg -P <package>
完全清除一个已安装的包裹。和 remove 不同的是,remove 只是删掉数据和可执行文件,purge 另外还删除所有的配制文件。

6. dpkg -L <package>
列出 <package> 安装的软件包安装的所有文件(软件名称可通过dpkg -I命令查看,其中-L等价于--listfiles)

7. dpkg -l <package>
查看<package>软件包的信息(软件名称可通过dpkg -I命令查看,其中-l等价于--list)

8. dpkg -s <package>
显示已安装包裹的详细信息。同时请看 apt-cache 显示 Debian 存档中的包裹信息,以及 dpkg -I 来显示从一个 .deb 文件中提取的包裹信息。(软件名称可通过dpkg -I命令查看,其中-s等价于--status)

9. dpkg-reconfigure <package>
重新配制一个已经安装的包裹,如果它使用的是 debconf (debconf 为包裹安装提供了一个统一的配制界面)。

.tar.gz

  1. .tar.gz的先tar -x

tar
-c: 建立压缩档案
-x:解压
-t:查看内容
-r:向压缩归档文件末尾追加文件
-u:更新原压缩包中的文件
这五个是独立的命令,压缩解压都要用到其中一个,可以和别的命令连用但只能用其中一个。下面的参数是根据需要在压缩或解压档案时可选的。

-z:有gzip属性的
-j:有bz2属性的
-Z:有compress属性的
-v:显示所有过程
-O:将文件解开到标准输出

下面的参数-f是必须的

-f: 使用档案名字,切记,这个参数是最后一个参数,后面只能接档案名。

$ tar -cf all.tar *.jpg
这条命令是将所有.jpg的文件打成一个名为all.tar的包。-c是表示产生新的包,-f指定包的文件名。

$ tar -rf all.tar *.gif
这条命令是将所有.gif的文件增加到all.tar的包里面去。-r是表示增加文件的意思。

$ tar -uf all.tar logo.gif
这条命令是更新原来tar包all.tar中logo.gif文件,-u是表示更新文件的意思。

$ tar -tf all.tar
这条命令是列出all.tar包中所有文件,-t是列出文件的意思

$ tar -xf all.tar
这条命令是解出all.tar包中所有文件,-x是解开的意思

压缩
tar –cvf jpg.tar .jpg //将目录里所有jpg文件打包成tar.jpg
tar –czf jpg.tar.gz
.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用gzip压缩,生成一个gzip压缩过的包,命名为jpg.tar.gz
tar –cjf jpg.tar.bz2 .jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用bzip2压缩,生成一个bzip2压缩过的包,命名为jpg.tar.bz2
tar –cZf jpg.tar.Z
.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用compress压缩,生成一个umcompress压缩过的包,命名为jpg.tar.Z
rar a jpg.rar .jpg //rar格式的压缩,需要先下载rar for Linux
zip jpg.zip
.jpg //zip格式的压缩,需要先下载zip for linux

解压
tar –xvf file.tar //解压 tar包
tar -xzvf file.tar.gz //解压tar.gz
tar -xjvf file.tar.bz2 //解压 tar.bz2
tar –xZvf file.tar.Z //解压tar.Z
unrar e file.rar //解压rar
unzip file.zip //解压zip

总结

  1. *.tar 用 tar –xvf 解压
  2. *.gz 用 gzip -d或者gunzip 解压
  3. *.tar.gz和*.tgz 用 tar –xzvf 解压
  4. *.bz2 用 bzip2 -d或者用bunzip2 解压
  5. *.tar.bz2用tar –xjf 解压
  6. *.Z 用 uncompress 解压
  7. *.tar.Z 用tar –xZf 解压
  8. *.rar 用 unrar e解压
  9. *.zip 用 unzip 解压

一般使用tar -xzvf tar -czvf

01-.tar格式
解包:[*******]$ tar xvf FileName.tar
打包:[*******]$ tar cvf FileName.tar DirName(注:tar是打包,不是压缩!)
02-.gz格式
解压1:[*******]$ gunzip FileName.gz
解压2:[*******]$ gzip -d FileName.gz
压 缩:[*******]$ gzip FileName

03-.tar.gz格式
解压:[*******]$ tar -zxvf FileName.tar.gz
压缩:[*******]$ tar -zcvf FileName.tar.gz DirName

04-.bz2格式
解压1:[*******]$ bzip2 -d FileName.bz2
解压2:[*******]$ bunzip2 FileName.bz2
压 缩: [*******]$ bzip2 -z FileName

05-.tar.bz2格式
解压:[*******]$ tar jxvf FileName.tar.bz2
压缩:[*******]$ tar jcvf FileName.tar.bz2 DirName

06-.bz格式
解压1:[*******]$ bzip2 -d FileName.bz
解压2:[*******]$ bunzip2 FileName.bz

07-.tar.bz格式
解压:[*******]$ tar jxvf FileName.tar.bz

08-.Z格式
解压:[*******]$ uncompress FileName.Z
压缩:[*******]$ compress FileName

09-.tar.Z格式
解压:[*******]$ tar Zxvf FileName.tar.Z
压缩:[*******]$ tar Zcvf FileName.tar.Z DirName

10-.tgz格式
解压:[*******]$ tar zxvf FileName.tgz

11-.tar.tgz格式
解压:[*******]$ tar zxvf FileName.tar.tgz
压缩:[*******]$ tar zcvf FileName.tar.tgz FileName

12-.zip格式
解压:[*******]$ unzip FileName.zip
压缩:[*******]$ zip FileName.zip DirName

13-.lha格式
解压:[*******]$ lha -e FileName.lha
压缩:[*******]$ lha -a FileName.lha FileName

14-.rar格式
解压:[*******]$ rar a FileName.rar
压缩:[*******]$ rar e FileName.rar
rar请到:下载!
解压后请将rar_static拷贝到/usr/bin目录(其他由$PATH环境变量
指定的目录也行):[*******]$ cp rar_static /usr/bin/rar

这样的要在把东西解压到/usr/local中 然后

1
2
3
4
5
tar  -xzvf node-v5.10.1-linux-x64.tar.gz  /usr/local/
cd /usr/local/
sudo mv node-v5.10.1-linux-x64/ nodejs
sudo ln -s /usr/local/nodejs/bin/node /usr/local/bin
sudo ln -s /usr/local/nodejs/bin/npm /usr/local/bin

还有一种

1
2
3
4
5
6
7
8
9
10
11
12
下载完安装包,并解压 tgz(以下演示的是 64 位 Linux上的安装) 。

curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.0.6.tgz # 下载
tar -zxvf mongodb-linux-x86_64-3.0.6.tgz # 解压

mv mongodb-linux-x86_64-3.0.6/ /usr/local/mongodb # 将解压包拷贝到指定目录

MongoDB 的可执行文件位于 bin 目录下,所以可以将其添加到 PATH 路径中:就是全局环境变量.window上那种

export PATH=<mongodb-install-directory>/bin:$PATH
export PATH=/usr/local/mongodb/bin:$PATH
<mongodb-install-directory> 为你 MongoDB 的安装路径。如本文的 /usr/local/mongodb 。

上面的重启就没了 怎么永久
https://www.cnblogs.com/lihao-blog/p/6945040.html
source /etc/profile后只在一个终端中有效
https://www.cnblogs.com/tomato0906/articles/6048383.html
https://bbs.deepin.org/forum.php?mod=viewthread&tid=143895

http://liuzhijun.iteye.com/blog/1744465

vscode上插件

发表于 2018-04-18 | 分类于 前端

vscode 上插件

setting sync

Settings Sync 这个插件可以通过 github 上面的 gist 来同步你的 vscode 的配置包括插件,自定义按键设置。还能够分享给别人使用。主要用在换电脑.

  1. 首先当然是下载 vscode 然后安装 Setting Sync

1

  1. 按 ctrl+shift+p 填入 sync 可以看到所有命令

2

  1. 然后选择 update 的 快捷键是 alt+shift+u

  2. 会弹出一个 github 的登录页,登录有进入 Developer settings => personal access token.这里设置好 gist, 然后会生成一个 token

3

  1. 再按下 alt+shift+u 就输入好了,最后会上传上去,然后就是得到一个 token 和 gist ID .

  2. 使用备份就是下载好 vscode 之后, 安装 Setting Sync, 然后按 ctrl+shif+p 搜 sync 选 download.(快捷键 alt+shift+d 当然同上),然后输入 token 在 gist ID . 插件下载要等一会

  3. 不行的话 reset 下 有个 Reset Extension Setting

总结下:

  1. 先去自己的 github 中的 personal access token 中获取 token
  2. 在使用 alt+shift+u 上传,这里使用 token,得到 gist ID
  3. 使用这个备份,alt+shift+d,一次输入 token 和 gist ID, 然后就是等待下载咯.

比如
GitHub Token: 293f9e53416c8ae5xx2c6a6e69604125fe80f3e8 自己生成一个, 上传更新才用到
GitHub Gist: b7e3625d05f1b3b31cb33180254a1e7d 这里才是下载更新用到的
GitHub Gist Type: Secret

数据结构中的常用算法总结

发表于 2018-04-15 | 分类于 前端

数据结构中的常用算法总结

首先介绍4个可视化数据结构的网址,推荐度从前往后

  1. visualgo
  2. sorting
  3. Data Structure Visualizations
  4. algo-visualizer

书籍

  1. Algorithms, 4th Edition
    • algorithmsSedgewick
    • assignment
  2. topal

排序 sort

  1. insert
  2. 折半
  3. shell
  4. bubble
  5. quick
  6. select
  7. heap
  8. merge
  9. 基数

Insertion Sort

ALGORITHM

1
2
3
4
5
for i = 1:n-1,
for (j = i; 1 <= j and a[j-1] > a[j]; j--)
swap a[j-1, j]
→ invariant: a[1..i] is sorted
end

注解: 默认数组a下标就是1开始. k从i开始,例如一开始就是从第2个元素起步a[2]和第一个元素a[1]比较, 一趟比较完后k随着i++进行下一趟比较, i是用来确定一趟的最后一个元素位置. k>1是说最后比较只会是a[1] a[2] 不会a[0] a[1]因为不存在a[0], 下标从1开始, 最后一趟是从a[n-1] a[n]开始比较.

注意这个不需要一个数组 用来特别往后移动.

外层循环记录着每趟起始和终止位置,代表趟数;内层是比较方式. 要根据可视化的过程来决定外层怎么写. 比如select的 是小的放最前还是大的放最外层不一样.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/* 
插入排序
基本思想:将元素逐个添加到已经排好序的数组中去。
平均时间复杂度O(n^2)
最好时间复杂度O(n)
最坏时间复杂度O(n^2)
空间复杂度O(1)
*/
void insert_sort (int a[], int n)
{

for(int i = 1; i <= n-1; i++){//假设第0个已在正确位置,从第一个开始插入
//第i趟插入需要在[0,i-1]中从后往前找到i的合适位置
for ( int j = i; 1 <= j && a[j-1] > a[j]; j--){
swap(a[j-1],a[j]);
}
}
}

void insert_sort (int a[], int n) {
//要进行n-1趟插入
for (int i = 1; i <= n-1; i++) {
for (int j = i; 0 < j && a[j-1] > a[j]; j--) { // 0<=j 不对哦 j是从1开始
swap(a[j-1], a[j]);
}
}
}

void insert_sort (int a[]) {
int n = a.length;
for (int i = 1; i <= n-1; i++) {
for (int j = i; 1 <= j && a[j-1] < a[j]; j--) {
swap(a[j-1], a[j]);
}
}
}

//在内循环中将较大的元素一次性向右移动而不是交换两个元素,这样访问数组的次数将减半 。其代码如下:
真的么?
void sort(int[] data) {
int size = data.length;
for (int i = 1; i < size; i++) {
int temp = data[i];
int index = 0; //要插入的位置
for (int j = i; j >= 1; j--) {
if (temp < data[j-1]) {
data[j] = data[j-1];
}else {
index = j;
break;
}
}
data[index] = temp;
}
}

DISCUSSION

尽管存在最坏情况(worst-case)是O(n2), 也就是逆序(reversed)的情况下, insertion sort 在data几乎有序(这个叫adaptive)和问题size很小(这个叫low overhead 低开销)的情况下还是一个很好的选择.

还有他是stable的, insertion sort is often used as the recursive base case (when the problem size is small) for higher overhead 分治divide-and-conquer sorting algorithms, such as merge sort or quick sort.

PROPERTIES

  • Stable
  • O(1) extra space
  • O(n2) comparisons and swaps
  • Adaptive: O(n) time when nearly sorted
  • Very low overhead

二分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
将直接插入排序中寻找a[i]插入位置的方法改为二分查找,然后再一次性向右移动元素。

public void sort(int[] a) {
int n = a.length;
for (int i = 1; i <= n-1; i++) {
int num = binaryFind(a, a[i], 0, i-1);
int temp = a[i];
//num后的元素向后移动
for (int j = i; num < j; j--) {
a[j] = a[j-1];
}
a[num] = temp;
}
}

//找出元素应在数组中插入的位置
public int binaryFind(int[] data, int temp, int down, int up) {
if(up<down || up>data.length || down<0) {
System.out.println("下标错误");
}
if(temp < data[down]) return down;
if(temp > data[up]) return up+1;
int mid = (up-down)/2 + down;
if(temp == data[mid]) {
return mid + 1;
}else if(temp < data[mid]) {
up = mid-1;
}else if(temp > data[mid]) {
down = mid+1;
}
return binaryFind(data,temp, down, up);
}

Shell Sort

ALGORITHM

1
2
3
4
5
6
7
h = 1
while h < n, h = 3*h + 1
while h > 0,
h = h / 3
for k = 1:h, insertion sort a[k:h:n]
→ invariant: each h-sub-array is sorted
end

注解: 首先按n大小计算递增序列,这里是按1 4 13 40 来, 然后从最大的h开始. 注意有一步h = h / 3 这就是比n小的最大的h, 因为前一个while跳出就是h>n的情况,还得返回去. 然后a[1,4] a[2, 5] 这样按insertion sort比较 a[k:h:n] 是从k到n以k为间隔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 
5、希尔排序
基本思想:将无序数组分成若干个子序列,子序列不是逐段分割的,而是相隔特定增量。对各个子序列进行插入排序。
然后再选择一个更小的增量,再将数组分割成多个子序列进行排序。最后选择增量为1,即使用直接插入排序,使最终数组成为有序数组。
平均时间复杂度O(n^1.3)
最好时间复杂度O(n)
最坏时间复杂度O(n^2)
空间复杂度O(1)
*/
void shell_sort(int a[], int n)
{
int gap;
for( gap = n/2; gap > 0; gap /= 2){
for(int i = gap; i < n; i ++){
for(int j = i - gap; j >= 0 && a[j] < a[j + gap]; j-=gap){//每个元素与自己组内的元素进行插入排序
swap(a[j], a[j + gap]);
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void shell_sort(int a[], int n)  
{
int h = 1;
while (h < n) {
h = 3*h +1;
}
while (h>0) {
h = h/3;
for ( int k = 0; k <= h-1; k++) { //根据增量分成若干组 每个h对应趟数
// insert_sort的理解
for(int i = k+h; i <= n-1; i=i+h){//假设第0个已在正确位置,从第一个开始插入
for ( int j = i; k+h <= j && a[j-h] > a[j]; j=j-h){ //不能加 k<= j 用k+h <= j也对
swap(a[j-h],a[j]);
}
}
}
//for ( int k = 0; k <= h-1; k++) { //根据增量分成若干组
// insert_sort()
// for(int i = k+h; i <= n-1; i=i+h){//假设第0个已在正确位置,从第一个开始插入
// for ( int j = i; k <= j && a[j-h] > a[j]; j=j-h){
// swap(a[j-h],a[j]);
// }
// }
//}
}
}


//核心算法,增量序列 1 4 13 ....(3*h+1)
void sort(int[] a) {
int n = a.length;
int h = 1;
while(h < n/3) // 注意这里变了
h = 3*h + 1;
while(h > 0 ) {
for(int i = h; i <= n-1; i++) { //这种事按4的比完,再按5比完.而不是048这样. 8的这种后面会比到的
for(int j = i; h <= j && a[j-h] > a[j]; j = j-h) {
swap(a[j-h], a[j]);
}
}
h = h/3;
}

}

DISCUSSION

shell sort最坏情况时间复杂度(The worse-case time complexity)依赖于递增序列(the increment sequence). 这里所使用的the increments 1 4 13 40 121…, 时间复杂度是 O(n3/2). For other increments, time complexity is known to be O(n4/3) and even O(n·lg2(n)). 既不存在时间复杂度的紧上界,也不知道最佳增量序列。

Because shell sort is based on insertion sort, shell sort inherits insertion sort’s adaptive properties. The adapation is not as dramatic because shell sort requires one pass through the data for each increment, but it is significant. For the increment sequence shown above, there are log3(n) increments, so the time complexity for nearly sorted data is O(n·log3(n)).

Because of its low overhead, relatively simple implementation, adaptive properties, and sub-quadratic time complexity, shell sort may be a viable alternative to the O(n·lg(n)) sorting algorithms for some applications when the data to be sorted is not very large.

PROPERTIES

  • Not stable
  • O(1) extra space
  • O(n3/2) time as shown (see below)
  • Adaptive: O(n·lg(n)) time when nearly sorted

Bubble Sort

ALGORITHM

1
2
3
4
5
6
7
8
9
for i = n-1:0,
swapped = false
for j = 1:i; n--
if a[j-1] > a[j],
swap a[j-1,j]
swapped = true
→ invariant: a[i..n-1] in final position
break if not swapped
end

注意 Bubble sort每趟排序完后最后一个元素到位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* 
冒泡排序
基本思想: 不断比较相邻的两个数,让较大的数不断地往后移。经过一番比较,就选出了最大的数。经过第二轮比较,就选出了次大的数。以此类推。
那么对于大小为N的数组,需要N-1轮比较。
平均时间复杂度O(N^2)
最好情况O(N)
最坏情况O(N^2)
空间复杂度O(1)
*/
void bubble_sort(int a[],int n)
{
//要进行N-1轮比较, 这里记录趟数
for(int i = 0; i <= n-2; i++ )//[0,n-2]恰好n-1轮比较
{
bool is_sorted = true; // 是否交换的
for(int j = 1; j <= n-1-i; j++)//已经排好序的最后i个不用比较,要比较的数的个数为n-i个,那么需要比较的次数为n-i-1
{
if(a[j-1] > a[j]){
is_sorted = false;
swap(a[j-1],a[j]);
}
}
if(is_sorted)//如果没有发生交换,说明已经排好序了,提前退出循环,所以最好情况下时间复杂度为O(N)
break;
}
}

void bubble_sort(int a[],int n)
{
for(int i = n-1; 1 <= i; i-- ) // 这个好理解
{
bool is_sorted = true; // 是否交换的标志
for(int j = 1; j <= i; j++)
{
if(a[j-1] > a[j]){
is_sorted = false;
swap(a[j-1],a[j]);
}
}
if(is_sorted) break;
}
}

DISCUSSION

Bubble sort has many of the same properties as insertion sort, but has slightly higher overhead. In the case of nearly sorted data, bubble sort takes O(n) time, but requires at least 2 passes through the data (whereas insertion sort requires something more like 1 pass).

PROPERTIES

  • Stable
  • O(1) extra space
  • O(n2) comparisons and swaps
  • Adaptive: O(n) when nearly sorted

Quick Sort

ALGORITHM

1
2
3
4
5
6
7
8
9
10
11
12
_# choose pivot_
swap a[1,rand(1,n)]

_# 2-way partition_
k = 1
for i = 2:n, if a[i] < a[1], swap a[++k,i]
swap a[1,k]
_→ invariant: a[1..k-1] < a[k] <= a[k+1..n]_

_# recursive sorts_
sort a[1..k-1]
sort a[k+1,n]

注意 Quick sort有好几种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/* 
3、快速排序
基本思想:采用分而治之的思想,将要排序的数分成左右两部分,其中一部分的数据比key小,另一部分数据比key大。然后将所分得的两部分数据进行同样的划分。重复执行以上的划分操作。
平均时间复杂度O(Nlog2(N))
最好情况O(Nlog2(N))
最坏情况O(N^2)
空间复杂度O(Nlog2(N))
*/
int partition(int arr[], int low, int high)//返回划分的中间值
{
int key;
key = arr[low];//相当于在索引low处挖坑,下一个就要找合适的据来填坑
if (low > high) return;

while(low < high)
{
while(low < high && key <= arr[high]){
high --;
}
if(low < high)
arr[low ++] = arr[high];//找到合适的数据填到了lo坑,但是形成了high坑,继续找合适的数据
while( low < high && arr[low] <= key)
low ++;
if( low < high)
arr[high --] = arr[low];//low又成了坑

arr[low] = key;//将key填到这个坑
return low;
}
}
void quick_sort(int num[], int low, int high)
{
int pos;
if(low < high){
pos = partition(num, low, high);
quick_sort(num, low, pos-1);
quick_sort(num, pos+1, high);
}
}

/*快速排序非递归版*/
void quicksort2(int num[], int low, int high)
{
int key = num[low];
stack<int> s;
if(low < high){
int pos = partition(num, low, high);
if(pos-1 > low){
s.push(pos - 1);
s.push(low);
}
if(pos+1 < high){
s.push(high);
s.push(pos + 1);
}
while(!s.empty()){
int l = s.top();
s.pop();
int r = s.top();
s.pop();
pos = partition(num, l, r);
if(pos - 1 > l){
s.push(pos - 1);
s.push(l);
}
if(pos + 1 < r){
s.push(r);
s.push(pos + 1);
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void quick_sort(int arr[],int low,int high)
{
int i=low,j=high,key=arr[low];//i j 是需要的 low high用来递归 不能动

if(i > j)
return ;

while(i < j)
{
while(i < j && key <= arr[j]){
j --;
}
if(i < j)
arr[i ++] = arr[j];//找到合适的数据填到了low坑,但是形成了high坑,继续找合适的数据
while( i < j && arr[i] <= key)
i ++;
if( i < j)
arr[j --] = arr[i];//low又成了坑
}
arr[i] = key;//将key填到这个坑 一趟后

quick_sort(arr,low,i-1);
quick_sort(arr,i+1,high);
}

DISCUSSION

When carefully implemented, quick sort is robust and has low overhead. When a stable sort is not needed, quick sort is an excellent general-purpose sort – although the 3-way partitioning version should always be used instead.

The 2-way partitioning code shown above is written for clarity rather than optimal performance; it exhibits poor locality, and, critically, exhibits O(n2) time when there are few unique keys. A more efficient and robust 2-way partitioning method is given in Quicksort is Optimal by Robert Sedgewick and Jon Bentley. The robust partitioning produces balanced recursion when there are many values equal to the pivot, yielding probabilistic guarantees of O(n·lg(n)) time and O(lg(n)) space for all inputs.

With both sub-sorts performed recursively, quick sort requires O(n) extra space for the recursion stack in the worst case when recursion is not balanced. This is exceedingly unlikely to occur, but it can be avoided by sorting the smaller sub-array recursively first; the second sub-array sort is a tail recursive call, which may be done with iteration instead. With this optimization, the algorithm uses O(lg(n)) extra space in the worst case.

PROPERTIES

  • Not stable
  • O(lg(n)) extra space (see discussion)
  • O(n2) time, but typically O(n·lg(n)) time
  • Not adaptive

Quick Sort 3 Way

ALGORITHM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
_# choose pivot_
swap a[n,rand(1,n)]

_# 3-way partition_
i = 1, k = 1, p = n
while i < p,
if a[i] < a[n], swap a[i++,k++]
else if a[i] == a[n], swap a[i,--p]
else i++
end
_→ invariant: a[p..n] all equal_
_→ invariant: a[1..k-1] < a[p..n] < a[k..p-1]_

_# move pivots to center_
m = min(p-k,n-p+1)
swap a[k..k+m-1,n-m+1..n]

_# recursive sorts_
sort a[1..k-1]
sort a[n-p+k+1,n]

DISCUSSION

The 3-way partition variation of quick sort has slightly higher overhead compared to the standard 2-way partition version. Both have the same best, typical, and worst case time bounds, but this version is highly adaptive in the very common case of sorting with few unique keys.

The 3-way partitioning code shown above is written for clarity rather than optimal performance; it exhibits poor locality, and performs more swaps than necessary. A more efficient but more elaborate 3-way partitioning method is given in Quicksort is Optimal by Robert Sedgewick and Jon Bentley.

When stability is not required, quick sort is the general purpose sorting algorithm of choice. Recently, a novel dual-pivot variant of 3-way partitioning has been discovered that beats the single-pivot 3-way partitioning method both in theory and in practice.

PROPERTIES

  • Not stable
  • O(lg(n)) extra space
  • O(n2) time, but typically O(n·lg(n)) time
  • Adaptive: O(n) time when O(1) unique keys

Selection Sort

ALGORITHM

1
2
3
4
5
6
7
8
for i = 0:n-1,
min = i
for j = i+1:n-1,
if a[min] > a[j], min = j
→ invariant: a[min] smallest of a[i..n-1]
swap a[i,min]
→ invariant: a[0..i] in final position
end

注意 每一趟可以选择最小的放到最前面,也可以选最大的放最后面. k使用存放临时极小值(极大值). 最后一趟比完才最后确定.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* 
选择排序
基本思想:首先,选出最小的数放在第一位,然后选择第二小的数,放在第二位;以此类推,直到所有的数从小到大排列.
那么,对于大小为N的数组需要N-1轮选择过程。第i轮选取第i小的数,请将其放在第i个位置上。
不稳定
平均时间复杂度O(N^2)
最好情况O(N^2)
最坏情况O(N^2)
空间复杂度O(1)
*/

void select_sort(int a[], int n)
{
for(int i = 0; i <= n-1-1; i++){//进行n-1轮选择,也就是i的取值为[0,n-2]
int min_index = i;
//记录第i小的数所在的索引
for(int j = i + 1; j <= n-1; j++){
if(a[min_index] > a[j]) //有点谐
min_index = j;
}
if(i != min_index){//根据记录的第i小的数的索引,找到了第i小的数。然后将该数放到其正确位置。也就是第i个位置。
swap(a[i] , a[min_index]);
}
}
}

void select_sort(int[] a) {
int n = a.length;
for(int i = 0; i <= n-1; i++) {
int min = i;
for(int j = i+1; j <= n-1; j++) {
if(a[j] < a[min])
min=j;
}
swap(a[i], a[min]);
}
}

DISCUSSION

从比较结果来看, 最好不要用selection sort should never be used. It does not adapt to the data in any way (notice that the four animations above run in lock step), 运行时间一直是平方项(quadratic).

但是有一个优点 selection sort 可以减少交换项数目. 可以应用于交换项cost很大的的情况.

PROPERTIES

  • Not stable
  • O(1) extra space
  • Θ(n2) comparisons
  • Θ(n) swaps
  • Not adaptive

Heap Sort

ALGORITHM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# heapify
for i = n/2:1, sink(a,i,n)
→ invariant: a[1,n] in heap order

# sortdown
for i = 1:n,
swap a[1,n-i+1]
sink(a,1,n-i)
→ invariant: a[n-i+1,n] in final position
end

# sink from i in a[1..n]
function sink(a,i,n):
# {lc,rc,mc} = {left,right,max} child index
lc = 2*i
if lc > n, return # no children
rc = lc + 1
mc = (rc > n) ? lc : (a[lc] > a[rc]) ? lc : rc
if a[i] >= a[mc], return # heap ordered
swap a[i,mc]
sink(a,mc,n)

注意完全二叉, 用的是数组,首先是建堆,然后再排序.
建堆(heapify)中确定大顶堆还是小顶堆. 从最后一个非叶子节点(n/2)开始比较,往上比较.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//堆排序:树形选择排序,将带排序记录看成完整的二叉树,第一步:建立初堆,第二步:调整堆
//第二步:调整堆
void HeapAdjust(int a[],int s,int n) //但习惯上用大顶堆
{
//调整为小根堆,从小到大
int rc=a[s];
for(int j=2*s;j<=n;j*=2)
{
if(j<n && a[j]>a[j+1])//判断左右子数大小,找小的
j++;
if(rc<=a[j])
break;
a[s]=a[j]; //小的放上去
s=j;
}
a[s]=rc; //大的放下来
}
//第一步:建初堆
void CreatHeap(int a[],int n)
{
//小根堆,从最后一个非叶子节点开始,根是1
for(int i=n/2;i>0;i--)
HeapAdjust(a,i,n);
}
//整合
void HeapSort(int a[],int n)
{
CreatHeap(a,n);//第一步,建立初堆
for(int i=n;i>1;i--)
{
int x=a[1];//堆顶与最后一个元素互换
a[1]=a[i];
a[i]=x;
HeapAdjust(a,1,i-1);
}
}
int main()
{
int n;
cin>>n;
int *a=new int[n+1];
for(int j=1;j<n;j++)//注意:这里是从1开始的
cin>>a[j];
HeapSort(a,n);
for(int i=1;i<n;i++)
cout<<a[i];
delete []a;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<stdio.h>

//  创建大堆顶,i为当节点,n为堆的大小
// 从第一个非叶子结点i从下至上,从右至左调整结构
// 从两个儿子节点中选出较大的来与父亲节点进行比较
// 如果儿子节点比父亲节点大,则进行交换
void CreatHeap(int a[], int i, int n) {

// 注意数组是从0开始计数,所以左节点为2*i+1,右节点为2*i+2
// 这里改了吧 应该从1开始好
for (; i >= 0; --i)
{
int left = i * 2 + 1; //左子树节点
int right = i * 2 + 2; //右子树节点
int j = 0;
//选出左右子节点中最大的
if (right < n) {
a[left] > a[right] ? j= left : j = right;
}
else
j = left;
//交换子节点与父节点
if (a[j] > a[i]) {
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
}

// 进行堆排序,依次选出最大值放到最后面
void HeapSort(int a[]) {
int n = a.length-1;
//初始化构造堆
CreatHeap(a, n/2-1, n);
  //交换第一个元素和最后一个元素后,堆的大小减1
for (int j = n-1; j >= 0; j--) {

//最后一个元素和第一个元素进行交换
int tmp = a[0];
a[0] = a[j];
a[j] = tmp;

int i = j / 2 - 1; //有必要-1么
CreatHeap(a, i, j);
}
}
int main() {
int a[] = { 10,6,5,7,12,8,1,3,11,4,2,9,16,13,15,14 };
int n = sizeof(a) / sizeof(int);
HeapSort(a);
printf("排序好的数组为:");
for (int l = 0; l < n; l++) {
printf("%d ", a[l]);
}
printf("\n");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void HeapAdjust(int a[],int s,int n)  //但习惯上用大顶堆
{
//调整为大根堆,
int rc = a[s];
for(int j = 2*s; j <= n; j*=2) // 有左子树,
{
if (j+1 <= n ) { //如果右子树存在
if(a[j] < a[j+1])//判断左右子数大小
j++;
} else if(rc >= a[j]) { //根和左子树比
break;
}
a[s] = a[j]; //大的放上去
s = j; // 继续往大的子树的子树找,保证当前堆是大顶堆
}
a[s] = rc; //小的放下来
}
//第一步:建初堆
void CreatHeap(int a[],int n)
{
//小根堆,从最后一个非叶子节点开始,根是1
for(int i = n/2; i > 0; i--)
HeapAdjust(a,i,n);
}
//整合
void HeapSort(int a[],int n)
{
CreatHeap(a,n);//第一步,建立初堆
for(int i = n-1 ; i >= 2; i--) //这里从n-1到2就可以了
{
swap(a[1], a[i]);
HeapAdjust(a,1,i-1);
}
}

DISCUSSION

Heap sort is simple to implement, performs an O(n·lg(n)) in-place sort, but is not stable.

The first loop, the Θ(n) “heapify” phase, puts the array into heap order. The second loop, the O(n·lg(n)) “sortdown” phase, repeatedly extracts the maximum and restores heap order.

The sink function is written recursively for clarity. Thus, as shown, the code requires Θ(lg(n)) space for the recursive call stack. However, the tail recursion in sink() is easily converted to iteration, which yields the O(1) space bound.

Both phases are slightly adaptive, though not in any particularly useful manner. In the nearly sorted case, the heapify phase destroys the original order. In the reversed case, the heapify phase is as fast as possible since the array starts in heap order, but then the sortdown phase is typical. In the few unique keys case, there is some speedup but not as much as in shell sort or 3-way quicksort.

PROPERTIES

  • Not stable
  • O(1) extra space (see discussion)
  • O(n·lg(n)) time
  • Not really adaptive

Merge Sort

ALGORITHM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# split in half
m = n / 2

# recursive sorts
sort a[1..m]
sort a[m+1..n]

# merge sorted sub-arrays using temp array
b = copy of a[1..m]
i = 1, j = m+1, k = 1
while i <= m and j <= n,
a[k++] = (a[j] < b[i]) ? a[j++] : b[i++]
→ invariant: a[1..k] in final position
while i <= m,
a[k++] = b[i++]
→ invariant: a[1..k] in final position
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/* 
6、归并排序:
基本思想:将待排序序列【0,n-1】看成是n个长度为1的有序序列,将相邻的有序表成对归并,得到n/2个长度为2的有序表。再次归并,得到n/4个长度为4的有序表。
依次类推,最后得到长度为n的1个有序表。
所以归并排序其实要做两件事:
1、先递归的分解数列,
2、再合并数列就完成了归并排序。

先来考虑如何合并?
每次合并过程中都要对两个有序的序列段进行合并,然后排序
待合并的两个有序序列段分别为 R[low, mid] 和 R[mid+1, high]
先将它们合并到一个暂存数组R2,合并完再将R2复制回R1中。
这样一次合并排序就完成了。

最好、最坏和平均时间复杂度都是O(nlogn),
空间复杂度是O(n)
*/
void merge(int a[], int low ,int mid, int high)
{
int tmp[];
int i,j,k;
i = low; //i 和 j是临时会动的 所以新定义一个变量
j = mid + 1;
k = 0;//k是存放临时合并数组的下表

while( i <= mid && j <= high){
if( a[i] < a[j]) // 小的元素放tmp
tmp[k++] = a[i++];
else
tmp[k++] = a[j++];
}
while( i <= mid)
tmp[k++] = a[i++];
while( j <= high)
tmp[k++] = a[i++];
//最后再复制回a
for(i = 0; i < k; i++ )
a[low+i] = tmp[i];//!!!!此处a是从low开始,tmp是从0开始。
}

//以上完整程序

void mergeSort (int a[]) {
sort (a, 0, a.length-1);
}

void sort (int a[], int left, int right) {
if (left >= right) return ;
int mid = (left+right) / 2; // (right - left)/2 + left
sort (a, left, mid);
sort (a, mid+1, right);
merge (a, left, mid, right);
print(a);
}


//改进 还有换一种自底向上的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。
首先考虑下如何将2个有序数列合并。这个非常简单,只要从比较2个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。

//将有序数组a[]和b[]合并到c[]中
void MemeryArray(int a[], int n, int b[], int m, int c[])
{
int i, j, k;

i = j = k = 0;
while (i < n && j < m)
{
if (a[i] < b[j])
c[k++] = a[i++];
else
c[k++] = b[j++];
}

while (i < n)
c[k++] = a[i++];

while (j < m)
c[k++] = b[j++];
}

DISCUSSION

Merge sort is very predictable. It makes between 0.5lg(n) and lg(n) comparisons per element, and between lg(n) and 1.5lg(n) swaps per element. The minima are achieved for already sorted data; the maxima are achieved, on average, for random data. If using Θ(n) extra space is of no concern, then merge sort is an excellent choice: It is simple to implement, and it is the only stable O(n·lg(n)) sorting algorithm. Note that when sorting linked lists, merge sort requires only Θ(lg(n)) extra space (for recursion).

Merge sort is the algorithm of choice for a variety of situations: when stability is required, when sorting linked lists, and when random access is much more expensive than sequential access (for example, external sorting on tape).

There do exist linear time in-place merge algorithms for the last step of the algorithm, but they are both expensive and complex. The complexity is justified for applications such as external sorting when Θ(n) extra space is not available.

PROPERTIES

  • Stable
  • Θ(n) extra space for arrays (as shown)
  • Θ(lg(n)) extra space for linked lists
  • Θ(n·lg(n)) time
  • Not adaptive
  • Does not require random access to data

基数排序

树中

堆说过了

HaiKu_OS

发表于 2018-03-17 | 分类于 多媒体
1…91011…14
Henry x

Henry x

this is description

133 日志
25 分类
135 标签
GitHub E-Mail
Links
  • weibo
© 2019 Henry x