Qeuroal's Blog

静幽正治

配置环境

安装 Hexo

接下来就需要安装 Hexo 了,这是一个博客框架,Hexo 官方还提供了一个命令行工具,用于快速创建项目、页面、编译、部署 Hexo 博客,所以在这之前我们需要先安装 Hexo 的命令行工具。 命令如下:

1
$ npm install -g hexo-cli

信息提前说明

配置文件说明

在 Hexo 中有两份主要的配置文件,其名称都是 _config.yml。 其中,一份位于*站点根目录下,主要包含 Hexo 本身的配置;另一份位于主题目录*下,这份配置由主题作者提供,主要用于配置主题相关的选项。

为了描述方便,在以下说明中,将前者称为 站点配置文件, 后者称为 主题配置文件

初始化 hexo

  1. 创建文件夹

    1
    $ mkdir io_github_qeuroal
  2. 初始化

    1
    2
    $ hexo init
    $ mkdir ~/Desktop/hexo && ls -al > ~/Desktop/hexo/hexo_init.info
  3. 将 Hexo 编译生成 HTML 代码

    1
    $ hexo generate
  4. 本地运行

    1
    $ hexo server
  5. 站点配置文件

    • 修改根目录下的 _config.yml 文件,找到 Site 区域,这里面可以配置站点标题 title、副标题 subtitle 等内容、关键字 keywords 等内容.

      例:

      1
      2
      3
      4
      5
      6
      # Site
      title: NightTeam
      subtitle: 一个专注技术的组织
      description: 涉猎的主要编程语言为 Python、Rust、C++、Go,领域涵盖爬虫、深度学习、服务研发和对象存储等。
      keywords: "Python, Rust, C++, Go, 爬虫, 深度学习, 服务研发, 对象存储"
      author: NightTeam
    • language 的字段设置为 zh-CN

配置主题

Pure 主题

使用 pure, 教程见这里的 《Hexo搭建个人博客并部署到Github》和《Hexo博客主题pure使用说明》

下载主题

  1. 下载

    1
    $ git clone https://github.com/cofess/hexo-theme-pure.git themes/pure
  2. 启用主题

    修改 站点配置文件

    1
    theme: pure

更新主题 (可选, 暂未使用过)

1
2
$ cd themes/pure
$ git pull

主题配置

导航菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 导航菜单
menu:
Home: .
Archives: archives # 归档
Categories: categories # 分类
Tags: tags # 标签
Repository: repository # github repositories
Books: books # 书单
Links: links # 友链
About: about # 关于

# 导航菜单图标(font awesome)
menu_icons:
enable: true # 是否启用菜单图标
home: fa-dashboard
archives: fa-delicious
categories: fa-folder
tags: fa-tags
repository: fa-code
books: fa-leanpub
links: fa-gg
about: fa-coffee

主题配置

1
2
3
4
5
6
7
8
9
10
11
# config
config:
skin: theme-black # 主题颜色 theme-blue theme-green theme-purple
layout: main-center # 布局方式 main-left main-center main-right
toc: true # 是否开启文章章节目录导航
menu_highlight: false # 是否开启当前菜单高亮显示
thumbnail: false # enable posts thumbnail, options: true, false
excerpt_link: Read More
#New
isNewTabLinks: true #是否链接打开新标签页
autoUnfold: true # 默认展开文章目录

页面显示

1
2
3
4
5
6
7
8
9
# Pagination
pagination:
number: false
prev:
alwayShow: true
next:
alwayShow: true
midSize: 2 # 当前页码左右到省略号显示的页码数,默认2,表现为 1 ... 4 5 6 7 8 ... 10
showContent: false # 页面文章小于2篇时显示文章内容

大图显示

1
2
# Fancybox
fancybox: false

捐赠

直接更改照片文件即可

修改为自己的名字

  • cofess<your name>

修改 example.com

全局搜索 example.com 将其替换成网站网址 Qeuroal.top

个人链接

1
2
3
4
5
6
links:
Github: https://github.com/Qeuroal
Blog: http://blog.Qeuroal.com
微博: http://weibo.com/Qeuroal
# 花瓣: http://huaban.com/Qeuroal
# Behance: https://www.behance.net/Qeuroal

个人标签

1
2
3
# My Personal Labels
labels:
- 后端

搜索 (未处理)

主题内置三种站内搜索方式:insight、swiftype、baidu

1
2
3
4
5
# Search
search:
insight: true # you need to install `hexo-generator-json-content` before using Insight Search
swiftype: # enter swiftype install key here
baidu: false # you need to disable other search engines to use Baidu search

文章启用目录索引

1
2
3
4
5
6
title: 文章标题
categories:
- 文章分类
tags:
- 文章标签
toc: true # 是否启用内容索引

分享

支持weibo,qq,qzone,wechat,tencent,douban,diandian,facebook,twitter,google,linkedin

1
2
3
4
5
6
# Share
# weibo,qq,qzone,wechat,tencent,douban,diandian,facebook,twitter,google,linkedin
share:
enable: true # 是否启用分享
sites: weibo,qq,wechat,facebook,twitter # PC端显示的分享图标
mobile_sites: weibo,qq,qzone # 移动端显示的分享图标

分类, 标签, 仓库, about等菜单的配置

/theme/pure/_source 下的所有文件复制到 /source

友情链接

复制theme/pure/_source/ 目录下links文件夹到blog path/source/ 目录下

在 hexo 目录下的 source 文件夹内创建一个名为 _data(禁止改名)的文件夹。

然后在文件内创建一个名为 links.yml 的文件,在其中添加相关数据即可。

单个友情链接的格式为:

1
2
3
4
Name:
link: http://example.com
avatar: http://example.com/avatar.png
desc: "这是一个描述"

添加多个友情链接,我们只需要根据上面的格式重复填写即可。

数学公式

Hexo默认使用 “hexo-renderer-marked” 引擎渲染网页,该引擎会把一些特殊的 markdown 符号转换为相应的 html 标签

解决方案

解决方案有很多,可以网上搜下,为了节省大家的时间,这里只提供亲身测试过的方法。

更换 Hexo 的 markdown 渲染引擎,hexo-renderer-markdown-it-plus 引擎替换默认的渲染引擎 hexo-renderer-marked 即可。

安装hexo-renderer-markdown-it-plus插件
1
2
npm un hexo-renderer-marked --save
npm i hexo-renderer-markdown-it-plus --save
配置

安装插件后,如果未正常渲染LaTeX数学公式,在博客配置文件_config.yml中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
markdown_it_plus:
highlight: true
html: true
xhtmlOut: true
breaks: true
langPrefix:
linkify: true
typographer:
quotes: “”‘’
plugins:
- plugin:
name: markdown-it-katex
enable: true
- plugin:
name: markdown-it-mark
enable: false
文章启用mathjax
1
2
title: Hello World
mathjax: true

文章简介内容不全显式

1
showContent: false # 页面文章小于2篇时显示文章内容

删除部分文件

  • themes/pure/_config.yml.example
  • themes/pure/README.cn.md
  • themes/pure/README.md

关闭订阅

  •  # rss
     # rss: /atom.xml
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    - ```yml
    social:
    links:
    github: https://github.com/Qeuroal
    weibo: http://weibo.com/Qeuroal
    twitter: https://twitter.com/Qeuroal
    # facebook: /
    # dribbble: /
    behance: https://www.behance.net/Qeuroal
    # rss: atom.xml

更改头像

1
2
3
profile:
# avatar: images/avatar.jpg
avatar: images/avatar.png

更改个人简介

文件: /source/about/index.md

安装插件

hexo-deployer-git (must, common)

安装
1
$ npm install hexo-deployer-git --save
站点配置(根目录) config.yml

_config.yml

1
2
3
4
5
6
# Deployment
## Docs: https://hexo.io/docs/deployment.html
deploy:
type: git
repo: {git repo ssh address}
branch: master

hexo-generator-feed (normal, 但不推荐订阅)

Github:https://github.com/hexojs/hexo-generator-feed

简介

RSS的生成插件,你可以在配置显示你站点的RSS,文件路径\atom.xml。

安装:
1
$ npm install hexo-generator-feed --save
配置:

在博客配置文件_config.yml中添加

1
2
3
4
5
6
feed:
type: atom
path: atom.xml
limit: 20
hub:
content:
  • type - Feed type. (atom/rss2)
  • path - Feed path. (Default: atom.xml/rss2.xml)
  • limit - Maximum number of posts in the feed (Use 0 or false to show all posts)
  • hub - URL of the PubSubHubbub hubs (Leave it empty if you don’t use it)
  • content - (optional) set to ‘true’ to include the contents of the entire post in the feed.

hexo-generator-json-content (must)

Github

https://github.com/alexbruno/hexo-generator-json-content

简介

用于生成静态站点数据,提供搜索功能的数据源。

安装
1
$ npm install hexo-generator-json-content --save
配置

在博客配置文件_config.yml中添加

1
2
3
4
5
6
7
jsonContent:
ignore:
- path/to/a/page
- url/to/one/post
- an-entire-category
- specific.file
- .ext # a file extension

在github上用hexo搭建了自己的博客,用typora写完一篇博文,满怀欣喜地deploy之后发现图片加载失败…真的很吐血。去网上一搜索,嗯,好像不少人都遇到了这个问题,解决方法看似一大堆实则无效或过时不匹配,绝大多数资料都是安装hexo-asset-image的插件,亲测对于markdown图片路径没有用啊!比出现问题更折磨人的是遇上一堆错误的解决方法…

我想要寻找在本地和网页上都能显示的办法,终于发现了一款插件hexo-image-link,是将markdown图片路径转换为asset_img语法,使得图片能够同时显示在typora和hexo上。

1
{% asset_img "local-image.png" "image file label" %} -> {% asset_img label local-image.png %}
具体步骤
  • 修改_config.yml中的post_asset_folder: true

  • 安装hexo-image-link

    1
    $ npm install hexo-image-link --save
  • 修改 站点配置文件post_asset_folder: true

  • 如果 npm下载比较慢的话,尝试 cnpm下载

  • 安装

    1
    2
    $ npm install -g cnpm --registry=https://registry.npm.taobao.org
    $ cnpm install hexo-image-link --save
  • 修改md文件中的图片路径

hexo-autonofollow (bad)

Github:https://github.com/liuzc/hexo-autonofollow

简介:自动为站外链接添加nofollow属性

安装:

1
$ npm install hexo-autonofollow --save

配置:

在博客配置文件_config.yml中添加

1
2
3
4
5
nofollow:
enable: true
exclude:
- exclude1.com
- exclude2.com
  • enable - 是否启用
  • exclude - 排除域名

配置主题无意义的组件

豆瓣书单 (关闭, 毫无意义)

复制theme/pure/_source/ 目录下books文件夹到blog path/source/ 目录下

1
2
3
4
5
# douban 豆瓣书单
douban:
user: *** # 豆瓣用户名
start: 0 # 从哪一条记录开始
count: 100 # 获取豆瓣书单数据条数
评论 (关闭, 鸡肋)
1
2
comment:
type: # 启用哪种评论系统, 不填即关闭

Next 主题

下载主题

  1. 最新版本:

    Download the Latest Master Branch

  2. 稳定版本

    Download the Latest Release Version

  3. 指定版本

    Download the Specific Release Version

    In rare cases useful, but not recommended.
    You must define version. Let’s take v8.0.0 as an example. Replace it with any version from tags list.

  4. git 下载

    1
    2
    $ cd io_github_qeuroal
    $ git clone https://github.com/next-theme/hexo-theme-next themes/next

教程: Installation

主题配置

修改 theme/next/_config.yml

主题配置文件

1
2
3
4
5
#Schemes
#scheme: Muse
#scheme: Mist
scheme: Pisces
#scheme: Gemini
  1. 将 about、tags、categories 前的 #号去掉,示例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    menu:
    home: / || fa fa-home
    about: /about/ || fa fa-user
    tags: /tags/ || fa fa-tags
    categories: /categories/ || fa fa-th
    archives: /archives/ || fa fa-archive
    #schedule: /schedule/ || fa fa-calendar
    #sitemap: /sitemap.xml || fa fa-sitemap
    #commonweal: /404/ || fa fa-heartbeat
  2. 新建相关页面

    在博客根目录下执行下列命令

    1
    2
    3
    4
    $ hexo new page "about"
    $ hexo new page "tags"
    $ hexo new page "categories"
    $ hexo new page 404
  3. 修改生成页面的配置

    1
    2
    3
    4
    source/about/index.md
    source/tags/index.md
    source/categories/index.md
    source/404/index.md
  4. 404 相关

    front-matter 中添加 permalink: /404,表示指定该页面固定链接为 http://“主页”/404.html

    1
    2
    3
    4
    5
    6
    7
    ---
    title: 404 Not Found:该页无法显示
    toc: false
    comments: false
    permalink: /404
    ---
    <script type="text/javascript" src="//www.qq.com/404/search_children.js" charset="utf-8" homePageUrl="<%- config.url %>" homePageName="回到我的主页"></script>

设置左上角或右上角 github 图标

主题配置文件,启用 github-banner 如下:

1
2
3
4
github_banner:
enable: true
permalink: https://https://github.com/Qeuroal
title: Follow me on GitHub

设置侧栏阅读进度百分比

编辑站点配置文件,修改 back2top 部分如下

1
2
3
4
copyback2top:
enable: true
sidebar: true
scrollpercent: true

设置网页底部信息

查看主题配置文件,修改 footer 配置如下:

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
footer:
# Specify the year when the site was setup. If not defined, current year will be used.
#since: 2021

# Icon between year and copyright info.
icon:
# Icon name in Font Awesome. See: https://fontawesome.com/icons
name: fa fa-heart
# If you want to animate the icon, set it to true.
animated: false
# Change the color of icon, using Hex Code.
color: "#ff0000"

# If not defined, `author` from Hexo `_config.yml` will be used.
# Set to `false` to disable the copyright statement.
copyright:

# Powered by Hexo & NexT
powered: true

# Beian ICP and gongan information for Chinese users. See: https://beian.miit.gov.cn, http://www.beian.gov.cn
beian:
enable: false
icp:
# The digit in the num of gongan beian.
gongan_id:
# The full num of gongan beian.
gongan_num:
# The icon for gongan beian. See: http://www.beian.gov.cn/portal/download
gongan_icon_url:

图片懒加载设置

在主题配置文件中启用 lazyload

1
copylazyload: true

设置代码块复制和代码高亮

在主题配置文件中修改 codeblock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
codeblock:
# Code Highlight theme
# All available themes: https://theme-next.js.org/highlight/
theme:
light: atom-one-dark
dark: stackoverflow-dark
prism:
light: prism
dark: prism-dark
# Add copy button on codeblock
copy_button:
enable: true
# Available values: default | flat | mac
style:
# Fold code block
fold:
enable: false
height: 500

修改文章底部标签样式

在主题配置文件中修改

1
copytag_icon: true

网站icon

1
2
3
4
5
6
favicon:
small: /images/favicon.png
medium: /images/favicon.png
apple_touch_icon: /images/avatar.jpg
safari_pinned_tab: /images/avatar.jpg
#android_manifest: /manifest.json

侧边栏头像

1
2
3
4
5
6
7
8
# Sidebar Avatar
avatar:
# Replace the default image and set the url here.
url: /images/avatar.png
# If true, the avatar will be displayed in circle.
rounded: true
# If true, the avatar will be rotated with the cursor.
rotated: true

侧边栏社交链接

修改主题配置文件

1
2
3
4
5
6
7
8
9
10
social:
GitHub: https://github.com/Qeuroal || fab fa-github
#E-Mail: mailto:yourname@gmail.com || fa fa-envelope
#Weibo: https://weibo.com/yourname || fab fa-weibo
#Twitter: https://twitter.com/yourname || fab fa-twitter
#FB Page: https://www.facebook.com/yourname || fab fa-facebook
#StackOverflow: https://stackoverflow.com/yourname || fab fa-stack-overflow
#YouTube: https://youtube.com/yourname || fab fa-youtube
#Instagram: https://instagram.com/yourname || fab fa-instagram
#Skype: skype:yourname?call|chat || fab fa-skype

在文章底部增加版权信息

编辑 主题配置文件,修改如下配置:

1
2
3
4
5
6
7
8
9
10
creative_commons:
# Available values: by | by-nc | by-nc-nd | by-nc-sa | by-nd | by-sa | cc-zero
license: by-nc-sa
# Available values: big | small
size: small
sidebar: false
post: true
# You can set a language value if you prefer a translated version of CC license, e.g. deed.zh
# CC licenses are available in 39 languages, you can find the specific and correct abbreviation you need on https://creativecommons.org
language:

开启缓存

1
2
3
# Allow to cache content generation. Introduced in NexT v6.0.0.
cache:
enable: true

压缩

1
2
# Remove unnecessary files after hexo generate.
minify: true

侧边栏显示分类条数

1
2
# Posts / Categories / Tags in sidebar.
site_state: true

友链

1
2
3
4
5
6
7
8
9
10
# Blog rolls 友链
links_settings:
icon: fa fa-link # 设置icon
title: 友情链接 # 设置标题
# Available values: block | inline
layout: block # css样式,是一行一条,还是自动换行

links:
友链1: http://yoursite.com
#... 依次追加即可

文章toc

1
2
3
4
5
6
toc:
enable: true # 是否开启
number: true # 自动显示文章编号
wrap: false # 溢出是否换行
expand_all: true # 展开所有还是手风琴
max_depth: 4 # 显示最大深度

顶部元信息

1
2
3
4
5
6
7
post_meta:
item_text: true # 使用中文显示
created_at: true
updated_at:
enable: true
another_day: true
categories: true

首页摘要

1
2
excerpt_description: false  # 关闭自动提取
read_more_btn: trues # 显示阅读全文
  1. 手动控制

    使用<!--more-->控制

  2. 使用 front-matter 中添加 descriptionexcerpt 字段

    1
    2
    3
    4
    ---
    description: your excerpt
    excerpt: your excerpt
    ---

安装插件

hexo-deployer-git (must, common)

  1. 安装

    1
    $ npm install hexo-deployer-git --save
  2. 站点配置(根目录) config.yml

    _config.yml

    1
    2
    3
    4
    5
    6
    # Deployment
    ## Docs: https://hexo.io/docs/deployment.html
    deploy:
    type: git
    repo: {git repo ssh address}
    branch: master

在github上用hexo搭建了自己的博客,用typora写完一篇博文,满怀欣喜地deploy之后发现图片加载失败…真的很吐血。去网上一搜索,嗯,好像不少人都遇到了这个问题,解决方法看似一大堆实则无效或过时不匹配,绝大多数资料都是安装hexo-asset-image的插件,亲测对于markdown图片路径没有用啊!比出现问题更折磨人的是遇上一堆错误的解决方法…

我想要寻找在本地和网页上都能显示的办法,终于发现了一款插件hexo-image-link,是将markdown图片路径转换为asset_img语法,使得图片能够同时显示在typora和hexo上。

1
{% asset_img "local-image.png" "image file label" %} -> {% asset_img label local-image.png %}
具体步骤
  • 修改_config.yml中的post_asset_folder: true

  • 安装hexo-image-link

    1
    $ npm install hexo-image-link --save
  • 修改 站点配置文件post_asset_folder: true

注:

  • 如果 npm下载比较慢的话,尝试 cnpm下载

  • 安装

    1
    2
    $ npm install -g cnpm --registry=https://registry.npm.taobao.org
    $ cnpm install hexo-image-link --save
  • 修改md文件中的图片路径

hexo-generator-searchdb

  1. 安装 exo-generator-searchdb 这个插件

    1
    $ npm install hexo-generator-searchdb --save
  2. 配置 主题配置文件: _config.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # Local Search
    # Dependencies: https://github.com/next-theme/hexo-generator-searchdb
    local_search:
    enable: true
    # If auto, trigger search by changing input.
    # If manual, trigger search by pressing enter key or search button.
    trigger: auto
    # Show top n results per article, show all results by setting to -1
    top_n_per_article: 1
    # Unescape html strings to the readable one.
    unescape: false
    # Preload the search data when the page loads.
    preload: false

部署

  1. 配置站点配置文件 _config.yml

    1
    2
    3
    4
    5
    6
    # Deployment
    ## Docs: https://hexo.io/docs/deployment.html
    deploy:
    type: git
    repo: {git repo ssh address}
    branch: master
  2. 部署

    1
    $ hexo deploy

自定义域名

添加 CNAME 文件

/source 文件夹下添加 CNAME 文件, 如下所示:

hexo 常用命令

1
2
3
4
5
6
7
$ hexo new "postName" # 新建文章
$ hexo new page "pageName" # 新建页面
$ hexo generate # 生成静态页面至public目录
$ hexo server # 开启预览访问端口(默认端口4000,'ctrl + c'关闭server)
$ hexo deploy # 将.deploy目录部署到GitHub
$ hexo help # 查看帮助
$ hexo version # 查看Hexo的版本

git

删除远程仓库里的某个文件/文件夹

在git中可以用git rm命令删除文件(删除远程仓库文件)

1
2
3
4
5
6
git clone 仓库地址
git add .
step1: git rm 文件 //本地中该文件会被删除
step2: git rm -r 文件夹 //删除文件夹
step3: git commit -m '删除某个文件'
step4: git push (origin master)

上面的方法会把对应的本地文件也删除掉,如果不想把本地文件删除,只把缓存区中的对应部分删除,则加上 --cached

1
2
git rm --cached 文件 //本地中该文件不会被删除
git rm -r --cached 文件夹 //删除文件夹

git add . 后面执行上面的命令,再推送到 github 远程仓库上的时候,仓库里面对应的文件/文件夹就会被删除

1
2
3
4
5
6
git clone 仓库地址
git add .
step1: git rm --cached 文件 //本地中该文件不会被删除
step2: git rm -r --cached 文件夹 //删除文件夹
step3: git commit -m '删除某个文件'
step4: git push (origin master)

git commit后,如何撤销commit

修改了本地的代码,然后使用:

1
2
git add file
git commit -m '修改原因'

复制

执行commit后,还没执行push时,想要撤销这次的commit,该怎么办?

解决方案: 使用命令:

1
git reset --soft HEAD^

复制

这样就成功撤销了commit,如果想要连着add也撤销的话,–soft改为–hard(删除工作空间的改动代码)。

1
2
HEAD^ 表示上一个版本,即上一次的commit,也可以写成HEAD~1
如果进行两次的commit,想要都撤回,可以使用HEAD~2

复制

1
2
--soft
不删除工作空间的改动代码 ,撤销commit,不撤销git add file

复制

1
2
--hard
删除工作空间的改动代码,撤销commit且撤销add

复制

另外一点,如果commit注释写错了,先要改一下注释,有其他方法也能实现,如:

1
2
git commit --amend
这时候会进入vim编辑器,修改完成你要的注释后保存即可。

撤销 git add . 并保留修改的方法

执行完 git add .

文件退出暂存区,但是修改保留:

1
git reset --mixed

撤销所有的已经 add 的文件:

1
git reset HEAD .

撤销某个文件或文件夹:

1
git reset HEAD  -filename

另外:可以用 git status Git 会告诉你可以通过那个命令来执行操作。

补充

可以用 git status Git 会告诉你可以通过那个命令来执行操作。

push 失败

case 1

OpenSSL SSL_read: Connection was reset, errno 10054的话就把代理关了

删除未跟踪文件

1
2
# 删除 untracked files
git clean -f
1
2
# 连 untracked 的目录也一起删掉
git clean -fd
1
2
# 连 gitignore 的untrack 文件/目录也一起删掉 (慎用,一般这个是用来删掉编译出来的 .o之类的文件用的)
git clean -xfd
1
2
3
4
# 在用上述 git clean 前,墙裂建议加上 -n 参数来先看看会删掉哪些文件,防止重要文件被误删
git clean -nxfd
git clean -nf
git clean -nfd

多终端同步

新建私有仓库

1
2
3
4
5
6
7
echo "# Readme" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M master
git remote add origin <your https repo address>
git push -u origin master

文件夹说明

文件夹 说明 是否需要上传github
node_modules hexo需要的模块,就是一些基础的npm安装模块,比如一些美化插件,在执行npm install的时候会重新生成 不需要
themes 主题文件 ==需要==
public hexo g命令执行后生成的静态页面文件 不需要
packages.json 记录了hexo需要的包的信息,之后换电脑了npm根据这个信息来安装hexo环境 ==需要==
_config.yml 全局配置文件,这个不用多说了吧 ==需要==
.gitignore hexo生成的默认的.gitignore模块 ==需要==
scaffolds 文章的模板 ==需要==
.deploy_git hexo g自动生成的 不需要

.gitignore内容

1
2
3
4
5
6
7
.DS_Store
Thumbs.db
db.json
*.log
node_modules/
public/
.deploy*/

删除.git文件

删除所有主题的 .git 文件,如:rm -rf ./theme/pure/.git

查找 .git 文件

在==根目录==下执行以下命令

1
find . -name ".git*"

上传

1
2
3
git add .
git commit -m "<yout commit statement>"
git push

新电脑上的操作

  1. 下载

    1
    git clone <your repo>
  2. 安装依赖

    1
    npm install

    npm install其实就是读取了 packages.json 里面的信息,自动安装依赖,有的小伙伴可能只执行npm install就行了,不过按照上面的三步是最稳妥的

  3. 仓库更新

    为了保证同步,推荐先合并更新再进行博客的编写

    • pull (不推荐)

      1
      git pull
    • fetch

      1
      2
      git fetch --all    #将远程git仓库上最新的内容拉取到本地,将本地库所关联的远程库更新至最新
      git reset --hard origin/master #强制将本地内容指向刚刚同步git云端内容

      reset 对所拉取的文件不做任何处理,此处不用 pull 是因为本地尚有许多文件,使用 pull 会有一些版本冲突,解决起来也麻烦,而本地的文件都是初始化生成的文件,较拉取的库里面的文件而言基本无用,所以直接丢弃。

  4. 部署

    1
    2
    hexo clean && hexo g && hexo s
    hexo d

更新脚本

1

QAs

Error: Cannot find module ‘hexo-util’

  • 方法1

    1. rm -rf node_modules
    2. npm install hexo-util
  • 方法2

    For those who also encounter this issue, please check your NPM version.

    • > 3: Still not work. Please remove node_modules directory and reinstall using npm install.
    • < 3: Please add hexo-util explicitly via npm install –save-dev hexo-util to you site package deps.

    reference: https://github.com/iissnan/hexo-theme-next/issues/1490

基本概念

简介

模型训练5要素

  • 数据:包括数据读取,数据清洗,进行数据划分和数据预处理,比如读取图片如何预处理及数据增强。
  • 模型:包括构建模型模块,组织复杂网络,初始化网络参数,定义网络层。
  • 损失函数:包括创建损失函数,设置损失函数超参数,根据不同任务选择合适的损失函数。
  • 优化器:包括根据梯度使用某种优化器更新参数,管理模型参数,管理多个参数组实现不同学习率,调整学习率。
  • 迭代训练:组织上面 4 个模块进行反复训练。包括观察训练效果,绘制 Loss/Accuracy 曲线,用 TensorBoard 进行可视化分析

张量

概念

张量

官方定义

A tensor is the primary data structure used by neural networks.

官方定义2

A PyTorch Tensor is conceptually identical to a numpy array: a Tensor is an n-dimensional array, and PyTorch provides many functions for operating on these Tensors.

通俗理解

多维数组(Tensors and nd-arrays are the same thing! So tensors are multidimensional arrays or nd-arrays for short.)

Indexes required Computer science Mathematics
n nd-array nd-tensor
  • A scalar is a $0$ dimensional tensor
  • A vector is a $1$ dimensional tensor
  • A matrix is a $2$ dimensional tensor
  • A nd-array is an $n$ dimensional tensor

拓展

We often see this kind of thing where different areas of study use different words for the same concept.

索引

obvious: 访问一个多维数组, 需要几个索引

即维数,或者说在张量中访问一个元素,需要的索引数

A tensor’s rank tells us how many indexes are needed to refer to a specific element within the tensor.

An axis of a tensor is a specific dimension of a tensor.

tensor 属性

torch.dtype

Data type dtype CPU tensor GPU tensor
32-bit floating point torch.float32 torch.FloatTensor torch.cuda.FloatTensor
64-bit floating point torch.float64 torch.DoubleTensor torch.cuda.DoubleTensor
16-bit floating point torch.float16 torch.HalfTensor torch.cuda.HalfTensor
8-bit integer (unsigned) torch.uint8 torch.ByteTensor torch.cuda.ByteTensor
8-bit integer (signed) torch.int8 torch.CharTensor torch.cuda.CharTensor
16-bit integer (signed) torch.int16 torch.ShortTensor torch.cuda.ShortTensor
32-bit integer (signed) torch.int32 torch.IntTensor torch.cuda.IntTensor
64-bit integer (signed) torch.int64 torch.LongTensor torch.cuda.LongTensor

torch.device

类型

  • CPU
  • GPU

指定GPU

1
device = torch.device('cuda:0')

注意

One thing to keep in mind about using multiple devices is that tensor operations between tensors must happen between tensors that ==exists on the same device==.

torch.layout

应该就是步长==?????==

总览

  • data: 被包装的 Tensor。
  • grad: data 的梯度。
  • grad_fn: 创建 Tensor 所使用的 Function,是自动求导的关键,因为根据所记录的函数才能计算出导数。
  • requires_grad: 指示是否需要梯度,并不是所有的张量都需要计算梯度。
  • is_leaf: 指示是否叶子节点(张量),叶子节点的概念在计算图中会用到,后面详细介绍。
  • dtype: 张量的数据类型,如 torch.FloatTensor,torch.cuda.FloatTensor。
  • shape: 张量的形状。如 (64, 3, 224, 224)
  • device: 张量所在设备 (CPU/GPU),GPU 是加速计算的关键

构建tensor的方法

直接构造tensor

  • torch.Tensor(data)
  • torch.tensor(data)
  • torch.as_tensor(data)
  • torch.from_numpy(data)

根据数值构造tensor

  • torch.eye(k): 生成 k 阶的单位矩阵

  • torch.zeros(shape): 生成元素值都为 0, 形状为 shape 的张量

  • torch.zeros_like(input, dtype=None, layout=None, device=None, requires_grad=False, memory_format=torch.preserve_format): 根据 input 形状创建全 0 张量

  • torch.ones(shape): 生成元素值都为 1, 形状为 shape 的张量

  • torch.rand(shape): 生成元素值随机, 形状为 shape 的张量

  • torch.full()

    1
    torch.full(size, fill_value, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
    • size: 张量的形状,如 (3,3)
    • fill_value: 张量中每一个元素的值
  • torch.full_like()

    1
    torch.full_like(input, fill_value, dtype=None, layout=torch.strided, device=None, requires_grad=False, memory_format=torch.preserve_format) → Tensor
    • input: the size of input will determine size of the output tensor.
    • fill_value: the number to fill the output tensor with.
  • torch.arange(): 创建等差的 1 维张量。注意区间为 $[start, end)$

    1
    torch.arange(start=0, end, step=1, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
    • start: 数列起始值
    • end: 数列结束值,开区间,取不到结束值
    • step: 数列公差,默认为 1
  • torch.linspace() : 创建均分的 1 维张量。数值区间为 $[start, end]$

    1
    torch.linspace(start, end, steps=100, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
    • start: 数列起始值
    • end: 数列结束值
    • steps: 数列长度 (元素个数)
  • touch.logspace() : 创建对数均分的 1 维张量, 数值区间为 $[start, end]$, 底为 base

    1
    torch.logspace(start, end, steps=100, base=10.0, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
    • start: 数列起始值
    • end: 数列结束值
    • steps: 数列长度 (元素个数)
    • base: 对数函数的底,默认为 10

根据概率构造tensor

  • torch.normal(mean, std, *, generator=None, out=None): 生成正态分布 (高斯分布)

    • mean: 均值
    • std: 标准差

    4种模式

    • mean 为标量, std 为标量, 则==需要设置 size==

      如: torch.normal(0., 1., size=(4,))

    • mean 为标量,std 为张量

    • mean 为张量,std 为标量

    • mean 为张量,std 为张量

  • torch.randn()torch.randn_like(): 生成标准正态分布。

    1
    torch.randn(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
    • size: 张量的形状
  • torch.rand()torch.rand_like(): 在区间 $[0, 1)$ 上生成均匀分布

    1
    torch.rand(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
  • torch.randint()torch.randint_like(): 在区间 $[low, high)$ 上生成整数均匀分布

    1
    randint(low=0, high, size, *, generator=None, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
    • size: 张量的形状
  • torch.randperm() :生成从 0 到 n-1 的随机排列。常用于生成索引。

    1
    torch.randperm(n, out=None, dtype=torch.int64, layout=torch.strided, device=None, requires_grad=False)
    • n: 张量的长度
  • torch.bernoulli()

    1
    torch.bernoulli(input, *, generator=None, out=None)

    功能:以 input 为概率,生成伯努利分布 (0-1 分布,两点分布)

    • input: 概率值

使用data的构造方法的不同

输出不同对比

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
data = np.array([1, 2, 3], dtype=np.int32)
print("numpy: ", data, "; dtype: ", data.dtype)

# torch.Tensor(data)
tensor1 = torch.Tensor(data)
print("tensor1: ", tensor1, "; dtype: ", tensor1.dtype)

# torch.tensor(data)
tensor2 = torch.tensor(data)
print("tensor2: ", tensor2, "; dtype: ", tensor2.dtype)

# torch.as_tensor(data)
tensor3 = torch.as_tensor(data)
print("tensor3: ", tensor3, "; dtype: ", tensor3.dtype)

# torch.from_numpy(data)
tensor4 = torch.from_numpy(data)
print("tensor4: ", tensor4, "; dtype: ", tensor4.dtype)

output

1
2
3
4
5
numpy:  [1 2 3] ; dtype:  int32
tensor1: tensor([1., 2., 3.]) ; dtype: torch.float32
tensor2: tensor([1, 2, 3], dtype=torch.int32) ; dtype: torch.int32
tensor3: tensor([1, 2, 3], dtype=torch.int32) ; dtype: torch.int32
tensor4: tensor([1, 2, 3], dtype=torch.int32) ; dtype: torch.int32

数据类型对比

torch.Tensor(data)

数据 数据类型
tensor([1., 2., 3.]) torch.float32

torch.tensor(data)

数据 数据类型
tensor([1, 2, 3], dtype=torch.int32) torch.int32

torch.as_tensor(data)

数据 数据类型
tensor([1, 2, 3], dtype=torch.int32) torch.int32

torch.from_numpy(data)

数据 数据类型
tensor([1, 2, 3], dtype=torch.int32) torch.int32

本质的不同

torch.Tensor() 和 torch.tensor()

  • torch.Tensor(data)

    这是构造函数

  • torch.tensor(data)

    • 这是工厂函数

      You can think of the torch.tensor() function as a factory that builds tensors given some parameter inputs.

    • 相比于 torch.Tensor(data) 更好

      the factory function torch.tensor() has better documentation and more configuration options, so it gets the winning spot at the moment.

dtype 的对比

  • ``torch.Tensor()`

    使用的是默认类型

    注意: 这个函数中的dtype也而==不可以==显式的设置, 如: torch.Tensor(data, dtype=torch.int32)

  • other(torch.tensor(), torch.as_tensor(), torch.from_numpy())

    使用的是推断类型(根据传入的原始数据, 自动推断出元素的类型)

    注意: 这些函数中的dtype也而可以显式的设置, 如: torch.tensor(data, dtype=torch.float32)

内存对比: copy vs share

代码

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
data = np.array([1, 2, 3], dtype=np.int32)
print("numpy: ", data, "; dtype: ", data.dtype)

## create tensor data
# torch.Tensor(data)
tensor1 = torch.Tensor(data)
# torch.tensor(data)
tensor2 = torch.tensor(data)
# torch.as_tensor(data)
tensor3 = torch.as_tensor(data)
# torch.from_numpy(data)
tensor4 = torch.from_numpy(data)

## show different
print("old: ")
print("\ttensor1: ", tensor1, "; dtype: ", tensor1.dtype)
print("\ttensor2: ", tensor2, "; dtype: ", tensor2.dtype)
print("\ttensor3: ", tensor3, "; dtype: ", tensor3.dtype)
print("\ttensor4: ", tensor4, "; dtype: ", tensor4.dtype)

# 更改数据
data[0] = 0

print("new:")
print("\ttensor1: ", tensor1, "; dtype: ", tensor1.dtype)
print("\ttensor2: ", tensor2, "; dtype: ", tensor2.dtype)
print("\ttensor3: ", tensor3, "; dtype: ", tensor3.dtype)
print("\ttensor4: ", tensor4, "; dtype: ", tensor4.dtype)

out:

1
2
3
4
5
6
7
8
9
10
old: 
tensor1: tensor([1., 2., 3.]) ; dtype: torch.float32
tensor2: tensor([1, 2, 3], dtype=torch.int32) ; dtype: torch.int32
tensor3: tensor([1, 2, 3], dtype=torch.int32) ; dtype: torch.int32
tensor4: tensor([1, 2, 3], dtype=torch.int32) ; dtype: torch.int32
new:
tensor1: tensor([1., 2., 3.]) ; dtype: torch.float32
tensor2: tensor([1, 2, 3], dtype=torch.int32) ; dtype: torch.int32
tensor3: tensor([0, 2, 3], dtype=torch.int32) ; dtype: torch.int32
tensor4: tensor([0, 2, 3], dtype=torch.int32) ; dtype: torch.int32

总结

Share Data Copy Data
torch.as_tensor() torch.tensor()
torch.from_numpy() torch.Tensor()
  • This sharing just means that the actual data in memory exists in a single place
  • Sharing data is more efficient and uses less memory than copying data because the data is not written to two locations in memory.

torch.as_tensor() 和 torch.from_numpy() 的选择

  • The torch.from_numpy() function only accepts numpy.ndarrays
  • the torch.as_tensor() function accepts a wide variety of array-like objects including other PyTorch tensors

张量操作

拼接

torch.cat()

将张量按照 dim 维度进行拼接

1
torch.cat(tensors, dim=0, out=None)
  • tensors: 张量序列
  • dim: 要拼接的维度

torch.stack()

将张量在新创建的 dim 维度上进行拼接

1
torch.stack(tensors, dim=0, out=None)
  • tensors: 张量序列
  • dim: 要拼接的维度

意义

使用stack可以保留两个信息:[1. 序列] 和 [2. 张量矩阵] 信息,属于【==扩张==再拼接】的函数

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 # 假设是时间步T1
T1 = torch.tensor([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# 假设是时间步T2
T2 = torch.tensor([[10, 20, 30],
[40, 50, 60],
[70, 80, 90]])

print(torch.stack((T1,T2),dim=0).shape)
print(torch.stack((T1,T2),dim=1).shape)
print(torch.stack((T1,T2),dim=2).shape)
print(torch.stack((T1,T2),dim=3).shape)


#################### outputs: #############
torch.Size([2, 3, 3])
torch.Size([3, 2, 3])
torch.Size([3, 3, 2])
'选择的dim>len(outputs),所以报错'

切分

torch.chunk()

1
torch.chunk(input, chunks, dim=0)

功能:将张量按照维度 dim 进行平均切分。若不能整除,则最后一份张量小于其他张量。

  • input: 要切分的张量
  • chunks: 要切分的份数
  • dim: 要切分的维度

torch.split()

1
torch.split(tensor, split_size_or_sections, dim=0)

功能:将张量按照维度 dim 进行平均切分。可以指定每一个分量的切分长度。

  • tensor: 要切分的张量
  • split_size_or_sections:
    • 为 int 时,表示每一份的长度,如果不能被整除,则最后一份张量小于其他张量;
    • 为 list 时,按照 list 元素作为每一个分量的长度切分。如果 list 元素之和不等于切分维度 (dim) 的值,就会报错。
  • dim: 要切分的维度

索引

torch.index_select()

1
torch.index_select(input, dim, index, out=None)

功能:在维度 dim 上,按照 index 索引取出数据拼接为张量返回。

  • input: 要索引的张量
  • dim: 要索引的维度
  • index: 要索引数据的序号

torch.mask_select()

1
torch.masked_select(input, mask, out=None)

功能:按照 mask 中的 True 进行索引拼接得到一维张量返回。

  • 要索引的张量
  • mask: 与 input 同形状的布尔类型张量

t.le() t.ge()

1
t.le(5)

变换

torch.reshape()

1
torch.reshape(input, shape)

功能:变换张量的形状。当张量在内存中是连续时,返回的张量和原来的张量共享数据内存,改变一个变量时,另一个变量也会被改变。

  • input: 要变换的张量
  • shape: 新张量的形状
    • -1 表示这个维度是根据其他维度计算得出的

torch.transpose()

1
torch.transpose(input, dim0, dim1)

功能:交换张量的两个维度。常用于图像的变换,比如把 $chw$ 变换为 $hwc$。

  • input: 要交换的变量
  • dim0: 要交换的第一个维度
  • dim1: 要交换的第二个维度

torch.t()

功能:2 维张量转置,对于 2 维矩阵而言,等价于torch.transpose(input, 0, 1)

torch.squeeze()

1
torch.squeeze(input, dim=None, out=None)

功能:压缩长度为 1 的维度。

  • dim: 若为 None,则移除所有长度为 1 的维度;若指定维度,则==当且仅当该维度长度为 1 时可以移除==。

torch.unsqueeze()

1
torch.unsqueeze(input, dim)

功能:根据 dim 扩展维度,长度为 1。

数学运算

torch.add()

1
2
torch.add(input, other, out=None)
torch.add(input, other, *, alpha=1, out=None)

功能:逐元素计算 input + alpha * other。因为在深度学习中经常用到先乘后加的操作。

  • input: 第一个张量
  • alpha: 乘项因子
  • other: 第二个张量

torch.addcdiv()

1
torch.addcdiv(input, tensor1, tensor2, *, value=1, out=None)

计算公式: $out_i = input_i + value \times \frac{tensor1_i}{tensor2_i}$

torch.addcmul()

1
torch.addcmul(input, tensor1, tensor2, *, value=1, out=None)

计算公式: $out_i = input_i + value \times tensor1_i \times tensor2_i$

bridge with Numpy

Tensor to numpy array

1
tensor.numpy()

函数

见这里

获取torch默认类型函数

1
torch.get_default_dtype()

suffix “_”

在函数的后面加上后缀 _, 将会改变作用的数据

示例

1
2
3
4
5
6
7
8
9
10
11
# source tensor
tensor = torch.eye(5)
print(tensor)

# tensor after using add()
tensor.add(5)
print(tensor)

# tensor after using add_()
tensor.add_(5)
print(tensor)

out

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
tensor([[1., 0., 0., 0., 0.],
[0., 1., 0., 0., 0.],
[0., 0., 1., 0., 0.],
[0., 0., 0., 1., 0.],
[0., 0., 0., 0., 1.]])
tensor([[1., 0., 0., 0., 0.],
[0., 1., 0., 0., 0.],
[0., 0., 1., 0., 0.],
[0., 0., 0., 1., 0.],
[0., 0., 0., 0., 1.]])
tensor([[6., 5., 5., 5., 5.],
[5., 6., 5., 5., 5.],
[5., 5., 6., 5., 5.],
[5., 5., 5., 6., 5.],
[5., 5., 5., 5., 6.]])

自动求导(autograd)

只要搭建好前向计算图,利用 torch.autograd 自动求导得到所有张量的梯度

torch.autograd.backward()

1
torch.autograd.backward(tensors, grad_tensors=None, retain_graph=None, create_graph=False, grad_variables=None)

功能

  • 自动求取梯度

参数

  • tensors: 用于求导的张量,如 loss

  • retain_graph: 保存计算图。PyTorch 采用动态图机制,默认每次反向传播之后都会释放计算图。这里设置为 True 可以不释放计算图。

    y.backward() 方法调用的是 torch.autograd.backward(self, gradient, retain_graph, create_graph)。但是在第二次执行 y.backward() 时会出错。因为 PyTorch 默认是每次求取梯度之后不保存计算图的,因此第二次求导梯度时,计算图已经不存在了。在第一次求梯度时使用 y.backward(retain_graph=True) 即可。

  • create_graph: 创建导数计算图,用于高阶求导

  • grad_tensors: 多梯度权重。当有多个 loss 混合需要计算梯度时,设置每个 loss 的权重。

retain_grad 参数

反向传播结束之后仍然需要保留非叶子节点的梯度

grad_tensors 参数

给 loss 设置权重

torch.autograd.grad()

1
torch.autograd.grad(outputs, inputs, grad_outputs=None, retain_graph=None, create_graph=False, only_inputs=True, allow_unused=False)

功能

求取梯度

参数

  • outputs: 用于求导的张量,如 loss
  • inputs: 需要梯度的张量
  • create_graph: 创建导数计算图,用于高阶求导
  • retain_graph:保存计算图
  • grad_outputs: 多梯度权重计算

返回值

返回结果是一个 tunple,需要取出第 0 个元素才是真正的梯度。

注意点

  • 在每次反向传播求导时,计算的梯度不会自动清零。如果进行多次迭代计算梯度而没有清零,那么梯度会在前一次的基础上叠加。

    故使用 w.grad.zero() 将梯度清零

  • 依赖与叶子节点的节点, requires_grad 属性默认为 True

  • 叶子节点不可以执行 inplace 操作

    1
    2
    3
    4
    5
    6
    7
    ## inplace 操作: 改变后的值和原来的值内存地址是同一个
    a += x
    a.add_(x)

    ## 非inplace 操作: 改变后的值和原来的值内存地址不是同一个
    a = a + x
    a.add(x)

    举例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    print("非 inplace 操作")
    a = torch.ones((1, ))
    print(id(a), a)
    # 非 inplace 操作,内存地址不一样
    a = a + torch.ones((1, ))
    print(id(a), a)

    print("inplace 操作")
    a = torch.ones((1, ))
    print(id(a), a)
    # inplace 操作,内存地址一样
    a += torch.ones((1, ))
    print(id(a), a)

    结果自己跑一下嘛

    问题

    如果在反向传播之前 inplace 改变了叶子的值, 再执行 backward() 会报错

逻辑回归

概念

二分类模型

模型表达式 $y=f(z)=\frac{1}{1+e^{-z}}$,其中 $z=WX+b$。$f(z)$ 称为 sigmoid 函数,也被称为 Logistic 函数

分类原则

逻辑回归是在线性回归的基础上加入了一个 sigmoid 函数,这是为了更好地描述置信度,把输入映射到 (0,1) 区间中,符合概率取值。

训练步骤

  1. 导入模型

  2. 计算误差 loss

  3. 后向传播 (call loss.backward())

  4. 加载优化器 optimizer: 注册模型中的所有参数

  5. 梯度下降 (call optim.step())

    各参数的梯度保存在 .grad 属性中

反向传播

To backpropagate the error all we have to do is to loss.backward(). You need to clear the existing gradients though, ==else gradients will be accumulated to existing gradients.==

模型构建总结

步骤

  1. 加载数据
  2. 定义神经网络
  3. 定义损失函数
  4. 训练网络
  5. 测试网络

PyTorch 构建模型需要 5 大步骤:

  • 数据:包括数据读取,数据清洗,进行数据划分和数据预处理,比如读取图片如何预处理及数据增强。
  • 模型:包括构建模型模块,组织复杂网络,初始化网络参数,定义网络层。
  • 损失函数:包括创建损失函数,设置损失函数超参数,根据不同任务选择合适的损失函数。
  • 优化器:包括根据梯度使用某种优化器更新参数,管理模型参数,管理多个参数组实现不同学习率,调整学习率。
  • 迭代训练:组织上面 4 个模块进行反复训练。包括观察训练效果,绘制 Loss/ Accuracy 曲线,用 TensorBoard 进行可视化分析。

数据

数据模块

细分

  • 数据收集: 样本和标签
  • 数据划分: 训练集, 验证集和测试集
  • 数据读取:对应于PyTorch 的 DataLoader。其中 DataLoader 包括 Sampler 和 DataSet。Sampler 的功能是生成索引, DataSet 是根据生成的索引读取样本以及标签。
  • 数据预处理:对应于 PyTorch 的 transforms

考虑

  • Who created the dataset?
  • How was the dataset created?
  • What transformations were used?
  • What intent does the dataset have?
  • Possible unintentional consequences?
  • Is the dataset biased?
  • Are there ethical issues with the dataset?

DataLoader

torch.utils.data.DataLoader()

1
torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, batch_sampler=None, num_workers=0, collate_fn=None, pin_memory=False, drop_last=False, timeout=0, worker_init_fn=None, multiprocessing_context=None)

功能

构建可迭代的装载器

参数

  • dataset: Dataset 类,决定数据从哪里读取以及如何读取
  • batchsize: 批大小
  • num_works: 是否多进程读取数据
  • sheuffle: 每个 epoch 是否乱序
  • drop_last: 当样本数不能被 batchsize 整除时,是否舍弃最后一批数据

Epoch, I’t’eration, Batchsize

  • Epoch: ==所有训练样本==都已经输入到模型中,称为一个 Epoch
  • Iteration: ==一批样本==输入到模型中,称为一个 Iteration
  • Batchsize: ==批大小==,决定一个 iteration 有多少样本,也决定了一个 Epoch 有多少个 Iteration

举例: 假设样本总数有 80,设置 Batchsize 为 8,则共有 $80 \div 8=10$ 个 Iteration。这里 $1 Epoch = 10 Iteration$。

torch.utils.data.Dataset

功能

Dataset 是抽象类,所有自定义的 Dataset 都需要继承该类,并且重写__getitem()__方法和__len__()方法

  • __getitem()__方法的作用是接收一个索引,返回索引对应的样本和标签,这是我们自己需要实现的逻辑
  • __len__()方法是返回所有样本的数量

数据读取

  • 读取哪些数据:每个 Iteration 读取一个 Batchsize 大小的数据,每个 Iteration 应该读取哪些数据。
  • 从哪里读取数据:如何找到硬盘中的数据,应该在哪里设置文件路径参数
  • 如何读取数据:不同的文件需要使用不同的读取方法和库。

步骤

  1. 划分数据集为: 训练集, 验证集和测试集, 比例为 $8 : 1 : 1$, 并构造路径
  2. 实现 get_img_info()__getitem__(self, index), __len__() 函数
  3. 构建模型

DataSet

MNIST 数据集

损失函数

均方误差 MSE

$MSE = \frac{1}{m}\sum_{i = 1}^{m}{(y_i-\hat{y_i})^2}$

  • $y_i$ 是预测值
  • $\hat{y_i}$ 是真实值

学习链接

常用变量

img

1
img = cv2.imread(filename)

图片相关

读图:imread

定义

1
imread(<文件名>, flag)

imread 第二个参数: flag

imread 第二个参数 含义
cv2.IMREAD_COLOR 缺省方式,读取图像为 BGR 8-bit 格式.
cv2.IMREAD_UNCHANGED 图像格式不做任何改变,可用在读取带 alpha 通道的图片
cv2.IMREAD_GRAYSCALE 读取图像为转换为灰度图

显示图:imshow

定义

1
imshow(<窗口名称>, < 图像实例 >)

waitkey

waitKey() 传入的参数如果为 0,会无限等待直到任何按键按下,或者传入其他数值参数表示等待时长,单位为 ms,时长结束后显示图像窗口会关闭。

参数 作用
0 无限等待直到任何按键按下
传入其他数值参数 表示等待时长 (单位: ms),时长结束后显示图像窗口会关闭。

返回值

返回: 键入的值

使用: 检查按键,键入 q or Q 退出

1
2
3
key = waitKey(20) & 0xff
if (key == ord('q') or key == ord('Q')):
break

waityKey() 返回的数值和 0xff 相与后再和字符的 ord() 值比较,是为了规避某些系统中 waitKey() 返回的数值在高字节为非 0 值的情况。

调整窗口

使用 imshow() 方法显示图像时,默认是以图像的像素大小显示的,可以通过 nameWindow(窗口名称, cv2.WINDOW_NORMAL)命名窗口,再使用resizeWindow(窗口名称, 窗口宽度, 窗口高度) 缩放窗口,最后使用 imshow() 显示图像,注意三者都 * 在同一个窗口名称上 * 操作。

销毁窗口

  • destroyWindow(窗口名称): 单独关闭某个显示窗口
  • destroyAllWindows(): 关闭所有显示窗口。

写入图片: imwrite

定义

1
imwrite(<写入的文件名称>, < 图像实例 >)

注:文件名称的后缀决定了图片文件的格式

视频相关

常用函数

方法 含义
cap = cv2.VideoCapture(‘文件名称’) 构建视频文件的 cap 实例
cap.read() 逐帧提取视频,每一帧为一幅图像 < br /> 方法返回的是一个二元组:
下标 0 的元素值为 True 或 False 如果为 Flase 表示读取文件完成。
下标 1 的元素为图像对象,也是一个 numpy 数组类型的数据。
cap.isOpened() 检查 cap 实例是否已经打开
cap.release() 释放实例

从视频文件获取图像

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

cap = cv2.VideoCapture('..\\vtest.avi')

while cap.isOpened():
ret, img = cap.read()
if ret is not True:
print("读取完成,退出")
break
#处理 img
cv2.imshow('vedio', img)

#检查按键
key = cv2.waitKey(20) & 0xff
if key == ord('q') or key == ord('Q') :
break

print('cap.isOpened():',cap.isOpened())
cap.release()
print('cap.isOpened():',cap.isOpened())

从相机获取图像

打开相机需要用相机的设备编号(数值型整数)作为入参传入 VideoCapture(相机编号),比如 cap = cv2.VideoCapture(0) 构建编号为 0 的相机访问实例,第 2 台相机则传入 1,以此类推,后续步骤的处理方法和读取视频文件一样。

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

cap = cv2.VideoCapture(0)

while cap.isOpened():
ret, img = cap.read()
if ret is not True:
print("读取失败,退出")
break
#处理 img
cv2.imshow('vedio', img)

#检查按键
key = cv2.waitKey(20) & 0xff
if key == ord('q') or key == ord('Q') :
break

print('cap.isOpened():',cap.isOpened())
cap.release()
print('cap.isOpened():',cap.isOpened())

写入视频文件

VideoWriter 对象

参数

  1. 文件名称
  2. 编码方式,其中编码方式和文件名称后缀有对应关系
  3. 每秒写入的帧数,参考数值为 25,符合人眼习惯
  4. 图像大小,int 类型

常用的文件名称后缀和编码方式

文件后缀 编码方式
avi XVID
avi MJPG
avi mp4v(小写)
mp4 mp4v(小写)

使用:创建 VideoWriter_fourcc 对象

1
2
3
4
# 方法 1
fourcc=cv2.VideoWriter_fourcc('M','J','P','G')
# 方法 2
fourcc=cv2.VideoWriter_fourcc(*'MJPG')

图像大小获取

cat.get(propId) 方法获取,但是该方法获取的是 float 类型,需要转换为 int 类型再传入 VideoWriter

举例

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
import cv2

#获取图像宽高
cap = cv2.VideoCapture(0)
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
# 转为 int 型
width = int(width)
height = int(height)
print(width,height)

#创建 VideoWriter 对象
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('output.avi', fourcc, 25.0, (width, height))
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
out2 = cv2.VideoWriter('output2.avi', fourcc, 25.0, (width, height))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out3 = cv2.VideoWriter('output3.mp4', fourcc, 25.0, (width, height))

while cap.isOpened():
ret, img = cap.read()
print(img.shape)
if ret is not True:
print("读取失败,退出")
break

#处理 img
cv2.imshow('vedio', img)
out.write(img)
out2.write(img)
out3.write(img)

#检查按键
key = cv2.waitKey(1) & 0xff
if key == ord('q') or key == ord('Q') :
break

cap.release()
out.release()
out2.release()
out3.release()

输出文字

乱码问题 (还是不要用中文吧)

**1、指定已有字体 **

查看字体

1
fontList = [i for i in dir(cv2) if (i.startswith("FONT"))]

2、使用 pillow

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
import cv2
import numpy as np
from PIL import Image,ImageDraw,ImageFont

# 白色背景
img = np.full((512,512,3),255,np.uint8)
img_pil = Image.fromarray(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))

text = 'VX: 桔子 code'
# 起始位置
start = (10,200)
# 微软雅黑字体,和具体操作系统相关
fonttype = 'msyh.ttc'
# 字体大小
fontscale = 30
font = ImageFont.truetype(fonttype,fontscale)
draw = ImageDraw.Draw(img_pil)
#PIL 中 BGR=(255,0,0) 表示红色
draw.text(start,text,font=font,fill=(255,0,0))
#PIL 图片转换为 numpy
img_ocv = np.array(img_pil)
#PIL 格式转换为 OpenCV 的 BGR 格式
img = cv2.cvtColor(img_ocv,cv2.COLOR_RGB2BGR)

cv2.imshow('juzicode.com',img)
cv2.waitKey()

图像属性

shape 属性

行列数

  • 行对应 img.shape[0]
  • 列对应 img.shape[1]

通道数

  • 必须是非灰度图才有通道数 *,对应 img.shape[2]

其他属性

属性 含义
ndim 维度 <=> len(img.shape)
itemsize 单个数据长度
size 总长度:有多少个数据
nbytes 占用的内存空间 = $itemsize \times size$
shape 形状 (type: tuple)
data 数据 buffer

dtype 属性

可以通过 img.astype(<np.uint32>) 转换得到,进行转换后看到 dtype 的变化,以及因为 dtype 变化引起 itemsize 相关属性的变化。

像素操作

通道

三通道: RGB

四通道: RGB + ahpha(透明度)

通道分离

方法 1

使用 cv2.split(img)

方法 2

使用索引方式(切片),如:r = img[:, :, 0]

通道合并

方法 1

使用 cv2.merge((r, g, b))

方法 2

1
2
3
4
5
6
newImg = np.zeros(img.shape, dtype=np.uint8)
for i in range(img.ndim):
newImg[:, :, i] = img[:, :, i]

cv2.imshow('new img', newImg)
cv2.waitKey(0)
  • 注 *:np.zeros 一定要指定 dtype 为 np.uint8

图像的加法

add()

cv2.add(img1, img2): 超过阈值会截断

+

img1 + img2: 超过阈值会溢出,也就是说对 255 取模

addWeighted()

参数

  1. 第 1 个参数是图像 1
  2. 第 2 个参数是图像 1 的权值
  3. 第 3 个参数为图像 2
  4. 第 4 个参数为图像 2 的权值
  5. 第 5 个参数附加数值 gamma,单个数值,即使是多通道图像也使用单个数值;
  6. 第 6 个参数可选,返回图像的实例,等同于函数返回结果
  7. 第 7 个参数可选,dtype,表示像素值的数据类型

图像的减法

** 目的:** 比较 2 幅图像的差异,比如判断前后 2 个时间点的图像是否发生了变化

subtract()

subtract(img1, img2) : img1 - img2,但是如果小于 0,那么就会截断为 0

-

计算结果对 256 求模运算

absdiff()

绝对值减法

图像和标量加减

图像之间的加减运算可以看成是 2 个向量之间的加减,将相同下标之间的元素值进行加减运算,如果和标量进行加减,则是将图像的每一个元素都和这个标量值进行加减。

当标量值只是 1 个数值时,只会对多通道图像的第 1 个通道进行运算,如果要进行多通道运算,标量值则使用一个包含 4 个数值的元组表示,即使是 3 通道的彩色图像也要用 4 元组表示这个标量值。

图像的乘法

multiply()

定义

dst=cv2.multiply(src1, src2[, dst[, scale[, dtype]]])

参数

  • src1 和 src2 为图像对象
  • 可选参数 scale 为放大倍数
  • 结果 dst 等于 $saturate(scale \times src1 \times src2)$

规则

multiply() 遵循 * 饱和运算规则 *,比如 uint8 类型的数据如果超过 255 会被截断到 255。

符号乘法 *

实际上就是 Numpy 数组的乘法, 和用 +/- 做 numpy 加减法一样, 在数值类型表示范围的上限加 1 取模,比如 uint8 类型的数据对 256 取模

图像的除法

divide()

用法

** 用法 1**

1
dst = cv2.divide(src1, src2[, dst[, scale[, dtype]]])
  • src1 和 src2 都是图像对象
  • scale: 制定 src1 的方法倍数
  • $dst = saturate(src1 \times scale \div src2)$

** 用法 2**

1
dst = cv2.divide(scale, src2[, dst[, dtype]])
  • scale: 数值类型
  • src2: 图像对象
  • $dst = saturate(scale \div src2)$

补充

  • uint8 等整数类型的除法,运算后的结果会做四舍五入取整
  • divide() 遵守 * 饱和运算规则 *

/

对应元素直接进行数学运算

divide() 除法中的 0

当有元素为 0 且作为被除数时,divide() 计算仍然是有实际意义的

scale 参数

scale 参数的用法比较特殊,要想实现 $scale \div src2$ 的用法,必须显式地声明形参的名称。

图像位运算

按位取反 bitwise_not()

将数值根据每个 bit 位 1 变 0,0 变 1,比如 0xf0 按位取反就变成了 0x0f

如果是 uint8 类型的数据,取反前后的数据相加结果为 0xff(255)

按位与 bitwise_and()

1
dst = cv2.bitwise_or(src1, src2[, dst[, mask]])

按位或 bitwise_or()

1
dst = cv2.bitwise_or(src1, src2[, dst[, mask]])

按位异或 bitwise_nor()

1
dst = cv2.bitwise_or(src1, src2[, dst[, mask]])

注意

  • 2 个图像的按位操作和算术运算一样,也要求 2 个图像的大小一样,通道数一样。不同于算术运算数据类型不一样时通过 dtype 声明新生成图像的数据类型,按位运算的接口中根本就没有 dtype 参数,所以位运算中 2 个图像的数据类型也必须一致。
  • 处理标量数据时不会自动填充第 4 通道为 0 而直接报错了,所以在处理 4 通道图像时则必须使用四元组。一个好的编程习惯是不管图像是多少通道的都使用四元组表示这个标量,如果不想对某些通道进行位运算,则用相应的全 0 或全 f 代替,比如一个 3 通道的 uint8 类型的图像,只需要对 2 通道和 0x33 相与,构造的四元组就是(0xff,0x33,0xff,0xff)。

色彩空间变换

cvtColor()

原型

1
dst=cv2.cvtColor(src, code[, dst[, dstCn]])

遍历所有色彩空间转换名称

1
colors = [i for in dir(cv) if (i.startswith('COLOR_'))]

applyColorMap()

原型

1
cv2.applyColorMap(src, colormap[, dst])
  • src 为输入图像,可以是单通道或 3 通道的 8bit 图像。

  • colormap 为颜色图模式,可以传入整数 0~21 对应各种不同的颜色图,或者用 cv2.COLORMAP_AUTUMN(等价于 0)、cv2.COLORMAP_BONE(等价于 1)等方式传入,OpenCV 源码头文件中定义的 22 种模式如下:

    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
    //! GNU Octave/MATLAB equivalent colormaps
    enum ColormapTypes
    {
    COLORMAP_AUTUMN = 0,
    COLORMAP_BONE = 1,
    COLORMAP_JET = 2,
    COLORMAP_WINTER = 3,
    COLORMAP_RAINBOW = 4,
    COLORMAP_OCEAN = 5,
    COLORMAP_SUMMER = 6,
    COLORMAP_SPRING = 7,
    COLORMAP_COOL = 8,
    COLORMAP_HSV = 9,
    COLORMAP_PINK = 10,
    COLORMAP_HOT = 11,
    COLORMAP_PARULA = 12,
    COLORMAP_MAGMA = 13,
    COLORMAP_INFERNO = 14,
    COLORMAP_PLASMA = 15,
    COLORMAP_VIRIDIS = 16,
    COLORMAP_CIVIDIS = 17,
    COLORMAP_TWILIGHT = 18,
    COLORMAP_TWILIGHT_SHIFTED = 19,
    COLORMAP_TURBO = 20,
    COLORMAP_DEEPGREEN = 21
    };

几何空间变换

缩放

原型

1
dst=cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]])

参数

  • src:源图像;
  • dsize:缩放后目标图像的尺寸,如果设置为 0,目标图像则使用源图像的尺寸乘以 fx 和 fy 得到;dsize 优先级高于 fx 和 fy,如果设置了 dsize,后面的 fx 和 fy 设置无效;
  • fx 和 fy:dsize 未设置的情况下,使用 fx 和 fy 分别作为宽度和高度的放大倍数;
  • interpolation:插值方法,默认使用双线性插值 cv2.INTER_LINEAR;

转置

作用

实现像素下标的 x 和 y 轴坐标进行对调:dst(i,j)=src(j,i)

原型

1
dst = cv2.transpose(src[, dst])

注意

不可以使用 numpy 的 transpose() 转置和 T 属性,会出现数组下标访问越界

翻转

作用

实现水平翻转、垂直翻转和双向翻转

原型

1
dst = cv2.flip(src, flipCode[, dst])

参数含义

  • src: 源图像
  • flipCode: 翻转方式,其中:0,水平轴翻转;大于 0 为垂直轴翻转;小于 0 作双向翻转

仿射变换

原型

1
dst=cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])
  • src: 输入图像。
  • M: 2×3 2 行 3 列变换矩阵。
  • dsize: 输出图像的大小。
  • dst: 可选,输出图像,由 dsize 指定大小,type 和 src 一样。
  • flags: 可选,插值方法
  • borderMode: 可选,边界像素模式
  • borderValue: 可选,边界填充值; 默认为 0。

平移

手动指定算子 M=[[1,0,X],[0,1,Y]] 可以实现图像的移位

  • X 表示向图像 x 方向(向右)移动的像素值
  • Y 表示图像向 y 方向(向下)移动的像素值

旋转

步骤

用 getRotationMatrix2D() 方法构造出 warpAffine() 的算子 M

getRotationMatrix2D() 原型

1
retval=cv2.getRotationMatrix2D(center, angle, scale)
  • center:旋转中心位置
  • angle:旋转角度
  • scale:缩放比例,不缩放时为 1

矫正

概念

因为拍摄角度或成像设备的原因发生畸变,可以用仿射变换进行矫正

步骤

使用 getAffineTransform() 构建 M 算子,入参为变换前和变化后的 2 组坐标点,每组坐标点包含 3 个位置参数

**getAffineTransform() 例子 **

1
2
3
pts1 = np.float32([[10,71],[37,320],[550,240]])
pts2 = np.float32([[10,10],[0,300],[550,300]])
M = cv2.getAffineTransform(pts1,pts2)

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import matplotlib.pyplot as plt
import numpy as np
import cv2

plt.rc('font',family='Youyuan',size='9')
plt.rc('axes',unicode_minus='False')

img = cv2.imread('..\\samples\\data\\imageTextR.png')
rows,cols,_ = img.shape

pts1 = np.float32([[10,71],[37,320],[550,240]])
pts2 = np.float32([[10,10],[0,300],[550,300]])
M = cv2.getAffineTransform(pts1,pts2)
img_ret1 = cv2.warpAffine(img,M,(cols,rows))

fig,ax = plt.subplots(1,2)
ax[0].set_title('VX: 桔子 code 原图')
ax[0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib 显示图像为 rgb 格式
ax[1].set_title('变换后')
ax[1].imshow(cv2.cvtColor(img_ret1,cv2.COLOR_BGR2RGB))
#ax[0,0].axis('off');ax[0,1].axis('off');ax[1,0].axis('off');ax[1,1].axis('off')# 关闭坐标轴显示
plt.show()

旋转

原型

1
cv2.rotate(src, rotateCode[, dst]) -> dst

参数

  • src: 源图像

  • rotateCode: 可选 3 种参数

    rotateCode 含义
    cv2.ROTATE_90_CLOCKWISE 顺时针旋转 90 度
    cv2.ROTATE_180 旋转 180 度,不区分顺时针或逆时针,效果一样
    cv2.ROTATE_90_COUNTERCLOCKWISE 逆时针旋转 90 度,等同于顺时针 270 度

本质

是对 flip()transpose() 的封装实现的

透视变换

概念

和仿射变换一样,透视变换也需要先构建变换 kernel,不过仿射变换只需要 3 个点构建,透视变换则需要找到 4 个点构建 kernel。

原型

1
cv2.warpPerspective(src,M,dsize[,dst[,flags[,borderMode[,borderValue]]]])->dst

参数

  • src: 输入图像。
  • M: 3×3 3 行 3 列变换矩阵。
  • dsize: 输出图像的大小。
  • dst: 可选,输出图像,由 dsize 指定大小,数据类型和 src 一样。
  • flags: 可选,插值方法
  • borderMode: 可选,边界像素模式
  • borderValue: 可选,边界填充值; 默认为 0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import matplotlib.pyplot as plt
import numpy as np
import cv2

plt.rc('font',family='Youyuan',size='9')
plt.rc('axes',unicode_minus='False')

img_src = cv2.imread('..\\samples\\data\\left02.jpg')
#构建 kernel
pts1 = np.float32([[192,40],[610,122],[216,363],[465,415]])
pts2 = np.float32([[0,0],[300,0],[0,350],[300,350]])
kernel = cv2.getPerspectiveTransform(pts1,pts2)
print('kernel:',kernel)
#透视变换,这里图像大小参考 pts2
img_pers = cv2.warpPerspective(img_src,kernel,(300,350))
#显示
fig,ax = plt.subplots(1,2)
ax[0].set_title('img_src')
ax[0].imshow(cv2.cvtColor(img_src,cv2.COLOR_BGR2RGB)) #matplotlib 显示图像为 rgb 格式
ax[1].set_title('img_pers')
ax[1].imshow(cv2.cvtColor(img_pers,cv2.COLOR_BGR2RGB))
plt.show()

阈值化

概念

概念

有些场合也称二值化,是图像分割的一种

作用

一般用于将感兴趣区域从背景中区分出来

方法

将每个像素和阈值进行对比,分离出来需要的像素设置为特定白色的 255 或者黑色 0,具体看实际的使用需求而定

threshold()

原型

1
cv2.threshold(src, thresh, maxval, type[, dst]) -> retval, dst

返回值

该方法返回 2 个值

  • 第 1 个值 retval 为阈值
  • 第 2 个值 dst 为阈值化后的图像

参数

  • src:源图像,8bit 或者 32bit 浮点类型,当 type 没有使用 cv2.THRESH_OTSUcv2.THRESH_TRIANGLE 标志时可以是多通道图像

  • thresh:比较的阈值;

  • maxval:阈值化方法为 THRESH_BINARYTHRESH_BINARY_INV 时单个像素转换后的最大值;

  • type:阈值化类型;

    标志 标志值 dst(x,y) 取值 条件 备注
    cv2.THRESH_BINARY 0 maxval if src(x,y)>thresh; 取值只有 2 种,真正意义的 “二值化”
    0 otherwise
    cv2.THRESH_BINARY_INV 1 0 if src(x,y)>thresh;
    maxval otherwise
    cv2.THRESH_TRUNC 2 threshold if src(x,y)>thresh; 最后的取值有多种
    src(x,y) otherwise
    cv2.THRESH_TOZERO 3 src(x,y) if src(x,y)>thresh;
    0 otherwise
    cv2.THRESH_TOZERO_INV 4 0 if src(x,y)>thresh;
    src(x,y) otherwise
    cv2.THRESH_MASK 7 / /
    cv2.THRESH_OTSU 8 / 标志位,使用大津法选择最佳阈值
    cv2.THRESH_TRIANGLE 16 / 标志位,使用三角算法选择最佳阈值

    注意

    cv2.THRESH_OTSU 或 cv2.THRESH_TRIANGLE 的使用比较特殊,并不能单独使用,需要和其他类型的数值按位或一起传入。

    比如这样使用 type=cv2.THRESH_BINARY | cv2.THRESH_OTSU。当然在实际使用中也常见将 cv2.THRESH_OTSUcv2.THRESH_TRIANGLE 这 2 种取值之一和前 5 种 type 相加,比如 type=cv2.THRESH_BINARY + cv2.THRESH_OTSU,这是因 cv2.THRESH_OTSUcv2.THRESH_TRIANGLE 和其他 5 种 type 按位或和相加得到的值是一致的。从上面的对照表也可以看到,如果使用 cv2.THRESH_BINARYcv2.THRESH_BINARY_INV 得到的图像像素值只有 2 种,要么是 maxval,要么是 0,可以称得上真正意义上的 “二值化”。

自适应阈值

原型

1
cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst]) -> dst

参数

  • src:源图像;
  • maxValue:单个像素转换后的最大值;
  • adaptiveMethod:自适应方法:cv2.ADAPTIVE_THRESH_MEAN_C(平均法)或 cv2.ADAPTIVE_THRESH_GAUSSIAN_C(高斯法)
    • cv2.ADAPTIVE_THRESH_MEAN_C(平均法):这时阈值等于窗口大小为 blockSize 的临近像素点的平均值减去 C;
    • cv2.ADAPTIVE_THRESH_GAUSSIAN_C(高斯法):阈值等于窗口大小为 blockSize 的临近像素点高斯窗口互相关加权和减去 C。
  • thresholdType:阈值化类型,THRESH_BINARY 或 THRESH_BINARY_INV 二选一;
  • blockSize:计算某个像素使用的阈值时采用的窗口大小,奇数值;
  • C:平均值或加权平均值减去的常量值;可以为正值,0 或负值;

总结

  • threshold() 方法使用大津法和三角法时不需要指定阈值,会自动根据像素值求出阈值,其他方法则需要指定阈值的大小。
  • threshold() 方法使用全局单一阈值,而 adaptiveThreshold() 方法使用局部阈值,所以在处理光线不均匀图片时能取得更好的

平滑处理

目的

图像在生成、传输或存储过程中可能因为外界干扰产生噪声,从而使图像在视觉上表现为出现一些孤立点或者像素值突然变化的点,图像平滑处理的目的就是 ==为了消除图像中的这类噪声==。

滑动窗口

定义

也叫滤波器模板、kernel。用这个 ksize=3×3 的窗口作用于原始图像上的每一个像素,被这个窗口覆盖的 9 个像素点都参与计算,这样在该像素点上就会得到一个新的像素值,当窗口沿着图像逐个像素进行计算,就会得到一幅新的图像。

滤波器模板的不同就构成了滤波算法的差异:

  • 均值平滑算法中滑动窗口中各个像素点的系数均为 1/(窗口高 * 窗口宽)
  • 高斯平滑中系数和中心点的距离满足高斯分布。

边沿处理

填 0、填 1、复制边沿等

均值平滑

原型

1
dst=cv2.blur(src, ksize[, dst[, anchor[, borderType]]])

参数

  • src:源图像,通道数不限,数据类型必须为 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
  • ksize:kernel 尺寸、窗口大小,二元组类型,元素值可以是偶数或奇数;
  • anchor:锚点,默认为(-1,-1),作用于滑动窗口的中心点;
  • borderType:边界处理类型;

效果

ksize 越大,图像越模糊,清晰度越低。

中值平滑

概念

中值平滑和均值平滑一样也用到了滑动窗口,但是它并不是计算滑动窗口中的某种加权和,而是使用原图像滑动窗口中所有像素值排序后的中值作为新图像的像素值。

原型

1
dst=cv2.medianBlur(src, ksize[, dst])

参数

  • src:源图像,通道数可以是 1,3 或 4,当 ksize 为 3 或者 5 时,数据类型可以是 CV_8U, CV_16U, CV_32F,当使用更大的 ksize 时,数据类型只能是 CV_8U;
  • ksize:kernel 尺寸、窗口大小,整数型,大于 1 的奇数值;

像素值对比

中值后模糊原因

均值和中值都会降低图像变化的程度

小结

平滑处理是图像滤波的一种,可以看做是低通滤波,它会 == 消除图像的高频 “信号”==,让图像看起来更模糊、平滑,通过将变化前后的图像像素值绘制曲线可以更形象地观察到这种平滑效果。

高斯平滑

概念

高斯平滑则 ==根据距离中心点的间距远近其权重会不同==,这种方式看起来更符合” 惯例”:身边的人对你影响会更大。

高斯分布:正态分布

滑动窗口的权重: 正态分布的归一化

原型

1
dst=cv2.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]])

参数

  • src:通道数任意,实际处理是分通道处理;图像深度只能是 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
  • ksize:元组类型,窗口大小,宽度和高度可以不一样,但是必须是正的奇数;如果设置为 0,则根据 sigma 计算得到。
  • sigmaX:图像 X 方向的标准差,对应前述二维高斯分布的σ1;
  • sigmaY:图像 Y 方向的标准差,对应前述二维高斯分布的σ2,如果传入 0,会等于 sigmaX,如果 sigmaX 和 sigmaY 都传入 0,sigmaX 和 sigmaX 则根据 ksize 计算;
  • borderType:边界处理方式;

注意

因为都是以滑动窗口中心点为原点,为了保证中心点 (x,y)=(0,0) 的权重为最大值,所以在 OpenCV 的高斯平滑中 μ1 和μ2 都设置为 0,这样在调用高斯平滑函数时只需要传入σ1(sigmaX)和σ2(sigmaY)。

效果

  • ksize 越大,图像越模糊
  • ksize 保持不变,sigma 越大时,原点的取值越小,周围点的取值更大,对应到图像上中心点的权重越低,周围点权重越高,所以 sigma 越大图像越模糊。

双边平滑

背景

均值、中值、高斯平滑的去躁是一种 “无差别攻击”,所有的像素都受到同一个加权系数的影响,所以在平滑过程中也会影响到图像的边沿(像素值突变的地方 ==???==)

双边滤波则可以在 ==去除噪声的同时又能保持图像的边沿==,也就是传说中的” 去噪保边”。

加权系数

函数原型

1
dst=cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]])

参数

  • src:8bit 或浮点类型;1 或 3 通道;
  • d:窗口大小,如果为非正数,根据 sigmaSpace 计算;d>5 时速度会比较慢,当噪声比较严重时可以选择 d>=9,但是此时不适合对时间敏感的处理;
  • sigmaColor:亮度差的 sigma 参数;
  • sigmaSpace:空间距离的 sigma 参数,同时作用于图像的 X 和 Y(行、列)2 个方向;
  • borderType:边界处理方式;

总结

高斯平滑对比均值和中值平滑其取值更符合 “惯例”,在空间距离上距离越近的像素用来计算新像素的值其权重越大。均值平滑、中值平滑和高斯平滑会对整幅图像实现无差别的平滑,一个固定系数的滑动窗口作用于整个图像,所以平滑后的图像虽然处理掉了噪声,但是边沿部分也会被削弱。而双边平滑在高斯平滑使用的系数基础上乘以像素差值的高斯函数,和中心点像素差值越大整个系数值越小,最后就能达到去躁保边的效果。

形态学变换

形态学概念

形态学变换是基于图像形状的变换过程,通常用来处理二值图像,当然也可以用在灰度图上。

OpenCV 中的形态学变换同平滑处理一样也是基于一种 “滑动窗口” 的操作,不过在形态学变换中 “滑动窗口” 有一个更专业的名词:“结构元”,也可以像平滑处理那样称呼为 kernel。

结构元的形状有方形、十字形、椭圆形等,其形状决定了形态学变换的特点。形态学变换主要有腐蚀、膨胀、开操作、闭操作等等。

结构元生成

结构元生成函数用来生成形态学变换的 kernel 参数。

函数原型

1
cv2.getStructuringElement(shape, ksize[, anchor]) ->retval

参数

  • shape:结构元 (kernel) 的形状;

    shape 属性 形状
    cv2.MORPH_RECT 方形,所有的数值均为 1
    cv2.MORPH_CROSS 十字交叉形,在锚点坐标的水平和竖直方向的元素为 1,其他为 0
    cv2.MORPH_ELLIPSE 椭圆形
  • ksize:结构元 (kernel) 的大小;

  • anchor:锚点,默认使用 (-1,-1) 表示中心点;

不同 kernel 的影响

以膨胀为例来看

  • 使用方形 MORPH_RECT 的结构元时,新图像的边界看起来仍然是方方正正的
  • 使用十字形 MORPH_CROSS 和椭圆形 MORPH_ELLIPSE 的结构元时,边界要显得 “圆滑” 的多。

腐蚀

概念

腐蚀操作可以将边界的白色(前景)像素 “腐蚀” 掉,但仍能保持大部分白色。类似平滑处理的滑动窗口,用某种结构元在图像上滑动,当结构元覆盖原始图像中的所有像素都为 “1” 时,新图像中该像素点的值才为“1”(CV8U 为 255)。腐蚀可以用来 == 去除噪声、去掉“粘连”==。

函数原型

1
cv2.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) ->dst

参数

  • src:通道数任意;图像深度只能是 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
  • kernel:可以由 getStructuringElement() 构建;
  • dst:输出图像,通道数和数据类型同 src;
  • anchor:锚点,默认使用 (-1,-1) 表示中心点;
  • iterations:腐蚀次数;
  • borderType:边界类型;
  • borderValue:边界值;

效果

kernel 的 ksize 越大,iterations 次数越多,图像看起来越 “廋”。

膨胀

概念

膨胀是腐蚀的逆操作,可以将边界的白色(前景)像素 “生长” 扩大。滑动窗口经过白色像素时,只要结构元中有 1 个像素为 “1” 时,新图像中该像素点的值就为“1”(CV8U 为 255)。

== 膨胀可以用来增强连接、填充凹痕。==

函数原型

1
cv2.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) ->dst

参数

  • src:通道数任意;图像深度只能是 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
  • kernel:可以由 getStructuringElement() 构建;
  • dst:输出图像,通道数和数据类型同 src;
  • anchor:锚点,默认使用 (-1,-1) 表示中心点;
  • iterations:膨胀次数;
  • borderType:边界类型;
  • borderValue:边界值;

效果

kernel 的 ksize 越大,iterations 次数越多,图像看起来越 “胖”。

morphologyEx() 函数

函数原型

1
cv2.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) ->dst

参数

  • src:源图像,通道数任意;图像深度只能是 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;其中 op 为 cv2.MORPH_HITMISS 时仅支持 CV_8UC1;

  • op:变换方式;

  • kernel:可以由 getStructuringElement() 构建

    ​ op 为 cv2.MORPH_HITMISS 时则由子图构建;

  • dst:输出图像,通道数和数据类型同 src;

  • anchor:锚点,默认使用 (-1,-1) 表示中心点;

  • iterations:迭代次数;

  • borderType:边界类型;

  • borderValue:边界值;

op 值与 erode、dilate 的关系

形态学变换 op 标志位 与 erode 和 dilate 关系
腐蚀 cv2.MORPH_ERODE dst=erode(src,element)
膨胀 cv2.MORPH_DILATE dst=dilate(src,element)
开操作 cv2.MORPH_OPEN dst=dilate(erode(src,element))
闭操作 cv2.MORPH_CLOSE dst=erode(dilate(src,element))
梯度 cv2.MORPH_GRADIENT dst=dilate(src,element)−erode(src,element)
顶帽 cv2.MORPH_TOPHAT dst=src−open(src,element)
黑帽 cv2.MORPH_BLACKHAT dst=close(src,element)−src
击中击不中 cv2.MORPH_HITMISS dst=erode(src,element) & erode(src,element)

开操作

概念

开操作的实质是 ==先进行腐蚀再膨胀==,可以用来消除小于结构元大小的细小区域

示例

1
img_open = cv2.morphologyEx(img_bin,cv2.MORPH_OPEN,kernel,iterations=1)

闭操作

概念

闭操作实际上是 ==先进行膨胀再腐蚀==,因为膨胀可以用来填充孔洞、修复缺失的连接,但是同时也会导致白色轮廓增大,当用同样的结构元 (kernel) 再进行一次腐蚀操作后,就可以保持外形轮廓和原来的一致。

示例

1
img_close = cv2.morphologyEx(img_bin,cv2.MORPH_CLOSE,kernel,iterations=1)

形态学梯度

概念

形态学梯度操作是用膨胀图像减去腐蚀图像的结果,因为膨胀可以增大边沿,腐蚀会缩小边沿,所以形态学梯度变换就 ==能将轮廓提取出来==

示例

1
img_gradient = cv2.morphologyEx(img_bin,cv2.MORPH_GRADIENT,kernel,iterations=1)

顶帽

概念

顶帽变换是用原图减去开操作图像,因为开操作会去除小于结构元的小区域,原图减去开操作图像后,会 ==将开操作去除的小区域保留下来==

示例

1
img_tophat = cv2.morphologyEx(img_bin,cv2.MORPH_TOPHAT,kernel,iterations=1)

黑帽

概念

黑帽变换和顶帽变换则相反,是将闭操作后的图像减去原图,因为闭操作会填充孔洞(小的黑色区域),孔洞部分变成白色,而原图中仍然为黑色,这样就会 ==将原图中的孔洞保留下来并变为白色区域==。

示例

1
img_blackhat = cv2.morphologyEx(img_bin,cv2.MORPH_BLACKHAT,kernel,iterations=1)

击中不击中

概念

击中击不中变换可以 ==用来在原图中查找子图==,假设要查找的图像中包含了多种子图,可以利用某个子图构造出 kernel,经过击中击不中变换就能在该子图中心保留一个非零的点。

** 注意:** 这里构造 kernel 不再是使用 getStructuringElement(),而是需要用子图构造。

一个构造 kernel 的例子如下,首先从子图中读取图像,然后和要做变换的原图做一样的阈值化,接下来构造一个和子图大小一样类型为 np.int8 型的 kernel,其中子图阈值化后值为 255 的位置设置为 1,阈值化后值为 0 的位置设置为 - 1:

1
2
3
4
5
6
#构建 kernel
img_kernel = cv2.imread('..\\samples\\picture\\hitmiss-kernel.bmp',cv2.IMREAD_GRAYSCALE)
_,img_kernel_bin = cv2.threshold(img_kernel,193,255,1) #阈值化,阈值和要做变换的原图一致
kernel = img_kernel.astype(np.int8) #构造一个和子图大小但是类型为 int8 型,可以保存负数
kernel[img_kernel_bin == 255] = 1
kernel[img_kernel_bin == 0] = -1

步骤

  1. 读取原图并进行阈值化
  2. 按照前面的方法构建 kernel
  3. 用 morphologyEx() 进行击中击不中变换
  4. 用 findNonZero() 查找非零点并用 circle() 绘图显示出来

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## 构建 kernel
img_kernel = cv2.imread('..\\samples\\picture\\hitmiss-kernel.bmp',cv2.IMREAD_GRAYSCALE)
# 阈值化,阈值和要做变换的原图一致
_,img_kernel_bin = cv2.threshold(img_kernel,193,255,1)
# 构造一个和子图大小但是类型为 int8 型,可以保存负数
kernel = img_kernel.astype(np.int8)
kernel[img_kernel_bin == 255] = 1
kernel[img_kernel_bin == 0] = -1

## 击中击不中变换
img_hitmiss = cv2.morphologyEx(img_src_bin,cv2.MORPH_HITMISS ,kernel,iterations=1)
print('countNonZero(img_hitmiss):',cv2.countNonZero(img_hitmiss))

# 绘制中心点
locations = cv2.findNonZero(img_hitmiss)
img_hitmiss_color = cv2.cvtColor(img_hitmiss,cv2.COLOR_GRAY2BGR)
if locations is not None:
print(type(locations), locations.shape ,locations)
print('locations:',locations[0][0]) # 第 2 个 [0] 固定,第 1 个 [0] 表示找到位置的个数
center=locations[0][0][0],locations[0][0][1]
cv2.circle(img_hitmiss_color,center, 15, (0,255,255), 5)
else:
print('未击中')

图像金字塔

概念

图像金字塔是一些列图像的几何,如下图所示。更高层图像尺寸更小,更底层图像尺寸更大,看起来就像是一个金字塔一样

pyrDown

概念

这里的 down 是指图像变小,所以原始图像在金字塔的底部。

步骤

  1. 将当前图像和高斯核卷积:

    【Note】:这个高斯核的尺寸为 5×5 大小,所有元素的值加起来正好为 256,最后再除以 256,得到的加权和正好为 1。其距离最中心越近数值越大,这正好和高斯平滑选择的高斯核类似。这个过程也类似于高斯平滑。

函数原型

1
cv2.pyrDown(src[, dst[, dstsize[, borderType]]]) ->dst

参数

  • src:源图像;
  • dst:目标图像;
  • dstsize:缩放后目标图像的尺寸,必须满足 std::abs(dsize.width2 – ssize.width) <= 2 && std::abs(dsize.height2 – ssize.height) <= 2
  • borderType:边界填充类型;

行为

经过 pyrDown() 处理的图像变得更加模糊 (平滑)。然后移除偶数行和偶数列,然后就能得到和原图相比是原图 1/4 大小的新的图像,在图像金字塔中就位于当前层的上一层。

pyrUp

概念

将图像的尺寸变大,所以原始图像位于图像金字塔的顶层。

函数原型

1
cv2.pyrUp(src[, dst[, dstsize[, borderType]]]) ->dst

参数

  • src:源图像;
  • dst:目标图像;
  • dstsize:缩放后目标图像的尺寸,必须满足 std::abs(dsize.width – ssize.width2) ==dsize.width % 2 && std::abs(dsize.height – ssize.height2)== dsize.height % 2
  • borderType:边界填充类型;

图像梯度

概念

高斯平滑、双边平滑均值平滑、中值平滑 介绍的平滑处理可以看做是图像的 “低通滤波”,它会滤除掉图像的“高频” 部分,使图像看起来更平滑。

图像梯度则可以看做是对图像进行 “高通滤波”,他会滤除图像中的低频部分,==为的是凸显图像的突变部分==

sobel

函数原型

1
dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])

参数

  • src:源图像;
  • ddepth:目标图像深度;
  • dx:x 方向求导阶数;
  • dy:y 方向求导阶数;
  • dst:目标图像;
  • ksize:kernel 尺寸,说明文档上指出必须是 1,3,5,7 中的一个,但是实验可以得到应该是小于 31 的正奇数;如果是 - 1 表示 scharr 滤波;
  • scale:缩放比例,默认为 1;
  • delta:叠加值,默认为 0;
  • borderType:边界填充类型;

效果

dx 与 dy 取值不同的效果

(dx, dy) 效果
(1, 0) 凸显 x 方向的梯度变化
(0, 1) 凸显 y 方向的梯度变化
(0.5, 0.5) 凸显边沿
(1, 1) dx 和 dy 都为 1 表明是与的关系,只有 x 和 y 方向都有梯度的时候才能被检测出来。

ksize 的取值不同

  • ksize 的值越大,梯度信息呈现的越多
  • ksize 相同,dx 的值越小,梯度的细节越多

scharr

背景

当 kernel 的尺寸为 3×3 时,Sobel 计算的结果不是很精确,为了得到更精确的计算结果常采用下图所示的 Scharr kernel

概念

Scharr 变换可以看做是使用了 Scharr 核的 Sobel 变换,是一种经过改进的 Sobel 变换,同样也要区分 x 和 y 方向分开计算梯度。

函数原型

1
dst = cv2.Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]])

参数

  • src:源图像;
  • ddepth:目标图像深度;
  • dx:x 方向求导阶数;
  • dy:y 方向求导阶数;
  • dst:目标图像;
  • scale:缩放比例,默认为 1;
  • delta:叠加值,默认为 0;
  • borderType:边界填充类型;

Notes

  • scharr() 没有 ksize 参数, scharrkernel 大小固定为 $3 \times 3$

  • dx 和 dy 要满足如下的关系,否则会抛异常

    CV_Assert(dx>= 0 && dy >= 0 && dx + dy == 1 )

    也就是每次只能求 x 方向或者 y 方向单个方向的梯度,而且只能求一阶梯度,不像 Sobel() 中 dx 或 dy 可以设置为更大的值计算更高阶的梯度。

Laplacian

概念

Laplacian 变换是对图像求二阶导数,下图是 2 种 $3 \times 3$ 尺寸的 kernel,这里 ksize 是 Laplacian() 的入参名称

Notes

  • Laplacian() 变换不需要区分图像的 x 和 y 方向计算梯度,从上图的 2 种 kernel 也可以看到其 x 和 y 方向是对称的。

  • 在 Laplacian() 变换中,==ksize 必须是小于 31 的正奇数 ==

    当 ksize 等于 1 时,这时 kernel 的尺寸大小并非是 1,其实际尺寸仍然为 3×3 (看源码)

函数原型

1
dst = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])

参数

  • src:源图像;
  • ddepth:目标图像深度;
  • dst:目标图像;
  • ksize:kernel 尺寸,小于 31 的正奇数;如果为 1 仍然是一个 3×3 的 kernel;
  • scale:缩放比例,默认为 1;
  • delta:叠加值,默认为 0;
  • borderType:边界填充类型;

Notes

  • Laplacian 变换中没有 dx 或 dy 参数,因为 Laplacian 是对图像求二阶导数。

效果

  • Laplacian() 中的 ksize 越大,梯度信息越丰富
  • 在相同的 ksize 时,二阶 Sobel 变换和 Laplacian 变换对比看,Laplacian 变换取得的梯度信息要更明显一些。

边沿检测

概念

图像梯度反应的是图像像素值的变化过程,不管变化大小都考虑在内,所以 Sobel, Laplacian 变换得到的是一个多级灰度图。

边沿洁厕也昆虫看做是图像梯度的一种延伸,不过边沿检测更注意图像的 “边沿部分”,图像梯度变化较小的部分会被忽略,只有较大变化的部分保留下来

Canny

概念

canny 边沿检测有低错误率、很好地定位边缘点、单一的边缘点响应等优点

步骤

  1. 高斯滤波器平滑输入图像;
  2. 计算梯度幅值图像和角度方向;
  3. 对梯度幅值图像应用非最大值抑制;
  4. 用双阈值处理和连接分析检测和连接边沿。

非极大值抑制

抑制非极大值的目标(去冗余),从而搜索出局部极大值的目标(找最优)

函数原型

函数原型1

1
edges=cv2.Canny(image, threshold1, threshold1[, edges[, apertureSize[, L2gradient]]])

参数

  • image:8bit源图像,可以是单通道或多通道;
  • threshold1:迟滞阈值1;
  • threshold2:迟滞阈值2,和threshold1没有大小要求,函数内部会调整交换;
  • edges:目标图像,二值图像;
  • apertureSize:kernel尺寸,默认为3;
  • L2gradient:是否使用L2范式,如果设置为True,计算梯度时使用的是2个方向梯度的平方和开平方,如果设置为False,则使用2个方向梯度的绝对值的和;

函数原型2

1
edges=cv2.Canny(dx, dy, threshold1, threshold2[, edges[, L2gradient]])

参数

  • dx:源图像的16bit(CV_16SC1 or CV_16SC3) x方向梯度图像;
  • dy:源图像的16bit(CV_16SC1 or CV_16SC3) y方向梯度图像;
  • threshold1:迟滞阈值1;
  • threshold2:迟滞阈值2;
  • edges:目标图像;
  • L2gradient:是否使用L2范式,如果设置为True,计算梯度时使用的是2个方向梯度的平方和开平方,如果设置为False,则使用2个方向梯度的绝对值的和;

注意

  • 第2种接口形式和第1种实现边沿检测的效果是一样的
  • 第2种形式需要先计算图像的x和y方向的梯度,所以计算梯度的kernel尺寸在第2种接口中就不需要了

ksize差异

  • 相同的threshold值,ksize越大边沿细节越多

    这点和Sobel(),Scharr(),Laplacian()计算图像梯度效果是一样的。

迟滞阈值

即: $\frac{threshold1}{2}$

2个迟滞阈值在函数内部会进行比较,较小者存入low_thresh

阈值宽度

即: $\left | threshold1 - threshold2 \right | $

效果

相同的ksize时,threshold1和threshold2的差值越小,边沿细节越多

像素值

  • canny() 变换后的边沿图像用直方图显示,可以看到变换后的图像是一个二值图像, 像素的取值为0或者255
  • Sobel()、Laplacian() 等梯度变换得到的是一个灰度图

小结

  • Canny() 变换中相同的 threshold 值,ksize 越大边沿细节越多;
  • threshold1 和 threshold2 的差值越小,边沿细节越多;
  • Canny() 变换后得到的是一个二值图像;
  • Canny() 第 2 种接口形式得到的图像效果和第 1 种相比几乎没有差别,因为需要先计算图像 x 和 y 方向的梯度图像,使用起来更繁杂些。

统计函数

非 0 值数量

函数原型

1
cv2.countNonZero(src) -> retval

参数

  • src:输入图像,必须为单通道图像;
  • retval:非零像素值个数

功能

countNonZero()用来统计元素值为非0值的像素点个数。

最小最大值及其位置

函数原型

1
cv2.minMaxLoc(src[, mask])->minVal, maxVal, minLoc, maxLoc

功能

minMaxLoc() 函数返回图像中的元素值的最小值和最大值,以及最小值和最大值的坐标。

参数

  • src:输入图像,必须为单通道图像;
  • mask:掩码;
  • minVal, maxVal, minLoc, maxLoc:依次为最小值,最大值,最小值的坐标,最大值的坐标;

返回值

返回 minLoc 和 maxLoc 的坐标位置是以 OpenCV 中 (x,y) 的形式组织的,但是在 numpy 中下标访问是按照 array[行][列] 形式,类似于 array[y][x] 的形式,所以 minLoc 和 maxLoc 的坐标值不能直接用于 numpy 的下标访问,需要对调后才可以使用

注意

  • minMaxLoc()内部是按照行扫描方式,如果找到一个最小值,后面没有比这个数值更小的数值,那最小值的位置就是最开始出现的那个位置,即使后面出现了这个最小数值相等的数值,找最大值也一样

元素值之和

函数原型

1
cv2.sumElems(src) -> retval

功能

sumElems() 统计所有元素值之和,如果有多通道,分通道计算,返回的是一个四元组,依次对应图像可能包含的第 0,1,2,3 通道,如果单通道图像则只有下标 0 对应的元素有意义,如果是 3 通道则只有前 3 个元素有意义。

参数

  • src:输入图像,可以是单通道,3通道或4通道图像;
  • retval:返回的是一个4元组,分别对应各通道元素的和。

平均值

函数原型

1
cv2.mean(src[, mask]) ->retval

功能

mean() 用来统计单个通道内像素值的平均值,如果有多个通道,分通道计算。

参数

  • src:输入图像,可以是单通道,3 通道或 4 通道图像;
  • mask:可选的掩码;

平均值与标准差

函数原型

1
cv2.meanStdDev(src[, mean[, stddev[, mask]]]) ->mean, stddev

功能

meanStdDev() 用来统计单通道内像素值的平均值和标准差,一次调用返回 2 个结果。

参数

  • src:输入图像,必须为单通道图像;
  • mask:可选的掩码;
  • mean:平均值;
  • stddev:标准差;
  • meanStdDev() 返回的是一个元组,下标 0 为平均值 mean,下标 1 为标准差 stddev。

单行/列的极值、和、均值

函数原型

1
cv2.reduce(src, dim, rtype[, dst[, dtype]]) ->dst

功能

reduce 用来统计二维数组的每一行或每一列中的最小值、最大值、平均值、和。这里 reduce 的含义也可以理解为将二维矩阵压缩成一维向量,压缩后的值根据入参类型可以是最小值、最大值、平均值或者和。

参数

  • src:源图像,可以是单通道也可以是多通道,多通道时分通道计算;
  • dim:如果为 0 表示统计每列的数据等价于压缩成行(row),如果为 1 表示统计每行的数据等价于压缩成列(column);
  • rtype:reduce 操作的类型;
  • dst:目标图像;
  • dtype:目标图像的类型,如果不指定默认为 - 1 表示用源图像 src 的数据类型;
  • dim 参数的理解:如果为 0 表示生成新的数据将是一个行向量,所以是在每一列上操作,将单个的列压缩成一个数值从而组成一个行向量;如果为 1 则表示生成新的数据是一个列向量,在每一行上操作,将单个的行压缩成一个数值从而组成一个列向量。

概述

Linux 简介

Linux 内核最初只是由芬兰人林纳斯·托瓦兹(Linus Torvalds)在赫尔辛基大学上学时出于个人爱好而编写的。

Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX(可移植操作系统接口) 和 UNIX多用户多任务支持多线程CPU 的操作系统。

Linux 能运行主要的 UNIX 工具软件、应用程序和网络协议。它支持 32 位和 64 位硬件。Linux 继承了 Unix网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

Linux 发行版

Linux 的发行版说简单点就是将 Linux 内核与应用软件做一个打包。

目前市面上较知名的发行版有:UbuntuRedHatCentOSDebianFedoraSuSEOpenSUSEArch LinuxSolusOSkali(安全渗透测试使用) 等。

Linux-distribution

今天各种场合都有使用各种 Linux 发行版,从嵌入式设备到超级计算机,并且在服务器领域确定了地位,通常服务器使用 LAMPLinux + Apache + MySQL + PHP)或 LNMPLinux + Nginx+ MySQL + PHP)组合。

目前 Linux 不仅在家庭与企业中使用,并且在政府中也很受欢迎。

  • 巴西联邦政府由于支持 Linux 而世界闻名。
  • 有新闻报道俄罗斯军队自己制造的 Linux 发布版的,做为 G.H.ost 项目已经取得成果。
  • 印度的 Kerala 联邦计划在向全联邦的高中推广使用 Linux
  • 中华人民共和国为取得技术独立,在龙芯处理器中排他性地使用 Linux
  • 在西班牙的一些地区开发了自己的 Linux 发布版,并且在政府与教育领域广泛使用,如 Extremadura 地区的 gnuLinEx 和 Andalusia 地区的 Guadalinex
  • 葡萄牙同样使用自己的 Linux 发布版 Caixa Mágica,用于 Magalh?es 笔记本电脑和 e-escola 政府软件。
  • 法国和德国同样开始逐步采用 Linux

Linux vs Windows

环境搭建

Linux 的安装,安装步骤比较繁琐(操作系统本身也是一个软件),现在其实云服务器挺普遍的,价格也便宜,如果直接不想搭建,也可以直接买一台学习用用!

安装CentOS(不推荐)

虚拟机安装,耗资源,在本地安装,这个不建议;

Linux 是一个操作系统,你也可以把自己电脑安装成双系统!建议使用虚拟机(VM

  1. 可以通过镜像进行安装!下载地址教程

  2. 可以使用已经制作好的镜像!CentOS7网盘地址:链接,提取码:76x5,登录密码: 123456

  3. VMware 虚拟机软件,然后打开镜像即可使用!

  4. 安装完成后,有如下界面:

  5. VMware 使用方式

    • 点击屏幕进入虚拟机;
    • ctrl+alt将聚焦退出虚拟机;

购买云服务器(推荐)

虚拟机安装后占用空间,也会有些卡顿,我们作为程序员其实可以选择购买一台自己的服务器,这样的话更加接近真实线上工作;

  1. 阿里云购买服务器:点击这里

  2. 购买完毕后,获取服务器的ip地址,重置服务器密码,就可以远程登录了

  3. 下载 xShell 工具,进行远程连接使用!连接成功效果如下:

注意事项

如果要打开端口,需要在阿里云的安全组面板中开启对应的出入规则,不然的话会被阿里拦截!

新手

如果前期不好操作,可以推荐安装宝塔面板,傻瓜式管理服务器

安装教程:点击这里

  1. 开启对应的端口

  2. 一键安装

  3. 安装完毕后会得到远程面板的地址,账号,密码,就可以登录了

  4. 登录之后就可以可视化的安装环境和部署网站!

走近Linux系统

开机登录

开机会启动许多程序。它们在Windows叫做”服务“(service),在Linux就叫做”守护进程“(daemon)。

开机成功后,它会显示一个文本登录界面,这个界面就是我们经常看到的登录界面,在这个登录界面中会提示用户输入用户名,而用户输入的用户将作为参数传给login程序来验证用户的身份,密码是不显示的,输完回车即可!

一般来说,用户的登录方式有三种:

  • 命令行登录
  • ssh登录
  • 图形界面登录

最高权限账户为 root,可以操作一切!

关机

linux领域内大多用在服务器上,很少遇到关机的操作。毕竟服务器上跑一个服务是永无止境的,除非特殊情况下,不得已才会关机。

关机指令为:shutdown

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sync # 将数据由内存同步到硬盘中。

shutdown # 关机指令,你可以man shutdown 来看一下帮助文档。例如你可以运行如下命令关机:

shutdown –h 10 # 这个命令告诉大家,计算机将在10分钟后关机

shutdown –h now # 立马关机

shutdown –h 20:25 # 系统会在今天20:25关机

shutdown –h +10 # 十分钟后关机

shutdown –r now # 系统立马重启

shutdown –r +10 # 系统十分钟后重启

reboot # 就是重启,等同于 shutdown –r now

halt # 关闭系统,等同于shutdown –h now 和 poweroff

最后总结一下,不管是重启系统还是关闭系统,首先要运行 sync 命令,把内存中的数据写到磁盘中。

系统目录结构

登录系统后,在当前命令窗口下输入命令:

1
ls /

你会看到如下图所示:

树状目录结构:(Linux的一切资源都挂载在这个 / 根节点下)

目录 说明 备注
/bin binBinary的缩写, 这个目录存放着最经常使用的命令。 不要动
/boot 这里存放的是启动Linux时使用的一些核心文件,包括一些连接文件以及镜像文件。 不要动
/dev devDevice(设备)的缩写, 存放的是Linux的外部设备,在Linux中访问设备的方式和访问文件的方式是相同的。 挂载第三方设备
/etc 这个目录用来存放所有的系统管理所需要的配置文件和子目录。
/home 用户的主目录,在Linux中,每个用户都有一个自己的目录,一般该目录名是以用户的账号命名的。
/lib 这个目录里存放着系统最基本的动态连接共享库,其作用类似于Windows里的DLL文件。 不要动
/lost+found 这个目录一般情况下是空的,当系统非法关机后,这里就存放了一些文件。 存放突然关机的一些文件
/media linux系统会自动识别一些设备,例如U盘、光驱等等,当识别后,Linux会把识别的设备挂载到这个目录下。
/mnt 系统提供该目录是为了让用户临时挂载别的文件系统的,我们可以将光驱挂载在/mnt/上,然后进入该目录就可以查看光驱里的内容了。 挂载第三方设备,我们一般会把一些本地文件挂载在这个目录下
/opt 这是给主机额外安装软件所摆放的目录。比如你安装一个ORACLE数据库则就可以放到这个目录下。默认是空的。
/proc 这个目录是一个虚拟的目录,它是系统内存的映射,我们可以通过直接访问这个目录来获取系统信息。 不用管
/root 该目录为系统管理员,也称作超级权限者的用户主目录。
/sbin s就是Super User的意思,这里存放的是系统管理员使用的系统管理程序。
/srv 该目录存放一些服务启动之后需要提取的数据。
/sys 这是linux2.6内核的一个很大的变化。该目录下安装了2.6内核中新出现的一个文件系统 sysfs
/tmp 这个目录是用来存放一些临时文件的。 用完即丢的文件,可以放在这个目录下,如:安装包
/usr 这是一个非常重要的目录,用户的很多应用程序和文件都放在这个目录下,类似于windows下的program files目录。
/usr/bin 系统用户使用的应用程序。
/usr/sbin 超级用户使用的比较高级的管理程序和系统守护程序。
/usr/src 内核源代码默认的放置目录。
/var 这个目录中存放着在不断扩充着的东西,我们习惯将那些经常被修改的目录放在这个目录下。包括各种日志文件。
/run 是一个临时文件系统,存储系统启动以来的信息。当系统重启时,这个目录下的文件应该被删掉或清除。
/www 存放服务器网站相关的资源,如:环境、网站项目等;

补充

  • 很多的大型项目都是部署在 Linux 服务器上;
  • 社会的生存法则:优胜劣汰;
  • 服务器都是使用命令行的,我们也是基于命令行来学习的;
  • Linux 中没有错误就代表操作成功;
  • 一切皆文件;
  • 根目录: /,所有的文件都挂载在这个节点下;

目录解释

  • /mnt: 后面会把一些本地文件挂载在这个目录下
  • /etc:配置文件:
  • /home
  • lost+found:存放 突然关机的一些文件
  • /opt:安装额外的软件
  • /root
  • /tmp:用完即丢

常用的基本命令

目录管理

  • 绝对路径:路径的全称,如:D:\anaconda3\condabin\xxx.xxx
  • 相对路径:比如说在 condabin 目录下,那这个 xxx.xxx 文件,对应我们的相对位置就是 ./xxx.xxx

cd:切换目录命令

  • cd 目录名:绝对路径:都是以 / 开头;相对路径:对于当前目录该如何寻找 .././

  • ./:当前目录

  • cd ..:返回上一级目录

ls:列出目录(常常被使用)

参数:

  • -a参数:all,查看全部文件,包括隐藏文件
  • -l参数:列出所有文件,包含文件的属性和权限,没有隐藏文件

实践:

pwd:显示当前用户所在的目录

mkdir:创建一个目录

rmdir:删除目录

  • rmdir 仅能删除空的目录,如果下面存在文件,需要先删除文件;递归删除多个目录加入 -p 参数即可

cp:复制文件或者目录

  • cp 原来的文件 目标目录或者目标目录/要改成的文件名

rm:移除文件或者目录!

参数:

  • -f:忽略不存在的文件,不会出现警告,强制删除;
  • -r:递归删除目录;
  • -i:互动,删除询问是否删除;

实践:

  • rm -rf /:系统中所有的文件就被删除了(删库跑路)

mv:移动文件或者目录;重命名文件

参数:

  • -f:强制
  • -u:只替换已经更新过的文件

实践:

基本属性

文件属性

root > user

Linux系统是一种典型的多用户系统,不同的用户处于不同的地位,拥有不同的权限。为了保护系统的安全性,Linux系统对不同的用户访问同一文件(包括目录文件)的权限做了不同的规定。

在Linux中我们可以使用ll或者ls –l命令来显示一个文件的属性以及文件所属的用户和组,如:

实例中,boot文件的第一个属性用”d“表示。”d“在Linux中代表该文件是一个目录文件。

Linux中第一个字符代表这个文件是目录、文件或链接文件等等:

  • 当为[ d ]则是目录
  • 当为[ - ]则是文件;
  • 若是[ l ]则表示为链接文档 ( link file )(类似于window的快捷方式);
  • 若是[ b ]则表示为装置文件里面的可供储存的接口设备 ( 可随机存取装置 );
  • 若是[ c ]则表示为装置文件里面的串行端口设备,例如键盘、鼠标 ( 一次性读取装置 )。

接下来的字符中,以三个为一组,且均为『rwx』 的三个参数的组合。

其中,[ r ]代表可读(read)、[ w ]代表可写(write)、[ x ]代表可执行(execute)。

要注意的是,这三个权限的位置不会改变,如果没有权限,就会出现减号[ - ]而已。

每个文件的属性由左边第一部分的10个字符来确定(如下图):

从左至右用0-9这些数字来表示。

第0位确定文件类型,第1-3位确定属主(该文件的所有者)拥有该文件的权限。第4-6位确定属组(所有者的同组用户)拥有该文件的权限,第7-9位确定其他用户拥有该文件的权限。

其中:

1、4、7位表示读权限,如果用”r“字符表示,则有读权限,如果用”-“字符表示,则没有读权限;

2、5、8位表示写权限,如果用”w“字符表示,则有写权限,如果用”-“字符表示没有写权限;

3、6、9位表示可执行权限,如果用”x“字符表示,则有执行权限,如果用”-“字符表示,则没有执行权限。

对于文件来说,它都有一个特定的所有者,也就是对该文件具有所有权的用户。

同时,在Linux系统中,用户是按组分类的,一个用户属于一个或多个组。

文件所有者以外的用户又可以分为文件所有者的同组用户和其他用户。

因此,Linux系统按文件所有者、文件所有者同组用户和其他用户来规定了不同的文件访问权限。

在以上实例中,boot 文件是一个目录文件,属主和属组都为 root

个人理解

  • 属主:这个文件/目录属于那个用户的

  • 属组:这个文件/目录属于哪个组的(属主不一定在这个组内)

修改文件属性

chgrp

更改文件属组

1
chgrp [-R] 属组名 文件名

-R:递归更改文件属组,就是在更改某个目录文件的属组时,如果加上-R的参数,那么该目录下的所有文件的属组都会更改。

chown

更改文件属主,也可以同时更改文件属组

1
2
chown [–R] 属主名 文件名
chown [-R] 属主名:属组名 文件名

chmod(必须要掌握)

更改文件9个属性

遇到:“你没有权限操作此文件!”,就要使用 chmod

1
chmod [-R] xyz 文件或目录

Linux文件属性有两种设置方法,一种是数字(常用的是数字),一种是符号。

Linux文件的基本权限就有九个,分别是owner/group/others三种身份各有自己的read/write/execute权限。

先复习一下刚刚上面提到的数据:文件的权限字符为:『-rwxrwxrwx』, 这九个权限是三个三个一组的!其中,我们可以使用数字来代表各个权限,各权限的分数对照表如下:

1
r:4     w:2         x:1

每种身份(owner/group/others)各自的三个权限(r/w/x)分数是需要累加的,例如当权限为:[-rwxrwx---] 分数则是:

  • owner = rwx = 4+2+1 = 7
  • group = rwx = 4+2+1 = 7
  • others= --- = 0+0+0 = 0
1
chmod 770 filename

实例

权限 命令 对应数字 命令
可读可写不可执行 rw- 6
可读可写可执行 rwx 7
文件赋予所有用户可读可写可执行权限! rwxrwxrwx 777 chmod 777 filename

内容查看

概述

Linux系统中使用以下命令来查看文件的内容:

命令 说明 备注 示例
cat 由第一行开始显示文件内容 用来读文章,或者读取配置文件,都使用 cat 命令
tac 从最后一行开始显示,可以看出 taccat 的倒着写! image-20210605164825483
nl 显示的时候,顺道输出行号! image-20210605164854211
more 一页一页的显示文件内容,带余下内容的 空格:翻页;enter:向下看一行;:f 显示行号 image-20210605165154807
less more 类似,但是比 more 更好的是,他可以往前翻页! 空格:翻页;上下键:滚动行;pageDown, pageUp:翻动页面;q:退出;/字符串:向下查询字符串;?字符串:向上查询字符串;n/N:向下/向上查找下一个 image-20210605165847793
head 只看头几行 通过 -n 参数来控制显示几行 image-20210605165921635
tail 只看尾巴几行 head image-20210605170013384

你可以使用 *man [命令]*来查看各个命令的使用文档,如 :man cp

  • 网络配置目录:/etc/sysconfig/network-scripts/

  • 默认网络配置文件:

  • 查看网络配置:

    • win : ipconfig
    • linux : ifconfig

cat

由第一行开始显示文件内容

语法:

1
cat [-AbEnTv]

选项与参数:

  • -A :相当於 -vET 的整合选项,可列出一些特殊字符而不是空白而已;
  • -b :列出行号,仅针对非空白行做行号显示,空白行不标行号!
  • -E :将结尾的断行字节 $ 显示出来;
  • -n :列印出行号,连同空白行也会有行号,与 -b 的选项不同;
  • -T :将 [tab] 按键以 ^I 显示出来;
  • -v :列出一些看不出来的特殊字符

测试:

1
2
3
4
5
# 查看网络配置: 文件地址 /etc/sysconfig/network-scripts/
[root@qeuroal ~]# cat /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
BOOTPROTO=dhcp
ONBOOT=yes

tac

taccat命令刚好相反,文件内容从最后一行开始显示,可以看出 taccat 的倒着写!如:

1
2
3
4
[root@qeuroal ~]# tac /etc/sysconfig/network-scripts/ifcfg-eth0
ONBOOT=yes
BOOTPROTO=dhcp
DEVICE=eth0

nl

显示行号

语法:

1
nl [-bnw] 文件

选项与参数:

  • -b :指定行号指定的方式,主要有两种:-b a :表示不论是否为空行,也同样列出行号(类似 cat -n);-b t :如果有空行,空的那一行不要列出行号(默认值);
  • -n :列出行号表示的方法,主要有三种:-n ln :行号在荧幕的最左方显示;-n rn :行号在自己栏位的最右方显示,且不加 0-n rz :行号在自己栏位的最右方显示,且加 0
  • -w :行号栏位的占用的位数。

测试:

1
2
3
4
[root@qeuroal ~]# nl /etc/sysconfig/network-scripts/ifcfg-eth0
1DEVICE=eth0
2BOOTPROTO=dhcp
3ONBOOT=yes

more

一页一页翻动

在 more 这个程序的运行过程中,你有几个按键可以按的:

  • 空白键 (space):代表向下翻一页;
  • Enter :代表向下翻『一行』;
  • /字串 :代表在这个显示的内容当中,向下搜寻『字串』这个关键字;
  • :f :立刻显示出档名以及目前显示的行数;
  • q :代表立刻离开 more ,不再显示该文件内容。
  • b[ctrl]-b :代表往回翻页,不过这动作只对文件有用,对管线无用。
1
2
3
[root@qeuroal etc]# more /etc/csh.login
....(中间省略)....
--More--(28%) # 重点在这一行喔!你的光标也会在这里等待你的命令

less

一页一页翻动,以下实例输出/etc/man.config文件的内容:

less运行时可以输入的命令有:

  • 空白键 :向下翻动一页;
  • [pagedown]:向下翻动一页;
  • [pageup] :向上翻动一页;
  • /字串 :向下搜寻『字串』的功能;
  • ?字串 :向上搜寻『字串』的功能;
  • n :重复前一个搜寻 (与 /? 有关!)
  • N :反向的重复前一个搜寻 (与 /? 有关!)
  • q :离开 less 这个程序;
1
2
3
[root@qeuroal etc]# more /etc/csh.login
....(中间省略)....
: # 这里可以等待你输入命令!

取出文件前面几行

语法:

1
head [-n number] 文件

选项与参数:**-n** 后面接数字,代表显示几行的意思!

默认的情况中,显示前面 10 行!若要显示前 20 行,就得要这样:

1
[root@qeuroal etc]# head -n 20 /etc/csh.login

tail

取出文件后面几行

语法:

1
tail [-n number] 文件

选项与参数:

  • -n :后面接数字,代表显示几行的意思

默认的情况中,显示最后 10 行!若要显示最后 20 行,就得要这样:

1
[root@qeuroal etc]# tail -n 20 /etc/csh.login

拓展:Linux 链接概念(了解即可)

Linux 链接分两种:

  • 硬链接(Hard Link):A------B:假设BA的硬链接,那么他们两个指向了同一个文件,允许一个文件拥有多个路径,用户可以通过这种机制建立硬链接到一些重要文件上,防止误删!
  • 符合链接(Symbolic Link)或叫软链接:类似 windows 下的快捷方式,即删除了源文件,快捷方式也访问不了。

命令

  • ln 命令产生硬链接;
  • touch 创建文件;
  • echo 输入字符串,也可以输入到文件中;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@iZbp16w4b9baac6xlzfcdmZ home]# ls
admin
[root@iZbp16w4b9baac6xlzfcdmZ home]# touch f1 # 创建一个f1文件
[root@iZbp16w4b9baac6xlzfcdmZ home]# ls
admin f1
[root@iZbp16w4b9baac6xlzfcdmZ home]# ln f1 f2 # 创建一个硬链接f2
[root@iZbp16w4b9baac6xlzfcdmZ home]# ls
admin f1 f2
[root@iZbp16w4b9baac6xlzfcdmZ home]# ln -s f1 f3 # 创建一个软链接(符号链接)f3
[root@iZbp16w4b9baac6xlzfcdmZ home]# ls
admin f1 f2 f3
[root@iZbp16w4b9baac6xlzfcdmZ home]# echo "hello" >> f1 # 给f1文件中写入一段字符串
[root@iZbp16w4b9baac6xlzfcdmZ home]# ls
admin f1 f2 f3
[root@iZbp16w4b9baac6xlzfcdmZ home]# cat f1
hello
[root@iZbp16w4b9baac6xlzfcdmZ home]# cat f2
hello
[root@iZbp16w4b9baac6xlzfcdmZ home]# ls
admin f1 f2 f3
[root@iZbp16w4b9baac6xlzfcdmZ home]# cat f3
hello

删除f1后,查看 f2f3 的区别:

1
2
3
4
5
6
7
[root@iZbp16w4b9baac6xlzfcdmZ home]# rm -rf f1
[root@iZbp16w4b9baac6xlzfcdmZ home]# ls
admin f2 f3
[root@iZbp16w4b9baac6xlzfcdmZ home]# cat f2 # f2硬链接还在
hello
[root@iZbp16w4b9baac6xlzfcdmZ home]# cat f3 # f3(软连接、符号链接)快捷方式失效
cat: f3: No such file or directory

注意:只要源文件发生改变,链接文件也会发生改变

硬连接

硬连接指通过索引节点来进行连接。在 Linux 的文件系统中,保存在磁盘分区中的文件不管是什么类型都给它分配一个编号,称为索引节点号(Inode Index)。在 Linux 中,多个文件名指向同一索引节点是存在的。比如:AB 的硬链接(AB 都是文件名),则 A 的目录项中的 inode 节点号与 B 的目录项中的 inode 节点号相同,即一个 inode 节点对应两个不同的文件名,两个文件名指向同一个文件,AB 对文件系统来说是完全平等的。删除其中任何一个都不会影响另外一个的访问。

硬连接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬连接到重要文件,以防止“误删”的功能。其原因如上所述,因为对应该目录的索引节点有一个以上的连接。只删除一个连接并不影响索引节点本身和其它的连接,只有当最后一个连接被删除后,文件的数据块及目录的连接才会被释放。也就是说,文件真正删除的条件是与之相关的所有硬连接文件均被删除。

软连接

另外一种连接称之为符号连接(Symbolic Link),也叫软连接。软链接文件有类似于 Windows 的快捷方式。它实际上是一个特殊的文件。在符号连接中,文件实际上是一个文本文件,其中包含的有另一文件的位置信息。比如:AB 的软链接(AB 都是文件名),A 的目录项中的 inode 节点号与 B 的目录项中的 inode 节点号不相同,AB 指向的是两个不同的 inode,继而指向两块不同的数据块。但是 A 的数据块中存放的只是 B 的路径名(可以根据这个找到 B 的目录项)。AB 之间是“主从”关系,如果 B 被删除了,A 仍然存在(因为两个是不同的文件),但指向的是一个无效的链接。

测试:

1
2
3
4
5
6
7
8
9
10
[root@qeuroal /]# cd /home
[root@qeuroal home]# touch f1 # 创建一个测试文件f1
[root@qeuroal home]# ls
f1
[root@qeuroal home]# ln f1 f2 # 创建f1的一个硬连接文件f2
[root@qeuroal home]# ln -s f1 f3 # 创建f1的一个符号连接文件f3
[root@qeuroal home]# ls -li # -i参数显示文件的inode节点信息
397247 -rw-r--r-- 2 root root 0 Mar 13 00:50 f1
397247 -rw-r--r-- 2 root root 0 Mar 13 00:50 f2
397248 lrwxrwxrwx 1 root root 2 Mar 13 00:50 f3 -> f1

从上面的结果中可以看出,硬连接文件 f2 与原文件 f1inode 节点相同,均为 397247,然而符号连接文件的 inode 节点不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
# echo 字符串输出 >> f1 输出到 f1文件
[root@qeuroal home]# echo "I am f1 file" >>f1
[root@qeuroal home]# cat f1
I am f1 file
[root@qeuroal home]# cat f2
I am f1 file
[root@qeuroal home]# cat f3
I am f1 file
[root@qeuroal home]# rm -f f1
[root@qeuroal home]# cat f2
I am f1 file
[root@qeuroal home]# cat f3
cat: f3: No such file or directory

通过上面的测试可以看出:当删除原始文件 f1 后,硬连接 f2 不受影响,但是符号连接 f1 文件无效;

依此您可以做一些相关的测试,可以得到以下全部结论:

  • 删除符号连接f3,对f1,f2无影响;
  • 删除硬连接f2,对f1,f3也无影响;
  • 删除原文件f1,对硬连接f2没有影响,导致符号连接f3失效;
  • 同时删除原文件f1,硬连接f2,整个文件会真正的被删除。

Vim编辑器

简介

Vim是从 vi 发展出来的一个文本编辑器。代码补完、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。尤其是Linux中,必须要会使用vim查看内容、编辑内容、保存内容

简单的来说, vi 是老式的字处理器,不过功能已经很齐全了,但是还是有可以进步的地方。

vim 则可以说是程序开发者的一项很好用的工具。

所有的 Unix Like 系统都会内建 vi 文书编辑器,其他的文书编辑器则不一定会存在。

连 vim 的官方网站 (http://www.vim.org) 自己也说 vim 是一个程序开发工具而不是文字处理软件。

补充:

  1. vim可以看成vi的升级版;
  2. vim 通过一些插件可以实现和IDE一样的功能;

Vim键盘图

三种使用模式

基本上 vi/vim 共分为三种模式,分别是命令模式(Command mode)输入模式(Insert mode)底线命令模式(Last line mode)。这三种模式的作用分别是:

命令模式:

用户刚刚启动 vi/vim,便进入了命令模式。

此状态下敲击键盘动作会被Vim识别为命令,而非输入字符。比如我们此时按下i,并不会输入一个字符,i被当作了一个命令。

以下是常用的几个命令:

命令 说明 备注
i 切换到输入模式,以输入字符。 image-20210606161102527
x 删除当前光标所在处的字符。
: 切换到底线命令模式,以在最底一行输入命令。 如果是编辑模式,需要先退出编辑模式:ESC

若想要编辑文本:启动Vim,进入了命令模式,按下i,切换到输入模式。

命令模式只有一些最基本的命令,因此仍要依靠底线命令模式输入更多命令。

输入模式:

在命令模式下按下i就进入了输入模式。

在输入模式中,可以使用以下按键:

命令 说明 备注
字符按键以及Shift组合 输入字符
ENTER 回车键,换行
BACK SPACE 退格键,删除光标前一个字符
DEL 删除键,删除光标后一个字符
方向键 在文本中移动光标
HOME/END 移动光标到行首/行尾
Page Up/Page Down 上/下翻页
Insert 切换光标为输入/替换模式,光标将变成竖线/下划线
ESC 退出输入模式,切换到命令模式

底线命令模式

在命令模式下按下:(英文冒号)就进入了底线命令模式。光标就移动到了最下面,就可以在这里输入一些底线命令了!

底线命令模式可以输入单个或多个字符的命令,可用的命令非常多。

在底线命令模式中,基本的命令有(已经省略了冒号):

  • q 退出程序
  • w 保存文件

ESC键可随时退出底线命令模式。

简单的说,我们可以将这三个模式想成底下的图标来表示:

体验

如果你想要使用 vi 来建立一个名为 test.txt 的文件时,你可以这样做:

1
[root@qeuroal home]# vim test.txt

然后就会进入文件

按下 i 进入输入模式(也称为编辑模式),开始编辑文字

在一般模式之中,只要按下 i, o, a 等字符就可以进入输入模式了!

在编辑模式当中,你可以发现在左下角状态栏中会出现 –INSERT- 的字样,那就是可以输入任意字符的提示。

这个时候,键盘上除了 Esc 这个按键之外,其他的按键都可以视作为一般的输入按钮了,所以你可以进行任何的编辑。

image-20210606160732328

按下 ESC 按钮回到一般模式

好了,假设我已经按照上面的样式给他编辑完毕了,那么应该要如何退出呢?是的!没错!就是给他按下 Esc 这个按钮即可!马上你就会发现画面左下角的 – INSERT – 不见了!

在一般模式中按下 :wq 储存后离开 vim!

OK! 这样我们就成功创建了一个 test.txt 的文件。

Vim按键说明

除了上面简易范例的 i, Esc, :wq 之外,其实 vim 还有非常多的按键可以使用。

第一部分:一般模式可用的光标移动、复制粘贴、搜索替换等

移动光标的方法 说明 备注
h 或 向左箭头键() 光标向左移动一个字符
j 或 向下箭头键() 光标向下移动一个字符
k 或 向上箭头键() 光标向上移动一个字符
l 或 向右箭头键() 光标向右移动一个字符
[Ctrl] + [f] 屏幕『向下』移动一页,相当于 [Page Down]按键 (常用)
[Ctrl] + [b] 屏幕『向上』移动一页,相当于 [Page Up] 按键 (常用)
[Ctrl] + [d] 屏幕『向下』移动半页
[Ctrl] + [u] 屏幕『向上』移动半页
+ 光标移动到非空格符的下一行
- 光标移动到非空格符的上一行 配置文件中,空格较多
n< space> 那个 n 表示『数字』,例如 20 。按下数字后再按空格键,光标会向右移动这一行的 n 个字符。 快捷切换光标
0功能键[Home] 这是数字『 0 』:移动到这一行的最前面字符处 (常用)
$功能键[End] 移动到这一行的最后面字符处(常用)
H 光标移动到这个屏幕的最上方那一行的第一个字符
M 光标移动到这个屏幕的中央那一行的第一个字符
L 光标移动到这个屏幕的最下方那一行的第一个字符
G 移动到这个档案的最后一行(常用)
nG n 为数字。移动到这个档案的第 n 行。例如 20G 则会移动到这个档案的第 20 行(可配合 :set nu)
gg 移动到这个档案的第一行,相当于 1G 啊!(常用)
n< Enter> n 为数字。光标向下移动 n 行(常用)
搜索替换
/word 向光标之下寻找一个名称为 word 的字符串。例如要在档案内搜寻 vbird 这个字符串,就输入 /vbird 即可!(常用)
?word 向光标之上寻找一个字符串名称为 word 的字符串。
n 这个 n 是英文按键。代表重复前一个搜寻的动作。举例来说, 如果刚刚我们执行 /vbird 去向下搜寻 vbird 这个字符串,则按下 n 后,会向下继续搜寻下一个名称为 vbird 的字符串。如果是执行 ?vbird 的话,那么按下 n 则会向上继续搜寻名称为 vbird 的字符串!
N 这个 N 是英文按键。与 n 刚好相反,为『反向』进行前一个搜寻动作。例如 /vbird 后,按下 N 则表示『向上』搜寻 vbird
删除、复制与粘贴 说明
x, X 在一行字当中,x 为向后删除一个字符 (相当于 [del] 按键), X 为向前删除一个字符(相当于 [backspace] 亦即是退格键) (常用)
nx n 为数字,连续向后删除 n 个字符。举例来说,我要连续删除 10 个字符, 『10x』
dd 删除游标所在的那一整行(常用)
ndd n 为数字。删除光标所在的向下 n 行,例如 20dd 则是删除 20 行 (常用)
d1G 删除光标所在到第一行的所有数据
dG 删除光标所在到最后一行的所有数据
d$ 删除游标所在处,到该行的最后一个字符
d0 那个是数字的 0 ,删除游标所在处,到该行的最前面一个字符
yy 复制游标所在的那一行(常用)
nyy n 为数字。复制光标所在的向下 n 行,例如 20yy 则是复制 20 行(常用)
y1G 复制游标所在行到第一行的所有数据
yG 复制游标所在行到最后一行的所有数据
y0 复制光标所在的那个字符到该行行首的所有数据
y$ 复制光标所在的那个字符到该行行尾的所有数据
p, P p 为将已复制的数据在光标下一行贴上,P 则为贴在游标上一行!举例来说,我目前光标在第 20 行,且已经复制了 10 行数据。则按下 p 后, 那 10 行数据会贴在原本的 20 行之后,亦即由 21 行开始贴。但如果是按下 P 呢?那么原本的第 20 行会被推到变成 30 行。(常用)
J 将光标所在行与下一行的数据结合成同一行
c 重复删除多个数据,例如向下删除 10 行,[ 10cj ]
u 复原前一个动作。(常用)
[Ctrl]+r 重做上一个动作。(常用)

第二部分:一般模式切换到编辑模式的可用的按钮说明

进入输入或取代的编辑模式 说明
i, I 进入输入模式(Insert mode):i 为『从目前光标所在处输入』, I 为『在目前所在行的第一个非空格符处开始输入』。(常用)
a, A 进入输入模式(Insert mode):a 为『从目前光标所在的下一个字符处开始输入』, A 为『从光标所在行的最后一个字符处开始输入』。(常用)
o, O 进入输入模式(Insert mode):这是英文字母 o 的大小写。o 为『在目前光标所在的下一行处输入新的一行』;O 为在目前光标所在处的上一行输入新的一行!(常用)
r, R 进入取代模式(Replace mode):r 只会取代光标所在的那一个字符一次;R会一直取代光标所在的文字,直到按下 ESC 为止;(常用)
[Esc] 退出编辑模式,回到一般模式中(常用)

第三部分:一般模式切换到指令行模式的可用的按钮说明

指令行的储存、离开等指令 说明 备注
:w 将编辑的数据写入硬盘档案中(常用)
:w! 若文件属性为『只读』时,强制写入该档案。不过,到底能不能写入, 还是跟你对该档案的档案权限有关啊!
:q 离开 vi (常用)
:q! 若曾修改过档案,又不想储存,使用 ! 为强制离开不储存档案。 注意一下啊,那个惊叹号 (!) 在 vi 当中,常常具有『强制』的意思~
:wq 储存后离开,若为 :wq! 则为强制储存后离开 (常用)
ZZ 这是大写的 Z 喔!若档案没有更动,则不储存离开,若档案已经被更动过,则储存后离开!
:w [filename] 将编辑的数据储存成另一个档案(类似另存新档)
:r [filename] 在编辑的数据中,读入另一个档案的数据。亦即将 『filename』 这个档案内容加到游标所在行后面
:n1,n2 w [filename] n1n2 的内容储存成 filename 这个档案。
:! command 暂时离开 vi 到指令行模式下执行 command 的显示结果!例如 『:! ls /home』即可在 vi 当中看 /home 底下以 ls 输出的档案信息!
:set nu 显示行号,设定之后,会在每一行的前缀显示该行的行号 设置行号,代码中经常会使用
:set nonu set nu 相反,为取消行号!

账号管理

一般在公司中国,用的应该都不是root账户!

简介

Linux系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这个账号的身份进入系统。

用户的账号一方面可以帮助系统管理员对使用系统的用户进行跟踪,并控制他们对系统资源的访问;另一方面也可以帮助用户组织文件,并为用户提供安全性保护。

每个用户账号都拥有一个唯一的用户名和各自的口令。

用户在登录时键入正确的用户名和口令后,就能够进入系统和自己的主目录。

实现用户账号的管理,要完成的工作主要有如下几个方面:

  • 用户账号的添加、删除与修改。
  • 用户口令的管理。
  • 用户组的管理。

用户账号的管理

用户账号的管理工作主要涉及到用户账号的添加、修改和删除。

添加用户账号就是在系统中创建一个新账号,然后为新账号分配用户号、用户组、主目录和登录Shell等资源。

属主,属组

添加账号

useradd

1
useradd 选项 用户名

参数说明:

  • 选项 :
    • -c comment 指定一段注释性描述。
    • -d 目录 指定用户主目录,如果此目录不存在,则同时使用-m选项,可以创建主目录。
    • -g 用户组 指定用户所属的用户组。
    • -G 用户组,用户组 指定用户所属的附加组。
    • -m 使用者目录如不存在则自动建立。自动创建这个用户的主目录 /home/qeuroal/
    • -s Shell文件 指定用户的登录Shell。
    • -u 用户号 指定用户的用户号,如果同时有-o选项,则可以重复使用其他用户的标识号。
  • 用户名 :
    • 指定新账号的登录名。

测试:

1
2
# 此命令创建了一个用户qeuroal,其中-m选项用来为登录名qeuroal产生一个主目录 /home/qeuroal
[root@iZbp16w4b9baac6xlzfcdmZ ~]# useradd -m qeuroal

增加用户账号就是在/etc/passwd文件中为新用户增加一条记录,同时更新其他系统文件如/etc/shadow, /etc/group等。

理解本质:Linux中一切皆文件,这里的添加用户说白了就是往某一个文件中写入用户的信息了!/etc/passwd

adduser 命令添加新用户

在Ubuntu中,有两个命令可用于创建新的用户。分别是useraddadduseruseradd是一个用于添加用户的最普遍命令,所有发行版都支持。

adduseruseradd的友好交互式前端,adduser是用Perl语言编写的。我们建议如果你在编写shell脚本时使用useradd添加用户。

如果你只是手动创建一个或者几个用户,在ubuntu中建议你使用adduser,adduser可以在一条命令完成创建用户的过程。

如果你需要在批量创建用户请参考我们的教程,Linux useradd命令创建用户

要在Ubuntu创建用户,请运行命令adduser,后跟用户名作为参数。例如命令sudo adduser myfreax将会创建用户myfreax。

命令将向你询问一系列的问题。密码是必需的,其他字段都是可选的。

1
sudo adduser myfreax

区别

使用 sudo useradd newuser/home 里并没有 newuser 的目录。

查询得知,可以用 useradd -m newuser,但是-m这个命令只有在你创建用户的时候才有用。

如果已经创建了用户且没有目录的话,useradd -m newuser是不会为用户创建目录的,也就是说必须删掉这个用户再重新useradd -m newuser才可以。

但是!使用adduser的话直接adduser username也会有目录,不用像useradd那样用-m了。但是 adduser一创建就会要求输入密码。useradd创建的时候就不会要求输入密码。

超级用户

默认情况下,在Ubuntu,sudo组的成员被授予sudo访问权限。如果您希望新创建的用户具有sudo权限,请将用户添加到sudo组

修改用户所属组的命令是usermod命令,我们不建议直接修改用户主要组。这可能导致某些权限问题。最好的方式将用户追加到sudo组中。

因此你将使用usermod命令的-aG选项,添加用户到sudo组,-a表示追加用户到指定组,-G选项表示不要将用户从其它组中移除。

1
sudo usermod -aG sudo username

删除帐号

userdel命令删除用户

如果一个用户的账号不再使用,可以从系统中删除。

删除用户账号就是要将/etc/passwd等系统文件中的该用户记录删除,必要时还删除用户的主目录。

删除一个已有的用户账号使用userdel命令,其格式如下:

1
userdel 选项 用户名

常用的选项是 -r,它的作用是把用户的主目录一起删除。

1
[root@iZbp16w4b9baac6xlzfcdmZ ~]# useradd -m qeuroal

此命令删除用户qeuroal在系统文件中(主要是/etc/passwd, /etc/shadow, /etc/group等)的记录,同时删除用户的主目录。

deluser 命令删除用户

如果不再需要用户,可以从命令行或通过GUI删除它。在ubuntu中删除用户方式也是有两种。

您可以使用两个命令来删除用户,分别是userdeldeluser。在Ubuntu,建议您使用deluser命令,因为它比userdel更友好。

同样在脚本中,我们建议你使用userdel而不是deluser。因为其它发行版不存在deluserdeluser仅在基于Ubuntu的发行版存在。

deluser仅在指定用户参数时,deluser将删除用户而不删除用户文件。如果你需要用户的家目录和邮件等信息请使用--remove-home选项。

1
2
3
sudo deluser username

sudo deluser --remove-home username

步骤

删除用户的操作分为 3 步:

  1. 执行userdelsudo userdel dongyuanxin_2016150127
  2. 删除用户目录:sudo rm -rf /home/dongyuanxin_2016150127
  3. 删除用户权限相关配置:删除或者注释掉/etc/sudoers中关于要删除用户的配置,否则无法再次创建同名用户。

修改帐号

修改用户账号就是根据实际情况更改用户的有关属性,如用户号、主目录、用户组、登录Shell等。

修改已有用户的信息使用usermod命令,其格式如下:

1
usermod 选项 用户名

常用的选项包括-c, -d, -m, -g, -G, -s, -u以及-o等,这些选项的意义与useradd命令中的选项一样,可以为用户指定新的资源值。

例如:

1
# usermod -s /bin/ksh -d /home/z –g developer qeuroal

修改完毕后查看配置文件即可:

1
cat /etc/passwd

此命令将用户qeuroal的登录Shell修改为ksh,主目录改为/home/z,用户组改为developer。

Linux下如何切换用户

root 用户

  1. 切换用户的命令为:su usernameusername是你的用户名哦】

  2. 从普通用户切换到root用户,还可以使用命令:sudo su

  3. 在终端输入exitlogout或使用快捷方式ctrl+d,可以退回到原来用户,其实ctrl+d也是执行的exit命令

  4. 在切换用户时,如果想在切换用户之后使用新用户的工作环境,可以在suusername之间加-,例如:【su - root

符号 说明 备注
$ 表示普通用户
# 表示超级用户,也就是root用户

补充

在阿里云买的服务器是一段随机字符串,可以通过以下方式修改:

用户口令的管理

用户管理的一项重要内容是用户口令的管理。用户账号刚创建时没有口令,但是被系统锁定,无法使用,必须为其指定口令后才可以使用,即使是指定空口令。

指定和修改用户口令的Shell命令是passwd。超级用户(root)可以为自己和其他用户指定口令,普通用户只能用它修改自己的口令。

linux上输入密码不会显示,正常输入即可,并不是系统的问题!

在公司中,一般拿不到公司服务器的root权限,都是一些分配的账号!

超级用户:

命令的格式为:

1
passwd 选项 用户名

可使用的选项:

  • -l 锁定口令,即禁用账号。
  • -u 口令解锁。
  • -d 使账号无口令。
  • - 强迫用户下次登录时修改口令。

普通用户名:

修改当前用户的口令。

例如,假设当前用户是qeuroal,则下面的命令修改该用户自己的口令:

1
2
3
4
$ passwd
Old password:******
New password:*******
Re-enter new password:*******

如果是超级用户,可以用下列形式指定任何用户的口令:

1
2
3
# passwd qeuroal
New password:*******
Re-enter new password:*******

普通用户修改自己的口令时,passwd命令会先询问原口令,验证后再要求用户输入两遍新口令,如果两次输入的口令一致,则将这个口令指定给用户;而超级用户为用户指定口令时,就不需要知道原口令。

为了系统安全起见,用户应该选择比较复杂的口令,例如最好使用8位长的口令,口令中包含有大写、小写字母和数字,并且应该与姓名、生日等不相同。

锁定账户

例如:test_user辞职了,冻结这个账号,一旦冻结,这个人就登录不上系统了。

为用户指定空口令时,执行下列形式的命令:

1
passwd -d test_user   # 没有密码不能登录

此命令将用户 test_user的口令删除,这样用户 test_user下一次登录时,系统就不再允许该用户登录了。

passwd 命令还可以用 -l(lock) 选项锁定某一用户,使其不能登录,例如:

1
passwd -l test_user	# 锁定之后这个用户就不能登录了

在公司中一般触及不到 root 用户!作为一个开发一般拿不到!如果拿到root账号后,首先把自己的权限提到最高,自己用户的组提到root组。

用户组管理

属主、属组

每个用户都有一个用户组,系统可以对一个用户组中的所有用户进行集中管理(开发、测试、运维、root)。不同Linux 系统对用户组的规定有所不同,如Linux下的用户属于与它同名的用户组,这个用户组在创建用户时同时创建。

用户组的管理涉及用户组的添加、删除和修改。组的增加、删除和修改实际上就是对/etc/group文件的更新。

增加用户组

增加一个新的用户组使用groupadd命令

1
groupadd 选项 用户组

可以使用的选项有:

  • -g GID 指定新用户组的组标识号(GID)。
  • -o 一般与-g选项同时使用,表示新用户组的GID可以与系统已有用户组的GID相同。

实例1:

1
# groupadd group1

此命令向系统中增加了一个新组group1,新组的组标识号是在当前已有的最大组标识号的基础上加1。

创建完一个用户组后可以得到一个组的 id,这个 id 是可以指定的!

实例2:

1
# groupadd -g 101 group2

此命令向系统中增加了一个新组group2,同时指定新组的组标识号是101

如果不指定就是自增 1

删除用户组

如果要删除一个已有的用户组,使用groupdel命令

1
groupdel 用户组

例如:

1
# groupdel group1

此命令从系统中删除组group1

修改用户组的属性

使用groupmod命令

1
groupmod 选项 用户组

常用的选项有:

  • -g GID 为用户组指定新的组标识号。
  • -o-g选项同时使用,用户组的新GID可以与系统已有用户组的GID相同。
  • -n 新用户组 将用户组的名字改为新名字
1
2
3
4
5
# 此命令将组group2的组标识号修改为102。
groupmod -g 102 group2

# 将组group2的标识号改为10000,组名修改为group3。
groupmod –g 10000 -n group3 group2

拓展:文件的查看

/etc/passwd

完成用户管理的工作有许多种方法,但是每一种方法实际上都是对有关的系统文件进行修改。

与用户和用户组相关的信息都存放在一些系统文件中,这些文件包括/etc/passwd, /etc/shadow, /etc/group等。

下面分别介绍这些文件的内容。

/etc/passwd文件是用户管理工作涉及的最重要的一个文件。

Linux系统中的每个用户都在/etc/passwd文件中有一个对应的记录行,它记录了这个用户的一些基本属性(主目录、属组)。

这个文件对所有用户都是可读的。它的内容类似下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
# cat /etc/passwd

root:x:0:0:Superuser:/:
daemon:x:1:1:System daemons:/etc:
bin:x:2:2:Owner of system commands:/bin:
sys:x:3:3:Owner of system files:/usr/sys:
adm:x:4:4:System accounting:/usr/adm:
uucp:x:5:5:UUCP administrator:/usr/lib/uucp:
auth:x:7:21:Authentication administrator:/tcb/files/auth:
cron:x:9:16:Cron daemon:/usr/spool/cron:
listen:x:37:4:Network daemon:/usr/net/nls:
lp:x:71:18:Printer administrator:/usr/spool/lp:

从上面的例子我们可以看到,/etc/passwd中一行记录对应着一个用户,每行记录又被冒号(:)分隔为7个字段,其格式和具体含义如下:

1
用户名:口令(登录密码,我们不可见):用户标识号:组标识号:注释性描述:主目录:登录Shell
  1. “用户名”是代表用户账号的字符串。

    通常长度不超过8个字符,并且由大小写字母和/或数字组成。登录名中不能有冒号(:),因为冒号在这里是分隔符。

    为了兼容起见,登录名中最好不要包含点字符(.),并且不使用连字符(-)和加号(+)打头。

  2. “口令”一些系统中,存放着加密后的用户口令字。

    虽然这个字段存放的只是用户口令的加密串,不是明文,但是由于/etc/passwd文件对所有用户都可读,所以这仍是一个安全隐患。因此,现在许多Linux 系统(如SVR4)都使用了shadow技术,把真正的加密后的用户口令字存放到/etc/shadow文件中,而在/etc/passwd文件的口令字段中只存放一个特殊的字符,例如“x”或者“*”。

  3. “用户标识号”是一个整数,系统内部用它来标识用户。

    一般情况下它与用户名是一一对应的。如果几个用户名对应的用户标识号是一样的,系统内部将把它们视为同一个用户,但是它们可以有不同的口令、不同的主目录以及不同的登录Shell等。

    通常用户标识号的取值范围是0~65 535。0是超级用户root的标识号,1~99由系统保留,作为管理账号,普通用户的标识号从100开始。在Linux系统中,这个界限是500

  4. “组标识号”字段记录的是用户所属的用户组。

    它对应着/etc/group文件中的一条记录。

  5. “注释性描述”字段记录着用户的一些个人情况。

    例如用户的真实姓名、电话、地址等,这个字段并没有什么实际的用途。在不同的Linux 系统中,这个字段的格式并没有统一。在许多Linux系统中,这个字段存放的是一段任意的注释性描述文字,用作finger命令的输出。

  6. “主目录”,也就是用户的起始工作目录。

    它是用户在登录到系统之后所处的目录。在大多数系统中,各用户的主目录都被组织在同一个特定的目录下,而用户主目录的名称就是该用户的登录名。各用户对自己的主目录有读、写、执行(搜索)权限,其他用户对此目录的访问权限则根据具体情况设置。

  7. 用户登录后,要启动一个进程,负责将用户的操作传给内核,这个进程是用户登录到系统后运行的命令解释器或某个特定的程序,即Shell。

    Shell是用户与Linux系统之间的接口。LinuxShell有许多种,每种都有不同的特点。常用的有sh(Bourne Shell), csh(C Shell), ksh(Korn Shell), tcsh(TENEX/TOPS-20 type C Shell), bash(Bourne Again Shell)等。

    系统管理员可以根据系统情况和用户习惯为用户指定某个Shell。如果不指定Shell,那么系统使用sh为默认的登录Shell,即这个字段的值为/bin/sh

    用户的登录Shell也可以指定为某个特定的程序(此程序不是一个命令解释器)。

    利用这一特点,我们可以限制用户只能运行指定的应用程序,在该应用程序运行结束后,用户就自动退出了系统。有些Linux 系统要求只有那些在系统中登记了的程序才能出现在这个字段中。

  8. 系统中有一类用户称为伪用户(pseudo users

    这些用户在/etc/passwd文件中也占有一条记录,但是不能登录,因为它们的登录Shell为空。它们的存在主要是方便系统管理,满足相应的系统进程对文件属主的要求。

    常见的伪用户如下所示:

    伪用户 含 义
    bin 拥有可执行的用户命令文件
    sys 拥有系统文件
    adm 拥有帐户文件
    uucp UUCP使用
    lp lp或lpd子系统使用
    nobody NFS使用

/etc/shadow

1、除了上面列出的伪用户外,还有许多标准的伪用户,例如:audit, cron, mail, usenet等,它们也都各自为相关的进程和文件所需要。

由于/etc/passwd文件是所有用户都可读的,如果用户的密码太简单或规律比较明显的话,一台普通的计算机就能够很容易地将它破解,因此对安全性要求较高的Linux系统都把加密后的口令字分离出来,单独存放在一个文件中,这个文件是/etc/shadow文件。有超级用户才拥有该文件读权限,这就保证了用户密码的安全性。

**2、/etc/shadow中的记录行与/etc/passwd**中的一一对应,它由pwconv命令根据/etc/passwd中的数据自动产生

它的文件格式与/etc/passwd类似,由若干个字段组成,字段之间用”:”隔开。这些字段是:

1
登录名:加密口令:最后一次修改时间:最小时间间隔:最大时间间隔:警告时间:不活动时间:失效时间:标志
  1. “登录名”是与/etc/passwd文件中的登录名相一致的用户账号
  2. “口令”字段存放的是加密后的用户口令字,长度为13个字符。如果为空,则对应用户没有口令,登录时不需要口令;如果含有不属于集合 { ./0-9A-Za-z }中的字符,则对应的用户不能登录。
  3. “最后一次修改时间”表示的是从某个时刻起,到用户最后一次修改口令时的天数。时间起点对不同的系统可能不一样。例如在SCO Linux 中,这个时间起点是1970年1月1日。
  4. “最小时间间隔”指的是两次修改口令之间所需的最小天数。
  5. “最大时间间隔”指的是口令保持有效的最大天数。
  6. “警告时间”字段表示的是从系统开始警告用户到用户密码正式失效之间的天数。
  7. “不活动时间”表示的是用户没有登录活动但账号仍能保持有效的最大天数。
  8. “失效时间”字段给出的是一个绝对的天数,如果使用了这个字段,那么就给出相应账号的生存期。期满后,该账号就不再是一个合法的账号,也就不能再用来登录了。

/etc/group

用户组的所有信息都存放在/etc/group文件中。

将用户分组是Linux 系统中对用户进行管理及控制访问权限的一种手段。

每个用户都属于某个用户组;一个组中可以有多个用户,一个用户也可以属于不同的组。

当一个用户同时是多个组中的成员时,在/etc/passwd文件中记录的是用户所属的主组,也就是登录时所属的默认组,而其他组称为附加组。

用户要访问属于附加组的文件时,必须首先使用newgrp命令使自己成为所要访问的组中的成员。

用户组的所有信息都存放在/etc/group文件中。此文件的格式也类似于/etc/passwd文件,由冒号(:)隔开若干个字段,这些字段有:

1
组名:口令:组标识号:组内用户列表
  1. “组名”是用户组的名称,由字母或数字构成。与/etc/passwd中的登录名一样,组名不应重复。
  2. “口令”字段存放的是用户组加密后的口令字。一般Linux 系统的用户组都没有口令,即这个字段一般为空,或者是*。(当然,你看不到明文密码)
  3. “组标识号”与用户标识号类似,也是一个整数,被系统内部用来标识组。即: 组的ID, GID
  4. “组内用户列表”是属于这个组的所有用户的列表/b],不同用户之间用逗号(,)分隔。这个用户组可能是用户的主组,也可能是附加组。
1
adm:x:4:syslog,admin

以下为各字段的说明:

  • adm 为组名称;
  • x 代表密码字段(当然你不会看到明文的密码);
  • 4 是组的ID即GID;
  • syslog 和 admin 是属于组 adm 中的用户。

切换组

如果一个用户同时属于多个用户组,那么用户可以在用户组之间切换,以便具有其他用户组的权限。

用户可以在登录后,使用命令newgrp切换到其他用户组,这个命令的参数就是目的用户组。例如:

1
$ newgrp root

这条命令将当前用户切换到root用户组,前提条件是root用户组确实是该用户的主组或附加组。

磁盘管理

概述

Linux磁盘管理好坏直接关系到整个系统的性能问题。

Linux磁盘管理常用命令为 dfdu

  • df :列出文件系统的整体磁盘使用量
  • du:检查磁盘空间使用量

df

df:列出文件系统整体的磁盘使用量

df命令参数功能:检查文件系统的磁盘空间占用情况。可以利用该命令来获取硬盘被占用了多少空间,目前还剩下多少空间等信息。

语法:

1
df [-ahikHTm] [目录或文件名]

选项与参数:

  • -a :列出所有的文件系统,包括系统特有的 /proc 等文件系统;
  • -k :以 KBytes 的容量显示各文件系统;
  • -m :以 MBytes 的容量显示各文件系统;
  • -h :以人们较易阅读的 GBytes, MBytes, KBytes 等格式自行显示;
  • -H :以 M=1000K 取代 M=1024K 的进位方式;
  • -T :显示文件系统类型, 连同该 partitionfilesystem 名称 (例如 ext3) 也列出;
  • -i :不用硬盘容量,而以 inode 的数量来显示

测试:

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
# 将系统内所有的文件系统列出来!
# 在 Linux 底下如果 df 没有加任何选项
# 那么默认会将系统内所有的 (不含特殊内存内的文件系统与 swap) 都以 1 Kbytes 的容量来列出来!
# Mounted on: 挂载目录
[root@qeuroal /]# df
Filesystem 1K-blocks Used Available Use% Mounted on
devtmpfs 889100 0 889100 0% /dev
tmpfs 899460 704 898756 1% /dev/shm
tmpfs 899460 496 898964 1% /run
tmpfs 899460 0 899460 0% /sys/fs/cgroup
/dev/vda1 41152812 6586736 32662368 17% /
tmpfs 179896 0 179896 0% /run/user/0
# 将容量结果以易读的容量格式(M/G)显示出来
[root@qeuroal /]# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 869M 0 869M 0% /dev
tmpfs 879M 708K 878M 1% /dev/shm
tmpfs 879M 496K 878M 1% /run
tmpfs 879M 0 879M 0% /sys/fs/cgroup
/dev/vda1 40G 6.3G 32G 17% /
tmpfs 176M 0 176M 0% /run/user/0
# 将系统内的所有特殊文件格式及名称都列出来
[root@qeuroal /]# df -aT
Filesystem Type 1K-blocks Used Available Use% Mounted on
sysfs sysfs 0 0 0 - /sys
proc proc 0 0 0 - /proc
devtmpfs devtmpfs 889100 0 889100 0% /dev
securityfs securityfs 0 0 0 - /sys/kernel/security
tmpfs tmpfs 899460 708 898752 1% /dev/shm
devpts devpts 0 0 0 - /dev/pts
tmpfs tmpfs 899460 496 898964 1% /run
tmpfs tmpfs 899460 0 899460 0% /sys/fs/cgroup
cgroup cgroup 0 0 0 - /sys/fs/cgroup/systemd
pstore pstore 0 0 0 - /sys/fs/pstore
cgroup cgroup 0 0 0 - /sys/fs/cgroup/freezer
cgroup cgroup 0 0 0 - /sys/fs/cgroup/cpuset
cgroup cgroup 0 0 0 - /sys/fs/cgroup/hugetlb
cgroup cgroup 0 0 0 - /sys/fs/cgroup/blkio
cgroup cgroup 0 0 0 - /sys/fs/cgroup/net_cls,net_prio
cgroup cgroup 0 0 0 - /sys/fs/cgroup/memory
cgroup cgroup 0 0 0 - /sys/fs/cgroup/pids
cgroup cgroup 0 0 0 - /sys/fs/cgroup/cpu,cpuacct
cgroup cgroup 0 0 0 - /sys/fs/cgroup/devices
cgroup cgroup 0 0 0 - /sys/fs/cgroup/perf_event
configfs configfs 0 0 0 - /sys/kernel/config
/dev/vda1 ext4 41152812 6586748 32662356 17% /
systemd-1 - - - - - /proc/sys/fs/binfmt_misc
mqueue mqueue 0 0 0 - /dev/mqueue
debugfs debugfs 0 0 0 - /sys/kernel/debug
hugetlbfs hugetlbfs 0 0 0 - /dev/hugepages
tmpfs tmpfs 179896 0 179896 0% /run/user/0
binfmt_misc binfmt_misc 0 0 0 - /proc/sys/fs/binfmt_misc
# 将 /etc 底下的可用的磁盘容量以易读的容量格式显示

[root@qeuroal /]# df -h /etc
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 40G 6.3G 32G 17% /

du

du:检查当前磁盘空间使用量

Linux du命令也是查看使用空间的,但是与df命令不同的是Linux du命令是对文件和目录磁盘使用的空间的查看,还是和df命令有一些区别的,这里介绍Linux du命令。

语法:

1
du [-ahskm] 文件或目录名称

选项与参数:

  • -a :列出所有的文件与目录容量,可以看到子文件夹,因为默认仅统计目录底下的文件量而已。
  • -h :以人们较易读的容量格式 (G/M) 显示;
  • -s :列出总量而已,而不列出每个各别的目录占用容量;
  • -S :不包括子目录下的总计,与 -s 有点差别。
  • -k :以 KBytes 列出容量显示;
  • -m :以 MBytes 列出容量显示;

测试:

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
# 只列出当前目录下的所有文件夹容量(包括隐藏文件夹):
# 直接输入 du 没有加任何选项时,则 du 会分析当前所在目录的文件与目录所占用的硬盘空间。
[root@qeuroal home]# du
16./redis
8./www/.oracle_jre_usage # 包括隐藏文件的目录
24./www
48. # 这个目录(.)所占用的总量
# 将文件的容量也列出来
[root@qeuroal home]# du -a
4./redis/.bash_profile
4./redis/.bash_logout
....中间省略....
4./kuangstudy.txt # 有文件的列表了
48.
# 检查根目录底下每个目录所占用的容量
[root@qeuroal home]# du -sm /*
0/bin
146/boot
.....中间省略....
0/proc
.....中间省略....
1/tmp
3026/usr # 系统初期最大就是他了啦!
513/var
2666/www

通配符 * 来代表每个目录。

df 不一样的是,du 这个命令其实会直接到文件系统内去搜寻所有的文件数据。

磁盘挂载与卸除

起因:MacLinux 挂载本地磁盘或者文件

根文件系统之外的其他文件要想能够被访问,都必须通过“关联”至根文件系统上的某个目录来实现,此关联操作即为“挂载”,此目录即为“挂载点”,解除此关联关系的过程称之为“卸载”

Linux 的磁盘挂载使用mount命令,卸载使用umount命令。

磁盘挂载语法:

1
mount [-t 文件系统] [-L Label名] [-o 额外选项] [-n] 装置文件名 挂载点

测试:

1
2
3
4
5
6
# 将 /dev/hdc6 挂载到 /mnt/hdc6 上面!
[root@www ~]# mkdir /mnt/hdc6
[root@www ~]# mount /dev/hdc6 /mnt/hdc6
[root@www ~]# df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/hdc6 1976312 42072 1833836 3% /mnt/hdc6

磁盘卸载命令 umount 语法:

1
umount [-fn] 装置文件名或挂载点

选项与参数:

  • -f :强制卸除!可用在类似网络文件系统 (NFS) 无法读取到的情况下;
  • -n :不升级 /etc/mtab 情况下卸除。

卸载/dev/hdc6

1
[root@www ~]# umount /dev/hdc6

除了这个之外,以后安装了JDK ,可以使用 java 中的一些命令来查看信息。

进程管理

Linux中一切皆文件(Linux:系统:(磁盘、进程)。文件:读写执行(查看,创建,删除,移动,复制,编辑),权限(用户、用户组)。)

对于我们开发人员来说,其实 Linux 更多偏向于使用即可!

进程基本概念

  1. Linux 中,每一个程序都有自己的一个进程,每一个进程都有一个 id 号;
  2. 每个进程都有一个父进程(被谁创建的);
  3. 进程可以有两种存在方式:前台!后台运行!
  4. 一般的话服务都是后台运行的,基本的程序都是前台运行的;

命令

查看进程

ps : 查看当前系统中正在执行的各种进程的信息

ps -xxx : 通过 ps -help man ps 查看帮助文档

  • -a : 显示当前终端运行的所有的进程信息(当期的进程,1个);
  • -u : 以用户的信息显示进程();
  • -x : 显示后台运行进程的参数;

实例

  • ps -aux | grep mysql

补充

  • |: 在 Linux 中,这个叫做管道符,如: A|B: 把 A 命令的结果作为输出操作 B 命令

  • grep: 查找文件中符合条件的字符串

  • 对于我们来说,这里目前只需要记住一个命令即可: ps -xxx | grep 进程名 过滤进程信息

  • ps -ef : 可以查到父进程信息

    1
    ps -ef | grep mysql # 看父进程我们一般可以通过目录树结构查看
  • pstree : 进程树

    • -p : 显示父 id

    • -u : 显示用户组

    • 实践

      1
      pstree -pu

结束进程

杀死进程,等价于 window 结束任务

1
kill -9 进程id	# 表示强制结束该进程

在服务器很少手动结束进程,平时写的代码死循环了,可以选择结束进程!杀进程!

shell

getopts

getpots是Shell命令行参数解析工具,旨在从Shell Script的命令行当中解析参数。

getopt

SSH

上传

上传本地文件到服务器

格式:scp 要上传的文件路径 用户名@服务器地址:服务器保存路径

例如:把本机 /home/test.txt 文件上传到 192.168.0.101 这台服务器上的 /data/ 目录中

1
scp /home/test.txt root@192.168.0.101:/data/

上传目录到服务器

格式:scp -r 要上传的目录 用户名@服务器地址:服务器的保存目录

例如:把 /home 目录上传到服务器的 /data/ 目录

1
scp -r /home root@192.168.0.101:/data/

下载

从服务器上下载文件

格式:scp 用户名@服务器地址:要下载的文件路径 保存文件的文件夹路径

例如:把 192.168.0.101 上的 /data/test.txt 的文件下载到 /home(本地目录)

1
scp root@192.168.0.101:/data/test.txt /home

从服务器下载整个目录

格式:scp -r 用户名@服务器地址:要下载的服务器目录 保存下载的目录

例如:把 192.168.0.101 上的 /data 目录下载到 /home(本地目录)

1
scp -r root@192.168.0.101:/data  /home/

注:目标服务器要开启写入权限。

命令

总览

  • 基础命令相关一:

    1
    2
    cd、ls、pwd、help、man、if、for、while、case、select、read、test、ansible、iptables、
    firewall-cmd、salt、mv、cut、uniq、sort、wc、source、sestatus、setenforce;
  • 基础命令相关二:

    1
    2
    date、ntpdate、crontab、rsync、ssh、scp、nohup、sh、bash、hostname、hostnamectl、
    source、ulimit、export、env、set、at、dir、db_load、diff、dmsetup、declare;
  • 用户权限相关:

    1
    2
    3
    Useradd、userdel、usermod、groupadd、groupmod、groupdel、Chmod、chown、
    chgrp、umask、chattr、lsattr、id、who、whoami、last、su、sudo、w、chpasswd、
    chroot;
  • 文件管理相关:

    1
    2
    Touch、mkdir、rm、rmdi、vi、vim、cat、head、tail、less、more、find、sed、
    grep、awk、echo、ln、stat、file;
  • 软件资源管理:

    1
    2
    Rpm、yum、tar、unzip、zip、gzip、wget、curl、rz、sz、jar、apt-get、bzip2、
    service、systemctl、make、cmake、chkconfig;
  • 系统资源管理:

    1
    2
    fdisk、mount、umount、mkfs.ext4、fsck.ext4、parted、lvm、dd、du、df、top、
    iftop、free、w、uptime、iostat、vmstat、iotop、ps、netstat、lsof、ss、sar;
  • 网络管理相关:

    1
    2
    ping、ifconfig、ip addr、ifup、ifdown、nmcli、route、nslookup、traceroute、
    dig、tcpdump、nmap、brctl、ethtool、setup、arp、ab、iperf;

grep

基本格式

1
grep [option] pattern file

常用参数

参数 含义
-b 显示匹配行距文件头部的偏移量
-c 只显示匹配的行数
-e 实现多个选项间的逻辑 or 关系
-E 支持扩展正则表达式
-f 从文件获取 PATTERN 匹配
-F 匹配固定字符串的内容
-h 搜索多文件时不显示文件名
-i 忽略关键词大小写
-l 只显示符合匹配条件的文件名
-n 显示所有匹配行及其行号
-o 显示匹配词距文件头部的偏移量
-q 静默执行模式
-r 递归搜索模式
-s 不显示没有匹配文本的错误信息
-v 显示不包含匹配文本的所有行,==相当于[^] 反向匹配==
-w 精准匹配整词
-x 精准匹配整行
-A <行数 x> 除了显示符合范本样式的那一列之外,并显示该行之后的 x 行内容。
-B <行数 x> 除了显示符合样式的那一行之外,并显示该行之前的 x 行内容
-C<行数 x> 除了显示符合样式的那一行之外,并显示该行之前后的 x 行内容

sed

功能

用于利用语法/脚本对文本文件进行批量的编辑操作

sed 会根据脚本命令来处理文本文件中的数据,这些命令要么从命令行中输入,要么存储在一个文本文件中,此命令执行数据的顺序如下:

  1. 每次==仅读取一行==内容;
  2. 根据提供的规则命令匹配并修改数据。注意,sed ==默认不会直接修改源文件数据==,而是会将数据复制到缓冲区中,修改也仅限于缓冲区中的数据;
  3. 将执行结果输出。

当一行数据匹配完成后,它会继续读取下一行数据,并重复这个过程,直到将文件中所有数据处理完毕。

语法

1
sed [选项] [脚本命令] 文件名

选项

参数 含义
-e 使用指定脚本来处理输入的文本文件
以选项中指定的 script 来处理输入的文本文件,这个-e可以省略,直接写表达式。
-f 使用指定脚本文件处理输入的文本文件
-h 显示帮助信息
-i 直接修改文件内容,而不输出到终端
-n 仅显示脚本处理后的结果
默认情况下,sed 会在所有的脚本指定执行完毕后,会自动输出处理后的内容,而该选项会屏蔽启动输出,需使用 print 命令来完成输出。
–quiet 仅显示脚本处理后的结果
–silent 仅显示脚本处理后的结果
-r 支持扩展正则表达式
-V 显示版本信息

动作说明

动作 说明
a 新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~
c 取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行!
d 删除,因为是删除啊,所以 d 后面通常不接任何咚咚;
i 插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行);
p 打印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行~
s 取代,通常这个 s 的动作可以搭配正规表示法,例如 1,20s/old/new/g 。

sed脚本命令

sed s 替换脚本命令

基本格式

1
[address]s/pattern/replacement/flags
  • address 表示指定要操作的具体行
  • pattern 指的是需要替换的内容
  • replacement 指的是要替换的新内容

常用的 flags 标记

flags 标记 功能
n 1~512 之间的数字,表示指定要替换的字符串出现第几次时才进行替换,例如,一行中有 3 个 A,但用户只想替换第二个 A,这是就用到这个标记;
g 对数据中所有匹配到的内容进行替换,如果没有 g,则只会在第一次匹配成功时做替换操作。例如,一行数据中有 3 个 A,则只会替换第一个 A;
p 会打印与替换命令中指定的模式匹配的行。此标记通常与 -n 选项一起使用。
w file 将缓冲区中的内容写到指定的 file 文件中;
& 用正则表达式匹配的内容进行替换;
\n 匹配第 n 个子串,该子串之前在 pattern 中用 () 指定。
\ 转义(转义替换部分包含:&、\ 等)。

注意

替换类似文件路径的字符串会比较麻烦,需要将路径中的正斜线进行转义,如:

1
sed 's/\/bin\/bash/\/bin\/csh/' /etc/passwd

sed d 替换脚本命令

基本格式

1
[address]d

sed a 和 i 脚本命令

  • a 命令表示在指定行的后面附加一行
  • i 命令表示在指定行的前面插入一行

基本格式

1
[address]a \新文本内容
1
[address]i \新文本内容

多行

如果你想将一个多行数据添加到数据流中,只需对要插入或附加的文本中的每一行末尾(除最后一行)添加反斜线即可

sed c 替换脚本命令

c 命令表示将指定行中的所有内容,替换成该选项后面的字符串。该命令的基本格式为:

1
[address]c\用于替换的新文本

sed y 转换脚本命令

y 转换命令是唯一可以处理单个字符的 sed 脚本命令,其基本格式如下:

1
[address]y/inchars/outchars/

转换命令会对 inchars 和 outchars 值进行一对一的映射,即 inchars 中的第一个字符会被转换为 outchars 中的第一个字符,第二个字符会被转换成 outchars 中的第二个字符…这个映射过程会一直持续到处理完指定字符。如果 inchars 和 outchars 的长度不同,则 sed 会产生一条错误消息。

sed p 打印脚本命令

p 命令表示搜索符号条件的行,并输出该行的内容,此命令的基本格式为:

1
[address]p

sed w 脚本命令

w 命令用来将文本中指定行的内容写入文件中,此命令的基本格式如下:

1
[address]w filename

这里的 filename 表示文件名,可以使用相对路径或绝对路径,但不管是哪种,运行 sed 命令的用户都必须有文件的写权限。

sed r 脚本命令

r 命令用于将一个独立文件的数据插入到当前数据流的指定位置,该命令的基本格式为:

1
[address]r filename

sed 命令会将 filename 文件中的内容插入到 address 指定行的后面

sed q 退出脚本命令

q 命令的作用是使 sed 命令在第一次匹配任务结束后,退出 sed 程序,不再进行对后续数据的处理。

sed 脚本命令的寻址方式

对各个脚本命令来说,address 用来表明该脚本命令作用到文本中的具体行

默认情况下,sed 命令会作用于文本数据的所有行。如果只想将命令作用于特定行或某些行,则必须写明 address 部分,表示的方法有以下 2 种:

  1. 以数字形式指定行区间;
  2. 用文本模式指定具体行区间。

以上两种形式==都可以使用如下这 2 种格式==,分别是:

1
[address]脚本命令

或者

1
2
3
address {
多个脚本命令
}

例如:

1
2
3
4
$ sed -n '/3/{
> p
> s/line/test/p
> }' data6.txt

以数字形式指定行区间

当使用数字方式的行寻址时,可以用行在文本流中的行位置来引用。sed 会将文本流中的第一行编号为 1,然后继续按顺序为接下来的行分配行号。

在脚本命令中,指定的地址可以是 单个行号,或是 用起始行号、逗号以及结尾行号指定的一定区间范围内的行

用文本模式指定行区间

sed 允许指定文本模式来过滤出命令要作用的行,格式如下:

1
/pattern/command

注意,必须用正斜线将要指定的 pattern 封起来,sed 会将该命令作用到包含指定文本模式的行上。

sed 允许在文本模式==使用正则表达式指明作用的具体行==

sed 多行命令

  1. Next 命令(N):将数据流中的下一行加进来创建一个多行组来处理。
  2. Delete(D):删除多行组中的一行。
  3. Print(P):打印多行组中的一行。

注意,以上命令的缩写,都为大写。

awk

和 sed 命令类似,awk 命令也是逐行扫描文件(从第 1 行到最后一行),寻找含有目标文本的行,如果匹配成功,则会在该行上执行用户想要的操作;反之,则不对行做任何处理。

awk 命令的基本格式为

1
awk [选项] '脚本命令' 文件名

awk 命令选项以及含义

选项 含义
-F fs 指定以 fs 作为输入行的分隔符,awk 命令默认分隔符为空格或制表符。
-f file 从脚本文件中读取 awk 脚本指令,以取代直接在命令行中输入指令。
-v var=val 在执行处理过程之前,设置一个变量 var,并给其设备初始值为 val。

脚本命令

awk 的强大之处在于脚本命令,它由 2 部分组成,分别为匹配规则和执行命令,如下所示

1
'匹配规则{执行命令}'

匹配规则

和 sed 命令中的 address 部分作用相同:用来指定脚本命令可以作用到文本内容中的具体行,可以使用字符串(比如 /demo/,表示查看含有 demo 字符串的行)或者正则表达式指定

注意

  • 整个==脚本命令是用单引号(’’)括起==,而其中的==执行命令部分需要用大括号({})括起来==
  • 在 awk 程序执行时,如果没有指定执行命令,则==默认会把匹配的行输出==;如果不指定匹配规则,则==默认匹配文本中所有的行==

awk 使用数据字段变量

awk 的主要特性之一是其处理文本文件中数据的能力,它会自动给一行中的每个数据元素分配一个变量。

默认情况下,awk 会将如下变量分配给它在文本行中发现的数据字段:

变量 数据字段
$0 代表整个文本行;
$1 代表文本行中的第 1 个数据字段;
$2 代表文本行中的第 2 个数据字段;
$n 代表文本行中的第 n 个数据字段。

在 awk 中,==默认的字段分隔符是任意的空白字符==(例如空格或制表符)。 在文本行中,每个数据字段都是通过字段分隔符划分的。awk 在读取一行文本时,会用预定义的字段分隔符划分每个数据字段。

awk 脚本命令使用多个命令

方法1

awk 允许将多条命令组合成一个正常的程序。要在命令行上的程序脚本中使用多条命令,只要在命令之间放个分号即可,例如:

1
echo "My name is Rich" | awk '{$4="Christine"; print $0}'

第一条命令会给字段变量 $4 赋值。第二条命令会打印整个数据字段。

方法2

可以一次一行地输入程序脚本命令,比如说:

1
2
3
echo "My name is Rich" | awk '{
> $4="Christine"
> print $0}'

在你用了表示起始的单引号后,bash shell 会使用 > 来提示输入更多数据,我们可以每次在每行加一条命令,直到输入了结尾的单引号。

awk从文件中读取程序

跟 sed 一样,awk 允许将脚本命令存储到文件中,然后再在命令行中引用,比如:

1
awk -F : -f awk.sh /etc/passwd

注意,在程序文件中,也可以指定多条命令,==只要一条命令放一行即可,之间不需要用分号==。

awk BEGIN关键字

awk 中还可以指定脚本命令的运行时机。默认情况下,awk 会从输入中读取一行文本,然后针对该行的数据执行程序脚本,但有时可能需要==在处理数据前运行一些脚本命令==,这就需要使用 BEGIN 关键字。

BEGIN 会强制 awk 在读取数据前执行该关键字后指定的脚本命令

例如:

1
2
awk 'BEGIN {print "The data3 File Contents:"}
> {print $0}' data3.txt

BEGIN 部分的脚本指令会在 awk 命令处理数据前运行,而真正用来处理数据的是第二段脚本命令。

awk END关键字

和 BEGIN 关键字相对应,END 关键字允许我们指定一些脚本命令,awk 会在==读完数据后执行它们==,例如:

1
2
3
awk 'BEGIN {print "The data3 File Contents:"}
> {print $0}
> END {print "End of File"}' data3.txt

当 awk 程序打印完文件内容后,才会执行 END 中的脚本命令。

awk 使用变量

在 awk 的脚本程序中,支持使用变量来存取值。awk 支持两种不同类型的变量:

  • 内建变量:awk 本身就创建好,用户可以直接拿来用的变量,这些变量用来存放处理数据文件中的某些字段和记录的信息。
  • 自定义变量:awk 支持用户自己创建变量。

内建变量

awk 程序使用内建变量来引用程序数据里的一些特殊功能

变量 功能
$0 代表整个文本行;
$1 代表文本行中的第 1 个数据字段;
$2 代表文本行中的第 2 个数据字段;
$n 代表文本行中的第 n 个数据字段。
FIELDWIDTHS 由空格分隔的一列数字,定义了每个数据字段的确切宽度。
一旦设定了 FIELDWIDTHS 变量的值,就不能再改变了,因此,这种方法并==不适用于变长的字段==
FNR 当前输入文档的记录编号,常在有多个输入文档时使用。
NR 输入流的当前记录编号。
FS 输入字段分隔符
RS 输入记录分隔符,默认为换行符 \n。
OFS 输出字段分隔符,默认为空格。
print 命令会自动将 OFS 变量的值放置在输出中的每个字段间
ORS 输出记录分隔符,默认为换行符 \n。
ARGC 命令行参数个数。
ARGIND 当前文件在 ARGC 中的位置。
ARGV 包含命令行参数的数组。
CONVFMT 数字的转换格式,默认值为 %.6g。
ENVIRON 当前 shell 环境变量及其值组成的关联数组。
ERRNO 当读取或关闭输入文件发生错误时的系统错误号。
FILENAME 当前输入文档的名称。
FNR ==当前数据文件==中的数据行数。
IGNORECASE 设成非 0 值时,忽略 awk 命令中出现的字符串的字符大小写。
NF 数据文件中的字段总数。
NR 已处理的输入记录==总数==。会持续计数直到处理完所有的数据文件
OFMT 数字的输出格式,默认值为 %.6g。
RLENGTH 由 match 函数所匹配的子字符串的长度。
TSTART 由 match 函数所匹配的子字符串的起始位置。

理解

  • 字段分隔符:作用于 awk 每次处理的一个单元
  • 记录分隔符:用于区分 awk 每次要处理的一个单元

自定义变量

awk 允许用户定义自己的变量在脚本程序中使用。awk 自定义变量名可以是任意数目的字母、数字和下划线,但不能以数字开头。更重要的是,awk 变量名==区分大小写==

也可以用 awk 命令行来给程序中的变量赋值,这允许我们在正常的代码之外赋值,即时改变变量的值,比如:

1
2
3
4
5
6
7
8
9
[root@localhost ~]# awk '
\> BEGIN{
\> testing="This is a test"
\> print testing
\> testing=45
\> print testing
\> }'
This is a test
45

需要注意的是,使用命令行参数来定义变量值会有一个问题,即设置了变量后,这个值在代码的 BEGIN 部分不可用,如下所示:

1
2
3
4
5
6
7
8
[root@localhost ~]$ cat script2
BEGIN{print "The starting value is",n; FS=","}
{print $n}
[root@localhost ~]$ awk -f script2 n=3 data1
The starting value is
data13
data23
data33

解决这个问题,可以用 -v 命令行参数,它可以实现在 BEGIN 代码之前设定变量。在命令行上,-v 命令行参数必须放在脚本代码之前,如下所示:

1
2
3
4
5
[root@localhost ~]$ awk -v n=3 -f script2 data1
The starting value is 3
data13
data23
data33

awk 使用数组

awk 使用分支结构

awk 使用循环结构

awk 使用函数

wget

wget是一个命令行的下载工具。对于我们这些 Linux 用户来说,几乎每天都在使用它。下面为大家介绍几个有用的 wget 小技巧,可以让你更加高效而灵活的使用 wget。

1
$ wget -r -np -nd http://example.com/packages/

这条命令可以下载 http://example.com 网站上 packages 目录中的所有文件。其中,-np的作用是不遍历父目录,-nd表示不在本机重新创建目录结构。

1
$ wget -r -np -nd --accept=iso http://example.com/centos-5/i386/

与上一条命令相似,但多加了一个–accept=iso选项,这指示 wget 仅下载 i386 目录中所有扩展名为 iso 的文件。你也可以指定多个扩展名,只需用逗号分隔即可。

1
$ wget -i filename.txt

此命令常用于批量下载的情形,把所有需要下载文件的地址放到 filename.txt 中,然后 wget 就会自动为你下载所有文件了。

1
$ wget -c http://example.com/really-big-file.iso

这里所指定的-c选项的作用为断点续传。

1
$ wget -m -k (-H) http://www.example.com/

该命令可用来镜像一个网站,wget 将对链接进行转换。如果网站中的图像是放在另外的站点,那么可以使用-H选项。

函数

函数指针

  • 函数的地址是存储其机器语言代码的内存开始的地址

  • 好处:可在在不同的时间使用不同的函数(可以把函数当做参数传递)

  • 声明:

    1
    2
    3
    4
    // 函数原型
    double sum(double, double)
    // 函数指针声明
    double (*ptrSum)(double, double)

    注意:

    • 该语句声明了一个指针ptrSum,指向一个函数
    • double *ptrSum(double, double)不是函数指针,而是:声明了一个函数ptrSum,返回 double *类型
  • 用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 声明power函数
    int power(int, int);
    // 声明函数指针
    int (*powerPtr)(int, int);
    // 让声明的函数指针指向函数,以便调用
    powerPtr = power;
    // 调用
    powerPtr(3, 4)
    (*powerPtr)(3, 4)
  • 应用

    • 题目

      用函数指针实现加减乘除

    • 代码

      • Caculator.h

        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
        /**
        * author Qeuroal
        * date 2021/5/19
        * description
        */
        #ifndef BLOG141_CACULATOR_H
        #define BLOG141_CACULATOR_H
        #include <iostream>
        using namespace std;

        // 自定义计算器,使用函数指针

        /**
        * 加法
        * @return
        */
        double addition(double, double);
        /**
        * 减法
        * @return
        */
        double subtraction(double, double);

        /**
        * 乘法
        * @return
        */
        double multiplication(double, double);

        /**
        * 除法
        * @return
        */
        double division(double, double);

        /**
        * 打印计算结果
        * param double (*)(double , double ) 函数指针
        */
        void printResult(double (*)(double , double ), double, double);

        void printResult(double (*calcPtr)(double, double), double num1, double num2) {
        // 调用函数,打印结果
        double result = calcPtr(num1, num2);
        cout << "运算结果为:" << result << endl;
        return;
        }

        double addition(double num1, double num2) {
        return num1 + num2;
        }

        double subtraction(double num1, double num2) {
        return num1 - num2;
        }

        double multiplication(double num1, double num2) {
        return num1 * num2;
        }

        double division(double num1, double num2) {
        if (num2 == 0) {
        cout << "除数不能为0!!" << endl;
        return 0;
        }
        return num1 / num2;
        }



        #endif //BLOG141_CACULATOR_H
      • FuncPtr.cpp

        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
        /**
        * author Qeuroal
        * date 2021/5/19
        * description
        */
        #include <iostream>
        #include "Caculator.h"

        using namespace std;

        int main() {
        // 定义函数指针
        double (*calcPtr)(double, double) = nullptr;
        // 定义函数指针数组
        double (*calcPtrArray[])(double, double) = {};
        cout << "请输入两个操作数:";
        double num1, num2;
        cin >> num1 >> num2;
        cout << "请输入运算符:";
        char op;
        cin >> op;
        switch (op) {
        case '+':
        calcPtr = addition;
        break;
        case '-':
        calcPtr = subtraction;
        break;
        case '*':
        calcPtr = multiplication;
        break;
        case '/':
        calcPtr = division;
        break;
        }
        printResult(calcPtr, num1, num2);
        }
  • 注意

    • 要用函数指针的话,先定义函数,再定义函数指针;

    • C11 中可以使用 auto funcPtr = addition; 自动推断类型(自动类型推断需要确保变量的类型与初值类型一致);

    • 使用auto时,需要初始化,否则会报错;

    • 可以使用 typedef 简化声明:

      1
      2
      3
      typedef double (*funcPtr)(double, double);
      // 此时funcPtr是一个新的类型,而不是一个函数指针,真正的函数指针是calcPtr
      funcPtr = calcPtr;

面向对象

注意点。

  • 类一般是在头文件定义
  • .h文件 声明类的结构,.cpp文件实现类的成员函数

实例化方法(以无参构造为例)

假设定义了 Person 类。

  • 省略了默认构造:Person person; (在栈内存中直接分配空间)

  • 标准写法:Person person(); (在栈内存中直接分配空间)

  • 指针写法:Person *person = new Person(); (在堆内存分配空间,并用指针 person 指向该内存空间)

  • 如果构造函数中只有一个是带唯一的参数,如下所示:

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      class Person {

      public:
      Person(int age) {
      this->age = age;
      }

      Person(string name, int age) {
      this->name = name;
      this->age = age;
      }
      private:
      string name;
      int age;
      };
    • 实例化

      1
      Person person =20;

构造函数

  • 正常写法(略)

  • 简化写法:初始化参数列表写法

    1
    Person::Person(string name, int age) : name(name), age(age) {}

简单介绍

  • HTML + CSS + JavaScript
  • 结构 + 表现 + 交互

如何学习

  1. css 是什么
  2. css怎么用(快速入门)
  3. css选择器(重点+难点)
  4. 美化网页(文字、阴影、超链接、列表、渐变……)
  5. 盒子模型
  6. 浮动
  7. 定位
  8. 网页动画(特效效果),教程:菜鸟教程

什么是css

  • cssCascading Style Sheet层叠(级联)样式表

  • css:表现(美化网页):字体、颜色、边距、高度、宽度、背景图片、网页定位、网页浮动……

发展史

  • css1.0
  • css2.0DIV(块)+ CSS,提出了htmlcss 结构分离的思想,网页变得简单,利于 SEO
  • css2.1:浮动和定位
  • css3.0:圆角、阴影、动画……(浏览器兼容性)

练习格式

快速入门

style

  • 基本入门

    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>

    <!--规范,<style>可以编写css代码,每一个声明最好使用分号结尾
    语法:
    选择器 {
    声明1;
    声明2;
    声明3;
    }
    -->
    <style>
    h1 {
    color: rebeccapurple;
    }
    </style>

    </head>
    <body>
    <h1>我是标题</h1>
    </body>
    </html>
  • 建议使用这种方式

css优势

  • 内容和表现分离
  • 网页结构表现统一,可以实现复用
  • 样式十分丰富
  • 建议使用独立于 HTMLCSS 文件
  • 利于 SEO,容易被搜索引擎收录

css的3种导入方式

行内样式

1
2
<!--行内样式:在标签元素中,编写一个style属性,编写样式即可-->
<h1 style="color:aqua;">我是标题</h1>

内部样式:style标签

外部样式(推荐)

优先级:

就近原则:最近的是:行内样式,然后看内部样式和外部样式谁在上面

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
/*注释*/
h1 {
color: green;
}
</style>

<!--外部样式-->
<link rel="stylesheet" href="css/style.css">
</head>
<body>

<!--行内样式:在标签元素中,编写一个style属性,编写样式即可-->
<h1>我是标题</h1>
</body>
</html>

拓展:外部样式两种写法

  • 链接式:html(推荐)

    1
    <link rel="stylesheet" href="css/style.css">
  • 导入式:css2.1css2.1 特有的

    1
    2
    3
    <style>
    @import url("css/style.css");
    </style>
  • 缺点:导入式会先显示结构,再去渲染;链接式是一起生效

选择器

作用:选择页面上的某一个或者某一类元素

基本选择器

标签选择器

选择一类标签

1
2
3
4
5
6
7
8
9
10
/*标签选择器,会选择到页面上所有的这个标签的元素*/
h1 {
color: red;
background: antiquewhite;
border-radius: 24px;
}
p {
font-size: 80px;

}

类选择器:class

  • 语法

    1
    2
    3
    4
    类选择器的格式:
    .类的名称 {
    样式
    }
  • 好处

    好处:可以多个标签归类,是同一个class,可以复用;可以跨标签

  • 示例

    1
    2
    3
    4
    5
    6
    7
    */
    .title1 {
    color: red;
    }
    .title2 {
    color: blueviolet;
    }

ID选择器

  • ID 一定要唯一

  • 语法

    1
    2
    3
    4
    5
    id选择器:id必须保证全局唯一
    格式:
    #id名称 {

    }
  • 示例

    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
    /*
    id选择器:id必须保证全局唯一
    格式:
    #id名称 {

    }
    */
    #title1 {
    color: blueviolet;
    }

    .title2 {
    color: red;
    }

    h1 {
    color: green;
    }
    </style>
    </head>
    <body>
    <h1 id="title1" class="title2">标题1</h1>
    <h1 class="title2">标题2</h1>
    <h1 class="title3">标题3</h1>
    <h1>标题4</h1>
    <h1>标题5</h1>
    </body>
    </html>

优先级:

不遵循就近原则,是固定的:

id选择器 > class选择器 > 标签选择器

层次选择器

模型

后代选择器:空格

在某个元素的后面,如:祖爷爷->爷爷->爸爸->你

1
2
3
4
/* 后代选择器*/
body p {
background: red;
}

子选择器: >

只有一代,即儿子

1
2
3
4
/*子选择器*/
body>p {
background: blueviolet;
}

相邻兄弟选择器:+

同辈:哥哥,姐姐

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
/*相邻兄弟选择器:只有一个,相邻:对下*/
.activate + p {
background: aqua;
}
</style>
</head>
<body>
<p>p0</p>
<p class="activate">p1</p>
<p>p2</p>
<p>p3</p>
<ul>
<li>
<p>p4</p>
</li>
<li>
<p>p5</p>
</li>
<li>
<p>p6</p>
</li>
</ul>

<p class="activate">p7</p>
<p>p8</p>
</body>
</html>

通用选择器:~

1
2
3
4
/*通用兄弟选择器:当前选中元素的向下的所有兄弟元素*/
.activate ~ p {
background: green;
}

结构伪类选择器

伪类选择器:xx:xxx {}

示例

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--避免使用class和id选择器-->
<style>
/*1. ul的第一个子元素*/
ul li:first-child {
background: red;
}
/*2. ul的最后一个子元素*/
ul li:last-child {
background: green;
}
/*3. 选中p1:定位到父元素,选择当前的第一个元素*/
/*选择当前p元素的父级元素,选中父级元素的第一个元素,并且是当前元素才生效*/
p:nth-child(1) {
background: aqua;
}
/*选择当前p元素的父级元素,选中父级元素类型是当前元素的第2个元素*/
p:nth-of-type(2) {
background: blueviolet;
}
</style>
</head>
<body>
<h1>h1</h1>
<p>p1</p>
<p>p2</p>
<p>p3</p>
<ul>
<li>li1</li>
<li>li2</li>
<li>li3</li>
</ul>

</body>
</html>

属性选择器(常用)

idclass 结合

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.demo a {
float: left;
display: block;
height: 50px;
width: 50px;
border-radius: 10px;
background: blue;
text-align: center;
color: white;
text-decoration: none;
margin-right: 5px;
font: blod 20px/50px Arial;
line-height: 50px;
}


/*存在id属性的元素
格式:
a[属性名、属性名=属性值(值可以用正则)、属性名*=属性值(值可以用正则)] {}
=绝对等于
*= 包含这个值
^= 以这个值开头
$= 以这个结尾
*/
a[id] {
background: black;
}
/*id=first的元素*/
a[id="first"] {
background: red;
}
/*class有links的元素*/
a[class*="links"] {
background: yellow;
}
/*选中href中以http开头的元素*/
a[href^="http"] {
background: blueviolet;
}
a[href$="pdf"] {
background: rebeccapurple;
}
</style>
</head>
<body>

<p class="demo">
<a href="http://www.baidu.com" class="links item first" id="first">1</a>
<a href="https://qeuroal.top" class="links item activate" target="_blank" title="test">2</a>
<a href="images/123.html" class="links item">3</a>
<a href="images123.png" class="links item">4</a>
<a href="images123.png" class="links item">5</a>
<a href="abc">6</a>
<a href="/a.pdf">7</a>
<a href="/abc.pdf">8</a>
<a href="/abc.doc">9</a>
<a href="/abcd.doc" class="links item last">10</a>
</p>

</body>
</html>

美化网页元素

为什么要美化网页

  • 有效的传递页面信息
  • 美化网页,页面漂亮才能吸引用户
  • 凸显页面的主题
  • 提高用户的体验

span标签

重点要突出的字,使用 span 套起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#title1 {
font-size: 50px;
}
</style>
</head>
<body>
欢迎学习 <span id="title1">css</span>
</body>
</html>

字体样式

  • px:像素

  • em:缩进

  • 分开写:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <!--
    font-family 字体
    font-size 字体大小
    font-weight 字体粗细
    color 字体颜色
    -->
    <style>
    body {
    color: #a13d30;
    font-family: "Consolas", 楷体;
    }
    h1 {
    font-size: 50px;
    }
    .p1 {
    font-weight: bold;
    }
    </style>
  • 合并写

    1
    2
    3
    4
    5
    6
    <!--字体风格-->
    <style>
    p {
    font: oblique bolder 18px "楷体" ;
    }
    </style>

文本样式

颜色

color

  • 单词
  • rgb0~F
  • rgba:透明度, 0~1

文本对齐方式

text-align=center排版:居中、左、右

首行缩进

text-indent: 段落首行缩进,一般是2em(首行缩进2字符)

块高

height 块高

行高

line-height:单行文字上下居中line-height=height

装饰

text-decoration

文本图片水平对齐

vertical-align: middle;

示例

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--
颜色:
单词
RGB:0~F
RGBA:透明度, 0~1

text-align 排版:居中、左、右
text-indent 段落首行缩进,一般是2em(首行缩进2字符)
height 块高
line-height 行高;和块的高度一致,就可以上下居中
-->
<style>
h1 {
/*color: rgba(0, 255, 255, 0.5);*/
color: rgb(0, 255, 255);
text-align: center;
}

.p1 {
text-indent: 2em;
}
.p3 {
background: red;
height: 300px;
line-height: 300px;
}

/*下划线*/
.l1 {
text-decoration: underline;
}
/*中划线*/
.l2 {
text-decoration: line-through;
}
/*上划线*/
.l2 {
text-decoration: underline;
}
/*a标签(超链接)去下划线*/
a {
text-decoration: none;
}
.img1 {
width: 500px;
height: 400px;
}
/*水平对齐:参照物,a和b对齐*/
img, span {
vertical-align: middle;
}
</style>
</head>
<body>

<h1>故事介绍</h1>
<a href="">hell</a>
<p class="l1">123123421</p>
<p class="l2">123123421</p>
<p class="l3">123123421</p>

<p class="p1">
平静安详的元泱境界,每隔333年,总会有一个神秘而恐怖的异常生物重生,它就是魁拔!魁拔的每一次出现,都会给元泱境界带来巨大的灾难!即便是天界的神族,也在劫难逃。在天地两界各种力量的全力打击下,魁拔一次次被消灭,但又总是按333年的周期重新出现。魁拔纪元1664年,天神经过精确测算后,在第六代魁拔苏醒前一刻对其进行毁灭性打击。但谁都没有想到,由于一个差错导致新一代魁拔成功地逃脱了致命一击。很快,天界魁拔司和地界神圣联盟均探测到了魁拔依然生还的迹象。因此,找到魁拔,彻底消灭魁拔,再一次成了各地热血勇士的终极目标。
</p>

<p class="p2">
在偏远的兽国窝窝乡,蛮大人和蛮吉每天为取得象征成功和光荣的妖侠纹耀而刻苦修炼,却把他们生活的村庄搅得鸡犬不宁。村民们绞尽脑汁把他们赶走。一天,消灭魁拔的征兵令突然传到窝窝乡,村长趁机怂恿蛮大人和蛮吉从军参战。然而,在这个一切都凭纹耀说话的世界,仅凭蛮大人现有的一块杂牌纹耀,不要说参军,就连住店的资格都没有。受尽歧视的蛮吉和蛮大人决定,混上那艘即将启程去消灭魁拔的巨型战舰,直接挑战魁拔,用热血换取至高的荣誉。
</p>

<p class="p3">
If you eone to wipe your tears, guess what? I'll be there. William Shakespeare
</p>

<p>
<img src="images/img1.png" alt="" class="img1">
<span>aaaaaaaaaaa</span>
</p>


</body>
</html>

阴影

1
2
3
4
5
6
/*text-shadow
params: 阴影颜色,水平偏移,垂直偏移,阴影半径
*/
#price {
text-shadow: #20c1b4 10px 10px 10px;
}

超链接伪类

正常情况下,aa:hover

1
2
3
4
5
6
7
8
9
10
11
12
/*鼠标悬浮的状态(只需要记住hover)*/
a:hover {
color: orange;
font-size: 20px;
}
/*鼠标按住未释放的状态*/
a:active {
color: mediumseagreen;
}
a:visited {
color: blueviolet;
}

列表

1
2
3
4
5
6
7
8
9
10
11
12
/*
list-style:
none 去掉圆点
circle 空心圆
decimal 有序列表(数字)
square 正方形
*/
ul li {
height: 30px;
list-style: none;
text-indent: 1em;
}

背景

背景颜色

1
background: #a0a0a0;

背景图片

法一

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
div {
width: 1000px;
height: 700px;
border: 1px solid red;
/*默认:全部平铺 repeat*/
background-image: url("images/pdx.jpg");
}

.div1 {
background-repeat: repeat-x;
}
.div2 {
background-repeat: repeat-y;
}
.div3 {
background-repeat: no-repeat;
}
</style>
</head>
<body>
<div class="div1"></div>
<div class="div2"></div>
<div class="div3"></div>
</body>
</html>

法二

1
2
/*颜色 图片 图片位置 平铺方式*/
background: red url("../images/down.png") 270px 10px no-repeat;

渐变

网站

盒子模型

什么是盒子

  • margin:外边距

    所有的 bodymargin 默认为8

  • padding:内边距

  • border:边框

边框

  • 边框的粗细
  • 边框的样式
  • 边框的颜色

实现

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
/*常见操作*/
h1, ul, li, a, body {
/*body总有一个默认的外边距margin: 0*/
margin: 0;
padding: 0;
text-decoration: none;
}
#app {
width: 300px;
/*border
params:粗细,样式(实线/虚线),颜色
*/
border: 3px solid red;
}

h2 {
font-size: 18px;
background: #4986ee;
text-align: center;
line-height: 40px;
color: white;
}
form {
background: #a0a0a0;
}
div:nth-of-type(1) input {
border: 2px solid black;
}
div:nth-of-type(2) input {
border: 2px dashed blueviolet;
}
div:nth-of-type(3) input {
border: 2px dashed green;
}

内外边距

margin, padding 参数

  • 一个参数:上下左右
  • 两个参数:上下、左右
  • 四个参数:上、左、下、右(顺时针)

盒子的计算方式

你这个元素到底多大?

美工+前端

盒子最终大小:margin + border + padding + 内容的大小(上图内容的大小为:296*22

示例

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--外边距的妙用:居中-->
<style>

#app {
width: 300px;
/*border
params:粗细,样式(实线/虚线),颜色
*/
border: 3px solid red;
padding: 0 0 0 0;
/*margin
一个参数:上下左右
两个参数:上下、左右
四个参数:上、左、下、右(顺时针)
*/
margin: 0 auto ;
}

h2 {
font-size: 18px;
background: #4986ee;
text-align: center;
line-height: 40px;
/*上下左右都为0*/
margin: 0;
color: white;
}
form {
background: #a0a0a0;
}
input {
border: 1px solid black;
}
div:nth-of-type(1) {
border: 1px solid blueviolet;
padding: 10px;
}
</style>
</head>
<body>
<!--div为了层次更清楚;id="app":在vue里面id设置为app,代表一个应用,整个应用不是在body里面,而是在div里面,是需要定位的-->
<div id="app">
<h2>会员登录</h2>
<form action="#">
<!--里面的属性都用div包起来-->
<div>
<span>姓名:</span>
<input type="text">
</div>
<div>
<span>密码:</span>
<input type="password">
</div>
<div>
<span>邮箱:</span>
<input type="text">
</div>
</form>
</div>
</body>
</html>

圆角边框

有4个角

1
2
3
4
5
6
7
8
9
10
11
12
div {
width: 100px;
height: 100px;
border: 10px solid red;
/*
一个参数:所有角
两个参数:左上 右下、右上 左下
四个参数:左上 右上 右下 左下(顺时针方向);而且一个参数管一个角,以及对应的两边的边长如下图
*/
/*圆圈:圆角=宽度的一半*/
border-radius: 50px 20px;
}

圆形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
div {
width: 50px;
height: 50px;
margin: 100px;
background: red;
/*border不在设定的长宽,所以得用border-radius也得算上去,这里给去了
/*border: 10px solid red;*/
border-radius: 50px 0px 0px 0px;
}
img {
width: 50px;
height: 50px;
border-radius: 25px;
}

这里是半圆形:可以这样理解:border-radius只控制半径为参数的那个 1/4的圆,widthheight 控制的是区域的大小

阴影

补充

  • img是内联元素 ,所以无法使用 margin: 0 auto; 居中

    1
    2
    3
    4
    5
    6
    7
    img {
    width: 150px;
    height: 150px;
    border-radius: 75px;
    margin: 0 auto;
    box-shadow: 10px 10px 100px yellow;
    }

    所以用 margin: 0 auto; 居中

    要求:

    • 外面是块元素
    • 块元素有固定的宽度

div的外层是body可以使用 margin: 0 auto;居中,因为body 是块元素,如果想让body里面的所有元素居中,可以这样

1
2
3
4
5
6
div {
width: 1000px;
border: 10px solid red;
margin: 0 auto;
text-align: center;
}

设置:text-align:center

  • 让内联 元素居中方法:

    • 块元素设置 text-align:center,使块内所有元素居中

    • 将内联元素设置为块元素: display: block

      1
      2
      3
      4
      5
      6
      7
      8
      img {
      width: 150px;
      height: 150px;
      border-radius: 75px;
      margin: 0 auto;
      box-shadow: 10px 10px 100px yellow;
      display: block;
      }

浮动

标准文档流

  • 不局部:自上而下的拼

  • 布局过:

  • 块级元素:独占一行,且只有块元素设置大小才能被显示

    • h1~h6
    • p
    • div
    • 列表
    • ……
  • 行内元素(内联元素):不独占一行

    • span
    • a
    • img
    • strong
    • ……

行内元素可以包含在块级元素里面,但是块级元素不能包含在行内元素里面

display

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
div {
width: 100px;
height: 100px;
border: 1px solid red;
display: inline-block;
}

span {
width: 100px;
height: 100px;
border: 1px solid red;
/*
inline-block 既是行内元素又是块元素(保持块元素的特性,可以写在一行)
block 块元素
inline 行内元素
none 消失
*/
display: inline-block;
}

这个也是一种实现行内元素排列的方式,但我们很多情况都是用 float

float

  • 左右浮动:float

    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
    div {
    margin: 10px;
    padding: 5px;
    }
    #app {
    border: 1px #000 solid;
    }

    .layer01 {
    border: 1px #f00 dashed;
    display: inline-block;
    float: right;
    clear:both;

    }
    .layer02 {
    border: 1px #00f dashed;
    display: inline-block;
    float: right;
    clear:both;

    }

    .layer03 {
    border: 1px #060 dashed;
    display: inline-block;
    float: right;
    clear:both;

    }

    .layer04 {
    border: 1px #666 dashed;
    font-size: 12px; display: inline-block;
    /*float: right;*/
    line-height: 23px;
    clear:both;
    }

父级边框塌陷的问题

clear

  • right: 右侧不允许有浮动元素,如果有,排到下面去(下同)
  • left: 左侧不允许有浮动元素
  • both: 两侧允许有浮动元素
  • none:

解决方案

  1. 增加父级元素高度

    1
    2
    3
    4
    #app {
    border: 1px #000 solid;
    height: 800px;
    }
  2. 增加一个空的 div 标签,清除浮动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div id="app">
    <div class="layer01"><img src="images/1.jpg" alt=""></div>
    <div class="layer02"><img src="images/5.jpg" alt=""></div>
    <div class="layer03"><img src="images/3.jpg" alt=""></div>
    <div class="layer04">
    浮动的盒子可以向左浮动,也可以向右浮动,直到它的外边缘碰到包含框或另一个浮动盒子位置
    </div>
    <div class="clear"></div>
    </div>
    1
    2
    3
    4
    5
    .clear {
    clear: both;
    margin: 0;
    padding: 0;
    }
  3. overflow

    • params:

      1
      2
      3
      hidden: 隐藏
      scroll: 滚动条
      auto:
    • 在父级元素中增加一个overflow: hidden

      1
      2
      3
      4
      5
      #app {
      border: 1px #000 solid;
      /*height: 800px;*/
      overflow: hidden;
      }
  4. 父类添加一个伪类:after (推荐)

    可以避免空 div

    1
    2
    3
    4
    5
    #app:after {
    content: "";
    display: block;
    clear: both;
    }
  5. 小结

    • 浮动元素后面增加空 div

      优势

      • 简单

      劣势:

      • 代码中尽量避免空 div
    • 设置父元素的高度

      优势:

      • 简单

      劣势:

      • 元素假设有了固定的高度,就会被限制
    • overflow: hidden

      优势:

      • 简单

      劣势:

      • 下拉的一些场景避免使用

      • 如果超出,就会被切掉,但是宁愿被切掉,也不要滚动条,很诡异

    • 父类添加一个伪类:after

      优势:

      • 写法稍微复杂一点,但是没有副作用,推荐使用

对比

  • display
    • 方向不可控
    • 不用管理父级边框塌陷
  • float
    • 浮动起来的话会脱离标准文档流,所以要解决父级边框塌陷的问题

定位

相对定位

  • position: reletive

  • 相对定位:相对于自己原来的位置进行偏移,相对定位的话,它仍然在标准文档流中,它原来的位置会被保留

    1
    2
    3
    4
    5
    /*距离上面-20px*/
    top: -20px;
    left: 10px;
    bottom: -10px;
    right: 20px;
  • 示例

    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--相对定位:相对于自己原来的位置进行偏移-->
    <style>
    body {
    padding: 20px;
    }
    div {
    margin: 10px;
    padding: 5px;
    font-size: 12px;
    line-height: 25px;
    }

    #app {
    border: 1px solid #666;
    padding: 0;
    }
    #first {
    background-color: #a0a0a0;
    border: 1px dashed #b61818;
    /*相对定位:上下左右*/
    position: relative;
    /*距离上面-20px*/
    top: -20px;
    left: 10px;
    }
    #second {
    background-color: #e2cccc;
    border: 1px dashed #59b631;
    }
    #third {
    background-color: #eade86;
    border: 1px dashed #bb21d5;
    position: relative;
    bottom: -10px;
    right: 20px;
    }
    </style>

    </head>
    <body>

    <div id="app">
    <div id="first">第1个盒子</div>
    <div id="second">第2个盒子</div>
    <div id="third">第3个盒子</div>
    </div>
    </body>
    </html>

练习

实现

代码

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#app {
width: 300px;
height: 300px;
border: 2px solid red;
padding: 10px;
}
a {
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
background-color: #ea85c2;
/*加了border位置就不准了,因为border+内容的大小才是整个元素的大小*/
/*border: 1px solid #000000;*/
color: white;
text-decoration: none;
display: block;
}

a:hover {
background-color: #0c91ea;
}

.a2, .a4 {
position: relative;
left: 200px;
top: -100px;
}
.a3 {
position: relative;
}
.a5 {
position: relative;
left: 100px;
top: -300px;
}
</style>
</head>
<body>
<div id="app">
<a href="#" class="a1">链接1</a>
<a href="#" class="a2">链接2</a>
<a href="#" class="a3">链接3</a>
<a href="#" class="a4">链接4</a>
<a href="#" class="a5">链接5</a>
</div>
</body>
</html>

绝对定位

  • 定位:基于xxx定位,只有上下左右
    • 没有父级元素定位的前提下,相对于浏览器定位;
    • 父级元素存在定位,我们通常相对于父级元素进行偏移;
    • 在父级元素范围内移动
  • 相对于父级或浏览器的位置进行偏移,绝对定位的话,它不在标准文档流中,它原来的位置不会被保留

固定定位

  • 定位:无论怎么走,都在固定位置

  • 示例

    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
    body {
    height: 1000px;
    }
    div:nth-of-type(1) {
    width: 100px;
    height: 100px;
    background-color: #ef67ad;
    text-align: center;
    line-height: 100px;
    /*绝对定位:相对于浏览器*/
    position: absolute;
    right: 0;
    bottom: 0;
    display: block;
    }

    div:nth-of-type(2) {
    width: 50px;
    height: 50px;
    background-color: #3383d9;
    /*固定定位:定死了;很多导航栏不动,就是通过固定定位来实现的*/
    position: fixed;
    right: 0;
    bottom: 0;
    display: block;
    }
    </style>
    </head>
    <body>

    <div>div1</div>
    <div>div2</div>

    </body>
    </html>

z-index

理解图层

实践

z-index:默认是0,最高无限;习惯用999代表最高层

  • html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="css/style.css">
    </head>
    <body>

    <div id="app">
    <ul>
    <li><img src="images/bg.webp" alt=""></li>
    <li class="tipText">学习微服务,找狂神</li>
    <li class="tipBg"></li>
    <li>时间:2021-1-1</li>
    <li>地点:卢浮宫</li>
    </ul>
    </div>
    </body>
    </html>
  • css

    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
    #app {
    width: 320px;
    padding: 0;
    margin: 0;
    overflow: hidden;
    font-size: 12px;
    line-height: 25px;
    border: 1px black solid;
    }

    ul, li {
    padding: 0;
    margin: 0;
    list-style: none;
    }

    /*父级元素相对定位*/
    #app ul {
    position: relative;
    }

    .tipText, .tipBg {
    position: absolute;
    width: 320px;
    height: 25px;
    top: 167px;
    color: white;
    }

    .tipText {
    /*最低为0*/
    /*z-index: 999;*/
    }

    .tipBg {
    background: black;
    /*背景透明度*/
    opacity: 0.5;
    /*现在一般不可以用,早期可以用*/
    filter: alpha(opacity=50);
    }

动画

菜鸟教程

canvas 动画:点击这里

卡巴斯基安全监控网站点击这里

源码之家点击这里

前端

  • HTML:网页基本结构

  • CSS:美化网页

  • JS:使得网页动起来产生交互性行为

初识HTML

什么是HTML

Hyper Text Markup Language(超文本标记语言)

  • 超文本:文字、图片、音频、视频、动画等

W3C标准

w3c: 万维网联盟

标准:

  • 结构化标准语言(HTMLXML)
  • 表现标准语言(css
  • 行为标准 (DOMECMAScript

基本信息

  • 注释:<!-- 注释 -->
  • <!DOCTYPE html>:告诉浏览器,我们要使用什么规范
  • <html> </html>: 总标签,只有在里面写才有意义
  • head标签:网页头部
  • body标签:网页主体
  • title标签:网页标题
  • meta 标签:描述性标签,用来描述网站的一些信息,可以用来被搜索到;一般用来做 SEO

基本标签

标题标签

1
2
3
4
5
6
<h1>1级标签</h1>
<h2>2级标签</h2>
<h3>3级标签</h3>
<h4>4级标签</h4>
<h5>5级标签</h5>
<h6>6级标签</h6>

段落标签

1
2
3
<p>hello world</p>
<p>hello world2</p>
<p>hello world3</p>

换行标签

即便换行了,也是一个段落

1
2
3
hello world <br/>
hello world2 <br/>
hello world3 <br/>

水平线标签

1
<hr/>

字体样式标签

  • 粗体斜体

    1
    2
    粗体:<strong>Hello html</strong>
    斜体:<em>Hello html</em>

注释和特殊符号

注释

1
<!--注释-->

特殊符号

  • 空格:空 格:空&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;格

  • 大于号:&gt;

  • 小于号:&lt;

  • 版权符号:&copy;

  • 记忆方式:

    • &字母:可以直接调
    • 百度查(不推荐)

图像标签

常见图像格式

  • JPG
  • GIF
  • PNG
  • BMP:位图
  • ……

嵌入图片

1
2
3
4
5
6
7
8
<!--
src:图片地址(必填):
绝对地址:E:\SourceCode\HTMLSpace\learnProject\resources\images\pdx.jpg
相对路径:../resources/images/pdx.jpg

alt: 图片名字(必填)
-->
<img src="../resources/images/pdx.jpg" alt="Qeuroal" title="悬停文字" width="300" height="480">

链接标签

可以点的东西都是链接

页面间跳转:从一个页面跳转到另一个页面

文本超链接

1
2
3
4
5
6
7
8
<!--a标签
href:必填,表示要跳转到哪个页面
target:表示窗口在哪里打开
_blank: 在新标签页中打开
_self:在自己的网页打开
-->
<a href="test1.html" target="_blank">点击我跳转test1</a>
<a href="https://qeuroal.top/" target="_self">点击我跳转到主页</a>

图像超链接

1
2
3
<a href="test1.html">
<img src="../resources/images/pdx.jpg" alt="Qeuroal" title="点击我跳转test1" width="300" height="480">
</a>

页面内跳转:页面内跳转

锚链接

1
2
3
4
5
6
7
8
9
10
<!--使用name作为标记-->
<a name="top">顶部</a>

<!--锚链接:页面内跳转
1. 需要一个锚标记
2. 跳转到标记
3. #: 通过#跳到标记
-->
<a href="#top">回到顶部</a>
<a href="链接标签.html#down">跳转</a>

功能性连接

邮件链接

1
2
3
4
<!--功能性连接
邮件链接:mailto
-->
<a href="mailto:xxxxx@163.com"></a>

qq链接

1
2
3
<a target="_blank" href="http://wpa.qq.com/msgrd?v=3&uin=&site=qq&menu=yes">
<img border="0" src="http://wpa.qq.com/pa?p=2::53" alt="你好,加我领取资料" title="你好,加我领取资料"/>
</a>

块元素和行内元素

块元素

  • 无论内容多少,该元素独占一行
  • ph1-h6、…)

行内元素

  • 内容撑开宽度,左右都是行内元素的可以排在一行
  • astrongem、…)

列表标签

什么是列表

列表的分类

有序列表

1
2
3
4
5
6
7
8
9
10
<!--有序列表
应用:试卷、问答……
-->
<ol>
<li>java</li>
<li>python</li>
<li>运维</li>
<li>前端</li>
<li>c/c++</li>
</ol>

无序列表

1
2
3
4
5
6
7
8
9
10
<!--无序列表
应用:导航、侧边栏……
-->
<ul>
<li>java</li>
<li>python</li>
<li>运维</li>
<li>前端</li>
<li>c/c++</li>
</ul>

自定义列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--自定义列表
dl:标签
dt:列表名称
dd:列表选项

应用:公司网站底部
-->
<dl>
<dt>学科</dt>
<dd>java</dd>
<dd>python</dd>
<dd>Linux</dd>
<dd>C</dd>

<dt>位置</dt>
<dd>唐山</dd>
<dd>石家庄</dd>
</dl>

表格标签

为什么使用表格

  • 简单通用
  • 结构稳定

基本结构

  • 单元格
  • 跨行
  • 跨列

实现

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
<!--表格table
行:tr
列:td
-->
<table border="1px">
<tr>
<!--colspan 跨列-->
<td colspan="4">1</td>
<td>2</td>
<td>3</td>
<td>123</td>

</tr>
<tr>
<!--colspan 跨行-->
<td rowspan="2">4</td>
<td>5</td>
<td>6</td>
</tr>
<tr>
<td>7</td>
<td>8</td>
<td>9</td>
</tr>
</table>

媒体元素

视频元素video

1
2
3
4
5
6
<!--视频
src: 资源路径
controls: 控制条
autoplay: 自动播放
-->
<video src="../resources/video/起风了.mp4" controls autoplay></video>

音频元素audio

1
2
<!--音频-->
<audio src="../resources/audio/起风了.mp3" controls autoplay></audio>

页面结构分析

1
2
3
4
5
6
7
8
9
10
11
<header>
<h2>网页头部</h2>
</header>

<section>
<h2>网页主体</h2>
</section>

<footer>
<h2>网页脚部</h2>
</footer>

iframe内联框架

1
2
3
4
5
6
7
8
<!--iframe内联框架
src: 地址
frameborder:
w-h:宽度-高度
-->
<iframe src="https://www.baidu.com" name="baidu" frameborder="0" width="1000px" height="800px"></iframe>

<a href="https://qeuroal.top" target="baidu">点击跳转</a>

表单语法

规定

  • input标签 都要有一个name

表单元素格式

文本输入框

1
2
3
4
5
6
7
8
<!--文本输入框:input type="text"
value="Qeuroal" 默认初始值
maxlength="8" 最长能写几个字符
size="30" 文本框的长度
-->
<p>用户名:<input type="text" placeholder="请输入用户名" name="username" ></p>
<!--密码框:input type="password"-->
<p>密码:<input type="password" placeholder="请输入密码" name="pwd"></p>

单选框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--单选框
input type="radio"
value: 单选框的值
name: 表示组,组一样才能选择一个
-->
<p>性别:
<input type="radio" value="boy" name="sex" checked/>
<input type="radio" value="girl" name="sex"/>
</p>

<p>
<input type="submit">
<input type="reset">
</p>

多选框

1
2
3
4
5
6
7
8
9
<!--多选框
input type="checkbox"
-->
<p> 爱好:
<input type="checkbox" value="sleep" name="hobby">睡觉
<input type="checkbox" value="chat" name="hobby">聊天
<input type="checkbox" value="code" name="hobby" checked>码代码
<input type="checkbox" value="game" name="hobby">游戏
</p>

按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--
按钮
input type="button" 普通按钮
input type="image" 图像按钮
input type="submit" 提交按钮
input type="reset" 充值
-->
<p>按钮:
<input type="button" name="btn1" value="点击变长">
<input type="image" name="imgBtn1" src="../resources/images/pdx.jpg">
</p>

<p>
<input type="submit">
<input type="reset" value="清空表单">
</p>

下拉框(列表框)

1
2
3
4
5
6
7
8
9
10
<!--下拉框,列表框
-->
<p>国家:
<select name="countries">
<option value="china">中国</option>
<option value="eth" selected>瑞士ETH</option>
<option value="us">美国</option>
<option value="dg">德国</option>
</select>
</p>

文本域

1
2
3
<p>反馈 <br>
<textarea name="textarea" cols="30" rows="10" placeholder="请输入反馈信息"></textarea>
</p>

文件域

1
2
3
4
<p>
<input type="file" name="files">
<input type="button" value="上传" name="upload">
</p>

搜索框及简单验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--邮件验证-->
<p> 邮箱:
<input type="email" name="email">
</p>

<!--url-->
<p> url:
<input type="url" name="url">
</p>

<!--数字验证-->
<p> 数字:
<input type="number" name="num" max="100" min="0" step="1">
</p>

<!--滑块-->
<p> 音量:
<input type="range" name="voice" min="0" max="100">
</p>

<!--搜索框-->
<p> 搜索:
<input type="search" name="search">
</p>

表单的应用

只读 readonly

1
<p>用户名:<input type="text" placeholder="请输入用户名" name="username" value="admin" readonly></p>

禁用 disabled

1
<input type="radio" value="boy" name="sex" checked disabled/>

隐藏域 hidden

1
<p>密码:<input type="password" placeholder="请输入密码" name="pwd" hidden></p>

效果

增强鼠标可用性

1
2
3
4
<p>
<label for="mark">你点我试试</label>
<input type="text" id="mark">
</p>

表单初级验证

为什么要进行表单验证

  • 减轻服务器的压力
  • 保证数据的安全性

常用方式

  • placeholder :提示信息

    1
    <textarea name="textarea"  cols="30" rows="10" placeholder="请输入反馈信息"></textarea>
  • required:非空判断

    1
    <input type="search" name="search" required>
  • pattern:正则表达式

    • 如何查:常用正则表达式点击这里

    • 使用:

      1
      2
      3
      4
      <!--自定义邮箱-->
      <p>自定义邮箱
      <input type="email" name="diyMail" pattern="\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*">
      </p>

总结

第一个flask程序

Flask(__name__)

1
2
3
4
5
# 初始化一个Flask对象
# Flask()
# 需要传递一个参数 __name__
# 1. 方便flask框架去寻找资源
# 2. 方便flask插件比如Flask-Sqlalchemy出现错误的时候,好去寻找问题所在的位置

@app.route()

1
2
3
4
5
6
7
# @app.route是一个装饰器(可以看成url的后缀:127.0.0.1:5000/login 的 /login)
# @开头,并且在函数的上面,说明是装饰器
# 这个装饰器的作用是做一个url和视图函数(习惯性的命名,就是这里的hello_world函数)的映射
# 127.0.0.1:5000 -> 去请求hello_world这个函数然后将结果返回给浏览器
@app.route('/')
def hello_world():
return “我是第一个flask程序”

app.run():

启动一个应用服务器,来接收用户的请求。

类似于:

1
2
while (True):
listen()

设置debug模式

  1. app.run() 中传入一个关键字参数debugapp.run(debug=True), 就设置当前项目为 debug 模式
  2. debug 模式的两大功能
    • 当程序出现问题时,可以在也米那种看到错误信息和出错的位置
    • 只要修改了项目中的 Python 文件,程序会自动加载,不需要手动重新启动服务器

使用配置文件

  1. 新建一个 .py文件,这里设置为 config.py

  2. 然后设置大写,如:DEBUG=True

  3. app进行关联

    1
    2
    3
    4
    # 假设配置文件为config
    import config

    app.config.from_object(config)
  4. 还有许多的其他参数,都是放在这个配置文件中,比如 SECRET_KEYSQLALCHEMY 这些配置都是在这个文件中

URL传参

  1. 参数的作用:可以在相同的url,但是指定不同的参数,来加载不同的数据

  2. 在flask中如何使用参数

    1
    2
    3
    @app.route("/article/<id>")
    def article(id):
    return "您请求的参数是:%s" %id
    • 参数需要放在两个尖括号中
    • 视图函数中需要放和url中的参数同名的参数

反转URL

正转

通过url可以取到视图函数的内容

反转

  1. 从视图函数到url的转换叫做反转url(通过视图函数的名称,可以得到指向的url
  2. 用处
    • 在页面重定向的时候,会使用url反转
    • 在模板中也会使用url反转

函数讲解

  • url_for():
    • params:视图函数名称, 视图函数对应的参数(如果视图函数有参数的话),如 url_for("article", id="123")
    • return: 视图函数对应的url

页面跳转和重定向

  1. 用处:在用户访问一些需要登录的页面的时候,如果用户没有登录,那么可以让他重定向到登录页面

  2. 实现

    1
    2
    3
    4
    5
    6
    7
    8
    from flask import Flask, redirect, url_for

    @app.route("/question/<isLogin>/")
    def question(isLogin):
    if isLogin == '1':
    return "这是发布问答页面"
    else:
    return redirect(url_for("login"))

函数讲解

  • redirect()
    • params: url,如 redirect("/login123/")或者 redirect(url_for("login")),建议使用第二种,只要视图函数名字不变, @app.route(/login123/)里面的 login123怎么变都没关系

模板渲染和参数

如何渲染模板

  1. 模板放在 templates 文件夹下

  2. flask 中导入 render_template 函数

  3. 在试图函数中,使用 render_template 函数渲染模板。

    注意:只需要填写模板的名字,不要填写 templates 这个文件夹的路径。如果在 templates 文件夹下创建了一个新的文件夹,那么就要添加上文件名(render_template() 函数的文件名是相对路径)

模板传参

  • 如果只有一个或者少量参数,直接在 render_template() 函数中添加关键字参数就可以了
  • 如果有多个参数的时候,那么可以先把所有的参数放在字典中,然后在 render_template 中,使用 **字典名 把字典转换为关键参数传递进去,这样的代码更方便管理和应用

函数讲解

  • render_template()
    • params: 渲染文件名(渲染文件放在template文件夹下), username = 用户名 (username 保证和渲染文件中的变量名保持一致), … 。如 render_template("index.html", username = "Qeuroal")
    • 如果参数特别多的话,可以先定义一个字典(context),然后把字典传入函数内,如 render_template("index.html", **contet)

模板访问属性和字典

在模板中如果要使用一个变量,语法是 {{ params }}

  • 访问类的属性:
    • 和在Python文件中一致
    • 或者类似于这样: Person["name"]
  • 访问字典:
    • py文件一样: dict[key]
    • dict.key

实现

template1.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@app.route("/")
def index():
class Person():
name = "Qeuroal"
age = 24

p = Person()
testDict = {
"key": "key",
"value": "value"
}
context = {
"username": "Qeuroal",
"gender": "男",
"age": 24,
"person": p,
"testDict": testDict
}

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
这是HTML文件中出现的文字
<p>用户名: {{ username }}</p>
<hr>
<p>用户名: {{ person.name }}</p>
<p>年龄: {{ person["age"] }}</p>
<hr>
<p>key: {{ testDict.key }}</p>
<p>value: {{ testDict['value'] }}</p>
</body>
</html>

HTML

if判断

语法

1
2
3
4
5
6
7
{% if xxx and xxx %}
xxxx
{% elif xxx and xxx%}
xxxx
{% else %}
xxxx
{% endif %}

if的使用和python中相差无几。

for循环遍历列表和字典

字典遍历

字典的遍历,语法和 python 一样,可以使用 items(), keys(), values(), iteritems(), iterkeys(), itervalues()

1
2
3
{% for k,v in user.items() %}
<p>{{ k }}: {{ v }}</p>
{% endfor%}

列表遍历

语法和 python 一样

1
2
3
{% for website in websites %}
<p>{{ website }}</p>
{% endfor %}

过滤器

作用对象是模板中的变量:

介绍和语法

  • 介绍:过滤器可以处理变量,把原始的遍历经过处理后再展示出来。作用的对象是变量

  • 语法:

    1
    {{ avator|default("xxx")}}

default过滤器

如果当前变量不存在,这时候可以指定默认值。

length过滤器

求列表,字符串,字典,元组的长度。类似于:python 中的 len()

常用的过滤器

  • abs(value)
  • default(value, defaultValue, boolean=false)
  • escape(value)
  • first(value)
  • format(value, *args, **kwargs)
  • last(value)
  • length(value)
  • join(value, d="")
  • safe(value)

继承和block

继承

  • 作用:可以把一些公共的代码放在父模板中,避免每个模板写同样的代码

  • 语法:

    1
    {% extends "base.html" %}
  • 如果想在子模板中实现自己的内容:需要定义接口,类似于 java 的接口:html子模板需要实现自己内容,必须要放在父模板定义的接口里面,即block,不能在接口外面写代码

block

  • 作用:可以让子模板实现一些自己的需求。父模板需要提前定义好。
  • 注意点:
    • 子模板中的代码,必须放在block块中;
    • 可以定义多个 block

URL链接

使用 url_for(视图函数名称) 可以反转成url。

加载静态文件

1
2
3
4
5
6
<style>
a {
background: red;
color: black;
}
</style>

样式代码一般不会写在模板中,因为:维护性差,一般样式文件抽取成 css 文件(static 文件夹中是存放静态文件的)

语法

url_for("static", filename="路径")

  • 如:url_for("static", filename="css/index.css")

  • 静态文件,flask会从 static 文件夹中开始寻找,所以不需要写 static 这个路径了

  • 可以加载 css文件, js 文件,image 文件。

示例代码

1
2
3
4
5
6
{# 加载css文件 #}
<link rel="stylesheet" href="{{ url_for("static", filename="css/index.css") }}">
{# 加载js文件 #}
<script src="{{ url_for("static", filename="js/index.js") }}"></script>
{# 加载image文件 #}
<img src="{{ url_for("static", filename="images/pdx.jpg") }}" alt="">

注意点

装饰器

  • 装饰器为 @app.route('/login/'),网址可以是:http://127.0.0.1:5000/loginhttp://127.0.0.1:5000/login/
  • 装饰器为 @app.route('/login'),网址只能是:http://127.0.0.1:5000/login/

MySQL

安装(win10)

  • 我选择的是 Server only

  • 设置密码:

  • 打开命令行,并输入上一步设置好的密码

  • 下载地址

  • 一般直接下一步

  • 设置初始密码命令:

    1
    mysqladmin -uroot password [password]
  • 如果没有安装 .net Framework 4,就在那个提示框中,找到下载的 url 下载;

  • 如果没有安装 Microsoft visual C++ x64,那么就需要谷歌或者百度下载这个软件进行安装即可。

MySQL-python中间件介绍与安装

linux, macos

  1. 进入虚拟环境
  2. 输入 pip install mysql-python

windows系统

  • windows 在这里下载,看 python 安装的是32位还是64位,对应下载
  • 进入虚拟环境: conda activate 虚拟环境名
  • 安装:pip install mysqlclient-1.4.6-cp37-cp37m-win_amd64.whl

Flask-SQLAlchemy的介绍与安装

  • ORM: Object Relationship Mapping (关系模型映射);

  • flask-sqlalchemy 是一套 ORM框架

  • 模型关系映射

    • delete(article1):把 article表中和article1属性对应相同的那条数据删除。
    • 好处:ORM使得我们操作数据库非常简单,就像操作对象是一样的,非常方便。因为一个表就抽象成了一个类,一条数据就抽象成了该类的一个对象。
  • 安装:pip install flask-sqlalhemy,如果是在 macos 或者 linux 中没有权限的话这样安装: sudo pip install flask-sqlalhemy

Flask-SQLAlchemy的使用

  1. 初始化和设置数据库配置信息:

    • 使用 flask_sqlalchemy 中的 SQLAlchemy 进行初始化

      1
      2
      3
      from flask_sqlalchemy import SQLAlchemy
      app = Flask(__name__)
      db = SQLAlchemy(app)
  2. 设置配置信息:在 config.py 文件中添加以下配置信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # dialect+driver://username:password@host:port/database
    DIALECT = "mysql"
    DRIVER = "mysqldb"
    USERNAME = "root"
    PASSWORD = "toor"
    HOST = "127.0.0.1"
    PORT = "3306"
    DATABASE = "dbDemo1"

    SQLALCHEMY_DATABASE_URI = "{}+{}://{}:{}@{}:{}/{}?charset=utf8".format(DIALECT, DRIVER, USERNAME, PASSWORD, HOST, PORT, DATABASE)
    # 去除警告
    SQLALCHEMY_TRACK_MODIFICATIONS = False
  3. 添加配置文件

    1
    2
    3
    import config

    app.config.from_object(config)
  4. 测试是否连接成功

    1
    db.create_all()

    如果没有报错,说明配置没有问题,如果有错误,可以根据错误进行修改。

建表

  1. cmd 进入 mysqlmysql -uroot -p
  2. create database dbDemo1 charset utf8

SQLAlchemy创建模型与表的映射

步骤

  1. 模型需要继承自 db.Model,然后需要映射到表中的属性,必须写成 db.Column()的数据类型

  2. 数据类型:

    • db.Integer: 整型
    • db.Sting: varcharchar,需要指定最长的长度
    • db.Text: text
  3. 其他参数

    • primary_key: 将这个字段设置为主键
    • autoincrement: 这个主键为自增长
    • nullable: 这个字段是否可以为空,默认为空,可以将这个值设置为 False ,在数据库中,这个值就不能为空了
  4. 最后需要调用 db.create_all() 来将模型真正的创建到数据库中,db.create_all()只会映射一次。

实例

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
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
import config

app = Flask(__name__)
app.config.from_object(config)
db = SQLAlchemy(app)

"""
创建article表
create table article (
id int primary key autoincrement,
title varchar(100) not null,
content text not null
)
"""
# 自己写的模型一定要继承sqlalchemy属性下的model模型, 字段->属性
class Article(db.Model):
# 如果不指定表名,默认为类名
__tablename__ = "article"
# 属性id映射到字段id
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
# varchar, char -> db.String(长度)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)

# 所有的模型映射到数据库的一张表
db.create_all()


@app.route('/')
def index():
return "index"

if __name__ == '__main__':
app.run()

SQLAlchemy数据增删改查

1
2
3
4
5
# 增加
article1 = Article(title="aaa", content="bbb")
db.session.add(article1)
# 事务
db.session.commit()

1
2
3
4
5
6
# 返回的是Query(<==> list); .all()查找所有数据, .first()返回的第一条数据,如果没有,返回null
result = Article.query.filter(Article.title == "aaa").all()
print(result[0], type(result[0]))
article1 = result[0]
print(article1.title)
print(article1.content)

1
2
3
4
5
6
# 1. 先把要修改的地方查找出来
article1 = Article.query.filter(Article.title == "aaa").first()
# 2. 把这条数据,你需要修改的地方进行修改
article1.title = "new title"
# 3. 做事务的提交
db.session.commit()

1
2
3
4
5
6
# 1. 把需要删除的数据查找出来
article1 = Article.query.filter(Article.content == "bbb").first()
# 2. 把这条数据删除掉
db.session.delete(article1)
# 3. 做事务提交
db.session.commit()

注意

  • 如果把增删改查放在 @app.route("/") 类似的视图函数中,访问一次 url,就会执行一遍相应的增删改查

Flask-SQLAlchemy外键及其关系

外键

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
"""
用户表
sql语句:
create table users (
id int primary key autoincrement,
username varchar(100) not null
)
"""
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(100), nullable=False)
# password = db.Column(db.String(100), nullable=False)

"""
文章表
create table article (
id int primary key autoincrement,
title varchar(100) not null,
content text not null,
authorId int,
foreign key "authorId" references "user.id"
)
"""
class Article(db.Model):
__tablename__ = "article"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
# 设置外键
authorId = db.Column(db.Integer, db.ForeignKey("user.id"))
# params: 模型名, backref:反向引用;db.backref(), params: 反向引用的名字,
author = db.relationship("User", backref=db.backref("articles"))

解释

  • authorId = db.Column(db.Integer, db.ForeignKey("user.id"))
    • Article 这个模型添加一个 author 属性,可以访问这篇文章的作者的数据,像访问普通模型一样
    • backref:定义反向引用,可以通过 User.articles 这个模型访问这个模型缩写的所有文章

补充

  • 添加数据(方法二):

    1
    2
    3
    4
    article = Article(title="aaa", content="bbb")
    article.author = User.query.filter(User.id==1).first()
    db.session.add(article)
    db.session.commit()

多对多

例子

在这张图中:

使用

  • 多对多的关系,要通过一个中间表进行关联;

  • 中间表,不能通过 class 的方式实现,只能通过 db.Table 的方式实现

  • 设置关联:

    1
    tags = db.relationship("Tag", secondary=articleTagRelation, backref=db.backref("articles"))

    需要使用一个关键字参数 secondary=中间表 进行关联

  • 访问和添加可以通过以下方式操作:

    • 添加数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      article1 = Article(title="aaa")
      article2 = Article(title="bbb")
      tag1 = Tag(name="111")
      tag2 = Tag(name="222")

      article1.tags.append(tag1)
      article1.tags.append(tag2)

      article2.tags.append(tag1)
      article2.tags.append(tag2)

      db.session.add(article1)
      db.session.add(article2)
      db.session.add(tag1)
      db.session.add(tag2)
      db.session.commit()
    • 访问数据

      1
      2
      3
      4
      article1 = Article.query.filter(Article.title=="aaa").first()
      tags = article1.tags
      for tag in tags:
      print(tag.name)

Flask-Script的介绍和安装

介绍

Flask-Script 的作用是可以通过命令行的形式来操作 Flask。例如通过命令跑一个开发版本的服务器、设置数据库、定时任务。

安装

  1. 进入到虚拟环境中
  2. pip install flask-script进行安装

使用

  1. 如果直接在主 manage.py 中写命令,那么在终端就只需要 python manage.py commandName 就可以了。

  2. 如果把一些命令集中在一个文件中,那么在终端就需要输入一个父命令,如 python manage.py db init

  3. 例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    from flask_script import Manager
    from flaskScriptDemo import app
    from dbScript import dbManager

    manager = Manager(app)

    @manager.command
    def runserver():
    print("服务器跑起来了")

    # params: 前缀(类似于别名),dbManager
    # 使用:python manage.py db init
    manager.add_command("db", dbManager)

    if __name__ == '__main__':
    manager.run()
  4. 有子命令的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from flask_script import Manager

    dbManager = Manager()

    @dbManager.command
    def init():
    print("数据库初始化完成")

    @dbManager.command
    def migrate():
    print("数据表迁移成功")

分开 models 及解决循环引用

循环引用

解决办法:

db 放在一个单独的文件中,切断循环引用的线条就可以了。

分开 models

  • 目的:为了让代码更加方便的管理

注意:

  • 使用了 db.init_app(app),就不能直接使用 db.create_all()了,会报错。

    1
    2
    3
    app = Flask(__name__)
    app.config.from_object(modelSepConfig)
    db.init_app(app)

    因为涉及到了上下文的问题:

    解决办法看 flask-migrate

flask-migrate

db.init_app(app)不能直接使用 db.create_all() 解决办法

手动推动 appapp栈

介绍

因为采用 db.create_all() 在后期修改字段的时候,不会自动的映射到数据库中,必须删除表,然后重新运行 db.create_all() 才会重新映射,这样不符合我们的需求。因此 flask-migrate 就是为了解决这个问题,它可以在每次修改模型后,可将修改的东西映射到数据库中

安装

  1. 进入虚拟环境
  2. pip install flask-migrate

使用

使用 flask_migrate 必须借助 flask-scripts,这个包的 MigrateCommand 中包含了所有和数据库相关的命令。

相关命令

  • python manage.py db init: 初始化一个迁移脚本的环境,只需要执行一次;
  • python manage.py db migrate: 将模型生成迁移文件,只要模型更改了,就需要执行一遍这个命令;
  • python manage.py db upgrade: 将掐你文件真正映射到数据库中,每次运行了 migrate 命令后,就记得要运行这个命令

注意点

  • 如果需要将你想要映射到数据库中的模型,都要导入到 manage.py 文件中,如果没有导入进去,就不会映射到数据库中

manage.py 的相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from flask_script import Manager
from migrateDemo import app
from flask_migrate import Migrate, MigrateCommand
from exts import db
from models import Article

# 模型 => 迁移文件 => 表
# init: 初始化迁移的环境
# migrate: 将模型生成迁移文件
# upgrade: 将迁移文件映射成表

manager = Manager(app)

# 1. 要使用 flask_migrate,必须绑定app和db
migrate = Migrate(app, db)
# 2. 把MigrateCommand命令添加到manager中
manager.add_command("db", MigrateCommand)




if __name__ == '__main__':
manager.run()

cookie和session

  • 出现的原因:在网站中,http 请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不知道当前请求是哪个用户。cookie的出现就是我了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的 cookie 数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是那个了;

  • 图解:

  • 如果服务器返回了 cookie 给浏览器,那么浏览器下次再请求相同的服务器的时候,就会自动的把 cookie 发送给服务器,这个过程,用户根本不需要管;

  • cookie 是保存在浏览器中的,相对对的是浏览器;

session

  • 介绍:sessioncookie 的作用有点类似,都是为了存储用户相关的信息。不同的是,cookie 是存储在本地服务器的,而 session 存储在服务器。存储在服务器的数据会更加安全,不容易被窃取。但存储在服务器也有一定的弊端,就是会占用服务器的资源,但现在服务器已经发展至今,一些 session 信息还是绰绰有余的。

  • 图解:

  • 使用 session 的好处:

    • 敏感数据不是直接发送给服务器,而是发送回一个 session_id ,服务器将 session_id 和敏感数据做一个映射存储在 session (服务器上面)中,更加安全;
    • session 可以设置过期时间,也可以从另外一方面,保证用户的账号安全。

注意

  • session_id:返回给浏览器的时候,是放在 cookie 中的。

flask中session工作机制

  • 讲解:把敏感数据经过加密后放入 session 中,然后再把 session 存放到 cookie 中,下次请求的时候,再从浏览器发送过来的 cookie 中读取 session,然后再从 session 中读取敏感数据,并进行解密,获取最终的用户数据;

  • flask 的这种 session 机制,可以节省服务器的开销,以内把所有的信息都存储到了客户端(浏览器);

  • 安全是相对的,没有绝对的安全,把 session 放到 cookie 中,经过机密,也是比较安全的;

  • 图解:

flask操作session

session的操作方式:

  • 使用 session 需要从 flask 中导入 session,以后所有和 session 相关的操作都是通过这个变量来的;
  • 使用 session 需要设置 SECRET_KEY,用来作为加密用的。并且这个 SECRET_KEY 如果每次服务器启动后都变化的话,那么之前的 session 就不能通过当前这个 SECRET_KEY 进行解密了;
  • 操作 session ,跟操作字典是一样的;
  • 添加 sessionsession[key]=value
  • 删除:session.pop(key) 或者 del session[key]
  • 清除所有 sessionsession.clear()
  • 获取 sessionsession.get(key)

设置session的过期时间

  • 如果没有指定session的过期时间,那么默认是浏览器关闭后就自动结束

  • 如果设置了 sessionpermanent 属性为 True,那么过期时间是31天

  • 可以通过给 app.config 设置 PERMANENT_SESSION_LIFETIME 来更改过期时间,这个值的数据类型是 datatime.timedelay 类型:

    1
    2
    3
    from datetime import timedelta

    PERMANENT_SESSION_LIFETIME = timedelta(days=7)

get请求和post请求

get请求

  • 使用场景:如果只对服务器获取数据,并没有对服务器产生任何影响,那么这时候使用 get 请求
  • 传参:get请求传参是防砸url中,并且是通过 ?的形式来指定 keyvalue

post请求

  • 使用场景:如果要对服务器产生影响,那么使用post请求,如: 登录:服务器要记录用户什么时候登陆过,要把数据保存在本地;
  • 传参:post请求传参不是放在 url 中,而是通过 form data 的形式发送给服务器的

获取参数

  • get请求:通过 flask.request.args 来获取
  • post请求:通过 flask.request.form 来获取

注意

  • post请求在模板中要注意几点:

    • input标签中,要写name来标识这个 valuekey,方便后台获取

    • 在写 form 表单的时候,要指定 method='post',并且要指定 action='/login/'

    • 示例代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <form action="{{ url_for("login") }}" method="post">
      <table>
      <tbody>
      <tr>
      <td>用户名</td>
      <td><input type="text" placeholder="请输入用户名" name="username"></td>
      </tr>

      <tr>
      <td>密码</td>
      <td><input type="text" placeholder="请输入密码" name="password"></td>
      </tr>

      <tr>
      <td></td>
      <td><input type="submit" placeholder="登录"></td>
      </tr>

      </tbody>
      </table>
      </form>

保存全局遍历的g属性

g: global

  • g对象 是专门用来保存用户的数据的
  • g对象 在一次请求中的所有的代码的地方,都是可以使用的

钩子函数(hook)

钩子函数

正常执行情况:先执行A,然后再执行B。

钩子函数:可以在运行时,把C直接插入到AB之间

before_request

  • before_request:在请求之前执行的函数,且每次请求都会执行一遍
  • before_request是在视图函数执行之前执行的
  • before_request这个函数只是一个装饰器,他可以把需要设置为钩子函数的代码放到视图函数执行之前来执行

context_processor(上下文处理器)

  • 出现的原因:多个不同的页面需要相同的参数,如:username
  • 上下文处理器应该返回一个字典,字典中的 key 会被模板当成变量来渲染
  • 上下文处理器中返回的字典,在所有页面中都是可用的。
  • 被这个装饰器修饰的钩子函数,必须要返回一个字典,即使为空,也要返回

装饰器

  • 需求1:在打印run之前,先要打印hello world

    1
    2
    3
    4
    5
    def run():
    print("hello world")
    print("run")

    run()
  • 需求2:在所有函数执行之前,都要打印一个 hello world

    • 方法一:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      def run():
      print("hello world")
      print("run")

      def run1():
      print("hello world")
      print("run1")

      run()
    • 方法二(如果成千上万个函数,比较麻烦,且难以维护):装饰器

      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
      # 装饰器的两个特别之处:
      # 1. 参数是一个函数
      # 2. 返回值是一个函数
      def myLog(func):

      def wrapper():
      print("hello world")
      func()

      """
      补充:
      wrapper: 函数体
      wrapper(): 执行wrapper这个函数
      """
      return wrapper

      # 如何用
      @myLog
      def run():
      print("run")
      """
      等价于上面
      run = myLog(run) <==> wrapper
      run() <==> wrapper() <==> print("hello world"); func() <==> print("hello world"); print("run")
      理解:func() 执行的才是真的 run() 方法
      """
      run()

讲解

  • 装饰器的两个特别之处:
    • 参数是一个函数
    • 返回值是一个函数
  • 上面的 myLog() 是无参情况下的装饰器

进阶:有参情况下的装饰器

需求:在所有函数执行之前,都要打印一个 hello world

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 装饰器的两个特别之处:
# 1. 参数是一个函数
# 2. 返回值是一个函数
def myLog(func):

def wrapper(a, b):
print("hello world")
func(a, b)
return wrapper

@myLog
def add(a, b):
print("结果是", a + b)
"""
等价于上面
add = myLog(add) <==> wrapper
add(1, 2) <==> wrapper(1, 2) # 因此,如果wrapper()无参,就会报错 `TypeError: wrapper() takes 0 positional arguments but 2 were given`
"""
add(1, 2)

再进阶:有无参同时存在情况下的装饰器

  • 解决办法: *args, **kwargs:可以表示任何参数

    • *args: 位置参数,如:add(a, b) 中的ab,参数是一一对应的
    • **kwargs:关键字参数(key=value),如:add(a=abc)a就是传入的abc
    1
    2
    3
    4
    5
    6
    def myLog(func):

    def wrapper(*args, **kwargs):
    print("hello world")
    func(*args, **kwargs)
    return wrapper

    run(), add() 同上

再进阶

  • 问题:加完装饰器后,函数名被偷偷更改掉了,因此如何保留住原来的函数的名字(把名字改掉是很危险的行为)?

    • 没有装饰器的情况

      1
      2
      3
      def run():
      print("run")
      print(run.__name__) # run.__name__代表的是run这个函数的名称

      输出:

      run

    • 有装饰器的情况

      1
      2
      3
      4
      @myLog
      def run():
      print("run")
      print(run.__name__)

      输出:

      wrapper

  • 解决办法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from functools import wraps

    def myLog(func):

    @wraps(func)
    def wrapper(*args, **kwargs):
    print("hello world")
    func(*args, **kwargs)
    return wrapper
    • 没加 @wraps 的情况
      run() <=> myLog(run)() <=> wrapper()

    • 加了 @wraps(func) 的情况

      • run <=> myLog(run) <=> @wraps(func)装饰的wrapper <=> wraps(wrapper) <=> wrapsFunction(这个函数是透明的,即看不到的,但是不管怎么样,返回的函数的__name__是run) => wrapsFunction.__name__ == "run"
      • ``run() <=> myLog(run)() <=> @wraps装饰的wrapper() <=> wraps(wrapper)() <=> wrapsFunction()`

再进阶

  • 需求:如果run()有返回值,该怎么办?

  • 解决办法:在 wrapper()return func(*args, **kwargs)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from functools import wraps

    def myLog(func):

    @wraps(func)
    def wrapper(*args, **kwargs):
    print("hello world")
    return func(*args, **kwargs)
    return wrapper

    @myLog
    def run():
    return 1+1
  • 讲解:

    1
    2
    3
    4
    5
    run = myLog(run) = wrapper
    ==>(推出,下同) run = wrapper
    ==> run() = wrapper() = print("hello world"); return run() = print("hello world"); return 1+1
    因为run()需要返回1+1,所以wrapper()也需要返回1+1
    如果wrapper没有return,那么func(*args, **kwargs),就只是执行了一下1+1,别的什么都没有了

小结

  • 装饰器的使用是通过 @ 符号,放在函数上面;
  • 装饰器中定义的函数,要使用 *args**kwargs 两对兄弟的组合,并且在这个函数中执行原始函数的时候也要把 *args**kwargs 传进去;
  • 需要使用 functools.wraps 在装饰器中的函数上把传进来的这个函数进行一个包裹,这样就不会丢失原来的函数 __name__ 等属性

实践

0%