From f0c8eb4ea58c3bc9ec82dc51e0b592815199641a Mon Sep 17 00:00:00 2001 From: yangyj13 Date: Fri, 5 Jul 2024 19:10:27 +0800 Subject: [PATCH] Site updated: YYYY-07-Jul 5, 2024 19:10:26 --- .../index.html | 21 +- 2016/10/02/git-command/index.html | 19 +- 2016/10/04/regular-expression/index.html | 19 +- 2016/10/05/FreeMarker/index.html | 19 +- 2016/10/12/PostgreSQL-1/index.html | 19 +- 2016/10/12/PostgreSQL-2/index.html | 19 +- 2016/10/12/PostgreSQL-3/index.html | 19 +- .../index.html | 19 +- .../index.html | 19 +- .../10/22/set-up-git-server-on-vps/index.html | 19 +- 2016/10/23/hexo-git-server-blog/index.html | 19 +- 2016/10/24/web-xml/index.html | 19 +- 2016/11/05/idea-shortcuts/index.html | 19 +- 2016/11/22/jQuery-selector/index.html | 19 +- .../jQuery-checkbox-radio-select/index.html | 19 +- 2016/12/01/linux-command-1-ls/index.html | 19 +- 2016/12/02/linux-command-2-cd/index.html | 19 +- 2016/12/03/linux-command-3-pwd/index.html | 19 +- 2016/12/04/linux-command-4-mkdir/index.html | 19 +- 2016/12/05/linux-command-5-rm/index.html | 19 +- 2016/12/06/linux-command-6-rmdir/index.html | 19 +- 2016/12/07/linux-command-7-mv/index.html | 19 +- 2016/12/08/linux-command-8-cp/index.html | 19 +- 2016/12/09/linux-command-9-touch/index.html | 19 +- 2016/12/10/linux-command-10-cat/index.html | 19 +- 2016/12/11/linux-command-11-nl/index.html | 19 +- 2016/12/12/linux-command-12-more/index.html | 19 +- 2016/12/13/linux-command-13-less/index.html | 19 +- 2016/12/14/linux-command-14-head/index.html | 19 +- 2016/12/15/linux-command-15-tail/index.html | 19 +- 2016/12/16/linux-command-16-which/index.html | 19 +- .../12/17/linux-command-17-whereis/index.html | 19 +- 2016/12/18/linux-command-18-locate/index.html | 19 +- .../index.html" | 19 +- .../index.html" | 19 +- .../index.html | 19 +- .../index.html" | 19 +- .../index.html" | 19 +- 2016/12/23/git-master/index.html | 19 +- .../index.html" | 19 +- 2016/12/24/git-reset/index.html | 19 +- 2016/12/24/linux-command-24-tar/index.html | 19 +- 2016/12/25/linux-command-25-gzip/index.html | 19 +- 2016/12/26/linux-command-26-chmod/index.html | 19 +- 2016/12/27/linux-command-27-chgrp/index.html | 19 +- 2016/12/28/linux-command-28-chown/index.html | 19 +- 2016/12/29/linux-command-29-group/index.html | 19 +- 2016/12/30/linux-command-30-df/index.html | 19 +- 2016/12/31/linux-command-31-du/index.html | 19 +- 2017/01/01/linux-command-32-top/index.html | 19 +- 2017/01/02/linux-command-33-free/index.html | 19 +- 2017/01/03/Fortress-Besieged/index.html | 19 +- 2017/01/03/linux-command-34-vmstat/index.html | 19 +- 2017/01/04/linux-command-35-iostat/index.html | 19 +- 2017/01/05/linux-command-36-lsof/index.html | 19 +- .../06/linux-command-37-ifconfig/index.html | 19 +- 2017/01/07/linux-command-38-route/index.html | 19 +- 2017/01/08/linux-command-39-ping/index.html | 19 +- .../09/linux-command-40-traceroute/index.html | 19 +- .../01/10/linux-command-41-netstat/index.html | 19 +- 2017/01/11/linux-command-42-ss/index.html | 19 +- 2017/01/12/linux-command-43-telnet/index.html | 19 +- 2017/01/12/linux-command-44-rcp/index.html | 19 +- 2017/01/13/linux-command-45-scp/index.html | 19 +- 2017/01/14/linux-command-46-ln/index.html | 19 +- 2017/01/15/linux-command-47-diff/index.html | 19 +- 2017/01/16/linux-command-48-date/index.html | 19 +- 2017/01/17/linux-command-49-cal/index.html | 19 +- 2017/01/18/linux-command-50-grep/index.html | 19 +- 2017/01/19/linux-command-51-wc/index.html | 19 +- 2017/01/20/linux-command-52-ps/index.html | 19 +- 2017/01/21/linux-command-53-watch/index.html | 19 +- 2017/01/22/linux-command-54-at/index.html | 19 +- .../01/23/linux-command-55-crontab/index.html | 19 +- 2017/01/23/linux-command/index.html | 19 +- 2017/01/31/reading-list/index.html | 19 +- 2017/02/08/pjax/index.html | 19 +- 2017/02/20/linux-command-56-tailf/index.html | 19 +- .../index.html | 19 +- 2017/02/24/hexo-top-sort/index.html | 19 +- 2017/02/25/hexo-create-404-page/index.html | 19 +- 2017/02/28/3-hexo-multiple-author/index.html | 19 +- 2017/03/04/AngularJs/index.html | 19 +- 2017/03/05/linux-command-57-sftp/index.html | 19 +- 2017/03/06/Vim-command/index.html | 19 +- 2017/03/06/linux-ftp/index.html | 19 +- 2017/03/07/3-hexo/index.html | 19 +- 2017/03/08/font-develop-rule/index.html | 25 +- .../index.html | 19 +- 2017/03/09/busuanzi-pjax/index.html | 19 +- 2017/03/09/duoshuo-pjax/index.html | 19 +- 2017/03/09/hexo-wordcount/index.html | 19 +- 2017/03/13/3-hexo-homepage/index.html | 19 +- 2017/03/13/3-hexo-logs/index.html | 19 +- 2017/03/14/Hexo-RSS-Sitemap/index.html | 19 +- 2017/03/16/npm/index.html | 19 +- 2017/03/17/windows-hexo/index.html | 19 +- 2017/03/23/3-hexo-instruction/index.html | 19 +- 2017/03/23/the-third-comment/index.html | 19 +- 2017/03/24/3-hexo-shortcuts/index.html | 19 +- 2017/03/27/hexo-error-collection/index.html | 19 +- 2017/03/30/deepin-linux/index.html | 19 +- 2017/04/14/postgres-sql-use/index.html | 19 +- .../index.html | 19 +- 2017/04/21/jsp-use-record/index.html | 19 +- 2017/04/22/encryption-algorithm/index.html | 19 +- .../index.html" | 19 +- 2017/05/16/git-log/index.html | 19 +- .../index.html" | 19 +- 2017/05/19/docker-first/index.html | 19 +- 2017/05/19/docker-image/index.html | 19 +- 2017/05/22/Dockerfile/index.html | 19 +- 2017/05/23/CentOS-DNS-GW-IP/index.html | 19 +- 2017/05/23/docker-container/index.html | 19 +- 2017/05/23/docker-data-manager/index.html | 19 +- 2017/05/23/docker-registry/index.html | 19 +- 2017/05/23/linux-command-57-sort/index.html | 19 +- 2017/05/24/tale-build-experience/index.html | 19 +- 2017/06/01/entering-docker/index.html | 19 +- 2017/06/01/vim-paste/index.html | 19 +- 2017/06/26/gitment/index.html | 19 +- 2017/07/03/CentOS7-anonymous-Samba/index.html | 19 +- 2017/07/05/3-hexo-mathjax/index.html | 19 +- 2017/07/05/MathJax-pjax/index.html | 19 +- 2017/08/04/mybatis-Mapper/index.html | 19 +- 2017/09/06/build-Maven-Nexus/index.html | 19 +- .../index.html" | 19 +- 2017/09/18/docker-save-export/index.html | 19 +- .../index.html" | 19 +- 2017/09/21/bf-cache/index.html | 19 +- 2017/09/21/hexo-fragment_cache/index.html | 19 +- 2017/09/21/iphone-bf-no-run-js/index.html | 19 +- .../09/25/ascii-ansi-unicode-utf-8/index.html | 19 +- 2017/09/25/docker-errors/index.html | 19 +- .../index.html" | 19 +- .../index.html" | 19 +- .../index.html" | 19 +- 2017/11/27/java-grammatical-sugar/index.html | 19 +- 2017/12/13/Mac-BetterTouchTool/index.html | 19 +- 2018/02/08/nginx-config/index.html | 19 +- 2018/09/08/shell-command/index.html | 19 +- 2019/05/02/to-live/index.html | 19 +- 2019/06/09/misfortune-intellectual/index.html | 19 +- 2019/09/24/3-hexo-jsfiddle/index.html | 19 +- 2019/09/24/3-hexo-toc/index.html | 19 +- 2019/10/08/3-hexo-add-music/index.html | 19 +- 2019/11/12/3-hexo-support-mermaid/index.html | 19 +- 2020/05/23/3-hexo-comment/index.html | 19 +- 2020/09/01/Docker-summary/index.html | 21 +- 2020/10/20/know-javascript-promise/index.html | 19 +- 2020/12/28/3-hexo-add-icon/index.html | 19 +- 2021/09/26/spring-cloud-skywalking/index.html | 19 +- 2022/06/24/el-drawer-drag-width/index.html | 19 +- 2022/06/27/vim-plugs-2022/index.html | 19 +- .../index.html | 19 +- .../index.html | 19 +- .../index.html | 19 +- 2023/05/17/mac-raycast/index.html | 19 +- .../17/idea-tips-percent-mach-xml/index.html | 19 +- 2023/12/11/springboot-jarlauncher/index.html | 19 +- 2024/01/02/macos-ocr-swift/index.html | 19 +- 2024/02/05/k8s-inner-training/index.html | 19 +- 2024/03/22/home-assistant/index.html | 19 +- 2024/06/23/macos-terminal/index.html | 19 +- .../Deserializer/index.html | 3015 +++++++++++++++++ 404.html | 17 +- README.html | 17 +- about/index.html | 19 +- archives/2016/09/index.html | 17 +- archives/2016/10/index.html | 17 +- archives/2016/10/page/2/index.html | 17 +- archives/2016/11/index.html | 17 +- archives/2016/12/index.html | 17 +- archives/2016/12/page/2/index.html | 17 +- archives/2016/12/page/3/index.html | 17 +- archives/2016/12/page/4/index.html | 17 +- archives/2016/index.html | 17 +- archives/2016/page/2/index.html | 17 +- archives/2016/page/3/index.html | 17 +- archives/2016/page/4/index.html | 17 +- archives/2016/page/5/index.html | 17 +- archives/2017/01/index.html | 17 +- archives/2017/01/page/2/index.html | 17 +- archives/2017/01/page/3/index.html | 17 +- archives/2017/02/index.html | 17 +- archives/2017/03/index.html | 17 +- archives/2017/03/page/2/index.html | 17 +- archives/2017/04/index.html | 17 +- archives/2017/05/index.html | 17 +- archives/2017/05/page/2/index.html | 17 +- archives/2017/06/index.html | 17 +- archives/2017/07/index.html | 17 +- archives/2017/08/index.html | 17 +- archives/2017/09/index.html | 17 +- archives/2017/10/index.html | 17 +- archives/2017/11/index.html | 17 +- archives/2017/12/index.html | 17 +- archives/2017/index.html | 17 +- archives/2017/page/2/index.html | 17 +- archives/2017/page/3/index.html | 17 +- archives/2017/page/4/index.html | 17 +- archives/2017/page/5/index.html | 17 +- archives/2017/page/6/index.html | 17 +- archives/2017/page/7/index.html | 17 +- archives/2017/page/8/index.html | 17 +- archives/2017/page/9/index.html | 17 +- archives/2018/02/index.html | 17 +- archives/2018/09/index.html | 17 +- archives/2018/index.html | 17 +- archives/2019/05/index.html | 17 +- archives/2019/06/index.html | 17 +- archives/2019/09/index.html | 17 +- archives/2019/10/index.html | 17 +- archives/2019/11/index.html | 17 +- archives/2019/index.html | 17 +- archives/2020/05/index.html | 17 +- archives/2020/09/index.html | 17 +- archives/2020/10/index.html | 17 +- archives/2020/12/index.html | 17 +- archives/2020/index.html | 17 +- archives/2021/09/index.html | 17 +- archives/2021/index.html | 17 +- archives/2022/06/index.html | 17 +- archives/2022/07/index.html | 17 +- archives/2022/08/index.html | 17 +- archives/2022/index.html | 17 +- archives/2023/05/index.html | 17 +- archives/2023/06/index.html | 17 +- archives/2023/12/index.html | 17 +- archives/2023/index.html | 17 +- archives/2024/01/index.html | 17 +- archives/2024/02/index.html | 17 +- archives/2024/03/index.html | 17 +- archives/2024/06/index.html | 17 +- archives/2024/07/index.html | 2617 ++++++++++++++ archives/2024/index.html | 17 +- archives/index.html | 17 +- archives/page/10/index.html | 17 +- archives/page/11/index.html | 17 +- archives/page/12/index.html | 17 +- archives/page/13/index.html | 17 +- archives/page/14/index.html | 17 +- archives/page/15/index.html | 17 +- archives/page/16/index.html | 17 +- archives/page/17/index.html | 17 +- archives/page/2/index.html | 17 +- archives/page/3/index.html | 17 +- archives/page/4/index.html | 17 +- archives/page/5/index.html | 17 +- archives/page/6/index.html | 17 +- archives/page/7/index.html | 17 +- archives/page/8/index.html | 17 +- archives/page/9/index.html | 17 +- atom.xml | 97 +- .../\345\220\216\347\253\257/index.html" | 17 +- .../page/2/index.html" | 17 +- .../index.html" | 17 +- .../page/2/index.html" | 17 +- .../\345\267\245\345\205\267/IDEA/index.html" | 17 +- .../\345\267\245\345\205\267/IOT/index.html" | 17 +- .../\345\267\245\345\205\267/git/index.html" | 17 +- .../\345\267\245\345\205\267/hexo/index.html" | 17 +- .../hexo/page/2/index.html" | 17 +- .../hexo/page/3/index.html" | 17 +- .../\345\267\245\345\205\267/index.html" | 17 +- .../page/2/index.html" | 17 +- .../page/3/index.html" | 17 +- .../page/4/index.html" | 17 +- .../page/5/index.html" | 17 +- .../index.html" | 17 +- .../\345\274\200\345\217\221/index.html" | 17 +- .../swift/index.html" | 17 +- .../index.html" | 17 +- .../\350\257\273\344\271\246/index.html" | 17 +- .../\344\271\246\345\215\225/index.html" | 17 +- .../index.html" | 17 +- .../\350\277\220\347\273\264/index.html" | 17 +- .../page/2/index.html" | 17 +- .../page/3/index.html" | 17 +- .../page/4/index.html" | 17 +- .../page/5/index.html" | 17 +- .../page/6/index.html" | 17 +- .../page/7/index.html" | 17 +- .../page/8/index.html" | 17 +- .../page/9/index.html" | 17 +- content.json | 2 +- index.html | 17 +- page/10/index.html | 17 +- page/11/index.html | 17 +- page/12/index.html | 17 +- page/13/index.html | 17 +- page/14/index.html | 17 +- page/15/index.html | 17 +- page/16/index.html | 17 +- page/17/index.html | 17 +- page/2/index.html | 17 +- page/3/index.html | 17 +- page/4/index.html | 17 +- page/5/index.html | 17 +- page/6/index.html | 17 +- page/7/index.html | 17 +- page/8/index.html | 17 +- page/9/index.html | 17 +- search.xml | 33 +- sitemap.txt | 3 +- sitemap.xml | 15 +- tags/3-hexo/index.html | 17 +- tags/3-hexo/page/2/index.html | 17 +- tags/AngularJs/index.html | 17 +- tags/ElementUI/index.html | 17 +- tags/Git/index.html | 17 +- tags/GitHub/index.html | 17 +- tags/IntellijIDEA/index.html | 17 +- tags/PostgreSQL/index.html | 17 +- tags/SkyWalking/index.html | 17 +- tags/Vue/index.html | 17 +- tags/centos/index.html | 17 +- tags/concurrent/index.html | 17 +- tags/deepin/index.html | 17 +- tags/docker/index.html | 17 +- tags/docker/page/2/index.html | 17 +- .../index.html" | 17 +- tags/dubbo/index.html | 17 +- tags/efficiency/index.html | 17 +- tags/emacs/index.html | 17 +- tags/encoding/index.html | 17 +- tags/encryption/index.html | 17 +- tags/firewall/index.html | 17 +- tags/fragment-cache/index.html | 17 +- tags/freemarker/index.html | 17 +- tags/ftp/index.html | 17 +- tags/gray-release/index.html | 17 +- tags/hexo/index.html | 17 +- tags/hexo/page/2/index.html | 17 +- tags/hexo/page/3/index.html | 17 +- tags/home-assistant/index.html | 17 +- tags/iot/index.html | 17 +- tags/jQuery/index.html | 17 +- tags/java/index.html | 17 +- tags/java/page/2/index.html | 17 +- tags/javaee/index.html | 17 +- tags/javascript/index.html | 17 +- tags/js/index.html | 17 +- tags/jsp/index.html | 17 +- tags/jstl/index.html | 17 +- tags/k8s/index.html | 17 +- tags/keybord/index.html | 17 +- tags/linux/index.html | 17 +- .../linux\345\221\275\344\273\244/index.html" | 17 +- .../page/2/index.html" | 17 +- .../page/3/index.html" | 17 +- .../page/4/index.html" | 17 +- .../page/5/index.html" | 17 +- .../page/6/index.html" | 17 +- tags/mac/index.html | 17 +- tags/macos/index.html | 17 +- tags/mathjax/index.html | 17 +- tags/maven/index.html | 17 +- tags/mybatis/index.html | 17 +- tags/nacos/index.html | 17 +- tags/neovim/index.html | 17 +- tags/nexus/index.html | 17 +- tags/nginx/index.html | 17 +- tags/node/index.html | 17 +- tags/ocr/index.html | 17 +- tags/pjax/index.html | 17 +- tags/postgres/index.html | 17 +- tags/reading/index.html | 17 +- tags/regex/index.html | 17 +- tags/samba/index.html | 17 +- tags/shell/index.html | 17 +- tags/spring-boot/index.html | 17 +- tags/spring/index.html | 17 +- tags/springcloud/index.html | 17 +- tags/springmvc/index.html | 17 +- tags/sql/index.html | 17 +- tags/swift/index.html | 17 +- tags/tale/index.html | 17 +- tags/terminal/index.html | 17 +- tags/translation/index.html | 17 +- tags/vim/index.html | 17 +- tags/zookeeper/index.html | 17 +- "tags/\346\264\273\347\235\200/index.html" | 17 +- .../index.html" | 17 +- .../index.html" | 17 +- "tags/\350\277\220\347\273\264/index.html" | 17 +- 386 files changed, 10822 insertions(+), 1743 deletions(-) create mode 100644 2024/07/05/Jackson Time Serializer/Deserializer/index.html create mode 100644 archives/2024/07/index.html diff --git a/2016/09/30/computer-mutiple-github-account/index.html b/2016/09/30/computer-mutiple-github-account/index.html index 6c8dd08af..acb72bc52 100644 --- a/2016/09/30/computer-mutiple-github-account/index.html +++ b/2016/09/30/computer-mutiple-github-account/index.html @@ -248,7 +248,7 @@
  • 全部文章 - (163) + (164)
  • @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@ - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 一个客户端设置多个github账号
    @@ -2496,7 +2505,7 @@

    $ git config --list -

    查看 one 的 remote.origin.url=git@github.com:one/one.github.com.git
    查看 two 的 remote.origin.url=git@github.com:two/two.github.com.git
    由于 one 使用的是默认的 Host ,所以不需要修改,但是 two 使用的是 two.github.com ,则需要进行修改

    +

    查看 one 的 remote.origin.url=git@github.com:one/one.github.com.git
    查看 two 的 remote.origin.url=git@github.com:two/two.github.com.git
    由于 one 使用的是默认的 Host ,所以不需要修改,但是 two 使用的是 two.github.com ,则需要进行修改

    $ git remote rm origin
     $ git remote add origin git@two.github.com:two/two.github.com.git
     
    diff --git a/2016/10/02/git-command/index.html b/2016/10/02/git-command/index.html index 4d59efc0e..2caf0436c 100644 --- a/2016/10/02/git-command/index.html +++ b/2016/10/02/git-command/index.html @@ -248,7 +248,7 @@
  • 全部文章 - (163) + (164)
  • @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@

    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Git常用命令
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 正则表达式详解
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + FreeMarker语法详解
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + PostgreSQL初体验
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + PostgreSQL常用操作
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + PostgreSQL的介绍与安装
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 如何给GitHub上的项目贡献代码
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Hexo+GitHub Pages搭建属于自己的blog
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 搭建Git服务器
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Hexo+Git服务器搭建blog
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + web.xml详解
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + idea常用快捷键
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + jQuery选择器与节点操作
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + jQuery之checkbox|radio|select操作
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(1): ls
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(2): cd
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(3): pwd
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(4): mkdir
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(5): rm
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(6): rmdir
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(7): mv
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(8): cp
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(9): touch
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(10): cat
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(11): nl
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(12): more
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(13): less
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(14): head
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(15): tail
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(16): which
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(17): whereis
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(18): locate
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(19): find命令概览
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(20): find命令之exec
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Git之SSH与HTTPS免密码配置
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(21): find命令之xargs
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(22): find命令的参数
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Git操作之高手过招
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(23): 用SecureCRT来上传
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Git之reset揭秘
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(24): tar
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(25): gzip
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(26): chmod
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(27): chgrp
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(28): chown
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(29): /etc/group文件详
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(30): df
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(31): du
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(32): top
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(33): free
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 人生若只如初见-《围城》
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(34): vmstat
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(35): iostat
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(36): lsof
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(37): ifconfig
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(38): route
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(39): ping
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(40): traceroute
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(41): netstat
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(42): ss
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(43): telnet
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(44): rcp
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(45): scp
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(46): ln
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(47): diff
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(48): date
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(49): cal
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(50): grep
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(51): wc
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(52): ps
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(53): watch
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(54): at
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(55): crontab
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + reading-list
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + pjax用法
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(56): tailf
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + [译]Java内存泄露介绍
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Hexo置顶及排序问题
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Hexo创建404页面
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 3-hexo多作者模式
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + AngularJs快速入门
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(57): sftp
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Vim命令速查表
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 如何在linux中搭建ftp服务
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Hexo主题3-hexo
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 前端页面开发规范
    @@ -2427,7 +2436,7 @@

    前端页面开发规范

    一、前言

    随着开发人员的不断增加,在没有规范的情况下,就会导致开发的页面不统一,不像是一个系统。为了解决这个问题,就有了此规范的出现,当然为了不影响各个功能的灵活性,此规范要求不高, 请耐心阅读,并应用到日常开发中。

    -

    当然,如果你有更好的建议,可以通过邮件联系 yangyj13@lenovo.com,进行沟通来完善此篇规范。

    +

    当然,如果你有更好的建议,可以通过邮件联系 yangyj13@lenovo.com,进行沟通来完善此篇规范。

    二、编程规范

    2.1 命名规范

    2.1.1 文件命名

    全部采用小写方式,以横杠分割。

    正例: resource.vueuser-info.vue

    反例: basic_data.vueEventLog.vue

    @@ -2446,11 +2455,11 @@

    2.3 组件使用

    2.3.1 table 表格

    表格组件推荐使用 vxe-table,功能更加全面,之后也会主力优化此表格。比如可编辑表格的样式经过优化:可编辑表格

    2.3.2 dialog 弹窗

    弹窗组件推荐使用 vxe-modal,代码设计更加合理,功能也更加全面。

    2.3.2 element-ui

    tablemodal 外,其他组件比如 formbuttonDateTimePicker 优先使用 element-ui

    -
    2.3.2.1 icon 图标

    图标优先使用 element-ui 的图标。如果没有合适的,可以在 iconfont 上寻找到合适的图标后,找 yangyj13@lenovo.com 进行添加。

    +
    2.3.2.1 icon 图标

    图标优先使用 element-ui 的图标。如果没有合适的,可以在 iconfont 上寻找到合适的图标后,找 yangyj13@lenovo.com 进行添加。

    2.3.2.2 button 按钮

    按钮大小:除了在表格中的按钮要使用 size="mini" 外,其他情况使用默认大小即可。

    按钮颜色:普通的 查询/修改/操作 等按钮使用蓝色 type="primary",新增使用绿色 type="success",删除等“危险”操作使用红色 type="danger"。推荐给按钮添加图标,可在 element-ui-icon 寻找合适的图标。

    image-20211227172602560

    -

    2.3.3 其他组件

    如果上述组件并不能满足业务需求,可以优先在网上找到合适的组件后,与 yangyj13@lenoov.com 联系后添加。

    +

    2.3.3 其他组件

    如果上述组件并不能满足业务需求,可以优先在网上找到合适的组件后,与 yangyj13@lenoov.com 联系后添加。

    2.4 页面布局

    2.4.1 新增/修改表单

    普通的表单,采用中间对其的方案,也就是整个表单的 label-width 设置为一样的。

    注意:一般的,新增修改使用弹窗的方式,展示表单。新增/修改可以共用代码,具体可以参考 common/system/va-config.vue

    <el-form
    diff --git a/2017/03/08/understanding-the-critical-rendering-path/index.html b/2017/03/08/understanding-the-critical-rendering-path/index.html
    index f2249d1b1..eb2148e2b 100644
    --- a/2017/03/08/understanding-the-critical-rendering-path/index.html
    +++ b/2017/03/08/understanding-the-critical-rendering-path/index.html
    @@ -248,7 +248,7 @@
         
  • 全部文章 - (163) + (164)
  • @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + [译]理解浏览器关键渲染路径
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 不蒜子适配pjax
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 多说适配pjax
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Hexo加入字数统计WordCount
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 3-hexo配置首页
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 3-hexo开发日志-持续更新
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 为Hexo添加RSS和Sitemap
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + npm使用介绍
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + windows环境下使用hexo搭建blog平台
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 3-hexo使用说明
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 关于第三方评论系统
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 3-hexo快捷键说明
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + hexo报错合集
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + deepin系统使用记录
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + PostgreSQL常用SQL操作
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + [转]SpringMVC执行流程及源码解析
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + JSP操作记录
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 加密算法简介
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 解决linux下zip文件解压乱码
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Git统计操作
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + linux无损调整分区大小
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + docker初体验
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + docker镜像
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Dockerfile指令详解
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + CentOS修改DNS/GW/IP
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + docker容器
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + docker数据管理
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + docker仓库
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 每天一个linux命令(58): sort
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + tale博客搭建及体验
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 进入docker容器命令制作
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 解决粘贴到vim缩进错乱问题
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 完美替代多说-gitment
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + CentOS7安装配置匿名访问Samba
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 3-hexo配置MathJax数学公式渲染
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + MathJax适配Pjax
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Mybatis常用Mapper语句
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 搭建Maven私服-Nexus
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + sudo命令免密码设置
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + docker备份恢复之save与export
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + CentOS7使用Firewalld
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + [转]浏览器前进/后退缓存(BF Cache)
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Hexo加速渲染速度之fragment_cache
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 解决iphone下后退不执行js的问题
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + [转]字符编解码的故事(ASCII,ANSI,Unicode
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + docker报错集锦
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 搭建dubbo+zookeeper平台
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + linux下修改按键ESC<=>CAPSLOCK和Control=>
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + PostgreSQL事务及隔离级别
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + [转]谈谈Java中的语法糖
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Mac神器-BTT(BetterTouchTool)不完全教程
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + nginx配置记录
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + shell速查表
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 人们在一本叫《活着》的书中纷纷死去
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + [记]《知识分子的不幸》-王小波
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 3-hexo支持jsfiddle渲染
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 3-hexo文章内toc生成
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 3-hexo 添加音乐插件
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 3-hexo支持mermaid图表
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 3-hexo评论设置
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Docker 技术整理
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 一文看懂JavaScript中的Promise
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 3-hexo添加自定义图标
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + SpringCloud系列之接入SkyWalking进行链路追踪
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + el-drawer 实现鼠标拖拽宽度[ElementUI]
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 2022年我在使用这些vim插件
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 基于 nacos/springcloud/k8s 的不停机服务更新[
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + [Java]通过 CompletableFuture 实现异步多线程
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 基于 nacos/灰度发布 实现减少本地启动微
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Raycast 最强效率软件推荐
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + ideavim 使用百分号%支持xml的对应标签跳转
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + Caused by: java.lang.ClassNotFoundException: org.sprin
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + swift 离线图片识别文字(ocr)
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + K8s-内部培训
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 离线安装Home Assistant
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 2024年MacOS终端大比拼
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + 最近

    在彭小六的六扇门内,研习阅读方法、知识管理、时间管理等方法。

    联系方式

    diff --git a/archives/2016/09/index.html b/archives/2016/09/index.html index bc247a939..9e1dbfdcf 100644 --- a/archives/2016/09/index.html +++ b/archives/2016/09/index.html @@ -248,7 +248,7 @@
  • 全部文章 - (163) + (164)
  • @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + + + + + 叶落阁 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + +
    +
    +
    +
    +
    + +
    1. Why Blog
      1. 对博客的理解
      2. 关于叶落阁
      3. 博客平台
    + + +
    +

    Why Blog

    对博客的理解

    喜欢写Blog的人,会经历三个阶段。

    +
    +

    第一阶段,刚接触Blog,觉得很新鲜,试着选择一个免费空间来写。

    +
    +
    +

    第二阶段,发现免费空间限制太多,就自己购买域名和空间,搭建独立博客。

    +
    +
    +

    第三阶段,觉得独立博客的管理太麻烦,最好在保留控制权的前提下,让别人来管,自己只负责写文章。

    +
    +

    我们每个人的在网络上产生的数据越来越多,这些信息是我们在互联网上存在过的痕迹,值得我们认真对待。但是它们被分散分布在各个网站上。很多时候我们很难将它们聚合在一起,而且各个网站的信息排布方式也没有办法自由控制,所以我们需要一个可以由自己主宰的空间——博客。

    +

    通过博客,我们可以记录自己的生活和成长的轨迹。它不像 Twitter 那样碎片化,也不像 Facebook 那样关系化,它是私人的空间。

    +

    关于叶落阁

    叶落阁 是阁主(杨玉杰)的个人站。

    +

    到目前为止已经写了篇文章, 共字。

    +

    本站访问人数:人次 , 访问量:

    +

    博客平台

    这个博客通过 Hexo 生成,部署在 GitHub Pages主题 3-hexo 已经在github上开源

    +

    3-hexo 主题使用交流可以加 Q群: 3-hexo使用交流

    +

    主要功能:

    +
      +
    • 搜索支持文章标题、标签(#标签)、作者(@作者)
    • +
    • pad/手机等移动端适配
    • +
    • 页面全局快捷键 3-hexo快捷键说明
    • +
    + +
    + +

    + +

    + + +
    + + + + + +
    + +
    +
    + + + +
    + +
    +
    + × +
    +

    喜欢就点赞,疼爱就打赏

    +
    +
    +
    + + +
    +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2024/index.html b/archives/2024/index.html index bc247a939..9e1dbfdcf 100644 --- a/archives/2024/index.html +++ b/archives/2024/index.html @@ -248,7 +248,7 @@
  • 全部文章 - (163) + (164)
  • @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + - 2024-07-05T01:50:55.063Z + 2024-07-05T11:10:22.659Z http://yelog.org/ @@ -16,12 +16,39 @@ Hexo + + Jackson 时间序列化/反序列化详解 + + http://yelog.org/2024/07/05/Jackson%20Time%20Serializer/Deserializer/ + 2024-07-05T08:00:00.000Z + 2024-07-05T11:10:22.659Z + + 前言

    最近在项目中遇到了时间序列化的问题,所以研究了一下 Jackson 的时间序列化/反序列化,这里做一个详细的总结。

    0. 准备工作

    准备实体类 User.java

    package com.example.testjava.entity;import lombok.Builder;import lombok.Data;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.util.Date;@Builder@Datapublic class User {    private String name;    private Date date;    private LocalDate localDate;    private LocalDateTime localDateTime;    private LocalTime localTime;    private java.sql.Date sqlDate;    private java.sql.Time sqlTime;    private java.sql.Timestamp timestamp;}

    简单查询

    package com.example.testjava.controller;import com.example.testjava.entity.User;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.Date;@RestController@RequestMapping("/jackson")public class JacksonTestController {    @GetMapping("/query")    public User testJavaDate() {        return User.builder()                .name("test")                .date(new Date())                .localDate(java.time.LocalDate.now())                .localDateTime(java.time.LocalDateTime.now())                .localTime(java.time.LocalTime.now())                .sqlDate(new java.sql.Date(System.currentTimeMillis()))                .sqlTime(new java.sql.Time(System.currentTimeMillis()))                .timestamp(new java.sql.Timestamp(System.currentTimeMillis()))                .build();    }}

    1. 序列化

    1.1. 默认返回

    {    "name": "test",    "date": "2024-07-05T08:09:47.100+00:00",    "localDate": "2024-07-05",    "localDateTime": "2024-07-05T16:09:47.100462",    "localTime": "16:09:47.100514",    "sqlDate": "2024-07-05",    "sqlTime": "16:09:47",    "timestamp": "2024-07-05T08:09:47.100+00:00"}

    1.2. 添加配置

    配置如下

    spring:  jackson:    date-format: yyyy-MM-dd HH:mm:ss

    返回效果

    {    "name": "test",    "date": "2024-07-05 08:16:07",    "localDate": "2024-07-05",    "localDateTime": "2024-07-05T16:16:07.097035",    "localTime": "16:16:07.09705",    "sqlDate": "2024-07-05",    "sqlTime": "16:16:07",    "timestamp": "2024-07-05 08:16:07"}

    可以发现, 日期时间类型中, 只有 java.time.LocalDateTime 没有按照配置序列化, java.util.Datejava.sql.Timestamp 按照配置序列化了。

    1.3. 添加注解

    package com.example.testjava.entity;import com.fasterxml.jackson.annotation.JsonFormat;import lombok.Builder;import lombok.Data;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.util.Date;@Builder@Datapublic class User {    private String name;    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")    private Date date;    private LocalDate localDate;    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")    private LocalDateTime localDateTime;    @JsonFormat(pattern = "HH:mm:ss")    private LocalTime localTime;    private java.sql.Date sqlDate;    private java.sql.Time sqlTime;    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")    private java.sql.Timestamp timestamp;}

    返回效果

    {    "name": "test",    "date": "2024-07-05 08:24:36",    "localDate": "2024-07-05",    "localDateTime": "2024-07-05 16:24:36",    "localTime": "16:24:36",    "sqlDate": "2024-07-05",    "sqlTime": "16:24:36",    "timestamp": "2024-07-05 08:24:36"}

    注解是可以都有效的

    2. 反序列化

    准备请求

        @PostMapping("/save")    public User save(@RequestBody User user) {        return user;    }

    请求参数

    {    "name": "test",    "date": "2024-07-05 08:24:36",    "localDate": "2024-07-05",    "localDateTime": "2024-07-05 16:24:36",    "localTime": "16:24:36",    "sqlDate": "2024-07-05",    "sqlTime": "16:24:36",    "timestamp": "2024-07-05 08:24:36"}

    2.1 默认效果

    默认报错

    JSON parse error: Cannot deserialize value of type `java.util.Date` from String \"2024-07-05 08:24:36\"

    2.2 添加配置

    有两种方法可以解决, 一个是自定义时间序列化, 一个是自定义 objectMapper

    2.2.1 自定义时间序列化

    /** * 此转换方法试用于 json 请求 * LocalDateTime 时间格式转换 支持 */@JsonComponent@Configurationpublic class LocalDateTimeFormatConfiguration extends JsonDeserializer<LocalDateTime> {    @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")    private String pattern;    /**     * LocalDate 类型全局时间格式化     * @return     */    @Bean    public LocalDateTimeSerializer localDateTimeDeserializer() {        return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern));    }    @Bean    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {        return builder -> builder.serializerByType(LocalDateTime.class, localDateTimeDeserializer());    }    @Override    public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {        return StrUtil.isEmpty(jsonParser.getText()) ? null : LocalDateTimeUtil.of(new DateTime(jsonParser.getText()));    }}
    @JsonComponent@Configurationpublic class DateFormatConfiguration extends JsonDeserializer<Date> {    @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")    private String pattern;    /**     * date 类型全局时间格式化     *     * @return     */    @Bean    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilder() {        return builder -> {            TimeZone tz = TimeZone.getTimeZone("UTC");            DateFormat df = new SimpleDateFormat(pattern);            df.setTimeZone(tz);            builder.failOnEmptyBeans(false)                    .failOnUnknownProperties(false)                    .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)                    .dateFormat(df);        };    }    @Override    public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {        return StrUtil.isEmpty(jsonParser.getText()) ? null : new DateTime(jsonParser.getText());    }}

    2.2.2 自定义 objectMapper

    package com.example.testjava.config;import cn.hutool.core.date.DatePattern;import cn.hutool.core.date.DateTime;import cn.hutool.core.date.LocalDateTimeUtil;import cn.hutool.core.util.StrUtil;import com.fasterxml.jackson.core.JacksonException;import com.fasterxml.jackson.core.JsonGenerator;import com.fasterxml.jackson.core.JsonParser;import com.fasterxml.jackson.databind.Module;import com.fasterxml.jackson.databind.*;import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.autoconfigure.AutoConfigureBefore;import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.io.IOException;import java.sql.Time;import java.sql.Timestamp;import java.text.SimpleDateFormat;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.time.format.DateTimeFormatter;import java.util.Date;@Configuration@AutoConfigureBefore(JacksonAutoConfiguration.class)public class JacksonConfig {    @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")    private String pattern;    @Bean    public ObjectMapper objectMapper() {        ObjectMapper objectMapper = new ObjectMapper();        // 在反序列化时, 如果对象没有对应的字段, 不抛出异常        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);        objectMapper.registerModule(javaTimeModule());        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);        return objectMapper;    }    private Module javaTimeModule() {        JavaTimeModule module = new JavaTimeModule();        // 序列化        module.addSerializer(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern)));        module.addSerializer(new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));        module.addSerializer(new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));        module.addSerializer(Date.class, new JsonSerializer<>() {            @Override            public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {                SimpleDateFormat sdf = new SimpleDateFormat(pattern);                jsonGenerator.writeString(sdf.format(date));            }        });        module.addSerializer(java.sql.Date.class, new JsonSerializer<>() {            @Override            public void serialize(java.sql.Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {                SimpleDateFormat sdf = new SimpleDateFormat(pattern);                jsonGenerator.writeString(sdf.format(date));            }        });        module.addSerializer(Timestamp.class, new JsonSerializer<>() {            @Override            public void serialize(Timestamp timestamp, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {                SimpleDateFormat sdf = new SimpleDateFormat(pattern);                jsonGenerator.writeString(sdf.format(timestamp));            }        });        module.addSerializer(Time.class, new JsonSerializer<>() {            @Override            public void serialize(Time time, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {                SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.NORM_TIME_PATTERN);                jsonGenerator.writeString(sdf.format(time));            }        });        // 反序列化        module.addDeserializer(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() {            @Override            public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {                return StrUtil.isEmpty(jsonParser.getText()) ? null : LocalDateTimeUtil.of(new DateTime(jsonParser.getText()));            }        });        module.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));        module.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));        module.addDeserializer(Date.class, new JsonDeserializer<Date>() {            @Override            public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {                return StrUtil.isEmpty(jsonParser.getText()) ? null : new DateTime(jsonParser.getText());            }        });        module.addDeserializer(java.sql.Date.class, new JsonDeserializer<java.sql.Date>() {            @Override            public java.sql.Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {                return StrUtil.isEmpty(jsonParser.getText()) ? null : new java.sql.Date(new DateTime(jsonParser.getText()).getTime());            }        });        module.addDeserializer(Timestamp.class, new JsonDeserializer<Timestamp>() {            @Override            public Timestamp deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {                return StrUtil.isEmpty(jsonParser.getText()) ? null : new Timestamp(new DateTime(jsonParser.getText()).getTime());            }        });        module.addDeserializer(Time.class, new JsonDeserializer<Time>() {            @Override            public Time deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {                return StrUtil.isEmpty(jsonParser.getText()) ? null : Time.valueOf(jsonParser.getText());            }        });        // 添加默认处理        return module;    }}

    效果可以返回正确的数据

    {    "name": "test",    "date": "2024-07-05 08:24:36",    "localDate": "2024-07-05",    "localDateTime": "2024-07-05 16:24:36",    "localTime": "16:24:36",    "sqlDate": "2024-07-05 00:00:00",    "sqlTime": "16:24:36",    "timestamp": "2024-07-05 08:24:36"}
    ]]> + + + + + <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近在项目中遇到了时间序列化的问题,所以研究了一下 Jackson 的时间序列化&#x2F;反序列化,这里做一个详细的总结。</p> +<h1 + + + + + + + + + + + + + 2024年MacOS终端大比拼 http://yelog.org/2024/06/23/macos-terminal/ 2024-06-23T07:54:00.000Z - 2024-07-05T01:50:55.063Z + 2024-07-05T11:10:22.346Z 最流行的终端

    横评

    | capability | Kitty | Alacritty | WezTerm | iTerm2 | Native |
    | —- | —- | —- | —- | —- | |
    | key-bind | | | | | |

    log

    2024-06-27

    放弃 Kitty -> 转为 WezTerm

    Kitty 绑定 cmd-shift-f 在 tmux 下无法使用, 且没有 vim-mode

    2024-06-26

    放弃使用 Alacritty -> 转为 Kitty

    因为在 vimnormal 模式下, 如果是中文输入法, 输入的内容会出现在输入法的候选框内, 然后按 <CAPS> 按键切换输入法, 候选框中的输入的字母, 会以 insert 的方式输出到光标所在的位置, 这个问题在 WezTermKitty 中没有出现.

    注意: Kittymap cmd+1 send_key cmd+1 能够正常映射到 NeoVim 中进行 maps.n["<D-1>"] = { "<cmd>Neotree left toggle<cr>", desc = "Toggle Explorer" } 绑定, 但是开启 tmux 后, cmd+1 映射到 NeoVim 中就不行了

    2024-06-20

    放弃 WezTerm -> 转为 Alacritty

    ]]>
    @@ -51,7 +78,7 @@ http://yelog.org/2024/03/22/home-assistant/ 2024-03-22T01:05:44.000Z - 2024-07-05T01:50:55.435Z + 2024-07-05T11:10:22.735Z 前言

    最近搬到了新家,家里的智能设备也越来越多, 引入很多米家设备, 但博主使用的是苹果生态, 需要将这些不支持 homekit 的米家设备接入到 homekit 中, 经过调研发现 Home Assistant 是一个不错的选择, 本文会介绍联网安装的过程, 并且如果有需要联网的步骤, 也会提供离线安装的方法.

    本篇主要介绍通过 docker 部署的方式

    安装 Docker

    如已有 docker 环境, 可以跳过这一步

    sudo apt-get updatesudo 
    ]]>
    @@ -79,7 +106,7 @@ http://yelog.org/2024/02/05/k8s-inner-training/ 2024-02-05T01:17:27.000Z - 2024-07-05T01:50:54.456Z + 2024-07-05T11:10:21.843Z K8s 内部培训

    介绍

    K8s 为 Kubernetes 的简称, 是一个开源的容器编排平台, 最初是由 Google 工程师开发和设计的, 后于 2015 年捐赠给了云原生计算机基金会-CNCF

    应用服务管理发展史

    早期服务应用大多以单包的形式运行在服务器上, 当我们增加一些服务时, 比如添加 JOB 应用时, 我们会另开一个新的应用, 但基本用一台服务器上就能完成

    所以我们早期应用的发展就如下面图表所示, 由于用户数量较少, 所以更新应用时短暂的暂停服务也是可以接受的

    ┌──────────────**石器时代**───────────────┐ ┌──────────────**石器时代**───────────────┐ ┌──────────────**石器时代**───────────────┐│ ┌─────────────`Server1`───────────────┐ │ │ ┌─────────────`Server1`───────────────┐ │ │ ┌─────────────`Server1`───────────────┐ ││ │ ┌──☕──┐ │ │ │ │ ┌──☕──┐ ┌──☕──┐ │ │ │ │ ┌──☕──┐ ┌──☕──┐ ┌──☕──┐ │ ││ │ │ APP1 │ │ │ │ │ │ APP1 │ │ APP2 │ │ │ │ │ │ APP1 │ │ APP2 │ │ APP3 │ │ ││ │ └──────┘ │ │ ==> │ │ └──────┘ └──────┘ │ │ ==> │ │ └──────┘ └──────┘ └──────┘ │ ││ ├─────────────────────────────────────┤ │ │ ├─────────────────────────────────────┤ │ │ ├─────────────────────────────────────┤ ││ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ │ 安装: JDK8, Tomcat, 需要手动启动服务│ ││ └─────────────────────────────────────┘ │ │ └─────────────────────────────────────┘ │ │ └─────────────────────────────────────┘ │└─────────────────────────────────────────┘ └─────────────────────────────────────────┘ └─────────────────────────────────────────┘

    随着用户数量的上升, 应用的并发也随之提高, 单台服务器的压力也随之增大, 有了如下情况:

    1. 高峰期经常出现卡顿
    2. 更新应用时的暂停服务已经不可接受
    3. 在服务器出现故障时的高可用有了更高的要求

    为了解决上面的问题, 我们就进入了下个时代(下图一), 采购多台服务器, 对应用进行支持集群的改造, 这时我们的应用分别在三台服务器上, 并发能力提高了3倍, 并且冗灾能力大幅提升

    尽管我们解决了上面的问题, 但是带来了新的问题, 因为服务器数量过多, 在安装应用需要的工具如 JDK、Tomcat、Node、Nginx、Redis 等等, 可能会因为安装版本和服务器系统版本不一致导致应用运行失败

    所以会在环境安装中浪费太多时间, 所以很多企业开始引入如 Docker 的虚拟化技术(下图二), 用来解决环境不一致的问题, 并且一并解决了守护进程, 开机启动等问题

    这时我们通过 docker-compose 技术, 升级应用、调整配置相比以前大大简化, 但是随着应用规模的扩大, 对应用高可用有了更高的要求, 纷纷开始进行微服务拆分, 应用数量和服务器数量越来越多, 服务的运维管理越来越复杂、

    大家开始开发各种集群管理, 让大家可以在一个地方并且可视化的管理集群中的所有 Docker, 以 Google 开源的 Kubernetes 做的最功能完善且灵活可配置, 从而开始爆火.

    于是众多企业开始上K8s(下图三), 不仅解决运维复杂的问题, 而且带来了更多更好的特性:

    1. 服务发现和负载均衡
    2. 存储编排
    3. 自动部署和回滚
    4. 自我修复
    5. 密钥和配置管理
    ┌──────────────**农耕文明**───────────────┐ ┌─────────────────**工业文明**───────────────┐ ┌─────────────────**信息文明**───────────────┐│ ┌─────────────`Server1`───────────────┐ │ │ ┌────────────────`Server1`───────────────┐ │ │ ┌────────────────`K8s 集群`──────────────┐ ││ │ ┌──☕──┐ ┌──☕──┐ ┌──☕──┐ │ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ ││ │ │ APP1 │ │ APP2 │ │ APP3 │ │ │ │ │ │ APP1 ││ APP2 ││ APP3 ││ │ │ │ │ POD1 ││ POD2 ││ POD3 ││ ││ │ └──────┘ └──────┘ └──────┘ │ │ │ │ ├───────────┤├───────────┤├───────────┤│ │ │ │ ├───────────┤├───────────┤├───────────┤│ ││ ├─────────────────────────────────────┤ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ ││ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ │ └───────────┘└───────────┘└───────────┘│ │ │ │ └───────────┘└───────────┘└───────────┘│ ││ └─────────────────────────────────────┘ │ │ └────────────────────────────────────────┘ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ ││ ┌─────────────`Server2`───────────────┐ │ │ ┌────────────────`Server1`───────────────┐ │ │ │ │ POD4 ││ POD5 ││ POD6 ││ ││ │ ┌──☕──┐ ┌──☕──┐ ┌──☕──┐ │ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ │ │ │ ├───────────┤├───────────┤├───────────┤│ ││ │ │ APP1 │ │ APP2 │ │ APP3 │ │ │ │ │ │ APP1 ││ APP2 ││ APP3 ││ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ ││ │ └──────┘ └──────┘ └──────┘ │ │ ==> │ │ ├───────────┤├───────────┤├───────────┤│ │ ==> │ │ └───────────┘└───────────┘└───────────┘│ ││ ├─────────────────────────────────────┤ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ ││ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ │ └───────────┘└───────────┘└───────────┘│ │ │ │ │ POD7 ││ POD8 ││ POD9 ││ ││ └─────────────────────────────────────┘ │ │ └────────────────────────────────────────┘ │ │ │ ├───────────┤├───────────┤├───────────┤│ ││ ┌─────────────`Server3`───────────────┐ │ │ ┌────────────────`Server1`───────────────┐ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ ││ │ ┌──☕──┐ ┌──☕──┐ ┌──☕──┐ │ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ │ │ │ └───────────┘└───────────┘└───────────┘│ ││ │ │ APP1 │ │ APP2 │ │ APP3 │ │ │ │ │ │ APP1 ││ APP2 ││ APP3 ││ │ │ ├────────────────────────────────────────┤ ││ │ └──────┘ └──────┘ └──────┘ │ │ │ │ ├───────────┤├───────────┤├───────────┤│ │ │ │ ┌───────┐ ┌───────┐ ┌───────┐ │ ││ ├─────────────────────────────────────┤ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ │ │ │ │Server1│ │Server2│ │Server3│ ••• │ ││ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ │ └───────────┘└───────────┘└───────────┘│ │ │ │ └───────┘ └───────┘ └───────┘ │ ││ └─────────────────────────────────────┘ │ │ └────────────────────────────────────────┘ │ │ └────────────────────────────────────────┘ │├─────────────────────────────────────────┤ ├────────────────────────────────────────────┤ ├────────────────────────────────────────────┤│ 优点: 节省资源, 需要掌握的知识较少 │ │ 优点: 环境搭建容易, 安装Docker和配置文件 │ │ 优点: 自动故障恢复, 监控完善, 操作方便 ││ 缺点: 运维操作繁杂, JDK版本难以统一 │ │ 缺点: 随着规模扩大, 日常运维也变得繁杂 │ │ 缺点: k8s 功能较多, 需要掌握的知识也多 │└─────────────────────────────────────────┘ └────────────────────────────────────────────┘ └────────────────────────────────────────────┘ 图一 图二 图三

    Kubernetes 组件

    Kubernetes 各组件

    • Control Plane Components: 控制平面组件
      • kube-apiserver: 负责公开 Kubernetes API, 处理请求, 类似 cloud 中的网关
      • etcd: key-value 存储, 用于保存集群数据
      • kube-scheduler: 任务调度, 监听有新创建但未运行的pods, 选择节点来让 pod 在上面运行
      • kube-controller-manager: 负责运行控制器进程, 有如下不同类型的控制器
        • Node Controller: 节点控制器, 负责节点出现故障时进行通知和响应
        • Job Controller: 任务控制器, 检测代表一次性任务的 Job 对象, 然后创建 Pod 来运行这些任务直至完成
        • EndpointSlice Controller: 端点分片控制器, 提供 Service 和 Pod 之间的链接
        • ServiceAccount Controller: 为新的命名空间创建默认的服务账号
      • cloud-controller-manager: 云控制管理器, 集成云提供商的API, 我们内网部署的用不到
    • Node Components: 节点组件, 运行在各个节点, 负责维护运行的 Pod, 提供 Kubernetes 的运行环境
      • kubelet: 在每个节点中运行, 保证容器都运行在 Pod 中, kubelet 接受一组 PodSpec, 确保 PodSpec 中描述的容器处于运行状态且健康
      • kube-proxy: 网络代理, 是实现 Service 的一部分
      • Container Runtime: 容器运行时, Kubernetes 支持需要容器运行环境, 例如: docker, containerd, CRI-O
    • Addons: 插件, 提供集群级别的功能, 插件提供的资源属于 kube-system 命名空间
      • DNS: 提供集群内的域名系统
      • Web UI/Dashboard: 通用的基于 Web 的用户界面, 它使用户可以集中管理集群中的应用已经集群本身
      • Container Resource Monitoring: 将容器的一些常见的时间序列度量值保存到一个集中的数据库中, 并提供浏览这些数据的界面
      • Cluster-level Logging: 集群级日志, 将容器日志保存到一个集中的日志存储中, 这种集中日志存储提供搜索和浏览接口
      • Network Plugins: 网络插件, 实现容器网络接口(CNI)规范的软件组件, 负责为 Pod 分配 IP 地址, 并使这些 Pod 能在集群内部互相通信

    Kubernetes 架构

    Node 节点

    Kubernetes 通过将容器放入在节点(Node) 上运行的 Pod 来执行你的负载. 节点可以是一个虚拟机或物理机, 每个节点包含 Pod 所需的服务器, 这些节点由 Control Plane 负责管理

    一个集群的节点数量可以是1个, 也可以是多个. 且节点名称是唯一的.

    可以通过 kubectl 来创建和修改 Node 对象

    # 查一下集群中的所有节点信息kubectl get node# 查看某个节点详细信息kubectl descibe $NODENAME# 标记一个 Node 为不可调度kubectl cordon $NODENAME

    Controllers 控制器

    在机器人和自动化领域, 又一个类似的概念叫控制回路 (Control Loop), 用于调节系统状态, 如: 房间里的温度自动调节器

    当你设置了温度, 温度自动调节器让其当前状态接近期望温度; 在 Kubernetes 中, 控制器通过监控集群的公共状态, 并致力于将当前的状态转为期望状态

    控制器是通过通知 apiserver 来管理状态的, 就像温度自动调节器是通过控制空调来调节气温的

    Container Runtime Interface/CRI 容器运行时接口

    CRI 是一个插件接口, 它使 kubelet 能够使用各种容器运行时, 定义了主要 gRPC 协议, 用于节点组件 kubelet 和容器运行时之间的通信

    Containers 容器

    容器将应用从底层主机设备中解耦, 这使得在不同的云或 OS 环境中部署更加容易

    Kubernetes 集群中的每个节点都会运行容器, 这些容器构成分配给该节点的 Pod, 单个 Pod 中的容器会在共同调度下, 运行在相同的节点

    容器镜像是一个随时可以运行的软件包, 它包含了运行容器程序所需要的一切, 代码和它需要的运行时、应用程序和系统库, 以及一些基本设置

    容器运行时这个基础组件使 Kubernetes 能够有效运行容器, 他负责管理 Kubernetes 环境中的容器的执行和生命周期

    Pod

    Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署计算单元

    Pod 是有一个或多个容器组成, 这些容器共享存储、网络、已经怎么样运行这些容器的声明, 统一调度.

    此外还可以包含 init container, 用于做一些启动主应用前的准备工作, 比如通过 init container 注入 tingyun 等 agent 包

    如下示例, 它由一个运行镜像 nginx:1.14.2 的容器组成

    apiVersion: v1kind: Podmetadata:  name: nginx  labels:    app: my-nginxspec:  containers:  - name: nginx    image: nginx:1.14.2    ports:    - containerPort: 80

    要创建上面显示的 Pod, 保存上面内容到 my-nginx.yaml, 可以通过如下命令

    kubectl apply -f my-nginx.yaml

    Workloads 工作负载

    工作负载是在 Kubernetes 上运行的应用程序, 无论是又一个还是多个组件构成, 你都可以通过一组 Pod 来运行它, Pod 代表的是集群上处于运行状态的一组容器的集合, 但通常一个 Pod 内只运行一个容器

    Kubernetes 提供若干种内置的工作负载资源:

    • DeploymentReplicaSet Deployment 适合无状态应用, Deployment 中的所有 Pod 都是互相等价的
    • StatefulSet 有状态应用, 比如可以独立持久化文件, 互不影响
    • DaemonSet 提供节点本地支撑设施的 Pod, 保证每个节点上一个
    • JobCronJob 定义只需要执行一次并且执行后视为完完成的任务

    Network 网络

    Service

    Service 是将一个或者一组 Pod 公开代理给集群内部, 使之能够各个应用之间通信, 甚至用于公开到集群外(NodePort 或者 代理给 Ingress)

    它提供了类似域名的访问方式, 使用者无需关心后面有多少个 Pod 在提供服务, 他们是否健康, 他们 IP 是否发生变化.

    定义 Service

    apiVersion: v1kind: Servicemetadata:  name: nginx-servicespec:  selector:    app: my-nginx  ports:  - name: name-of-service-port    protocol: TCP    port: 80    targetPort: http-web-svc

    服务类型(type):

    • ClusterIp: 默认值, 智能在集群内访问
    • NodePort: 直接向集群外暴露, 通过节点端口访问
    • LoadBalancer: 使用云平台的负载均衡, Kubernetes 不直接提供
    • Externalname: 将服务映射到 externalName 字段的内容, 例如api.foo.bar.example
    ]]>
    @@ -105,7 +132,7 @@ http://yelog.org/2024/01/02/macos-ocr-swift/ 2024-01-02T02:09:19.000Z - 2024-07-05T01:50:55.446Z + 2024-07-05T11:10:22.750Z 背景

    最近打算写一个 macos 翻译软件, 需要用到 ocr 图像识别, 并且因为速度问题, 一开始就考虑使用系统的自带能力来实现.

    经过翻阅文档和 chatgpt 拉扯了一下午, 最终成功实现.

    代码

    代码逻辑为, 接受参数: 图片路径, 然后获取图片, 通过 VNImageRequestHandler 对图片进行文字识别

    如下代码可以直接放进一个 ocr.swift, 然后执行 swiftc -o ocr ocr.swift , 在执行 ./ocr /Users/yelog/Desktop/3.png 后面为你实际的有文字的图片路经

    ////  ocr.swift//  Fast Translation////  Created by 杨玉杰 on 2023/12/31.//import SwiftUIimport Visionfunc handleDetectedText(request: VNRequest?, error: Error?) {    if let error = error {        print("ERROR: \(error)")        return    }    guard let results = request?.results, results.count > 0 else {        print("No text found")        return    }    for result in results {        if let observation = result as? VNRecognizedTextObservation {            for text in observation.topCandidates(1) {                let string = text.string                print("识别: \(string)")            }        }    }}func ocrImage(path: String) {    let cgImage = NSImage(byReferencingFile: path)?.ciImage()?.cgImage    let requestHandler = VNImageRequestHandler(cgImage: cgImage!)    let request = VNRecognizeTextRequest(completionHandler: handleDetectedText)    // 设置文本识别的语言为英文    request.recognitionLanguages = ["en"]    request.recognitionLevel = .accurate    do {        try requestHandler.perform([request])    } catch {        print("Unable to perform the requests: \(error).")    }}extension NSImage {    func ciImage() -> CIImage? {        guard let data = self.tiffRepresentation,              let bitmap = NSBitmapImageRep(data: data) else {            return nil        }        let ci = CIImage(bitmapImageRep: bitmap)        return ci    }}// 执行函数,从命令行参数中获取图片的地址ocrImage(path: CommandLine.arguments[1])

    然后准备待识别的有文字的图片

    待识别的图片

    # 编译 swift 文件swiftc -o ocr ocr.swift# 执行并且传递图片路径参数./ocr /Users/yelog/Desktop/3.png

    执行识别效果

    最后

    最近打算着手写一些 macos 的小工具, 如果对 swift 或者 macos 感兴趣的可以关注或评论.

    ]]>
    @@ -136,7 +163,7 @@ http://yelog.org/2023/12/11/springboot-jarlauncher/ 2023-12-11T08:58:00.000Z - 2024-07-05T01:50:55.380Z + 2024-07-05T11:10:22.663Z 问题现象

    今天同事再升级框架后(spring-cloud 2022.0.4 -> 2023.0.0)(spring-boot 3.1.6 -> 3.2.0)

    同时因为 spring-boot 的版本问题, 需要将 maven 升级到 3.6.3+

    maven version

    升级后构建 jar 包和构建镜像都是正常的, 但是发布到测试环境就报错 Caused by: java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher

    error log

    问题分析

    报错为 JarLauncher 找不到, 检查了 Jenkins 中的打包任务, 发现并没有编译报错, 同事直接使用打包任务中产生的 xx.jar, 可以正常运行.

    说明在打包 Docker 镜像前都没有问题, 这时就想起来我们在打包镜像时, 先解压 xx.jar, 然后直接执行 org.springframework.boot.loader.JarLauncher, 所以很可能是升级后, 启动文件 JarLauncher 的路径变了.

    为了验证我们的猜想, 我们得看一下实际容器内的文件结构, 但是这时容器一直报错导致无法启动, 不能直接通过 Rancher 查看文件结构, 我们可以通过文件拷贝的方式来解决, 如下:

    # 下载有问题的镜像, 并且创建容器(不启动)docker create -it --name dumy 10.188.132.123:5000/lemes-cloud/lemes-gateway:develop-202312111536 bash# 直接拷贝容器内的我们想要看的目录docker cp dumy:/data .

    到本地后, 就可以通过合适的工具查找 JarLauncher 文件, 我这里通过 vim 来寻找, 如下图:

    JarLauncher Path

    发现比原来多了一层目录 launch, 所以问题就发生在这里了.

    解决方案

    我们在打包脚本 JenkinsCommon.groovy 中根据当前打包的 JDK 版本来判断使用的启动类路径, 如下:

    Jenkins

    再次打包, 应用正常启动.

    Reference

    JarLauncher

    ]]>
    @@ -164,7 +191,7 @@ http://yelog.org/2023/06/17/idea-tips-percent-mach-xml/ 2023-06-17T08:09:00.000Z - 2024-07-05T01:50:55.202Z + 2024-07-05T11:10:22.475Z 前言

    由于最近几年使用 vim 的频率越来越高, 所以在 idea 中也大量开始使用 vim 技巧, 在一年多前碰到个问题, 终于在最近解决了。

    问题描述

    在 idea 中, 在 normal 模式下, 使用 % 不能在匹配标签(xml/html等) 之间跳转

    解决方案

    ~/.ideavimrc 中添加如下设置, 重启 idea 即可

    set matchit

    效果

    最后

    最近会把一些加的 tips 分享出来,大家有什么建议和问题都可以在评论区讨论.

    ]]>
    @@ -194,7 +221,7 @@ http://yelog.org/2023/05/17/mac-raycast/ 2023-05-17T09:45:00.000Z - 2024-07-05T01:50:55.050Z + 2024-07-05T11:10:22.335Z 软件介绍

    Raycast

    最近在很多平台上看到 Raycast 的推荐文章, 今天就尝试了一下, 发现确实不错, 完全可以替代我目前对 Alfred 的使用, 甚至很多地方做得更好, 所以本文就是介绍我使用 Raycast 的一些效果(多图预警), 方便那些还没有接触这个软件的人对它有个了解, 如果有插件推荐, 欢迎在评论区进行讨论。

    Raycast 是 MacMac 平台独占的效率工具, 主要包含如下功能:

    1. 应用启动
    2. 文件查找
    3. 窗口管理
    4. 剪贴板历史
    5. Snippets
    6. 应用菜单查询

    插件扩展功能

    1. 翻译
    2. 斗图
    3. 结束进程
    4. 查询端口占用
    5. 查询ip

    除此之外, Raycast提供的在线插件商店, 可以很方便的进行功能扩展

    Raycast官网

    核心功能

    应用启动

    并且支持卸载应用, 找到应用, cmd+k 打开操作菜单, 下拉到最后

    文件查找

    窗口管理

    剪贴板历史

    推荐使用 Clipboard History 这个扩展,和 Alfred 的一样, 并且有分类,效果如下

    设置快捷键 cmd+shift+v

    Snippets

    通过 Snippets 可以保存自定义片段, 通过关键字快速查询并输出到光标处, 如常用语、 邮箱、手机号、税号、代码片段等等

    创建 Snippets 可以通过搜索 Create Snippet, 搜索 Snippets 可以通过搜索Search Snippet

    应用菜单查询

    查询当前激活应用的所有菜单, 不限层级. 可以通过搜索 Search Menu Items 来查询。

    推荐插件

    Easydict(翻译软件)

    超强的翻译软件, 完美替代我在 Alfred 的 workflow 中配置的有道翻译, 我配置了如下功能

    • 输入中文, 自动翻译成英文
    • 输入英文, 自动翻译成中文
    • 支持一键发音
    • Open AI 翻译长文本

    Doutu

    表情包查询,选中回车就复制到剪贴板了, 就可以粘贴到 Discord/QQ/Wechat 斗图了, 非常方便。

    Kill Process

    关键字查询, 并一键结束进程

    Kill Port

    查询端口占用的进程, 并支持一键结束进程

    MyIp

    查询当前ip

    ]]>
    @@ -222,7 +249,7 @@ http://yelog.org/2022/08/01/[Java]optimize-request-processing-speed-by-completablefuture/ 2022-08-01T12:06:14.000Z - 2024-07-05T01:50:55.423Z + 2024-07-05T11:10:22.719Z 零、背景

    我们在写后端请求的时候, 可能涉及多次 SQL 执行(或其他操作), 当这些请求相互不关联, 在顺序执行时就浪费了时间, 这些不需要先后顺序的操作可以通过多线程进行同时执行, 来加速整个逻辑的执行速度.

    既然有了目标和大致思路, 如果有做过前端的小伙伴应该能想起来 Js 里面有个 Promise.all 来解决这个问题, 在 Java 里也有类似功能的类 CompletableFuture , 它可以实现多线程和线程阻塞, 这样能够保证等待多个线程执行完成后再继续操作.

    一、CompletableFuture 是什么

    首先我们先了解一下 CompletableFuture 是干什么, 接下来我们通过简单的示例来介绍他的作用.

     long startTime = System.currentTimeMillis();//生成几个任务List<CompletableFuture<String>> futureList = new ArrayList<>();futureList.add(CompletableFuture.supplyAsync(()->{    sleep(4000);    System.out.println("任务1 完成");    return "任务1的数据";}));futureList.add(CompletableFuture.supplyAsync(()->{    sleep(2000);    System.out.println("任务2 完成");    return "任务2的数据";}));futureList.add(CompletableFuture.supplyAsync(()->{    sleep(3000);    System.out.println("任务3 完成");    return "任务3的数据";}));//完成任务CompletableFuture<Void> allTask = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]))        .whenComplete((t, e) -> {            System.out.println("所有任务都完成了, 返回结果集: "                    + futureList.stream().map(CompletableFuture::join).collect(Collectors.joining(",")));        });// 阻塞主线程allTask.join();System.out.println("main end, cost: " + (System.currentTimeMillis() - startTime));

    执行结果

    任务2 完成任务3 完成任务1 完成所有任务都完成了, 返回结果集: 任务1的数据,任务2的数据,任务3的数据main end, cost: 4032

    结果分析: 我们需要执行3个任务, 3个任务同时执行, 互不影响

    1. 其中任务2耗时最短,提前打印完成
    2. 其次是任务3
    3. 最后是执行1完成
    4. 当所有任务完成后, 触发 whenComplete 方法, 打印任务的返回结果
    5. 最后打印总耗时为 4.032s
    6. 结论: 多线程执行后, 耗时取决于最耗时的操作, 而单线程是所有操作耗时之和

    二、封装工具类

    经过上面的测试, 通过 CompletableFuture 已经能够实现我们的预想, 为了操作方便, 我们将封装起来, 便于统一管理

    package org.yelog.java.usage.concurrent;import java.util.ArrayList;import java.util.List;import java.util.concurrent.CompletableFuture;import java.util.function.Consumer;import java.util.function.Function;import java.util.function.Predicate;/** * 执行并发任务 * * @author yangyj13 * @date 11/7/22 9:49 PM */public class MultiTask<T> {    private List<CompletableFuture<T>> futureList;    /**     * 添加待执行的任务     *     * @param completableFuture 任务     * @return 当前对象     */    public MultiTask<T> addTask(CompletableFuture<T> completableFuture) {        if (futureList == null) {            futureList = new ArrayList<>();        }        futureList.add(completableFuture);        return this;    }    /**     * 添加待执行的任务(无返回)     *     * @param task 任务     * @return 当前对象     */    public MultiTask<T> addTask(Consumer<T> task) {        addTask(CompletableFuture.supplyAsync(() -> {            task.accept(null);            return null;        }));        return this;    }    /**     * 添加待执行的任务(有返回)     *     * @param task 任务     * @return 当前对象     */    public MultiTask<T> addTask(Function<Object, T> task) {        addTask(CompletableFuture.supplyAsync(() -> task.apply(null)));        return this;    }    /**     * 开始执行任务     *     * @param callback                当所有任务都完成后触发的回调方法     * @param waitTaskExecuteComplete 是否阻塞主线程     */    private void execute(Consumer<List<T>> callback, Boolean waitTaskExecuteComplete) {        CompletableFuture<Void> allFuture = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]))                .whenComplete((t, e) -> {                    if (callback != null) {                        List<T> objectList = new ArrayList<>();                        futureList.forEach((future) -> {                            objectList.add(future.join());                        });                        callback.accept(objectList);                    }                });        if (callback != null || waitTaskExecuteComplete == null || waitTaskExecuteComplete) {            allFuture.join();        }    }    /**     * 开始执行任务     * 等待所有任务完成(阻塞主线程)     */    public void execute() {        execute(null, true);    }    /**     * 开始执行任务     *     * @param waitTaskExecuteComplete 是否阻塞主线程     */    public void execute(Boolean waitTaskExecuteComplete) {        execute(null, waitTaskExecuteComplete);    }    /**     * 开始执行任务     *     * @param callback 当所有任务都完成后触发的回调方法     */    public void execute(Consumer<List<T>> callback) {        execute(callback, true);    }}

    那么上一步我们测试的流程转换成工具类后如下

    long startTime = System.currentTimeMillis();MultiTask<String> multiTask = new MultiTask<>();multiTask.addTask(t -> {    sleep(1000);    System.out.println("任务1 完成");}).addTask(t -> {    sleep(3000);    System.out.println("任务2 完成");}).addTask(CompletableFuture.supplyAsync(()->{    sleep(2000);    System.out.println("任务3 完成");    return "任务3的数据";})).execute(resultList->{    System.out.println("all complete: " + resultList);});System.out.println("main end, cost: " + (System.currentTimeMillis() - startTime));

    三、应用到实际的效果

    执行两次数据库的操作如下

    public interface TestMapper {    @Select("select count(*) from test_user where score < 1000 and user_id = #{userId}")    int countScoreLess1000(Integer userId);    @Select("select count(1) from test_log where success = true and user_id = #{userId}")    int countSuccess(Integer userId);}

    调用方法:

    long start = System.currentTimeMillis();testMapper.countScoreLess1000(userId);long countScoreLess1000End = System.currentTimeMillis();log.info("countScoreLess1000 cost: " + (countScoreLess1000End - start));testMapper.countSuccess(userId);long countSuccessEnd = System.currentTimeMillis();log.info("countSuccess cost: " + (countSuccessEnd - countScoreLess1000End));log.info("all cost: " + (countSuccessEnd - start));

    顺序执行的平均时间如下

    countScoreLess1000 cost: 368countSuccess cost: 404all cost: 772

    当我们应用的上面的工具类后的调用方法

    MultiTask multiTask = new MultiTask<>();multiTask.addTask(t -> {    testMapper.countScoreLess1000(userId);    log.info("countScoreLess1000 cost: " + (System.currentTimeMillis() - start));}).addTask(t -> {    testMapper.countSuccess(userId);    log.info("countSuccess cost: " + (System.currentTimeMillis() - start));}).execute();log.info("all cost: " + (System.currentTimeMillis() - start));

    效果如下

    countScoreLess1000 cost: 433countSuccess cost: 463all cost: 464

    可以看到各子任务执行时长是差不多的, 但是总耗时使用多线程后有了明显下降

    四、总结

    通过使用 CompletableFuture 实现多线程阻塞执行后, 大幅降低这类请求, 并且当可以异步执行的子任务越多, 效果越明显.

    ]]>
    @@ -248,7 +275,7 @@ http://yelog.org/2022/08/01/reducing-local-springcloud-base-on-nacos-and-gray-release/ 2022-08-01T12:06:14.000Z - 2024-07-05T01:50:55.376Z + 2024-07-05T11:10:22.654Z 一、背景

    后台框架是基于 spring cloud 的微服务体系, 当开发同学在自己电脑上进行开发工作时, 比如开发订单模块, 除了需要启动订单模块外, 还需要启动网关模块、权限校验模块、公共服务模块等依赖模块, 非常消耗开发同学的本地电脑的资源, 也及其浪费时间.

    Spring Cloud

    二、解决方案

    2.1 目标和关键问题

    能不能开发同学本地只需要启动需要开发的模块:订单模块, 其他模块均适用测试环境中正在运行的服务.

    既然要实现的目标有了, 我们就开始研究可行性和关键问题

    1. 开发环境和测试环境要在同一个 nacos 的 namespace 中, 这样才有可能让开发环境调用到测试环境的服务.
    2. 测试环境只能调用测试环境的微服务, 实现和开发环境的服务隔离
    3. 开发同学之间的微服务也要实现服务隔离

    2.2 思路

    既要在同一个 namespace 下, 又要能够实现不同人访问不同的副本, 很容易想到可以利用灰度发布来实现:

    1. 测试环境设置 metadata lemes-env=product 来标识测试环境副本, 用于区分开发环境的微服务测测试环境的微服务
    2. 开发同学本地启动注册开发环境副本, 都会携带唯一IP, 则我们可以通过IP来区分不同开发同学的副本

    假设我们需要开发的 API 的后台服务调用链条如下:

    请求调用

    我们需要开发的 API 为 /addMo, 打算写在 Order 这个微服务里面, 并且他会调用 common 这个微服务的 /getDict 获取一个字典数据, /getDict 是现成的, 不需要开发, 如果是之前的情况, 开发本地至少需要启动5个微服务才能进行调试.

    实现效果

    三、具体实现

    3.1 测试环境设置 metadata

    由于测试环境都是通过容器部署的, 那么启动方式就是下面容器中的 CMD, 我们在其中加入 -Dspring.cloud.nacos.discovery.metadata.lemes-env=product, 用于区分开发环境的微服务测测试环境的微服务

    # 说明:Dockerfile 过程分为两部分。第一次用来解压 jar 包,并不会在目标镜像内产生 history/layer。第二部分将解压内容分 layer 拷贝到目标镜像内# 目的:更新镜像时,只需要传输代码部分,依赖没有变动则不更新,节省发包时的网络传输量# 原理:在第二部分中,每次 copy 就会在目标镜像内产生一层 layer,将依赖和代码分开,#      绝大部分更新都不会动到依赖,所以只需更新代码几十k左右的代码层即可FROM 10.176.66.20:5000/library/amazoncorretto:11.0.11  as builderWORKDIR /buildARG ARTIFACT_IDCOPY target/${ARTIFACT_ID}.jar app.jarRUN java -Djarmode=layertools -jar app.jar extract && rm app.jarFROM 10.176.66.20:5000/library/amazoncorretto:11.0.11LABEL maintainer="yangyj13@lenovo.com"WORKDIR /dataARG ARTIFACT_IDENV ARTIFACT_ID ${ARTIFACT_ID}# 依赖COPY --from=builder /build/dependencies/ ./COPY --from=builder /build/snapshot-dependencies/ ./COPY --from=builder /build/spring-boot-loader/ ./# 应用代码COPY --from=builder /build/application/ ./# 容器运行时启动命令CMD echo "NACOS_ADDR: ${NACOS_ADDR}"; \    echo "JAVA_OPTS: ${JAVA_OPTS}"; \    echo "TZ: ${TZ}"; \    echo "ARTIFACT_ID: ${ARTIFACT_ID}"; \    # 去除了 server 的应用名    REAL_APP_NAME=${ARTIFACT_ID//-server/}; \    echo "REAL_APP_NAME: ${REAL_APP_NAME}"; \    # 获取当前时间    now=`date +%F+%T+%Z`; \    # java 启动命令    java $JAVA_OPTS \    -Dtingyun.app_name=${REAL_APP_NAME}-${TINGYUN_SUFFIX} \    -Dspring.cloud.nacos.discovery.metadata.lemes-env=product \    -Dspring.cloud.nacos.discovery.metadata.startup-time=${now} \    -Dspring.cloud.nacos.discovery.server-addr=${NACOS_ADDR} \    -Dspring.cloud.nacos.discovery.group=${NACOS_GROUP} \    -Dspring.cloud.nacos.config.namespace=${NACOS_NAMESPACE} \    -Dspring.cloud.nacos.discovery.namespace=${NACOS_NAMESPACE} \    -Dspring.cloud.nacos.discovery.ip=${HOST_IP} \    org.springframework.boot.loader.JarLauncher

    set nacos metadata

    3.2 开发前端传递开启智能连接

    const devIp = getLocalIP('10.')module.exports = {  devServer: {    proxy: {      '/lemes-api': {        target: 'http://10.176.66.58/lemes-api',        ws: true,        pathRewrite: {          '^/lemes-api': '/'        },        headers: {          'dev-ip': devIp,          'dev-sc': 'true'        }      }    }  },}// 获取本机 IPfunction getLocalIP(prefix) {  const excludeNets = ['docker', 'cni', 'flannel', 'vi', 've']  const os = require('os')  const osType = os.type() // 系统类型  const netInfo = os.networkInterfaces() // 网络信息  const ipList = []  if (prefix) {    for (const netInfoKey in netInfo) {      if (excludeNets.filter(item => netInfoKey.startsWith(item)).length === 0) {        for (let i = 0; i < netInfo[netInfoKey].length; i++) {          const net = netInfo[netInfoKey][i]          if (net.family === 'IPv4' && net.address.startsWith(prefix)) {            ipList.push(net.address)          }        }      }    }  }  if (ipList.length === 0) {    if (osType === 'Windows_NT') {      for (const dev in netInfo) {        // win7的网络信息中显示为本地连接,win10显示为以太网        if (dev === '本地连接' || dev === '以太网') {          for (let j = 0; j < netInfo[dev].length; j++) {            if (netInfo[dev][j].family === 'IPv4') {              ipList.push(netInfo[dev][j].address)            }          }        }      }    } else if (osType === 'Linux') {      ipList.push(netInfo.eth0[0].address)    } else if (osType === 'Darwin') {      ipList.push(netInfo.en0[0].address)    }  }  console.log('识别到的网卡信息', JSON.stringify(ipList))  return ipList.length > 0 ? ipList[0] : ''}

    3.3 后端灰度处理

    不论是 gateway 还是 openfeign 都是通过 spring 的 loadbalancer 进行应用选择的, 那我们通过实现或者继承 ReactorServiceInstanceLoadBalancer 来重写选择的过程.

    @Log4j2public class LemesLoadBalancer implements ReactorServiceInstanceLoadBalancer{    @Autowired    private NacosDiscoveryProperties nacosDiscoveryProperties;    final AtomicInteger position;    // loadbalancer 提供的访问当前服务的名称    final String serviceId;    // loadbalancer 提供的访问的服务列表    ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;    public LemesLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {        this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000));    }    public LemesLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,                             String serviceId, int seedPosition) {        this.serviceId = serviceId;        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;        this.position = new AtomicInteger(seedPosition);    }    @Override    public Mono<Response<ServiceInstance>> choose(Request request) {        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider                .getIfAvailable(NoopServiceInstanceListSupplier::new);        RequestDataContext context = (RequestDataContext) request.getContext();        RequestData clientRequest = context.getClientRequest();        return supplier.get(request).next()                .map(serviceInstances -> processInstanceResponse(clientRequest,supplier, serviceInstances));    }    private Response<ServiceInstance> processInstanceResponse(RequestData clientRequest,ServiceInstanceListSupplier supplier,                                                              List<ServiceInstance> serviceInstances) {        Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(clientRequest,serviceInstances);        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {            ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());        }        return serviceInstanceResponse;    }    private Response<ServiceInstance> getInstanceResponse(RequestData clientRequest, List<ServiceInstance> instances) {        if (instances.isEmpty()) {            if (log.isWarnEnabled()) {                log.warn("No servers available for service: " + serviceId);            }            return new EmptyResponse();        }        int pos = Math.abs(this.position.incrementAndGet());        // 筛选后的服务列表        List<ServiceInstance> filteredInstances;        String devSmartConnect = clientRequest.getHeaders().getFirst(CommonConstants.DEV_SMART_CONNECT);        if (StrUtil.equals(devSmartConnect, "true")) {            String devIp = clientRequest.getHeaders().getFirst(CommonConstants.DEV_IP);            // devIp 为空,为异常情况不处理,返回空实例集合            if (StrUtil.isBlank(devIp)) {                log.warn("devIp is NULL,No servers available for service: " + serviceId);                return new EmptyResponse();            }            // 智能连接: 如果本地启动了服务,则优先访问本地服务,如果本地没有启动,则访问测试环境服务            // 优先调用本地自有服务            filteredInstances = instances.stream().filter(item -> StrUtil.equals(devIp, item.getHost())).collect(Collectors.toList());            // 如果本地服务没有开启,则调用生产/测试服务            if (CollUtil.isEmpty(filteredInstances)) {                filteredInstances = instances.stream()                        .filter(item -> StrUtil.equals(CommonConstants.LEMES_ENV_PRODUCT, item.getMetadata().get("lemes-env")))                        .collect(Collectors.toList());                // 解决开发环境无法访问 k8s 集群内 ip 的问题                String oneNacosIp = nacosDiscoveryProperties.getServerAddr().split(",")[0].replaceAll(":[\\s\\S]*", "");                filteredInstances.forEach(item -> {                    NacosServiceInstance instance = (NacosServiceInstance) item;                    // cloud 以 80 端口启动,认为是 k8s 内的应用                    if (instance.getPort() == 80) {                        instance.setHost(oneNacosIp);                        instance.setPort(Integer.parseInt(item.getMetadata().get("port")));                    }                });            }        } else {            // 不是智能访问,则只访问一个环境            // 当前服务 ip            String currentIp = nacosDiscoveryProperties.getIp();            String lemesEnv = nacosDiscoveryProperties.getMetadata().get("lemes-env");            filteredInstances = instances.stream()                    .filter(item -> StrUtil.equals(lemesEnv, CommonConstants.LEMES_ENV_PRODUCT)                            // 访问测试环境                            ? StrUtil.equals(CommonConstants.LEMES_ENV_PRODUCT, item.getMetadata().get("lemes-env"))                            // 访问开发环境                            : StrUtil.equals(currentIp, item.getHost()))                    .collect(Collectors.toList());        }        if (filteredInstances.isEmpty()) {            log.warn("No oneself servers and beta servers available for service: " + serviceId + ", use other instances");            // 找不到自己注册IP对应的服务和测试服务,则用nacos中其它的服务            filteredInstances = instances;        }        //最终的返回的 serviceInstance        ServiceInstance instance = filteredInstances.get(pos % filteredInstances.size());        return new DefaultResponse(instance);    }}
    ]]>
    @@ -280,7 +307,7 @@ http://yelog.org/2022/07/27/springboot-graceful-shutdown-based-on-nacos2-and-k8s/ 2022-07-27T07:35:39.000Z - 2024-07-05T01:50:55.156Z + 2024-07-05T11:10:22.434Z 背景

    我们的 SpringCloud 是部署在 k8s 上的, 当通过 k8s 进行滚动升级时, 会有请求 500 的情况, 不利于用户体验, 严重的可能造成数据错误的问题

    k8s 滚动更新策略介绍
    假设我们要升级的微服务在环境上为3个副本的集群, 升级应用时, 会先启动1个新版本的副本, 然后下线一个旧版本的副本, 之后再启动1个新版本的副本, 一次类推,直到所有旧副本都替换新副本.

    通过链路追踪分析, 报错的原因分别由以下两种情况

    1. SpringCloud 中的微服务在升级过程中, 当旧的微服务中还有没有处理完成的请求时, 就开始关闭动作, 造成请求中断
    2. 当旧应用执行关闭动作时, 已经开始拒绝请求, 但是 nacos 中的路由并没有及时更新, 造成 gateway/openfeign 在路由时仍会命中正在关闭的应用, 造成请求报错

    为了解决这个问题, 我们将利用 springboot 的 graceful shutdown 功能和 nacos 的主动下线功能来解决这个问题. 具体思路如下:

    比如当我们执行订单微服务(3个副本)滚动更新时

    1. 先启动一个新版本副本4
    2. 然后准备关闭副本1, 在关闭之前先通知 nacos 订单服务的副本1下线, 然后由 nacos 通知给其他应用(nacos2.x 是grpc, 所以通知速度比较快), 这样, 订单服务的副本1就不会再接收到请求, 然后执行 graceful shutdown(springboot 原生支持, 启用方法可以看后面代码), 所有请求处理完成后关闭应用. 这样就完成了 副本1 的关闭
    3. 启动新版本副本5
    4. 再优雅关闭副本2(参考第2点副本1的流程)
    5. 然后启动新版本副本6
    6. 再优雅关闭副本3
    7. 完成了服务不中断的应用升级

    实现关键点

    为了实现上面背景中提到的思路, 主要从如下几个方面入手

    创建从 nacos 中下线副本的API

    我们通过创建自定义名为 deregisterendpoint 来通知 nacos 下线副

    import com.alibaba.cloud.nacos.NacosDiscoveryProperties;import com.alibaba.cloud.nacos.registry.NacosRegistration;import com.alibaba.cloud.nacos.registry.NacosServiceRegistry;import lombok.extern.log4j.Log4j2;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.actuate.endpoint.annotation.Endpoint;import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;import org.springframework.stereotype.Component;@Component@Endpoint(id = "deregister")@Log4j2public class LemesNacosServiceDeregisterEndpoint {    @Autowired    private NacosDiscoveryProperties nacosDiscoveryProperties;    @Autowired    private NacosRegistration nacosRegistration;    @Autowired    private NacosServiceRegistry nacosServiceRegistry;    /**     * 从 nacos 中主动下线,用于 k8s 滚动更新时,提前下线分流流量     *     * @param     * @return com.lenovo.lemes.framework.core.util.ResultData<java.lang.String>     * @author Yujie Yang     * @date 4/6/22 2:57 PM     */    @ReadOperation    public String endpoint() {        String serviceName = nacosDiscoveryProperties.getService();        String groupName = nacosDiscoveryProperties.getGroup();        String clusterName = nacosDiscoveryProperties.getClusterName();        String ip = nacosDiscoveryProperties.getIp();        int port = nacosDiscoveryProperties.getPort();        log.info("deregister from nacos, serviceName:{}, groupName:{}, clusterName:{}, ip:{}, port:{}", serviceName, groupName, clusterName, ip, port);        // 设置服务下线        nacosServiceRegistry.setStatus(nacosRegistration, "DOWN");        return "success";    }}

    支持 Graceful Shutdown

    由于 springboot 原生支持, 我们只需要在 bootstrap.yaml 中添加如下配置即可

    server:  # 开启优雅下线  shutdown: gracefulspring:  lifecycle:    # 优雅下线超时时间    timeout-per-shutdown-phase: 5m# 暴露 shutdown 接口management:  endpoint:    shutdown:      enabled: true  endpoints:    web:      exposure:        include: shutdown

    K8s 配置

    有了上面两个 API, 接下来就配置到 k8s 上

    1. terminationGracePeriodSeconds 如果关闭应用的时间超过 10 分钟, 则向容器发送 TERM 信号, 防止应用长时间下线不了
    2. preStop 先执行下线操作, 等待30s, 留够通知到其他应用的时间, 然后执行 graceful shutdown 关闭应用
    ---apiVersion: apps/v1kind: Deploymentmetadata:  name: lemes-service-common  labels:    app: lemes-service-commonspec:  replicas: 2  selector:    matchLabels:      app: lemes-service-common#  strategy:#    type: RollingUpdate#    rollingUpdate:##     replicas - maxUnavailable < running num  < replicas + maxSurge#      maxUnavailable: 1#      maxSurge: 1  template:    metadata:      labels:        app: lemes-service-common    spec:#      容器重启策略 Never Always OnFailure#      restartPolicy: Never#     如果关闭时间超过10分钟, 则向容器发送 TERM 信号      terminationGracePeriodSeconds: 600      affinity:        podAntiAffinity:          preferredDuringSchedulingIgnoredDuringExecution:            - podAffinityTerm:                topologyKey: "kubernetes.io/hostname"                labelSelector:                  matchExpressions:                    - key: app                      operator: In                      values:                        - lemes-service-common              weight: 100#          requiredDuringSchedulingIgnoredDuringExecution:#            - labelSelector:#                matchExpressions:#                  - key: app#                    operator: In#                    values:#                      - lemes-service-common#              topologyKey: "kubernetes.io/hostname"      volumes:        - name: lemes-host-path          hostPath:            path: /data/logs            type: DirectoryOrCreate        - name: sidecar          emptyDir: { }      containers:        - name: lemes-service-common          image: 10.176.66.20:5000/lemes-cloud/lemes-service-common-server:v0.1          imagePullPolicy: Always          volumeMounts:            - name: lemes-host-path              mountPath: /data/logs            - name: sidecar              mountPath: /sidecar          ports:            - containerPort: 80          resources:#           资源通常情况下的占用            requests:              memory: '2048Mi'#           资源占用上限            limits:              memory: '4096Mi'          livenessProbe:            httpGet:              path: /actuator/health/liveness              port: 80            initialDelaySeconds: 5#           探针可以连续失败的次数            failureThreshold: 10#           探针超时时间            timeoutSeconds: 10#           多久执行一次探针查询            periodSeconds: 10          startupProbe:            httpGet:              path: /actuator/health/liveness              port: 80            failureThreshold: 30            timeoutSeconds: 10            periodSeconds: 10          readinessProbe:            httpGet:              path: /actuator/health/readiness              port: 80            initialDelaySeconds: 5            timeoutSeconds: 10            periodSeconds: 10          lifecycle:            preStop:              exec:#               应用关闭操作:1. 从 nacos 下线,2. 等待30s, 保证 nacos 通知到其他应用 2.触发 springboot 的 graceful shutdown                command:                  - sh                  - -c                  - curl http://127.0.0.1/actuator/deregister;sleep 30;curl -X POST http://127.0.0.1/actuator/shutdown;
    ]]>
    @@ -310,7 +337,7 @@ http://yelog.org/2022/06/27/vim-plugs-2022/ 2022-06-27T07:07:39.000Z - 2024-07-05T01:50:54.888Z + 2024-07-05T11:10:22.200Z 前言

    从第一次接触 vim 已逾期 10 年, 期间大部分都是一些简单操作,
    最近一两年开始深度使用 vim, 目前使用 neovim 版本.
    本文将记录一些笔者觉得好用的一些 Plugin, 本文也将持续更新.

    注意: 笔者使用的插件管理器是 vim-plug,
    所以以下示例都是基于 vim-plug 来写的.

    Goto/Open

    vim-open-url

    vim-open-url
    可以用浏览器打开光标下的 url.

    • gB 用默认浏览器打开光标下的 url
    • g<CR> 使用默认搜索引擎搜索光标下的单词
    • gG 使用 Google 搜索光标下的单词
    • gW 使用 Wikipedia 搜索光标下的单词
    Plug 'dhruvasagar/vim-open-url'

    Auto Complete

    neoclide/coc.nvim

    ]]>
    @@ -336,7 +363,7 @@ http://yelog.org/2022/06/24/el-drawer-drag-width/ 2022-06-24T11:38:00.000Z - 2024-07-05T01:50:55.114Z + 2024-07-05T11:10:22.394Z 实现效果

    el-drawer-drag-width

    实现思路

    通过指令的方式, 在 drawer 的左侧边缘, 添加一个触发拖拽的长条形区域, 监听鼠标左键按下时启动 document.onmousemove 的监听, 监听鼠标距离浏览器右边的距离, 设置为 drawer 的宽度, 并添加约束: 不能小于浏览器宽度的 20%, 不能大于浏览器宽度的 80%.

    指令代码

    创建文件 src/directive/elment-ui/drawer-drag-width.js, 内容如下

    import Vue from 'vue'/** * el-drawer 拖拽指令 */Vue.directive('el-drawer-drag-width', {  bind(el, binding, vnode, oldVnode) {    const drawerEle = el.querySelector('.el-drawer')    console.log(drawerEle)    // 创建触发拖拽的元素    const dragItem = document.createElement('div')    // 将元素放置到抽屉的左边边缘    dragItem.style.cssText = 'height: 100%;width: 5px;cursor: w-resize;position: absolute;left: 0;'    drawerEle.append(dragItem)    dragItem.onmousedown = (downEvent) => {      // 拖拽时禁用文本选中      document.body.style.userSelect = 'none'      document.onmousemove = function(moveEvent) {        // 获取鼠标距离浏览器右边缘的距离        let realWidth = document.body.clientWidth - moveEvent.pageX        const width30 = document.body.clientWidth * 0.2        const width80 = document.body.clientWidth * 0.8        // 宽度不能大于浏览器宽度 80%,不能小于宽度的 20%        realWidth = realWidth > width80 ? width80 : realWidth < width30 ? width30 : realWidth        drawerEle.style.width = realWidth + 'px'      }      document.onmouseup = function(e) {        // 拖拽时结束时,取消禁用文本选中        document.body.style.userSelect = 'initial'        document.onmousemove = null        document.onmouseup = null      }    }  }})

    然后在 main.js 中将其导入

    import './directive/element-ui/drawer-drag-width'

    指令使用

    el-drawer 上添加指令 v-el-drawer-drag-width 即可, 如下

    <el-drawer  v-el-drawer-drag-width  :visible.sync="helpDrawer.show"  direction="rtl"  class="my-drawer">  <template #title>    <div class="draw-title">{{ helpDrawer.title }}</div>  </template>  <Editor    v-model="helpDrawer.html"    v-loading="helpDrawer.loading"    class="my-wang-editor"    style="overflow-y: auto;"    :default-config="helpDrawer.editorConfig"    :mode="helpDrawer.mode"    @onCreated="onCreatedHelp"  /></el-drawer>
    ]]>
    @@ -362,7 +389,7 @@ http://yelog.org/2021/09/26/spring-cloud-skywalking/ 2021-09-26T10:08:00.000Z - 2024-07-05T01:50:55.404Z + 2024-07-05T11:10:22.696Z 前言

    前一段时间一直在研究升级公司项目的架构,在不断学习和试错后,最终确定了一套基于 k8s 的高可用架构体系,未来几期会将这套架构体系的架设过程和注意事项以系列文章的形式分享出来,敬请期待!

    由于集群和分布式规模的扩大,对微服务链路的监控和日志收集,越来越有必要性,所以在筛选了了一些方案后,发现 SkyWalking 完美符合我们的预期,对链路追踪和日志收集都有不错的实现。

    SkyWalking 简介

    SkyWalking 是一款 APM(应用程序监控)系统,转为微服务、云原生、基于容器的架构而设计。主要包含了一下核心功能

    1. 对服务、运行实例、API进行指标分析
    2. 链路检测,检查缓慢的服务和API
    3. 对基础设施(VM、网络、磁盘、数据库)进行监控
    4. 对超出阈值的情况进行警报
    5. 等等

    开源地址:apache/skywalking

    官网:Apache SkyWalking

    SpringCloud 整合 SkyWalking

    1. 搭建 SkyWalking 服务

    在使用 SkyWalking 进行链路追踪和日志收集之前,需要先搭建起一套 SkyWalking 的服务,然后才能通过 agent 将 SpringCloud 的运行状态和日志发送给 SkyWalking 进行解析和展示。

    SkyWalking 的搭建方式有很多中,我这里介绍两种 docker-compose(非高可用,快速启动,方便测试、学习) 和 k8s(高可用、生产级别)

    docker-compose 的方式

    docker 和 docker-compose 的安装不是本文的重点,所以有需要可以自行查询。

    以下操作会启动三个容器

    1. elasticsearch 作为 skywalking 的存储,保存链路和日志数据等
    2. oap 数据接收和分析 Observability Analysis Platform
    3. ui web端的数据展示
    # 创建配置文件保存的目录mkdir -p /data/docker/admin/skywalking# 切换到刚创建的目录cd /data/docker/admin/skywalking# 将下面的 docker-compose.yml 文件保存到这个目录vi docker-compose.yml# 拉去镜像并启动docker-compose up -d# 查看日志docker-compose logs -f

    docker-compose.yml

    version: '3.8'services:  elasticsearch:    image: docker.elastic.co/elasticsearch/elasticsearch:7.14.1    container_name: elasticsearch    restart: always    ports:      - 9200:9200    healthcheck:      test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]      interval: 30s      timeout: 10s      retries: 3      start_period: 40s    environment:      - discovery.type=single-node      - bootstrap.memory_lock=true      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"      - TZ=Asia/Shanghai    ulimits:      memlock:        soft: -1        hard: -1  oap:    image: apache/skywalking-oap-server:8.7.0-es7    container_name: oap    depends_on:      - elasticsearch    links:      - elasticsearch    restart: always    ports:      - 11800:11800      - 12800:12800    healthcheck:      test: ["CMD-SHELL", "/skywalking/bin/swctl"]      interval: 30s      timeout: 10s      retries: 3      start_period: 40s    environment:      TZ: Asia/Shanghai      SW_STORAGE: elasticsearch7      SW_STORAGE_ES_CLUSTER_NODES: elasticsearch:9200  ui:    image: apache/skywalking-ui:8.7.0    container_name: ui    depends_on:      - oap    links:      - oap    restart: always    ports:      - 8088:8080    environment:      TZ: Asia/Shanghai      SW_OAP_ADDRESS: http://oap:12800

    启动之后浏览器访问 服务ip:8080 即可

    k8s

    等待更新。。

    2. 下载 agent 代理包

    点击链接进行下载,skywalking-apm-8.7

    其他版本可以看 apache 归档站,找到对应版本的 .tar.gz 后缀的包,进行下载

    通过命令或者软件进行解压 tar -zxvf apache-skywalking-apm-8.7.0.tar.gz

    3. java 命令使用代码启动 jar 包

    springcloud/springboot 一般是通过 java -jar xxx.jar 进行启动。我们只需要在其中加上 -javaagent 参数即可,如下

    其中 自定义服务名 可以改为应用名 如 lemes-auth服务ip 为第一步搭建的 SkyWalking 服务的ip,端口11800 为启动的 oap 这个容器的端口

    java -javaagent:上一步解压目录/agent/skywalking-agent.jar=agent.service_name=自定义服务名,collector.backend_service=服务ip:11800 -jar xx.jar

    执行命令启动后,访问以下接口,就可以在第一步 服务ip:8080 中看到访问的链接和调用链路。

    链路追踪
    拓扑图

    4. 开启日志收集

    本文主要以 log4j2 来介绍,其他的大同小异,可以网上找教程。SpringCloud 集成 log4j2 不是本文重点,所以请自行 Google。

    引入依赖

    要开启日志收集,必须要添加依赖,如下

    <dependency>    <groupId>org.apache.skywalking</groupId>    <artifactId>apm-toolkit-log4j-2.x</artifactId>    <version>8.7.0</version></dependency>

    修改 log4j2.xml

    需要修改 log4j2.xml 主要添加下面两个关键点

    • 添加 %traceId 来打印 traceid
    • 声明 GRPCLogClientAppender

    完整内容如下

    <?xml version="1.0" encoding="UTF-8"?><!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --><!-- Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,     你会看到log4j2内部各种详细输出。可以设置成OFF(关闭) 或 Error(只输出错误信息)。--><!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数--><configuration status="WARN" monitorInterval="30">    <Properties>        <Property name="log.path">logs/lemes-auth</Property>        <Property name="logging.lemes.pattern">            %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%traceId] [%logger{50}.%M:%L] - %msg%n        </Property>    </Properties>    <Appenders>        <!-- 输出控制台日志的配置 -->        <Console name="Console" target="SYSTEM_OUT">            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>            <!-- 输出日志的格式 -->            <PatternLayout pattern="${logging.lemes.pattern}"/>        </Console>        <RollingRandomAccessFile name="debugRollingFile" fileName="${log.path}/debug.log"                                 filePattern="${log.path}/debug/$${date:yyyy-MM}/debug.%d{yyyy-MM-dd}-%i.log.gz">            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>            <PatternLayout charset="UTF-8" pattern="${logging.lemes.pattern}"/>            <Policies>                <TimeBasedTriggeringPolicy interval="1"/>                <SizeBasedTriggeringPolicy size="100 MB"/>            </Policies>            <DefaultRolloverStrategy max="30"/>        </RollingRandomAccessFile>        <GRPCLogClientAppender name="grpc-log">            <PatternLayout pattern="${logging.lemes.pattern}"/>        </GRPCLogClientAppender>    </Appenders>    <Loggers>        <!-- ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF -->        <Logger name="com.lenovo.lemes" level="debug"/>        <Logger name="org.apache.kafka" level="warn"/>        <Root level="info">            <AppenderRef ref="Console"/>            <AppenderRef ref="debugRollingFile"/>            <AppenderRef ref="grpc-log"/>        </Root>    </Loggers></configuration>

    启动命令中声明上报日志

    在上一步的 agent 中添加上报日志的参数 plugin.toolkit.log.grpc.reporter.server_host=服务ip,plugin.toolkit.log.grpc.reporter.server_port=11800

    完整如下

    java -javaagent:上一步解压目录/agent/skywalking-agent.jar=agent.service_name=自定义服务名,collector.backend_service=服务ip:11800,plugin.toolkit.log.grpc.reporter.server_host=服务ip,plugin.toolkit.log.grpc.reporter.server_port=11800 -jar xx.jar

    日志收集效果

    这样启动日志中就会打印 traceid , N/A 代表的是非请求的日志,有 traceid 的为 api 请求日志

    traceid

    在 skywalking 中就能看到我们上报的日志

    skywalking 日志上报

    重点:SkyWalking 可以在链路追踪中查看当前请求的所有日志(不同实例/模块)

    SkyWalking 链路日志

    SkyWalking 链路日志

    5. 兼容 spring-cloud-gateway

    经过上面的步骤之后,链路已经搭建完成,查看发现了一个问题,gateway 模块的 traceId 和 业务模块的 traceId 不统一。

    拓扑图

    这是由于 SkyWalking 对于 spring-cloud-gateway 的支持不是默认的,所以需要将 agent/optional-plugins/apm-spring-cloud-gateway-2.1.x-plugin-8.7.0.jar 复制到 agent/plugins 下,然后重启即可。

    优化过 gateway 的拓扑图

    最后

    SkyWalking 上面这两个功能就已经非常强大,能够有效帮助我们优化我们的程序,监控系统的问题,并及时报警。日志收集也解决的在大规模分布式集群下日志查询难的问题。

    SkyWalking 还支持 VM、浏览器、k8s等监控,后续如果有实践,将会逐步更新。

    ]]>
    @@ -390,7 +417,7 @@ http://yelog.org/2020/12/28/3-hexo-add-icon/ 2020-12-28T14:00:00.000Z - 2024-07-05T01:50:55.222Z + 2024-07-05T11:10:22.494Z 一、前言

    鉴于许多人问过如何添加自定义图标,这里就详细说明一下,以备后人乘凉。

    这篇文章主要讲解是从 iconfont 添加图标。

    二、添加彩色图标

    2.1 登录并添加图标

    访问 iconfont,点击如下图位置登录,可以使用 Github 账号登录。

    iconfont 登录

    登录成功后,搜索合适的图标,然后点击添加到购物车,如下图所示。

    添加了多个后,可以点击右上角的“购物车”,添加到项目,点击加号创建项目,如下图所示。

    添加完成后回到项目页面,找到自己刚刚创建的项目。

    如果没有到项目页面,可以点击上面菜单进入:资源管理 -> 我的项目

    2.2 引入 3-hexo 中

    点击下载到本地,解压并复制其中的 iconfont.js 到项目 3-hexo/source/js/ 下,并改名 custom-iconfont.js

    在文件 3-hexo/layout/_partial/meta.ejs 最后追加下面一行。

    <script src="<%=theme.blog_path?theme.blog_path.lastIndexOf("/") === theme.blog_path.length-1?theme.blog_path.slice(0, theme.blog_path.length-1):theme.blog_path:'' %>/js/custom-iconfont.js?v=<%=theme.version%>" ></script>

    2.3 在配置文件中添加生效

    修改 3-hexo/_config.yml 如下图所示

    完成!

    图标名如上面的 gitee 可以在 网站上修改,如下图所示

    三、添加黑白图标

    link.theme=white

    3.1 同 2.1

    3.2 引入 3-hexo 中

    点击生成代码,如下图所示。

    复制生成的代码,修改 font-family 的值为 custom-iconfont,添加到 3-hexo/source/css/_partial/font.styl 最后,并写入图标信息,content 可以移到图标上进行复制,注意前面斜杠转译和去掉后面的分号。

    @font-face {  font-family: 'custom-iconfont';  /* project id 2298064 */  src: url('//at.alicdn.com/t/font_2298064_34vkk4c9945.eot');  src: url('//at.alicdn.com/t/font_2298064_34vkk4c9945.eot?#iefix') format('embedded-opentype'),  url('//at.alicdn.com/t/font_2298064_34vkk4c9945.woff2') format('woff2'),  url('//at.alicdn.com/t/font_2298064_34vkk4c9945.woff') format('woff'),  url('//at.alicdn.com/t/font_2298064_34vkk4c9945.ttf') format('truetype'),  url('//at.alicdn.com/t/font_2298064_34vkk4c9945.svg#iconfont') format('svg');}.icon-gitee:before {  content: "\e602";}.icon-youtubeautored:before {  content: "\e649";}

    3.3 在配置文件中添加生效 同2.2

    结束!

    ]]>
    @@ -419,7 +446,7 @@ http://yelog.org/2020/10/20/know-javascript-promise/ 2020-10-20T11:43:44.000Z - 2024-07-05T01:50:55.098Z + 2024-07-05T11:10:22.378Z 一、Promise 是什么

    PromiseES6 提供的原生对象,用来处理异步操作

    它有三种状态

    • pending: 初始状态,不是成功或失败状态。
    • fulfilled: 意味着操作成功完成。
    • rejected: 意味着操作失败。

    二、使用

    2.1 创建 Promise

    通过 new Promise 来实例化,支持链式调用

    new Promise((resolve, reject)=>{  // 逻辑}).then(()=>{  //当上面"逻辑"中调用 resolve() 时触发此方法}).catch(()=>{  //当上面"逻辑"中调用 reject() 时触发此方法})

    2.2 执行顺序

    Promise一旦创建就立即执行,并且无法中途取消,执行逻辑和顺序可以从下面的示例中获得

    如下,可修改 if 条件来改变异步结果,下面打印开始的数字是执行顺序

    在线调试此示例 - jsbin

    console.log('1.开始创建并执行 Promise')new Promise(function(resolve, reject) {  console.log('2.由于创建会立即执行,所以会立即执行到本行')  setTimeout(()=>{ // 模拟异步请求    console.log('4. 1s之期已到,开始执行异步操作')    if (true) {        // 一般我们符合预期的结果时调用 resolve(),会在 .then 中继续执行        resolve('成功')    } else {        // 不符合预期时调用 reject(),会在 .catch 中继续执行        reject('不符合预期')    }  }, 1000)}).then((res)=>{  console.log('5.调用了then,接收数据:' + res)}).catch((error)=>{  console.log('5.调用了catch,错误信息:' + error)})console.log('3.本行为同步操作,所以先于 Promise 内的异步操作(setTimeout)')

    执行结果如下

    "1.开始创建并执行 Promise""2.由于创建会立即执行,所以会立即执行到本行""3.本行为同步操作,所以先于 Promise 内的异步操作(setTimeout)""4. 1s之期已到,开始执行异步操作""5.调用了then,接收数据:成功"

    2.3 用函数封装 Promise

    这是比较常用的方法,如下用 setTimeout 模拟异步请求,封装通用请求函数

    在线调试此示例 - jsbin

    // 这是一个异步方法function ajax(url){  return new Promise(resolve=>{    console.log('异步方法开始执行')    setTimeout(()=>{      console.log('异步方法执行完成')      resolve(url+'的结果集')    }, 1000)  })}// 调用请求函数,并接受处理返回结果ajax('/user/list').then((res)=>{  console.log(res)})

    执行结果

    "异步方法开始执行""异步方法执行完成""/user/list的结果集"

    三、高级用法

    3.1 同时支持Callback与Promise

    在线调试此示例 - jsbin

    function ajax(url, success, fail) {  if (typeof success === 'function') {    setTimeout(() => {      if (true) {        success({user: '羊'})      } else if (typeof fail === 'function') {        console.log(typeof fail)        fail('用户不存在')      }    }, 1000)  } else {    return new Promise((resolve, reject) => {      this.ajax(url, resolve, reject)    })  }}// callback 调用方式ajax('/user/get', (res)=>{  console.log('Callback请求成功!返回结果:', res)}, (error)=>{  console.log('Callback请求失败!错误信息:', error)})// Promise 调用方式ajax('/user/get').then((res)=>{  console.log('Pormise请求成功!返回结果:', res)}).catch((error)=>{  console.log('Promise请求失败!返回结果:', error)})

    执行结果

    Callback请求成功!返回结果: {user: "羊"}Pormise请求成功!返回结果: {user: "羊"}

    3.2 链式调用

    .then 支持返回 Promise 对象进行链式调用

    ajax('/user/info').then((res)=>{  // 用户信息查询成功后,可以根据返回结果查询后续信息  console.log('用户信息:', res)  return ajax('/user/score')}).then((res)=>{  console.log('用户成绩:', res)  return ajax('/user/friends')}).then((res)=>{  console.log('用户朋友:', res)})

    3.3 Promise.all

    Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
    在线调试此示例 - jsbin

    // 生成一个Promise对象的数组var promises = [2, 3, 5, 7, 11, 13].map(function(id){  return new Promise((resolve, reject)=>{    if (id % 3 === 0) {      resolve(id)    } else {      reject(id)    }  });});Promise.all(promises).then(function(post) {  console.log('全部通过')}).catch(function(reason){  console.log('未全部通过,有问题id:'+reason)});

    执行结果

    未全部通过,有问题id:2

    Reference

    mozilla web docs

    ]]>
    @@ -443,9 +470,9 @@ http://yelog.org/2020/09/01/Docker-summary/ 2020-09-01T14:11:00.000Z - 2024-07-05T01:50:54.938Z + 2024-07-05T11:10:22.245Z - 一、概述

    1.1 什么是docker

    Docker 诞生于 2013 年初,由 dotCloud 公司(后改名为 Docker Inc)基于 Go 语言实现并开源的项目。此项目后来加入 Linux基金会,遵从了 Apache 2.0 协议

    Docker 项目的目标是实现轻量级的操作系统虚拟化解决方案。Docker 是在 Linux 容器技术(LXC)的基础上进行了封装,让用户可以快速并可靠的将应用程序从一台运行到另一台上。

    使用容器部署应用被称为容器化,容器化技术的几大优势:

    1. 灵活:甚至复杂的应用也可以被容器化
    2. 轻量:容器利用和共享宿主机内核,从而在利用系统资源比虚拟机更加的有效
    3. 可移植:你可以在本地构建,在云端部署并在任何地方运行
    4. 松耦合:容器是高度封装和自给自足的,允许你在不破环其他容器的情况下替换或升级任何一个
    5. 可扩展:你可以通过数据中心来新增和自动分发容器
    6. 安全:容器依赖强约束和独立的进程

    1.2 和传统虚拟机的区别

    容器在Linux上本地运行,并与其他容器共享主机的内核。它运行一个离散进程,不占用任何其他可执行文件更多的内存,从而使其轻巧。

    image.png

    1.3 相关链接

    官网:https://www.docker.com/

    文档:https://docs.docker.com/

    二、Image镜像

    2.1 介绍

    Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变

    1. 父镜像:每个镜像都可能依赖于有一个或多个下层组成的另一个镜像。下层那个镜像就是上层镜像的父镜像
    2. 基础镜像:一个没有任何父镜像的镜像,被称为基础镜像
    3. 镜像ID:所有镜像都是通过一个 64 位十六进制字符串(256 bit 的值)来标识的。为了简化使用,前 12 个自负可以组成一个短ID,可以在命令行中使用。短ID还是有一定的碰撞几率,所以服务器总是返回长ID

    2.2 从仓库下载镜像

    可以通过 docker pull 命令从仓库获取所需要的镜像

    docker pull [选项] [Docker Registry 地址]<镜像名>:<标签>

    选项:

    1. –all-tags,-a : 拉去所有 tagged 镜像
    2. –disable-content-trust:忽略镜像的校验,默认
    3. –platform:如果服务器是开启多平台支持的,则需要设置平台
    4. –quiet,-q:静默执行,不打印详细信息

    标签: 下载指定标签的镜像,默认 latest

    示例

    # 从 Docker Hub 下载最新的 debian 镜像docker pull debian# 从 Docker Hub 下载 jessie 版 debian 镜像docker pull debian:jessie# 下载指定摘要(sha256)的镜像docker pull ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2

    2.3 列出本地镜像

    # 列出已下载的镜像 image_name: 指定列出某个镜像docker images [选项] [image_name]

    选项

    参数描述
    –all, -a展示所有镜像(包括 intermediate 镜像)
    –digests展示摘要
    –filter, -f添加过滤条件
    –format使用 Go 模版更好的展示
    –no-trunc不删减输出
    –quiet, -q静默输出,仅仅展示 IDs

    示例

    # 展示本地所有下载的镜像docker images# 在本地查找镜像名是 "java" 标签是 "8" 的 奖项docker images: java:8# 查找悬挂镜像docker images --filter "dangling=true"# 过滤 lable 为 "com.example.version" 的值为 0.1 的镜像docker images --filter "label=com.example.version=0.1"

    2.4 Dockerfile创建镜像

    为了方便分享和快速部署,我们可以使用 docker build 来创建一个新的镜像,首先创建一个文件 Dockerfile,如下

    # This is a commentFROM ubuntu:14.04MAINTAINER Chris <jaytp@qq.com>RUN apt-get -qq updateRUN apt-get -qqy install ruby ruby-devRUN gem install sinatra

    然后在此 Dockerfile 所在目录执行 docker build -t yelog/ubuntu:v1 . 来生成镜像,所属组织/镜像名:标签

    2.5 上传镜像

    用户可以通过 docker push 命令,把自己创建的镜像上传到仓库中来共享。例如,用户在 Docker Hub 上完成注册后,可以推送自己的镜像到仓库中。

    docker push yelog/ubuntu

    2.6 导出和载入镜像

    docker 支持将镜像导出为文件,然后可以再从文件导入到本地镜像仓库

    # 导出docker load --input yelog_ubuntu_v1.tar# 载入docker load < yelog_ubuntu_v1.tar

    2.7 移除本地镜像

    # -f 强制删除docker rmi [-f] yelog/ubuntu:v1# 删除悬挂镜像docker rmi $(docker images -f "dangling=true" -q)# 删除所有未被容器使用的镜像docker image prune -a

    三、容器

    3.1 介绍

    容器和镜像,就像面向对象中的 示例 一样,镜像是静态的定义,容器是镜像运行的实体,容器可以被创建、启动、停止、删除和暂停等

    容器的实质是进城,耽于直接的宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的 root 文件系统、网络配置和进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。

    3.2 创建容器

    我们可以通过命令 docker run 命令创建容器

    如下,启动一个容器,执行命令输出 “Hello word”,之后终止容器

    docker run ubuntu:14.04 /bin/echo 'Hello world'

    下面的命令则是启动一个 bash 终端,允许用户进行交互

    docker run -t -i ubuntu:14.04 /bin/bash

    -t 让 Dcoker 分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上

    -i 责让容器的标准输入保持打开

    更多参数可选

    -a stdin指定标准输入输出内容类型
    -d后台运行容器,并返回容器ID
    -i以交互模式运行容器,通常与 -t 同时使用
    -P随机端口映射,容器端口内部随即映射到宿主机的端口上
    -p指定端口映射, -p 宿主机端口:容器端口
    -t为容器重新分配一个伪输入终,通常与 -i 同时使用
    –name=”gate”为容器指定一个名称
    –dns 8.8.8.8指定容器的 DNS 服务器,默认与宿主机一致
    –dns-search example.com指定容器 DNS 搜索域名,默认与宿主机一致
    -h “gate”指定容器的 hostname
    -e username=’gate’设置环境变量
    –env-file=[]从指定文件读入环境变量
    –cpuset=”0-2” or –cpuset=”0,1,2”绑定容器到指定 CPU 运行
    -m设置容器使用内存最大值
    –net=”bridge”指定容器的网络连接类型支持 bridge/host/none/container
    –link=[]添加链接到另一个容器
    –expose=[]开放一个端口或一组端口
    –volume,-v绑定一个卷

    当利用 docker run 来创建容器时,Dcoker 在后台运行的标准操作包括:

    • 检查本地是否存在指定的镜像,不存在就从公有仓库下载
    • 利用镜像创建并启动一个容器
    • 分配一个文件系统,并在只读的镜像外面挂在一层可读写层
    • 从宿主主机配置的网桥接口中桥接一个虚拟借口到容器中去
    • 从地址池配置一个 ip 地址给容器
    • 执行用户指定的应用程序
    • 执行用户指定的应用程序
    • 执行完毕后容器被终止

    3.3 启动容器

    # 创建一个名为 test 的容器,容器任务是:打印一行 Hello worddocker run --name='test' ubuntu:14.04 /bin/echo 'Hello world'# 查看所有可用容器 [-a]包括终止在内的所有容器docker ps -a# 启动指定 name 的容器docker start test# 重启指定 name 的容器docker restart test# 查看日志运行日志(每次启动的日志均被查询出来)$ docker logs testHello worldHello world

    3.4 守护态运行

    前面创建的容器都是执行任务(打印Hello world)后,容器就终止了。更多的时候,我们需要让 Docker 容器在后台以守护态(Daemonized)形式运行。此时,可以通过添加 -d 参数来实现

    注意:docker是否会长久运行,和 docker run 指定的命令有关

    # 创建 docker 后台守护进程的容器docker run --name='test2' -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"# 查看容器$ docker psCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES237e555d4457        ubuntu:14.04        "/bin/sh -c 'while t…"   52 seconds ago      Up 51 seconds                                           test2# 获取容器的输出信息$ docker logs test2hello worldhello worldhello world

    3.5 进入容器

    上一步我们已经实现了容器守护态长久运行,某些时候需要进入容器进行操作,可以使用 attachexec 进入容器。

    # 不安全的,ctrl+d 退出时容器也会终止docker attach [容器Name]# 以交互式命令行进入,安全的,推荐使用docker exec -it [容器Name] /bin/bash

    命令优化

    1. 使用 docker exec 命令时,好用,但是命令过长,我们可以通过自定义命令来简化使用
    2. 创建文件 /user/bin/ctn 命令文件,内容如下
    docker exec -it $1 /bin/bash
    1. 检查环境变量有没有配置目录 /usr/bin (一般是有配置在环境变量里面的,不过最好再确认一下)
    $PATHbash: /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games: No such file or directory
    1. 完成上面步骤后,就可以直接通过命令 ctn 来进入容器

    注意:如果是使用非 root 账号创建的命令,而 docker 命令是 root 权限,可能存在权限问题,可以通过设置 chmod 777 /usr/bin/ctn 设置权限,使用 sudo ctn [容器Name] 即可进入容器

    $ ctn [容器Name]
    1. 使用上面命令时,容器Name 需要手动输入,容器出错。我们可以借助 complete 命令来补全 容器Name,在 ~/.bashrc (作用于当前用户,如果想要所要用户上校,可以修改 /etc/bashrc)文件中添加一行,内容如下。保存后执行 source ~/.bashrc 使之生效,之后我们输入 ctn 后,按 tab 就会提示或自动补全容器名了了
    # ctn auto completecomplete -W "$(docker ps --format"{{.Names}}")" ctn

    注意: 由于提示的 容器Name 是 ~/.bashrc 生效时的列表,所有如果之后 docker 容器列表有变动,需要重新执行 source ~/.bashrc 使之更新提示列表

    3.6 终止容器

    通过 docker stop [容器Name] 来终止一个运行中的容器

    # 终止容器名为 test2 的容器docker stop test2# 查看正在运行中的容器docker ps# 查看所有容器(包括终止的)docker ps -a

    3.7 将容器保存为镜像

    我们修改一个容器后,可以经当前容器状态打包成镜像,方便下次直接通过镜像仓库生成当前状态的容器。

    # 创建容器docker run -t -i training/sinatra /bin/bash# 添加两个应用gem install json# 将修改后的容器打包成新的镜像docker commit -m "Added json gem" -a "Docker Newbee" 0b2616b0e5a8 ouruser/sinatra:v2

    3.8 导出/导入容器

    容器 ->导出> 容器快照文件 ->导入> 本地镜像仓库 ->新建> 容器

    $ docker ps -aCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES2a8bffa405c8        ubuntu:14.04        "/bin/sh -c 'while t…"   About an hour ago   Up 3 seconds                                            test2# 导出$ docker export 2a8bffa405c8 > ubuntu.tar# 导入为镜像$ docker ubuntu.tar | docker import - test/ubuntu:v1.0# 从指定 URL 或者某个目录导入$ docker import http://example.com/exampleimage.tgz example/imagerepo

    注意:用户既可以通过 docker load 来导入镜像存储文件到本地镜像仓库,也可以使用 docker import 来导入一个容器快找到本地镜像仓库,两者的区别在于容器快照将丢失所有的历史记录和元数据信息,仅保存容器当时的状态,而镜像存储文件将保存完成的记录,体积要更大。所有容器快照文件导入时需要重新指定标签等元数据信息。

    3.9 删除容器

    可以使用 docker rm [容器Name] 来删除一个终止状态的容器,如果容器还未终止,可以先使用 docker stop [容器Name] 来终止容器,再进行删除操作

    docker rm test2# 删除容器 -f: 强制删除,无视是否运行$ docker [-f] rm myubuntu# 删除所有已关闭的容器$ docker rm $(docker ps -a -q)

    3.10 查看容器状态

    docker stats $(docker ps --format={{.Names}})

    四、数据卷

    4.1 介绍

    数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多特性:

    • 数据卷可以在容器之间共享和重用
    • 对数据卷的修改会立马生效
    • 对数据卷的更新,不会影响镜像
    • 卷会一直存在,直到没有容器使用

    数据卷类似于 Linux 下对目录或文件进行 mount

    4.2 创建数据卷

    在用 docker run 命令的时候,使用 -v 标记来创建一个数据卷并挂在在容器里,可同时挂在多个。

    # 创建一个 web 容器,并加载一个数据卷到容器的 /webapp 目录docker run -d -P --name web -v /webapp training/webapp python app.py# 挂载一个宿主机目录 /data/webapp 到容器中的 /opt/webappdocker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py# 默认是读写权限,也可以指定为只读docker run -d -P --name web -v /src/webapp:/opt/webapp:ro# 挂载单个文件docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash

    4.3 数据卷容器

    如果需要多个容器共享数据,最好创建数据卷容器,就是一个正常的容器,撰文用来提供数据卷供其他容器挂载的

    # 创建一个数据卷容器 dbdatadocker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres# 其他容器挂载 dbdata 容器的数据卷docker run -d --volumes-from dbdata --name db1 training/postgresdocker run -d --volumes-from dbdata --name db2 training/postgres

    五、网络

    5.1 外部访问容器

    在容器内运行一些服务,需要外部可以访问到这些服务,可以通过 -P-p 参数来指定端口映射。

    当使用 -P 标记时,Docker 会随即映射一个 49000~49900 的端口到内部容器开放的网络端口。

    使用 docker ps 可以查看端口映射情况

    $ docker psCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES7f43807dc042        training/webapp     "python app.py"          3 seconds ago       Up 2 seconds        0.0.0.0:32770->5000/tcp             amazing_liskov

    -p 指定端口映射,支持格式 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort

    # 不限制ip访问docker run -d -p 5000:5000 training/webapp python app.py# 只允许宿主机回环地址访问docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py# 宿主机自动分配绑定端口docker run -d -p 127.0.0.1::5000 training/webapp python app.py# 指定 udp 端口docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py# 指定多个端口映射docker run -d -p 5000:5000  -p 3000:80 training/webapp python app.py# 查看映射端口配置$ docker port amazing_liskov5000/tcp -> 0.0.0.0:32770

    5.2 容器互联

    容器除了跟宿主机端口映射外,还有一种容器间交互的方式,可以在源/目标容器之间建立一个隧道,目标容器可以看到源容器指定的信息。

    可以通过 --link name:alias 来连接容器,下面就是 “web容器连接db容器” 的例子

    # 创建 容器dbdocker run -d --name db training/postgres# 创建 容器web 并连接到 容器dbdocker run -d -P --name web --link db:db training/webapp python app.py# 进入 容器web,测试连通性$ ctn web$ ping dbPING db (172.17.0.3) 56(84) bytes of data.64 bytes from db (172.17.0.3): icmp_seq=1 ttl=64 time=0.254 ms64 bytes from db (172.17.0.3): icmp_seq=2 ttl=64 time=0.190 ms64 bytes from db (172.17.0.3): icmp_seq=3 ttl=64 time=0.389 ms

    5.3 访问控制

    容器想要访问外部网络,需要宿主机的转发支持。在 Linux 系统中,通过以下命令检查是否打开

    $ sysctl net.ipv4.ip_forwardnet.ipv4.ip_forward = 1

    如果是 0,说明没有开启转发,则需要手动打开。

    $ sysctl -w net.ipv4.ip_forward=1

    5.4 配置 docker0 桥接

    Docker 服务默认会创建一个 docker0 网桥,他在内核层连通了其他物理或虚拟网卡,这就将容器和主机都放在同一个物理网络。

    Docker 默认制定了 docker0 接口的IP地址和子网掩码,让主机和容器间可以通过网桥相互通信,他还给了 MTU(接口允许接收的最大单元),通常是 1500 Bytes,或宿主机网络路由上支持的默认值。这些都可以在服务启动的时候进行配置。

    • --bip=CIDR ip地址加子网掩码格式,如 192.168.1.5/24
    • --mtu=BYTES 覆盖默认的 Docker MTU 配置

    可以通过 brctl show 来查看网桥和端口连接信息

    5.5 网络配置文件

    Docker 1.2.0 开始支持在运行中的容器里编辑 /etc/hosts/etc/hostsname/etc/resolve.conf 文件,修改都是临时的,重新容器将会丢失修改,通过 docker commit 也不会被提交。

    六、Dockerfile

    6.1 介绍

    Dockerfile 是由一行行命令组成的命令集合,分为四个部分:

    1. 基础镜像信息
    2. 维护着信息
    3. 镜像操作指令
    4. 容器启动时执行指令

    如下:

    # 最前面一般放这个 Dockerfile 的介绍、版本、作者及使用说明等# This dockerfile uses the ubuntu image# VERSION 2 - EDITION 1# Author: docker_user# Command format: Instruction [arguments / command] ..# 使用的基础镜像,必须放在非注释第一行FROM ubuntu# 维护着信息信息: 名字 联系方式MAINTAINER docker_user docker_user@email.com# 构建镜像的命令:对镜像做的调整都在这里RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.listRUN apt-get update && apt-get install -y nginxRUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf# 创建/运行 容器时的操作指令 # 可以理解为 docker run 后跟的运行指令CMD /usr/sbin/nginx

    6.2 指令

    指令一般格式为 INSTRUCTION args,包括 FORMMAINTAINERRUN

    FORM第一条指令必须是 FORM 指令,并且如果在同一个Dockerfile 中创建多个镜像,可以使用多个 FROM 指令(每个镜像一次)FORM ubuntuFORM ubuntu:14.04
    MAINTAINER维护者信息MAINTAINER Chris xx@gmail.com
    RUN每条 RUN 指令在当前镜像基础上执行命令,并提交为新的镜像。当命令过长时可以使用 \ 来换行在 shell 终端中运行命令RUN apt-get update && apt-get install -y nginxexec 中执行:RUN ["/bin/bash", "-c", "echo hello"]
    CMD指定启动容器时执行的命令,每个 Dockerfile 只能有一条 CMD 命令。如果指定了多条命令,只有最后一条会被执行。CMD ["executable","param1","param2"] 使用 exec 执行,推荐方式;CMD command param1 param2/bin/sh 中执行,提供给需要交互的应用;CMD ["param1","param2"] 提供给 ENTRYPOINT 的默认参数;
    EXPOSE告诉服务端容器暴露的端口号,EXPOSE
    ENV指定环境变量ENV PG_MAJOR 9.3ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
    ADDADD 该命令将复制指定的 到容器中的 。其中 可以是 Dockerfile 所在目录的一个相对路径,也可以是一个URL;还可以是一个 tar文件(自动解压为目录)
    COPY格式为 COPY 复制本地主机的 (为 Dockerfile 所在目录的相对路径)到容器中的 。当使用本地目录为源目录时,推荐使用 COPY
    ENTRYPOINT配置容器启动执行的命令,并且不可被 docker run 提供的参数覆盖每个Docekrfile 中只能有一个 ENTRYPOINT ,当指定多个时,只有最后一个起效两种格式ENTRYPOINT ["executable", "param1", "param2"]``ENTRYPOINT command param1 param2(shell中执行)
    VOLUME创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。VOLUME [“/data”]
    USER指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户USER daemon
    WORKDIR为后续的 RUNCMDENTRYPOINT 指令配置工作目录。可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。格式为 WORKDIR /path/to/workdir。 WORKDIR /aWORKDIR bWORKDIR cRUN pwd最后的路径为 /a/b/c
    ONBUILD配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。格式为 ONBUILD [INSTRUCTION]

    6.3 创建镜像

    编写完成 Dockerfile 之后,可以通过 docker build 命令来创建镜像

    docker build [选项] 路径 该命令江都区指定路径下(包括子目录)的Dockerfile,并将该路径下所有内容发送给 Docker 服务端,有服务端来创建镜像。可以通过 .dockerignore 文件来让 Docker 忽略路径下的目录与文件

    # 使用 -t 指定镜像的标签信息docker build -t myrepo/myimage .

    七、Docker Compose

    7.1 介绍

    Docker Compose 是 Docker 官方编排项目之一,负责快速在集群中部署分布式应用。维护地址:https://github.com/docker/compose,由 Python 编写,实际调用 Docker提供的API实现。

    Dockerfile 可以让用户管理一个单独的应用容器,而 Compose 则允许用户在一个模版(YAML格式)中定义一组相关联的应用容器(被称为一个project/项目),例如一个 web容器再加上数据库、redis等。

    7.2 安装

    # 使用 pip 进行安装pip install -U docker-compose# 查看用法docker-ompose -h# 添加 bash 补全命令curl -L https://raw.githubusercontent.com/docker/compose/1.2.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose

    7.3 使用

    术语

    • 服务/service: 一个应用容器,实际上可以运行多个相同镜像的实例
    • 项目/project: 有一组关联的应用容器组成的完成业务单元

    示例:创建一个 Haproxy 挂载三个 Web 容器

    创建一个 compose-haproxy-web 目录,作为项目工作目录,并在其中分别创建两个子目录: haproxyweb

    compose-haproxy-webcompose-haproxy-webgit clone https://github.com/yelog/compose-haproxy-web.git

    目录长这样:

    compose-haproxy-web├── docker-compose.yml├── haproxy│   └── haproxy.cfg└── web    ├── Dockerfile    ├── index.html    └── index.py

    在该目录执行 docker-compose up 命令,会整合输出所有容器的输出

    $ docker-compose upStarting compose-haproxy-web_webb_1 ... doneStarting compose-haproxy-web_webc_1 ... doneStarting compose-haproxy-web_weba_1 ... doneRecreating compose-haproxy-web_haproxy_1 ... doneAttaching to compose-haproxy-web_webb_1, compose-haproxy-web_weba_1, compose-haproxy-web_webc_1, compose-haproxy-web_haproxy_1haproxy_1  | [NOTICE] 244/131022 (1) : haproxy version is 2.2.2haproxy_1  | [NOTICE] 244/131022 (1) : path to executable is /usr/local/sbin/haproxyhaproxy_1  | [ALERT] 244/131022 (1) : parsing [/usr/local/etc/haproxy/haproxy.cfg:14] : 'listen' cannot handle unexpected argument ':70'.haproxy_1  | [ALERT] 244/131022 (1) : parsing [/usr/local/etc/haproxy/haproxy.cfg:14] : please use the 'bind' keyword for listening addresses.haproxy_1  | [ALERT] 244/131022 (1) : Error(s) found in configuration file : /usr/local/etc/haproxy/haproxy.cfghaproxy_1  | [ALERT] 244/131022 (1) : Fatal errors found in configuration.compose-haproxy-web_haproxy_1 exited with code 1

    此时访问本地的 80 端口,会经过 haproxy 自动转发到后端的某个 web 容器上,刷新页面,可以观察到访问的容器地址的变化。

    7.4 命令说明

    大部分命令都可以运行在一个或多个服务上。如果没有特别的说明,命令则应用在项目所有的服务上。

    执行 docker-compose [COMMAND] --help 查看具体某个命令的使用说明

    使用格式

    docker-compose [options] [COMMAND] [ARGS...]
    build构建/重建服务服务一旦构建后,将会带上一个标记名,例如 web_db可以随时在项目目录运行 docker-compose build 来重新构建服务
    help获得一个命令的信息
    kill通过发送 SIGKILL 信号来强制停止服务容器,支持通过参数来指定发送信号,例如docker-compose kill -s SIGINT
    logs查看服务的输出
    port打印绑定的公共端口
    ps列出所有容器
    pull拉去服务镜像
    rm删除停止的服务容器
    run在一个服务上执行一个命令docker-compose run ubuntu ping docker.com
    scale设置同一个服务运行的容器个数通过 service=num 的参数来设置数量docker-compose scale web=2 worker=3
    start启动一个已经存在的服务容器
    stop停止一个已经运行的容器,但不删除。可以通过 docker-compose start 再次启动
    up构建、创建、启动、链接一个服务相关的容器链接服务都将被启动,除非他们已经运行docker-compose up -d 将后台运行并启动docker-compose up 已存在容器将会重新创建docker-compose up --no-recreate 将不会重新创建容器

    7.5 环境变量

    环境变量可以用来配置 Compose 的行为

    Docker_ 开头的变量用来配置 Docker 命令行客户端使用的一样

    COMPOSE_PROJECT_NAME设置通过 Compose 启动的每一个容器前添加的项目名称,默认是当前工作目录的名字。
    COMPOSE_FILE设置要使用的 docker-compose.yml 的路径。默认路径是当前工作目录。
    DOCKER_HOST设置 Docker daemon 的地址。默认使用 unix:///var/run/docker.sock,与 Docker 客户端采用的默认值一致。
    DOCKER_TLS_VERIFY如果设置不为空,则与 Docker daemon 交互通过 TLS 进行。
    DOCKER_CERT_PATH配置 TLS 通信所需要的验证(ca.pemcert.pemkey.pem)文件的路径,默认是 ~/.docker

    7.6 docker-compose.yml

    默认模版文件是 docker-compose.yml ,启动定义了每个服务都必须经过 image 指令指定镜像或 build 指令(需要 Dockerfile) 来自动构建。

    其他大部分指令跟 docker run 类似

    如果使用 build 指令,在 Dockerfile 中设置的选项(如 CMDEXPOSE 等)将会被自动获取,无需在 docker-compose.yml 中再次设置。

    **image**

    指定镜像名称或镜像ID,如果本地仓库不存在,将尝试从远程仓库拉去此镜像

    image: ubuntuimage: orchardup/postgresqlimage: a4bc65fd**build**

    指定 Dockerfile 所在文件的路径。Compose 将利用它自动构建这个镜像,然后使用这个镜像。

    build: /path/to/build/dir**command**

    覆盖容器启动默认执行命令

    command: bundle exec thin -p 3000**links**

    链接到其他服务中的容器,使用服务名称或别名

    links:    - db  - db:database  - redis

    别名会自动在服务器中的 /etc/hosts 里创建。例如:

    172.17.2.186  db172.17.2.186  database172.17.2.187  redis**external_links**

    连接到 docker-compose.yml 外部的容器,甚至并非 Compose 管理的容器。

    external_links: - redis_1 - project_db_1:mysql - project_db_1:postgresql

    ports

    暴露端口信息 HOST:CONTAINER

    格式或者仅仅指定容器的端口(宿主机会随机分配端口)

    ports: - "3000" - "8000:8000" - "49100:22" - "127.0.0.1:8001:8001"

    注:当使用 HOST:CONTAINER 格式来映射端口时,如果你使用的容器端口小于 60 你可能会得到错误得结果,因为 YAML 将会解析 xx:yy 这种数字格式为 60 进制。所以建议采用字符串格式。

    **expose**

    暴露端口,但不映射到宿主机,只被连接的服务访问

    expose: - "3000" - "8000"

    volumes

    卷挂载路径设置。可以设置宿主机路径 (HOST:CONTAINER) 或加上访问模式 (HOST:CONTAINER:ro)。

    volumes: - /var/lib/mysql - cache/:/tmp/cache - ~/configs:/etc/configs/:ro

    **
    **

    volumes_from

    从另一个服务或容器挂载它的所有卷。

    volumes_from: - service_name - container_name
    environment

    设置环境变量。你可以使用数组或字典两种格式。

    只给定名称的变量会自动获取它在 Compose 主机上的值,可以用来防止泄露不必要的数据。

    environment:  RACK_ENV: development  SESSION_SECRET:environment:  - RACK_ENV=development  - SESSION_SECRET

    env_file

    从文件中获取环境变量,可以为单独的文件路径或列表。

    如果通过 docker-compose -f FILE 指定了模板文件,则 env_file 中路径会基于模板文件路径。

    如果有变量名称与 environment 指令冲突,则以后者为准。

    env_file: .envenv_file:  - ./common.env  - ./apps/web.env  - /opt/secrets.env

    环境变量文件中每一行必须符合格式,支持 # 开头的注释行。

    # common.env: Set Rails/Rack environmentRACK_ENV=development

    extends

    基于已有的服务进行扩展。例如我们已经有了一个 webapp 服务,模板文件为 common.yml

    # common.ymlwebapp:  build: ./webapp  environment:    - DEBUG=false    - SEND_EMAILS=false

    编写一个新的 development.yml 文件,使用 common.yml 中的 webapp 服务进行扩展。

    # development.ymlweb:  extends:    file: common.yml    service: webapp  ports:    - "8000:8000"  links:    - db  environment:    - DEBUG=truedb:  image: postgres

    后者会自动继承 common.yml 中的 webapp 服务及相关环节变量。

    **
    **

    net

    设置网络模式。使用和 docker client--net 参数一样的值。

    net: "bridge"net: "none"net: "container:[name or id]"net: "host"

    **
    **

    pid

    跟主机系统共享进程命名空间。打开该选项的容器可以相互通过进程 ID 来访问和操作。

    pid: "host"

    dns

    配置 DNS 服务器。可以是一个值,也可以是一个列表。

    dns: 8.8.8.8dns:  - 8.8.8.8  - 9.9.9.9

    cap_add, cap_drop

    添加或放弃容器的 Linux 能力(Capabiliity)。

    cap_add:  - ALLcap_drop:  - NET_ADMIN  - SYS_ADMIN

    **
    **

    dns_search

    配置 DNS 搜索域。可以是一个值,也可以是一个列表。

    dns_search: example.comdns_search:  - domain1.example.com  - domain2.example.com

    **
    **

    working_dir, entrypoint, user, hostname, domainname, mem_limit, privileged, restart, stdin_open, tty, cpu_shares

    这些都是和 docker run 支持的选项类似。

    八、安全

    8.1 内核命名空间

    当使用 docker run 启动一个容器时,在后台 Docker 为容器创建一个独立的命名空间和控制集合。命名空间踢空了最基础的也是最直接的隔离,在容器中运行的进程不会被运行在主机上的进程和其他容器发现和作用。

    8.2 控制组

    控制组是 Linux 容器机制的另一个关键组件,负责实现资源的审计和限制。

    它提供了很多特性,确保哥哥容器可以公平地分享主机的内存、CPU、磁盘IO等资源;当然,更重要的是,控制组确保了当容器内的资源使用产生压力时不会连累主机系统。

    8.3 内核能力机制

    能力机制是 Linux 内核的一个强大特性,可以提供细粒度的权限访问控制。 可以作用在进程上,也可以作用在文件上。

    例如一个服务需要绑定低于 1024 的端口权限,并不需要 root 权限,那么它只需要被授权 net_bind_service 能力即可。

    默认情况下, Docker 启动的容器被严格限制只允许使用内核的一部分能力。

    使用能力机制加强 Docker 容器的安全有很多好处,可以按需分配给容器权限,这样,即便攻击者在容器中取得了 root 权限,也不能获取宿主机较高权限,能进行的破坏也是有限的。

    参考资料

    https://docs.docker.com/engine/reference/commandline/images/

    http://www.dockerinfo.net/

    ]]>
    + 一、概述

    1.1 什么是docker

    Docker 诞生于 2013 年初,由 dotCloud 公司(后改名为 Docker Inc)基于 Go 语言实现并开源的项目。此项目后来加入 Linux基金会,遵从了 Apache 2.0 协议

    Docker 项目的目标是实现轻量级的操作系统虚拟化解决方案。Docker 是在 Linux 容器技术(LXC)的基础上进行了封装,让用户可以快速并可靠的将应用程序从一台运行到另一台上。

    使用容器部署应用被称为容器化,容器化技术的几大优势:

    1. 灵活:甚至复杂的应用也可以被容器化
    2. 轻量:容器利用和共享宿主机内核,从而在利用系统资源比虚拟机更加的有效
    3. 可移植:你可以在本地构建,在云端部署并在任何地方运行
    4. 松耦合:容器是高度封装和自给自足的,允许你在不破环其他容器的情况下替换或升级任何一个
    5. 可扩展:你可以通过数据中心来新增和自动分发容器
    6. 安全:容器依赖强约束和独立的进程

    1.2 和传统虚拟机的区别

    容器在Linux上本地运行,并与其他容器共享主机的内核。它运行一个离散进程,不占用任何其他可执行文件更多的内存,从而使其轻巧。

    image.png

    1.3 相关链接

    官网:https://www.docker.com/

    文档:https://docs.docker.com/

    二、Image镜像

    2.1 介绍

    Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变

    1. 父镜像:每个镜像都可能依赖于有一个或多个下层组成的另一个镜像。下层那个镜像就是上层镜像的父镜像
    2. 基础镜像:一个没有任何父镜像的镜像,被称为基础镜像
    3. 镜像ID:所有镜像都是通过一个 64 位十六进制字符串(256 bit 的值)来标识的。为了简化使用,前 12 个自负可以组成一个短ID,可以在命令行中使用。短ID还是有一定的碰撞几率,所以服务器总是返回长ID

    2.2 从仓库下载镜像

    可以通过 docker pull 命令从仓库获取所需要的镜像

    docker pull [选项] [Docker Registry 地址]<镜像名>:<标签>

    选项:

    1. –all-tags,-a : 拉去所有 tagged 镜像
    2. –disable-content-trust:忽略镜像的校验,默认
    3. –platform:如果服务器是开启多平台支持的,则需要设置平台
    4. –quiet,-q:静默执行,不打印详细信息

    标签: 下载指定标签的镜像,默认 latest

    示例

    # 从 Docker Hub 下载最新的 debian 镜像docker pull debian# 从 Docker Hub 下载 jessie 版 debian 镜像docker pull debian:jessie# 下载指定摘要(sha256)的镜像docker pull ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2

    2.3 列出本地镜像

    # 列出已下载的镜像 image_name: 指定列出某个镜像docker images [选项] [image_name]

    选项

    参数描述
    –all, -a展示所有镜像(包括 intermediate 镜像)
    –digests展示摘要
    –filter, -f添加过滤条件
    –format使用 Go 模版更好的展示
    –no-trunc不删减输出
    –quiet, -q静默输出,仅仅展示 IDs

    示例

    # 展示本地所有下载的镜像docker images# 在本地查找镜像名是 "java" 标签是 "8" 的 奖项docker images: java:8# 查找悬挂镜像docker images --filter "dangling=true"# 过滤 lable 为 "com.example.version" 的值为 0.1 的镜像docker images --filter "label=com.example.version=0.1"

    2.4 Dockerfile创建镜像

    为了方便分享和快速部署,我们可以使用 docker build 来创建一个新的镜像,首先创建一个文件 Dockerfile,如下

    # This is a commentFROM ubuntu:14.04MAINTAINER Chris <jaytp@qq.com>RUN apt-get -qq updateRUN apt-get -qqy install ruby ruby-devRUN gem install sinatra

    然后在此 Dockerfile 所在目录执行 docker build -t yelog/ubuntu:v1 . 来生成镜像,所属组织/镜像名:标签

    2.5 上传镜像

    用户可以通过 docker push 命令,把自己创建的镜像上传到仓库中来共享。例如,用户在 Docker Hub 上完成注册后,可以推送自己的镜像到仓库中。

    docker push yelog/ubuntu

    2.6 导出和载入镜像

    docker 支持将镜像导出为文件,然后可以再从文件导入到本地镜像仓库

    # 导出docker load --input yelog_ubuntu_v1.tar# 载入docker load < yelog_ubuntu_v1.tar

    2.7 移除本地镜像

    # -f 强制删除docker rmi [-f] yelog/ubuntu:v1# 删除悬挂镜像docker rmi $(docker images -f "dangling=true" -q)# 删除所有未被容器使用的镜像docker image prune -a

    三、容器

    3.1 介绍

    容器和镜像,就像面向对象中的 示例 一样,镜像是静态的定义,容器是镜像运行的实体,容器可以被创建、启动、停止、删除和暂停等

    容器的实质是进城,耽于直接的宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的 root 文件系统、网络配置和进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。

    3.2 创建容器

    我们可以通过命令 docker run 命令创建容器

    如下,启动一个容器,执行命令输出 “Hello word”,之后终止容器

    docker run ubuntu:14.04 /bin/echo 'Hello world'

    下面的命令则是启动一个 bash 终端,允许用户进行交互

    docker run -t -i ubuntu:14.04 /bin/bash

    -t 让 Dcoker 分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上

    -i 责让容器的标准输入保持打开

    更多参数可选

    -a stdin指定标准输入输出内容类型
    -d后台运行容器,并返回容器ID
    -i以交互模式运行容器,通常与 -t 同时使用
    -P随机端口映射,容器端口内部随即映射到宿主机的端口上
    -p指定端口映射, -p 宿主机端口:容器端口
    -t为容器重新分配一个伪输入终,通常与 -i 同时使用
    –name=”gate”为容器指定一个名称
    –dns 8.8.8.8指定容器的 DNS 服务器,默认与宿主机一致
    –dns-search example.com指定容器 DNS 搜索域名,默认与宿主机一致
    -h “gate”指定容器的 hostname
    -e username=’gate’设置环境变量
    –env-file=[]从指定文件读入环境变量
    –cpuset=”0-2” or –cpuset=”0,1,2”绑定容器到指定 CPU 运行
    -m设置容器使用内存最大值
    –net=”bridge”指定容器的网络连接类型支持 bridge/host/none/container
    –link=[]添加链接到另一个容器
    –expose=[]开放一个端口或一组端口
    –volume,-v绑定一个卷

    当利用 docker run 来创建容器时,Dcoker 在后台运行的标准操作包括:

    • 检查本地是否存在指定的镜像,不存在就从公有仓库下载
    • 利用镜像创建并启动一个容器
    • 分配一个文件系统,并在只读的镜像外面挂在一层可读写层
    • 从宿主主机配置的网桥接口中桥接一个虚拟借口到容器中去
    • 从地址池配置一个 ip 地址给容器
    • 执行用户指定的应用程序
    • 执行用户指定的应用程序
    • 执行完毕后容器被终止

    3.3 启动容器

    # 创建一个名为 test 的容器,容器任务是:打印一行 Hello worddocker run --name='test' ubuntu:14.04 /bin/echo 'Hello world'# 查看所有可用容器 [-a]包括终止在内的所有容器docker ps -a# 启动指定 name 的容器docker start test# 重启指定 name 的容器docker restart test# 查看日志运行日志(每次启动的日志均被查询出来)$ docker logs testHello worldHello world

    3.4 守护态运行

    前面创建的容器都是执行任务(打印Hello world)后,容器就终止了。更多的时候,我们需要让 Docker 容器在后台以守护态(Daemonized)形式运行。此时,可以通过添加 -d 参数来实现

    注意:docker是否会长久运行,和 docker run 指定的命令有关

    # 创建 docker 后台守护进程的容器docker run --name='test2' -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"# 查看容器$ docker psCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES237e555d4457        ubuntu:14.04        "/bin/sh -c 'while t…"   52 seconds ago      Up 51 seconds                                           test2# 获取容器的输出信息$ docker logs test2hello worldhello worldhello world

    3.5 进入容器

    上一步我们已经实现了容器守护态长久运行,某些时候需要进入容器进行操作,可以使用 attachexec 进入容器。

    # 不安全的,ctrl+d 退出时容器也会终止docker attach [容器Name]# 以交互式命令行进入,安全的,推荐使用docker exec -it [容器Name] /bin/bash

    命令优化

    1. 使用 docker exec 命令时,好用,但是命令过长,我们可以通过自定义命令来简化使用
    2. 创建文件 /user/bin/ctn 命令文件,内容如下
    docker exec -it $1 /bin/bash
    1. 检查环境变量有没有配置目录 /usr/bin (一般是有配置在环境变量里面的,不过最好再确认一下)
    $PATHbash: /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games: No such file or directory
    1. 完成上面步骤后,就可以直接通过命令 ctn 来进入容器

    注意:如果是使用非 root 账号创建的命令,而 docker 命令是 root 权限,可能存在权限问题,可以通过设置 chmod 777 /usr/bin/ctn 设置权限,使用 sudo ctn [容器Name] 即可进入容器

    $ ctn [容器Name]
    1. 使用上面命令时,容器Name 需要手动输入,容器出错。我们可以借助 complete 命令来补全 容器Name,在 ~/.bashrc (作用于当前用户,如果想要所要用户上校,可以修改 /etc/bashrc)文件中添加一行,内容如下。保存后执行 source ~/.bashrc 使之生效,之后我们输入 ctn 后,按 tab 就会提示或自动补全容器名了了
    # ctn auto completecomplete -W "$(docker ps --format"{{.Names}}")" ctn

    注意: 由于提示的 容器Name 是 ~/.bashrc 生效时的列表,所有如果之后 docker 容器列表有变动,需要重新执行 source ~/.bashrc 使之更新提示列表

    3.6 终止容器

    通过 docker stop [容器Name] 来终止一个运行中的容器

    # 终止容器名为 test2 的容器docker stop test2# 查看正在运行中的容器docker ps# 查看所有容器(包括终止的)docker ps -a

    3.7 将容器保存为镜像

    我们修改一个容器后,可以经当前容器状态打包成镜像,方便下次直接通过镜像仓库生成当前状态的容器。

    # 创建容器docker run -t -i training/sinatra /bin/bash# 添加两个应用gem install json# 将修改后的容器打包成新的镜像docker commit -m "Added json gem" -a "Docker Newbee" 0b2616b0e5a8 ouruser/sinatra:v2

    3.8 导出/导入容器

    容器 ->导出> 容器快照文件 ->导入> 本地镜像仓库 ->新建> 容器

    $ docker ps -aCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES2a8bffa405c8        ubuntu:14.04        "/bin/sh -c 'while t…"   About an hour ago   Up 3 seconds                                            test2# 导出$ docker export 2a8bffa405c8 > ubuntu.tar# 导入为镜像$ docker ubuntu.tar | docker import - test/ubuntu:v1.0# 从指定 URL 或者某个目录导入$ docker import http://example.com/exampleimage.tgz example/imagerepo

    注意:用户既可以通过 docker load 来导入镜像存储文件到本地镜像仓库,也可以使用 docker import 来导入一个容器快找到本地镜像仓库,两者的区别在于容器快照将丢失所有的历史记录和元数据信息,仅保存容器当时的状态,而镜像存储文件将保存完成的记录,体积要更大。所有容器快照文件导入时需要重新指定标签等元数据信息。

    3.9 删除容器

    可以使用 docker rm [容器Name] 来删除一个终止状态的容器,如果容器还未终止,可以先使用 docker stop [容器Name] 来终止容器,再进行删除操作

    docker rm test2# 删除容器 -f: 强制删除,无视是否运行$ docker [-f] rm myubuntu# 删除所有已关闭的容器$ docker rm $(docker ps -a -q)

    3.10 查看容器状态

    docker stats $(docker ps --format={{.Names}})

    四、数据卷

    4.1 介绍

    数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多特性:

    • 数据卷可以在容器之间共享和重用
    • 对数据卷的修改会立马生效
    • 对数据卷的更新,不会影响镜像
    • 卷会一直存在,直到没有容器使用

    数据卷类似于 Linux 下对目录或文件进行 mount

    4.2 创建数据卷

    在用 docker run 命令的时候,使用 -v 标记来创建一个数据卷并挂在在容器里,可同时挂在多个。

    # 创建一个 web 容器,并加载一个数据卷到容器的 /webapp 目录docker run -d -P --name web -v /webapp training/webapp python app.py# 挂载一个宿主机目录 /data/webapp 到容器中的 /opt/webappdocker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py# 默认是读写权限,也可以指定为只读docker run -d -P --name web -v /src/webapp:/opt/webapp:ro# 挂载单个文件docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash

    4.3 数据卷容器

    如果需要多个容器共享数据,最好创建数据卷容器,就是一个正常的容器,撰文用来提供数据卷供其他容器挂载的

    # 创建一个数据卷容器 dbdatadocker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres# 其他容器挂载 dbdata 容器的数据卷docker run -d --volumes-from dbdata --name db1 training/postgresdocker run -d --volumes-from dbdata --name db2 training/postgres

    五、网络

    5.1 外部访问容器

    在容器内运行一些服务,需要外部可以访问到这些服务,可以通过 -P-p 参数来指定端口映射。

    当使用 -P 标记时,Docker 会随即映射一个 49000~49900 的端口到内部容器开放的网络端口。

    使用 docker ps 可以查看端口映射情况

    $ docker psCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES7f43807dc042        training/webapp     "python app.py"          3 seconds ago       Up 2 seconds        0.0.0.0:32770->5000/tcp             amazing_liskov

    -p 指定端口映射,支持格式 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort

    # 不限制ip访问docker run -d -p 5000:5000 training/webapp python app.py# 只允许宿主机回环地址访问docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py# 宿主机自动分配绑定端口docker run -d -p 127.0.0.1::5000 training/webapp python app.py# 指定 udp 端口docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py# 指定多个端口映射docker run -d -p 5000:5000  -p 3000:80 training/webapp python app.py# 查看映射端口配置$ docker port amazing_liskov5000/tcp -> 0.0.0.0:32770

    5.2 容器互联

    容器除了跟宿主机端口映射外,还有一种容器间交互的方式,可以在源/目标容器之间建立一个隧道,目标容器可以看到源容器指定的信息。

    可以通过 --link name:alias 来连接容器,下面就是 “web容器连接db容器” 的例子

    # 创建 容器dbdocker run -d --name db training/postgres# 创建 容器web 并连接到 容器dbdocker run -d -P --name web --link db:db training/webapp python app.py# 进入 容器web,测试连通性$ ctn web$ ping dbPING db (172.17.0.3) 56(84) bytes of data.64 bytes from db (172.17.0.3): icmp_seq=1 ttl=64 time=0.254 ms64 bytes from db (172.17.0.3): icmp_seq=2 ttl=64 time=0.190 ms64 bytes from db (172.17.0.3): icmp_seq=3 ttl=64 time=0.389 ms

    5.3 访问控制

    容器想要访问外部网络,需要宿主机的转发支持。在 Linux 系统中,通过以下命令检查是否打开

    $ sysctl net.ipv4.ip_forwardnet.ipv4.ip_forward = 1

    如果是 0,说明没有开启转发,则需要手动打开。

    $ sysctl -w net.ipv4.ip_forward=1

    5.4 配置 docker0 桥接

    Docker 服务默认会创建一个 docker0 网桥,他在内核层连通了其他物理或虚拟网卡,这就将容器和主机都放在同一个物理网络。

    Docker 默认制定了 docker0 接口的IP地址和子网掩码,让主机和容器间可以通过网桥相互通信,他还给了 MTU(接口允许接收的最大单元),通常是 1500 Bytes,或宿主机网络路由上支持的默认值。这些都可以在服务启动的时候进行配置。

    • --bip=CIDR ip地址加子网掩码格式,如 192.168.1.5/24
    • --mtu=BYTES 覆盖默认的 Docker MTU 配置

    可以通过 brctl show 来查看网桥和端口连接信息

    5.5 网络配置文件

    Docker 1.2.0 开始支持在运行中的容器里编辑 /etc/hosts/etc/hostsname/etc/resolve.conf 文件,修改都是临时的,重新容器将会丢失修改,通过 docker commit 也不会被提交。

    六、Dockerfile

    6.1 介绍

    Dockerfile 是由一行行命令组成的命令集合,分为四个部分:

    1. 基础镜像信息
    2. 维护着信息
    3. 镜像操作指令
    4. 容器启动时执行指令

    如下:

    # 最前面一般放这个 Dockerfile 的介绍、版本、作者及使用说明等# This dockerfile uses the ubuntu image# VERSION 2 - EDITION 1# Author: docker_user# Command format: Instruction [arguments / command] ..# 使用的基础镜像,必须放在非注释第一行FROM ubuntu# 维护着信息信息: 名字 联系方式MAINTAINER docker_user docker_user@email.com# 构建镜像的命令:对镜像做的调整都在这里RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.listRUN apt-get update && apt-get install -y nginxRUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf# 创建/运行 容器时的操作指令 # 可以理解为 docker run 后跟的运行指令CMD /usr/sbin/nginx

    6.2 指令

    指令一般格式为 INSTRUCTION args,包括 FORMMAINTAINERRUN

    FORM第一条指令必须是 FORM 指令,并且如果在同一个Dockerfile 中创建多个镜像,可以使用多个 FROM 指令(每个镜像一次)FORM ubuntuFORM ubuntu:14.04
    MAINTAINER维护者信息MAINTAINER Chris xx@gmail.com
    RUN每条 RUN 指令在当前镜像基础上执行命令,并提交为新的镜像。当命令过长时可以使用 \ 来换行在 shell 终端中运行命令RUN apt-get update && apt-get install -y nginxexec 中执行:RUN ["/bin/bash", "-c", "echo hello"]
    CMD指定启动容器时执行的命令,每个 Dockerfile 只能有一条 CMD 命令。如果指定了多条命令,只有最后一条会被执行。CMD ["executable","param1","param2"] 使用 exec 执行,推荐方式;CMD command param1 param2/bin/sh 中执行,提供给需要交互的应用;CMD ["param1","param2"] 提供给 ENTRYPOINT 的默认参数;
    EXPOSE告诉服务端容器暴露的端口号,EXPOSE
    ENV指定环境变量ENV PG_MAJOR 9.3ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
    ADDADD 该命令将复制指定的 到容器中的 。其中 可以是 Dockerfile 所在目录的一个相对路径,也可以是一个URL;还可以是一个 tar文件(自动解压为目录)
    COPY格式为 COPY 复制本地主机的 (为 Dockerfile 所在目录的相对路径)到容器中的 。当使用本地目录为源目录时,推荐使用 COPY
    ENTRYPOINT配置容器启动执行的命令,并且不可被 docker run 提供的参数覆盖每个Docekrfile 中只能有一个 ENTRYPOINT ,当指定多个时,只有最后一个起效两种格式ENTRYPOINT ["executable", "param1", "param2"]``ENTRYPOINT command param1 param2(shell中执行)
    VOLUME创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。VOLUME [“/data”]
    USER指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户USER daemon
    WORKDIR为后续的 RUNCMDENTRYPOINT 指令配置工作目录。可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。格式为 WORKDIR /path/to/workdir。 WORKDIR /aWORKDIR bWORKDIR cRUN pwd最后的路径为 /a/b/c
    ONBUILD配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。格式为 ONBUILD [INSTRUCTION]

    6.3 创建镜像

    编写完成 Dockerfile 之后,可以通过 docker build 命令来创建镜像

    docker build [选项] 路径 该命令江都区指定路径下(包括子目录)的Dockerfile,并将该路径下所有内容发送给 Docker 服务端,有服务端来创建镜像。可以通过 .dockerignore 文件来让 Docker 忽略路径下的目录与文件

    # 使用 -t 指定镜像的标签信息docker build -t myrepo/myimage .

    七、Docker Compose

    7.1 介绍

    Docker Compose 是 Docker 官方编排项目之一,负责快速在集群中部署分布式应用。维护地址:https://github.com/docker/compose,由 Python 编写,实际调用 Docker提供的API实现。

    Dockerfile 可以让用户管理一个单独的应用容器,而 Compose 则允许用户在一个模版(YAML格式)中定义一组相关联的应用容器(被称为一个project/项目),例如一个 web容器再加上数据库、redis等。

    7.2 安装

    # 使用 pip 进行安装pip install -U docker-compose# 查看用法docker-ompose -h# 添加 bash 补全命令curl -L https://raw.githubusercontent.com/docker/compose/1.2.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose

    7.3 使用

    术语

    • 服务/service: 一个应用容器,实际上可以运行多个相同镜像的实例
    • 项目/project: 有一组关联的应用容器组成的完成业务单元

    示例:创建一个 Haproxy 挂载三个 Web 容器

    创建一个 compose-haproxy-web 目录,作为项目工作目录,并在其中分别创建两个子目录: haproxyweb

    compose-haproxy-webcompose-haproxy-webgit clone https://github.com/yelog/compose-haproxy-web.git

    目录长这样:

    compose-haproxy-web├── docker-compose.yml├── haproxy│   └── haproxy.cfg└── web    ├── Dockerfile    ├── index.html    └── index.py

    在该目录执行 docker-compose up 命令,会整合输出所有容器的输出

    $ docker-compose upStarting compose-haproxy-web_webb_1 ... doneStarting compose-haproxy-web_webc_1 ... doneStarting compose-haproxy-web_weba_1 ... doneRecreating compose-haproxy-web_haproxy_1 ... doneAttaching to compose-haproxy-web_webb_1, compose-haproxy-web_weba_1, compose-haproxy-web_webc_1, compose-haproxy-web_haproxy_1haproxy_1  | [NOTICE] 244/131022 (1) : haproxy version is 2.2.2haproxy_1  | [NOTICE] 244/131022 (1) : path to executable is /usr/local/sbin/haproxyhaproxy_1  | [ALERT] 244/131022 (1) : parsing [/usr/local/etc/haproxy/haproxy.cfg:14] : 'listen' cannot handle unexpected argument ':70'.haproxy_1  | [ALERT] 244/131022 (1) : parsing [/usr/local/etc/haproxy/haproxy.cfg:14] : please use the 'bind' keyword for listening addresses.haproxy_1  | [ALERT] 244/131022 (1) : Error(s) found in configuration file : /usr/local/etc/haproxy/haproxy.cfghaproxy_1  | [ALERT] 244/131022 (1) : Fatal errors found in configuration.compose-haproxy-web_haproxy_1 exited with code 1

    此时访问本地的 80 端口,会经过 haproxy 自动转发到后端的某个 web 容器上,刷新页面,可以观察到访问的容器地址的变化。

    7.4 命令说明

    大部分命令都可以运行在一个或多个服务上。如果没有特别的说明,命令则应用在项目所有的服务上。

    执行 docker-compose [COMMAND] --help 查看具体某个命令的使用说明

    使用格式

    docker-compose [options] [COMMAND] [ARGS...]
    build构建/重建服务服务一旦构建后,将会带上一个标记名,例如 web_db可以随时在项目目录运行 docker-compose build 来重新构建服务
    help获得一个命令的信息
    kill通过发送 SIGKILL 信号来强制停止服务容器,支持通过参数来指定发送信号,例如docker-compose kill -s SIGINT
    logs查看服务的输出
    port打印绑定的公共端口
    ps列出所有容器
    pull拉去服务镜像
    rm删除停止的服务容器
    run在一个服务上执行一个命令docker-compose run ubuntu ping docker.com
    scale设置同一个服务运行的容器个数通过 service=num 的参数来设置数量docker-compose scale web=2 worker=3
    start启动一个已经存在的服务容器
    stop停止一个已经运行的容器,但不删除。可以通过 docker-compose start 再次启动
    up构建、创建、启动、链接一个服务相关的容器链接服务都将被启动,除非他们已经运行docker-compose up -d 将后台运行并启动docker-compose up 已存在容器将会重新创建docker-compose up --no-recreate 将不会重新创建容器

    7.5 环境变量

    环境变量可以用来配置 Compose 的行为

    Docker_ 开头的变量用来配置 Docker 命令行客户端使用的一样

    COMPOSE_PROJECT_NAME设置通过 Compose 启动的每一个容器前添加的项目名称,默认是当前工作目录的名字。
    COMPOSE_FILE设置要使用的 docker-compose.yml 的路径。默认路径是当前工作目录。
    DOCKER_HOST设置 Docker daemon 的地址。默认使用 unix:///var/run/docker.sock,与 Docker 客户端采用的默认值一致。
    DOCKER_TLS_VERIFY如果设置不为空,则与 Docker daemon 交互通过 TLS 进行。
    DOCKER_CERT_PATH配置 TLS 通信所需要的验证(ca.pemcert.pemkey.pem)文件的路径,默认是 ~/.docker

    7.6 docker-compose.yml

    默认模版文件是 docker-compose.yml ,启动定义了每个服务都必须经过 image 指令指定镜像或 build 指令(需要 Dockerfile) 来自动构建。

    其他大部分指令跟 docker run 类似

    如果使用 build 指令,在 Dockerfile 中设置的选项(如 CMDEXPOSE 等)将会被自动获取,无需在 docker-compose.yml 中再次设置。

    **image**

    指定镜像名称或镜像ID,如果本地仓库不存在,将尝试从远程仓库拉去此镜像

    image: ubuntuimage: orchardup/postgresqlimage: a4bc65fd**build**

    指定 Dockerfile 所在文件的路径。Compose 将利用它自动构建这个镜像,然后使用这个镜像。

    build: /path/to/build/dir**command**

    覆盖容器启动默认执行命令

    command: bundle exec thin -p 3000**links**

    链接到其他服务中的容器,使用服务名称或别名

    links:    - db  - db:database  - redis

    别名会自动在服务器中的 /etc/hosts 里创建。例如:

    172.17.2.186  db172.17.2.186  database172.17.2.187  redis**external_links**

    连接到 docker-compose.yml 外部的容器,甚至并非 Compose 管理的容器。

    external_links: - redis_1 - project_db_1:mysql - project_db_1:postgresql

    ports

    暴露端口信息 HOST:CONTAINER

    格式或者仅仅指定容器的端口(宿主机会随机分配端口)

    ports: - "3000" - "8000:8000" - "49100:22" - "127.0.0.1:8001:8001"

    注:当使用 HOST:CONTAINER 格式来映射端口时,如果你使用的容器端口小于 60 你可能会得到错误得结果,因为 YAML 将会解析 xx:yy 这种数字格式为 60 进制。所以建议采用字符串格式。

    **expose**

    暴露端口,但不映射到宿主机,只被连接的服务访问

    expose: - "3000" - "8000"

    volumes

    卷挂载路径设置。可以设置宿主机路径 (HOST:CONTAINER) 或加上访问模式 (HOST:CONTAINER:ro)。

    volumes: - /var/lib/mysql - cache/:/tmp/cache - ~/configs:/etc/configs/:ro

    **
    **

    volumes_from

    从另一个服务或容器挂载它的所有卷。

    volumes_from: - service_name - container_name
    environment

    设置环境变量。你可以使用数组或字典两种格式。

    只给定名称的变量会自动获取它在 Compose 主机上的值,可以用来防止泄露不必要的数据。

    environment:  RACK_ENV: development  SESSION_SECRET:environment:  - RACK_ENV=development  - SESSION_SECRET

    env_file

    从文件中获取环境变量,可以为单独的文件路径或列表。

    如果通过 docker-compose -f FILE 指定了模板文件,则 env_file 中路径会基于模板文件路径。

    如果有变量名称与 environment 指令冲突,则以后者为准。

    env_file: .envenv_file:  - ./common.env  - ./apps/web.env  - /opt/secrets.env

    环境变量文件中每一行必须符合格式,支持 # 开头的注释行。

    # common.env: Set Rails/Rack environmentRACK_ENV=development

    extends

    基于已有的服务进行扩展。例如我们已经有了一个 webapp 服务,模板文件为 common.yml

    # common.ymlwebapp:  build: ./webapp  environment:    - DEBUG=false    - SEND_EMAILS=false

    编写一个新的 development.yml 文件,使用 common.yml 中的 webapp 服务进行扩展。

    # development.ymlweb:  extends:    file: common.yml    service: webapp  ports:    - "8000:8000"  links:    - db  environment:    - DEBUG=truedb:  image: postgres

    后者会自动继承 common.yml 中的 webapp 服务及相关环节变量。

    **
    **

    net

    设置网络模式。使用和 docker client--net 参数一样的值。

    net: "bridge"net: "none"net: "container:[name or id]"net: "host"

    **
    **

    pid

    跟主机系统共享进程命名空间。打开该选项的容器可以相互通过进程 ID 来访问和操作。

    pid: "host"

    dns

    配置 DNS 服务器。可以是一个值,也可以是一个列表。

    dns: 8.8.8.8dns:  - 8.8.8.8  - 9.9.9.9

    cap_add, cap_drop

    添加或放弃容器的 Linux 能力(Capabiliity)。

    cap_add:  - ALLcap_drop:  - NET_ADMIN  - SYS_ADMIN

    **
    **

    dns_search

    配置 DNS 搜索域。可以是一个值,也可以是一个列表。

    dns_search: example.comdns_search:  - domain1.example.com  - domain2.example.com

    **
    **

    working_dir, entrypoint, user, hostname, domainname, mem_limit, privileged, restart, stdin_open, tty, cpu_shares

    这些都是和 docker run 支持的选项类似。

    八、安全

    8.1 内核命名空间

    当使用 docker run 启动一个容器时,在后台 Docker 为容器创建一个独立的命名空间和控制集合。命名空间踢空了最基础的也是最直接的隔离,在容器中运行的进程不会被运行在主机上的进程和其他容器发现和作用。

    8.2 控制组

    控制组是 Linux 容器机制的另一个关键组件,负责实现资源的审计和限制。

    它提供了很多特性,确保哥哥容器可以公平地分享主机的内存、CPU、磁盘IO等资源;当然,更重要的是,控制组确保了当容器内的资源使用产生压力时不会连累主机系统。

    8.3 内核能力机制

    能力机制是 Linux 内核的一个强大特性,可以提供细粒度的权限访问控制。 可以作用在进程上,也可以作用在文件上。

    例如一个服务需要绑定低于 1024 的端口权限,并不需要 root 权限,那么它只需要被授权 net_bind_service 能力即可。

    默认情况下, Docker 启动的容器被严格限制只允许使用内核的一部分能力。

    使用能力机制加强 Docker 容器的安全有很多好处,可以按需分配给容器权限,这样,即便攻击者在容器中取得了 root 权限,也不能获取宿主机较高权限,能进行的破坏也是有限的。

    参考资料

    https://docs.docker.com/engine/reference/commandline/images/

    http://www.dockerinfo.net/

    ]]>
    @@ -469,7 +496,7 @@ http://yelog.org/2020/05/23/3-hexo-comment/ 2020-05-23T14:26:23.000Z - 2024-07-05T01:50:55.231Z + 2024-07-05T11:10:22.503Z 前言

    目前 3-hexo 已经集成了评论系统有 gitalkgitmentdisqus来必力utteranc

    一、gitalk

    gitalk 是一款基于 Github Issue 和 Preact 开发的评论插件 官网: https://gitalk.github.io/

    1. 登录 github ,注册应用

    点击进行注册 ,如下

    注册应用

    注册完后,可得到 Client IDClient Secret

    2. 新建存放评论的仓库

    因为 gitalk 是基于 Github 的 Issue 的,所以需要指定一个仓库,用来承接 gitalk 的评论,我们一般使用 Github Page 来做我们博客的评论,所以,新建仓库名为 xxx.github.io,其中 xxx 为你的 Github 用户名

    3. 配置主题

    在主题下 _config.yml 中找到如下配置,启用评论,并使用 gitalk

    ##########评论设置#############comment:  on: true  type: gitalk

    在主题下 _config.yml 中找到 gitalk 配置,将 第1步 得到的 Client IDClient Secret 复制到如下位置

    gitalk:  githubID:    # 填你的 github 用户名  repo:  xxx.github.io # 承载评论的仓库,一般使用 Github Page 仓库  ClientID:   # 第1步获得 Client ID  ClientSecret:  # 第1步获得 Client Secret  adminUser:     # Github 用户名  distractionFreeMode: true  language: zh-CN  perPage: 10

    二、来必力

    1. 创建来必力账号,并选择 City 免费版

    官网http://livere.com/ ,创建账号,点击上面的安装,选择 City 免费版

    选择 city 免费版

    复制获取到的代码中的 data-uid

    复制 data-uid

    2. 主题选择使用来必力评论

    在主题下 _config.yml

    在找到来必力配置如下,第一步中复制的 data-uid 粘贴到下面 data_uid

    livere:  data_uid: xxxxxx

    找到以下代码, 开启并选择 livere (来必力)

    ##########评论设置#############comment:  on: true  type: livere

    三、utteranc

    官网地址:https://utteranc.es/

    1. 安装 utterances

    点我进行安装

    2. 配置主题

    在主题下 _config.yml 中找到 utteranc 的配置 ,修改 repo 为自己的仓库名

    utteranc:  repo: xxx/xxx.github.io # 承载评论的仓库,填上自己的仓库  issue_term: pathname    # Issue 与 博客文章 之间映射关系  label: utteranc         # 创建的 Issue 添加的标签  theme: github-light     # 主题,可选主题请查看官方文档 https://utteranc.es/#heading-theme# 官方文档 https://utteranc.es/# 使用说明 https://yelog.org//2020/05/23/3-hexo-comment/

    在主题下 _config.yml 中找到如下配置,启用评论,并使用 utteranc

    comment:  on: true  type: utteranc
    ]]>
    @@ -495,7 +522,7 @@ http://yelog.org/2019/11/12/3-hexo-support-mermaid/ 2019-11-12T01:55:37.000Z - 2024-07-05T01:50:55.302Z + 2024-07-05T11:10:22.576Z 一、说明

    开启

    1. 安装hexo插件
    npm install hexo-filter-mermaid-diagrams
    1. 修改themes/3-hexo/_config.ymlmermaid.on,开启主题支持
    # Mermaid 支持mermaid:  on: true  cdn: //cdn.jsdelivr.net/npm/mermaid@8.4.2/dist/mermaid.min.js  #cdn: //cdnjs.cloudflare.com/ajax/libs/mermaid/8.3.1/mermaid.min.js  options: # 更多配置信息可以参考 https://mermaidjs.github.io/#/mermaidAPI    theme: 'default'    startOnLoad: true    flowchart:      useMaxWidth: false      htmlLabels: true
    1. 在markdown中,像写代码块一样写图表

    二、示例

    以下示例源码可以在这边查看 本文源码
    更多示例可以查看官网:https://mermaidjs.github.io

    1. flowchart

    graph TD;    A-->B;    A-->C;    B-->D;    C-->D;
    graph TB    c1-->a2    subgraph one    a1-->a2    end    subgraph two    b1-->b2    end    subgraph three    c1-->c2    end

    2.Sequence diagrams

    sequenceDiagram    participant Alice    participant Bob    Alice->>John: Hello John, how are you?    loop Healthcheck        John->>John: Fight against hypochondria    end    Note right of John: Rational thoughts 
    prevail! John-->>Alice: Great! John->>Bob: How about you? Bob-->>John: Jolly good!

    3.Class diagrams

    classDiagram     Animal <|-- Duck     Animal <|-- Fish     Animal <|-- Zebra     Animal : +int age     Animal : +String gender     Animal: +isMammal()     Animal: +mate()     class Duck{         +String beakColor         +swim()         +quack()     }     class Fish{         -int sizeInFeet         -canEat()     }     class Zebra{         +bool is_wild         +run()     }

    4.State diagrams

    stateDiagram       [*] --> Active       state Active {           [*] --> NumLockOff           NumLockOff --> NumLockOn : EvNumLockPressed           NumLockOn --> NumLockOff : EvNumLockPressed           --           [*] --> CapsLockOff           CapsLockOff --> CapsLockOn : EvCapsLockPressed           CapsLockOn --> CapsLockOff : EvCapsLockPressed           --           [*] --> ScrollLockOff           ScrollLockOff --> ScrollLockOn : EvCapsLockPressed           ScrollLockOn --> ScrollLockOff : EvCapsLockPressed       }

    5.Gantt diagrams

    gantt       dateFormat  YYYY-MM-DD       title Adding GANTT diagram functionality to mermaid       section A section       Completed task            :done,    des1, 2014-01-06,2014-01-08       Active task               :active,  des2, 2014-01-09, 3d       Future task               :         des3, after des2, 5d       Future task2              :         des4, after des3, 5d       section Critical tasks       Completed task in the critical line :crit, done, 2014-01-06,24h       Implement parser and jison          :crit, done, after des1, 2d       Create tests for parser             :crit, active, 3d       Future task in critical line        :crit, 5d       Create tests for renderer           :2d       Add to mermaid                      :1d       section Documentation       Describe gantt syntax               :active, a1, after des1, 3d       Add gantt diagram to demo page      :after a1  , 20h       Add another diagram to demo page    :doc1, after a1  , 48h       section Last section       Describe gantt syntax               :after doc1, 3d       Add gantt diagram to demo page      :20h       Add another diagram to demo page    :48h

    6.Pie chart diagrams

    pie    "Dogs" : 386    "Cats" : 85    "Rats" : 15
    ]]>
    @@ -523,7 +550,7 @@ http://yelog.org/2019/10/08/3-hexo-add-music/ 2019-10-08T02:44:30.000Z - 2024-07-05T01:50:55.244Z + 2024-07-05T11:10:22.516Z 网易云音乐

    1. 复制网易云音乐插件代码

    前往网易云音乐官网,搜索一个作为背景音乐的歌曲,并进入播放页面,点击 生成外链播放器
    生成外链播放器

    设置好想要显示的样式后,复制 html 代码

    最好外层在加一个 div,如下,可直接将上一步复制的 iframe 替换下方里面的 iframe

    <div id="musicMouseDrag" style="position:fixed; z-index: 9999; bottom: 0; right: 0;">    <div id="musicDragArea" style="position: absolute; top: 0; left: 0; width: 100%;height: 10px;cursor: move; z-index: 10;"></div>    <iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=38592976&auto=1&height=66"></iframe></div>

    2. 将插件引入到主题中

    将上一步加过 div 的代码粘贴到主题下 layout/_partial/footer.ejs 的最后面
    效果

    3. 调整位置

    默认给的样式是显示在右下角,可以通过调整上一步粘贴的 divstylebottomright 来调整位置。

    4. 自由拖动

    如果需要自由拖动,在刚才添加的代码后面,再添加下面代码即可,鼠标就可以在音乐控件的 上边沿 点击拖动

    <!--以下代码是为了支持随时拖动音乐控件的位置,如没有需求,可去掉下面代码--><script>    var $DOC = $(document)    $('#musicMouseDrag').on('mousedown', function (e) {      // 阻止文本选中      $DOC.bind("selectstart", function () {        return false;      });      $('#musicDragArea').css('height', '100%');      var $moveTarget = $('#musicMouseDrag');      $moveTarget.css('border', '1px dashed grey')      var div_x = e.pageX - $moveTarget.offset().left;      var div_y = e.pageY - $moveTarget.offset().top;      $DOC.on('mousemove', function (e) {        var targetX = e.pageX - div_x;        var targetY = e.pageY - div_y;        targetX = targetX < 0 ? 0 : (targetX + $moveTarget.outerWidth() >= window.innerWidth) ? window.innerWidth - $moveTarget.outerWidth() : targetX;        targetY = targetY < 0 ? 0 : (targetY + $moveTarget.outerHeight() >= window.innerHeight) ? window.innerHeight - $moveTarget.outerHeight() : targetY;        $moveTarget.css({'left': targetX + 'px', 'top': targetY + 'px', 'bottom': 'inherit', 'right': 'inherit'})      }).on('mouseup', function () {        $DOC.unbind("selectstart");        $DOC.off('mousemove')        $DOC.off('mouseup')        $moveTarget.css('border', 'none')        $('#musicDragArea').css('height', '10px');      })    })</script>
    ]]>
    @@ -535,34 +562,6 @@ - - - - - - - - - -
    - - - 3-hexo支持jsfiddle渲染 - - http://yelog.org/2019/09/24/3-hexo-jsfiddle/ - 2019-09-24T01:59:37.000Z - 2024-07-05T01:50:55.252Z - - 1. canvas 粒子效果

    2. 复选框动画

    ]]>
    - - - - - <h3 id="1-canvas-粒子效果"><a href="#1-canvas-粒子效果" class="headerlink" title="1. canvas 粒子效果"></a>1. canvas 粒子效果</h3><script async src="//jsfidd - - - - diff --git "a/categories/\345\220\216\347\253\257/index.html" "b/categories/\345\220\216\347\253\257/index.html" index bc247a939..9e1dbfdcf 100644 --- "a/categories/\345\220\216\347\253\257/index.html" +++ "b/categories/\345\220\216\347\253\257/index.html" @@ -248,7 +248,7 @@
  • 全部文章 - (163) + (164)
  • @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + .layui-select-title>input.layui-input{ border-bottom: 0} select[multiple]+.layui-form-select dd{ padding:0;} select[multiple]+.layui-form-select .layui-form-checkbox[lay-skin=primary]{ margin:0 !important; display:block; line-height:36px !important; position:relative; padding-left:26px;} select[multiple]+.layui-form-select .layui-form-checkbox[lay-skin=primary] span{line-height:36px !important; float:none;} select[multiple]+.layui-form-select .layui-form-checkbox[lay-skin=primary] i{ position:absolute; left:10px; top:0; margin-top:9px;} .multiSelect{ line-height:normal; height:auto; padding:4px 10px; overflow:hidden;min-height:38px; margin-top:-38px; left:0; z-index:99;position:relative;background:none;} .multiSelect a{ padding:2px 5px; background:#908e8e; border-radius:2px; color:#fff; display:block; line-height:20px; height:20px; margin:2px 5px 2px 0; float:left;} .multiSelect a span{ float:left;} .multiSelect a i {float:left;display:block;margin:2px 0 0 2px;border-radius:2px;width:8px;height:8px;padding:4px;position:relative;-webkit-transition:all .3s;transition:all .3s} .multiSelect a i:before, .multiSelect a i:after {position:absolute;left:8px;top:2px;content:'';height:12px;width:1px;background-color:#fff} .multiSelect a i:before {-webkit-transform:rotate(45deg);transform:rotate(45deg)} .multiSelect a i:after {-webkit-transform:rotate(-45deg);transform:rotate(-45deg)} .multiSelect a i:hover{ background-color:#545556;} /* 下面是页面内样式,无需引用 */ .layui-block { margin-bottom: 10px; } .layui-form-label { width: 180px; } .code { color: gray; margin-left: 10px; } .unshow>#result { display: none; } pre { padding: 5px; margin: 5px; } .string { color: green; } .number { color: darkorange; } .boolean { color: blue; } .null { color: magenta; } .key { color: red; } 基础属性 属性名 属性值 备注 multiple 无 开启多选 lay-search 无 开启搜索 lay-case 无 大小写敏感 lay-omit 无 开启多选简写,显示勾选条数 单选select 单选 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon <select> 单选+搜索+大小写不敏感 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon <select lay-search> 单选+搜索+大小写敏感 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon <select lay-search lay-case> 查看表单信息 多选select 多选 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon <select multiple> 简化多选 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon <select multiple lay-omit> 多选+搜索+大小写不敏感 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon <select multiple lay-search> 简化多选+搜索+大小写敏感 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon <select multiple lay-search lay-case lay-omit> 查看表单信息 赋值 // 有两种赋值方式: 1. 直接在option中写selected。 2. 通过 js 赋值。 // 1. 直接在option中写selected 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon // 2. 通过 js 赋值。 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon <script> // 手动赋值 $('select[name=\"简化多选+搜索+大小写敏感\"]').val(['sing1', 'movie2']); form.render(); </script> layui.use(['form','code'], function () { var form = layui.form, $ = layui.$; // 代码块 layui.code({ title: 'html', encode: true, about: false }); // 手动赋值 $('select[name=\"简化多选+搜索+大小写敏感\"]').val(['sing1', 'movie2']); form.render(); // 提交事件 form.on(\"submit(*)\", function (data) { $('#result').html(syntaxHighlight(data.field)); layer.open({ type: 1, title: '提交信息', shadeClose: true, content:$('#result') }); return false; }); // json 格式化+高亮 function syntaxHighlight(json) { if (typeof json != 'string') { json = JSON.stringify(json, undefined, 2); } json = json.replace(/&/g, '&').replace(//g, '>'); return json.replace(/(\"(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\\"])*\"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)/g, function(match) { var cls = 'number'; if (/^\"/.test(match)) { if (/:$/.test(match)) { cls = 'key'; } else { cls = 'string'; } } else if (/true|false/.test(match)) { cls = 'boolean'; } else if (/null/.test(match)) { cls = 'null'; } return '' + match + ''; }); } })"},{"title":"404 Not Found:该页无法显示","date":"2016-09-27T03:31:01.000Z","updated":"2023-07-10T00:28:41.032Z","comments":true,"path":"/404.html","permalink":"http://yelog.org/404.html","excerpt":"","text":"很抱歉,您所访问的地址并不存在"},{"title":"关于&留言板","date":"2016-09-27T02:27:42.000Z","updated":"2023-07-10T00:28:41.049Z","comments":true,"path":"about/index.html","permalink":"http://yelog.org/about/index.html","excerpt":"","text":"个人简介杨玉杰, 毕业于厦小理,目前从事 Java 及相关工作。 喜欢研究新兴技术和未来发展方向。 最近在彭小六的六扇门内,研习阅读方法、知识管理、时间管理等方法。 联系方式 QQ : 872336115 邮箱 : jaytp@qq.com"}],"posts":[{"title":"2024年MacOS终端大比拼","slug":"mac-terminal","date":"2024-06-23T07:54:00.000Z","updated":"2024-07-05T01:50:55.063Z","comments":true,"path":"2024/06/23/macos-terminal/","permalink":"http://yelog.org/2024/06/23/macos-terminal/","excerpt":"","text":"最流行的终端横评| capability | Kitty | Alacritty | WezTerm | iTerm2 | Native || —- | —- | —- | —- | —- | || key-bind | | | | | | log2024-06-27 放弃 Kitty -> 转为 WezTerm Kitty 绑定 cmd-shift-f 在 tmux 下无法使用, 且没有 vim-mode 2024-06-26 放弃使用 Alacritty -> 转为 Kitty 因为在 vim 的 normal 模式下, 如果是中文输入法, 输入的内容会出现在输入法的候选框内, 然后按 <CAPS> 按键切换输入法, 候选框中的输入的字母, 会以 insert 的方式输出到光标所在的位置, 这个问题在 WezTerm 和 Kitty 中没有出现. 注意: Kitty 的 map cmd+1 send_key cmd+1 能够正常映射到 NeoVim 中进行 maps.n["<D-1>"] = { "<cmd>Neotree left toggle<cr>", desc = "Toggle Explorer" } 绑定, 但是开启 tmux 后, cmd+1 映射到 NeoVim 中就不行了 2024-06-20 放弃 WezTerm -> 转为 Alacritty","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"软件记录","slug":"工具/软件记录","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95/"}],"tags":[{"name":"mac","slug":"mac","permalink":"http://yelog.org/tags/mac/"},{"name":"efficiency","slug":"efficiency","permalink":"http://yelog.org/tags/efficiency/"},{"name":"terminal","slug":"terminal","permalink":"http://yelog.org/tags/terminal/"}]},{"title":"离线安装Home Assistant","slug":"home-assistant","date":"2024-03-22T01:05:44.000Z","updated":"2024-07-05T01:50:55.435Z","comments":true,"path":"2024/03/22/home-assistant/","permalink":"http://yelog.org/2024/03/22/home-assistant/","excerpt":"","text":"前言最近搬到了新家,家里的智能设备也越来越多, 引入很多米家设备, 但博主使用的是苹果生态, 需要将这些不支持 homekit 的米家设备接入到 homekit 中, 经过调研发现 Home Assistant 是一个不错的选择, 本文会介绍联网安装的过程, 并且如果有需要联网的步骤, 也会提供离线安装的方法. 本篇主要介绍通过 docker 部署的方式 安装 Docker如已有 docker 环境, 可以跳过这一步 sudo apt-get update sudo","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"IOT","slug":"工具/IOT","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/IOT/"}],"tags":[{"name":"home-assistant","slug":"home-assistant","permalink":"http://yelog.org/tags/home-assistant/"},{"name":"iot","slug":"iot","permalink":"http://yelog.org/tags/iot/"}]},{"title":"K8s-内部培训","slug":"K8s-内部培训","date":"2024-02-05T01:17:27.000Z","updated":"2024-07-05T01:50:54.456Z","comments":true,"path":"2024/02/05/k8s-inner-training/","permalink":"http://yelog.org/2024/02/05/k8s-inner-training/","excerpt":"","text":"K8s 内部培训介绍K8s 为 Kubernetes 的简称, 是一个开源的容器编排平台, 最初是由 Google 工程师开发和设计的, 后于 2015 年捐赠给了云原生计算机基金会-CNCF 应用服务管理发展史早期服务应用大多以单包的形式运行在服务器上, 当我们增加一些服务时, 比如添加 JOB 应用时, 我们会另开一个新的应用, 但基本用一台服务器上就能完成 所以我们早期应用的发展就如下面图表所示, 由于用户数量较少, 所以更新应用时短暂的暂停服务也是可以接受的 ┌──────────────**石器时代**───────────────┐ ┌──────────────**石器时代**───────────────┐ ┌──────────────**石器时代**───────────────┐ │ ┌─────────────`Server1`───────────────┐ │ │ ┌─────────────`Server1`───────────────┐ │ │ ┌─────────────`Server1`───────────────┐ │ │ │ ┌──☕──┐ │ │ │ │ ┌──☕──┐ ┌──☕──┐ │ │ │ │ ┌──☕──┐ ┌──☕──┐ ┌──☕──┐ │ │ │ │ │ APP1 │ │ │ │ │ │ APP1 │ │ APP2 │ │ │ │ │ │ APP1 │ │ APP2 │ │ APP3 │ │ │ │ │ └──────┘ │ │ ==> │ │ └──────┘ └──────┘ │ │ ==> │ │ └──────┘ └──────┘ └──────┘ │ │ │ ├─────────────────────────────────────┤ │ │ ├─────────────────────────────────────┤ │ │ ├─────────────────────────────────────┤ │ │ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ └─────────────────────────────────────┘ │ │ └─────────────────────────────────────┘ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────┘ └─────────────────────────────────────────┘ └─────────────────────────────────────────┘ 随着用户数量的上升, 应用的并发也随之提高, 单台服务器的压力也随之增大, 有了如下情况: 高峰期经常出现卡顿 更新应用时的暂停服务已经不可接受 在服务器出现故障时的高可用有了更高的要求 为了解决上面的问题, 我们就进入了下个时代(下图一), 采购多台服务器, 对应用进行支持集群的改造, 这时我们的应用分别在三台服务器上, 并发能力提高了3倍, 并且冗灾能力大幅提升 尽管我们解决了上面的问题, 但是带来了新的问题, 因为服务器数量过多, 在安装应用需要的工具如 JDK、Tomcat、Node、Nginx、Redis 等等, 可能会因为安装版本和服务器系统版本不一致导致应用运行失败 所以会在环境安装中浪费太多时间, 所以很多企业开始引入如 Docker 的虚拟化技术(下图二), 用来解决环境不一致的问题, 并且一并解决了守护进程, 开机启动等问题 这时我们通过 docker-compose 技术, 升级应用、调整配置相比以前大大简化, 但是随着应用规模的扩大, 对应用高可用有了更高的要求, 纷纷开始进行微服务拆分, 应用数量和服务器数量越来越多, 服务的运维管理越来越复杂、 大家开始开发各种集群管理, 让大家可以在一个地方并且可视化的管理集群中的所有 Docker, 以 Google 开源的 Kubernetes 做的最功能完善且灵活可配置, 从而开始爆火. 于是众多企业开始上K8s(下图三), 不仅解决运维复杂的问题, 而且带来了更多更好的特性: 服务发现和负载均衡 存储编排 自动部署和回滚 自我修复 密钥和配置管理 ┌──────────────**农耕文明**───────────────┐ ┌─────────────────**工业文明**───────────────┐ ┌─────────────────**信息文明**───────────────┐ │ ┌─────────────`Server1`───────────────┐ │ │ ┌────────────────`Server1`───────────────┐ │ │ ┌────────────────`K8s 集群`──────────────┐ │ │ │ ┌──☕──┐ ┌──☕──┐ ┌──☕──┐ │ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ │ │ │ │ APP1 │ │ APP2 │ │ APP3 │ │ │ │ │ │ APP1 ││ APP2 ││ APP3 ││ │ │ │ │ POD1 ││ POD2 ││ POD3 ││ │ │ │ └──────┘ └──────┘ └──────┘ │ │ │ │ ├───────────┤├───────────┤├───────────┤│ │ │ │ ├───────────┤├───────────┤├───────────┤│ │ │ ├─────────────────────────────────────┤ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ │ │ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ │ └───────────┘└───────────┘└───────────┘│ │ │ │ └───────────┘└───────────┘└───────────┘│ │ │ └─────────────────────────────────────┘ │ │ └────────────────────────────────────────┘ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ │ │ ┌─────────────`Server2`───────────────┐ │ │ ┌────────────────`Server1`───────────────┐ │ │ │ │ POD4 ││ POD5 ││ POD6 ││ │ │ │ ┌──☕──┐ ┌──☕──┐ ┌──☕──┐ │ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ │ │ │ ├───────────┤├───────────┤├───────────┤│ │ │ │ │ APP1 │ │ APP2 │ │ APP3 │ │ │ │ │ │ APP1 ││ APP2 ││ APP3 ││ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ │ │ │ └──────┘ └──────┘ └──────┘ │ │ ==> │ │ ├───────────┤├───────────┤├───────────┤│ │ ==> │ │ └───────────┘└───────────┘└───────────┘│ │ │ ├─────────────────────────────────────┤ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ │ │ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ │ └───────────┘└───────────┘└───────────┘│ │ │ │ │ POD7 ││ POD8 ││ POD9 ││ │ │ └─────────────────────────────────────┘ │ │ └────────────────────────────────────────┘ │ │ │ ├───────────┤├───────────┤├───────────┤│ │ │ ┌─────────────`Server3`───────────────┐ │ │ ┌────────────────`Server1`───────────────┐ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ │ │ │ ┌──☕──┐ ┌──☕──┐ ┌──☕──┐ │ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ │ │ │ └───────────┘└───────────┘└───────────┘│ │ │ │ │ APP1 │ │ APP2 │ │ APP3 │ │ │ │ │ │ APP1 ││ APP2 ││ APP3 ││ │ │ ├────────────────────────────────────────┤ │ │ │ └──────┘ └──────┘ └──────┘ │ │ │ │ ├───────────┤├───────────┤├───────────┤│ │ │ │ ┌───────┐ ┌───────┐ ┌───────┐ │ │ │ ├─────────────────────────────────────┤ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ │ │ │ │Server1│ │Server2│ │Server3│ ••• │ │ │ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ │ └───────────┘└───────────┘└───────────┘│ │ │ │ └───────┘ └───────┘ └───────┘ │ │ │ └─────────────────────────────────────┘ │ │ └────────────────────────────────────────┘ │ │ └────────────────────────────────────────┘ │ ├─────────────────────────────────────────┤ ├────────────────────────────────────────────┤ ├────────────────────────────────────────────┤ │ 优点: 节省资源, 需要掌握的知识较少 │ │ 优点: 环境搭建容易, 安装Docker和配置文件 │ │ 优点: 自动故障恢复, 监控完善, 操作方便 │ │ 缺点: 运维操作繁杂, JDK版本难以统一 │ │ 缺点: 随着规模扩大, 日常运维也变得繁杂 │ │ 缺点: k8s 功能较多, 需要掌握的知识也多 │ └─────────────────────────────────────────┘ └────────────────────────────────────────────┘ └────────────────────────────────────────────┘ 图一 图二 图三 Kubernetes 组件 Control Plane Components: 控制平面组件 kube-apiserver: 负责公开 Kubernetes API, 处理请求, 类似 cloud 中的网关 etcd: key-value 存储, 用于保存集群数据 kube-scheduler: 任务调度, 监听有新创建但未运行的pods, 选择节点来让 pod 在上面运行 kube-controller-manager: 负责运行控制器进程, 有如下不同类型的控制器 Node Controller: 节点控制器, 负责节点出现故障时进行通知和响应 Job Controller: 任务控制器, 检测代表一次性任务的 Job 对象, 然后创建 Pod 来运行这些任务直至完成 EndpointSlice Controller: 端点分片控制器, 提供 Service 和 Pod 之间的链接 ServiceAccount Controller: 为新的命名空间创建默认的服务账号 cloud-controller-manager: 云控制管理器, 集成云提供商的API, 我们内网部署的用不到 Node Components: 节点组件, 运行在各个节点, 负责维护运行的 Pod, 提供 Kubernetes 的运行环境 kubelet: 在每个节点中运行, 保证容器都运行在 Pod 中, kubelet 接受一组 PodSpec, 确保 PodSpec 中描述的容器处于运行状态且健康 kube-proxy: 网络代理, 是实现 Service 的一部分 Container Runtime: 容器运行时, Kubernetes 支持需要容器运行环境, 例如: docker, containerd, CRI-O Addons: 插件, 提供集群级别的功能, 插件提供的资源属于 kube-system 命名空间 DNS: 提供集群内的域名系统 Web UI/Dashboard: 通用的基于 Web 的用户界面, 它使用户可以集中管理集群中的应用已经集群本身 Container Resource Monitoring: 将容器的一些常见的时间序列度量值保存到一个集中的数据库中, 并提供浏览这些数据的界面 Cluster-level Logging: 集群级日志, 将容器日志保存到一个集中的日志存储中, 这种集中日志存储提供搜索和浏览接口 Network Plugins: 网络插件, 实现容器网络接口(CNI)规范的软件组件, 负责为 Pod 分配 IP 地址, 并使这些 Pod 能在集群内部互相通信 Kubernetes 架构 Node 节点Kubernetes 通过将容器放入在节点(Node) 上运行的 Pod 来执行你的负载. 节点可以是一个虚拟机或物理机, 每个节点包含 Pod 所需的服务器, 这些节点由 Control Plane 负责管理 一个集群的节点数量可以是1个, 也可以是多个. 且节点名称是唯一的. 可以通过 kubectl 来创建和修改 Node 对象 # 查一下集群中的所有节点信息 kubectl get node # 查看某个节点详细信息 kubectl descibe $NODENAME # 标记一个 Node 为不可调度 kubectl cordon $NODENAME Controllers 控制器在机器人和自动化领域, 又一个类似的概念叫控制回路 (Control Loop), 用于调节系统状态, 如: 房间里的温度自动调节器 当你设置了温度, 温度自动调节器让其当前状态接近期望温度; 在 Kubernetes 中, 控制器通过监控集群的公共状态, 并致力于将当前的状态转为期望状态 控制器是通过通知 apiserver 来管理状态的, 就像温度自动调节器是通过控制空调来调节气温的 Container Runtime Interface/CRI 容器运行时接口CRI 是一个插件接口, 它使 kubelet 能够使用各种容器运行时, 定义了主要 gRPC 协议, 用于节点组件 kubelet 和容器运行时之间的通信 Containers 容器容器将应用从底层主机设备中解耦, 这使得在不同的云或 OS 环境中部署更加容易 Kubernetes 集群中的每个节点都会运行容器, 这些容器构成分配给该节点的 Pod, 单个 Pod 中的容器会在共同调度下, 运行在相同的节点 容器镜像是一个随时可以运行的软件包, 它包含了运行容器程序所需要的一切, 代码和它需要的运行时、应用程序和系统库, 以及一些基本设置 容器运行时这个基础组件使 Kubernetes 能够有效运行容器, 他负责管理 Kubernetes 环境中的容器的执行和生命周期 PodPod 是可以在 Kubernetes 中创建和管理的、最小的可部署计算单元 Pod 是有一个或多个容器组成, 这些容器共享存储、网络、已经怎么样运行这些容器的声明, 统一调度. 此外还可以包含 init container, 用于做一些启动主应用前的准备工作, 比如通过 init container 注入 tingyun 等 agent 包 如下示例, 它由一个运行镜像 nginx:1.14.2 的容器组成 apiVersion: v1 kind: Pod metadata: name: nginx labels: app: my-nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 要创建上面显示的 Pod, 保存上面内容到 my-nginx.yaml, 可以通过如下命令 kubectl apply -f my-nginx.yaml Workloads 工作负载工作负载是在 Kubernetes 上运行的应用程序, 无论是又一个还是多个组件构成, 你都可以通过一组 Pod 来运行它, Pod 代表的是集群上处于运行状态的一组容器的集合, 但通常一个 Pod 内只运行一个容器 Kubernetes 提供若干种内置的工作负载资源: Deployment 和 ReplicaSet Deployment 适合无状态应用, Deployment 中的所有 Pod 都是互相等价的 StatefulSet 有状态应用, 比如可以独立持久化文件, 互不影响 DaemonSet 提供节点本地支撑设施的 Pod, 保证每个节点上一个 Job 和 CronJob 定义只需要执行一次并且执行后视为完完成的任务 Network 网络ServiceService 是将一个或者一组 Pod 公开代理给集群内部, 使之能够各个应用之间通信, 甚至用于公开到集群外(NodePort 或者 代理给 Ingress) 它提供了类似域名的访问方式, 使用者无需关心后面有多少个 Pod 在提供服务, 他们是否健康, 他们 IP 是否发生变化. 定义 Service apiVersion: v1 kind: Service metadata: name: nginx-service spec: selector: app: my-nginx ports: - name: name-of-service-port protocol: TCP port: 80 targetPort: http-web-svc 服务类型(type): ClusterIp: 默认值, 智能在集群内访问 NodePort: 直接向集群外暴露, 通过节点端口访问 LoadBalancer: 使用云平台的负载均衡, Kubernetes 不直接提供 Externalname: 将服务映射到 externalName 字段的内容, 例如api.foo.bar.example","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"},{"name":"k8s","slug":"k8s","permalink":"http://yelog.org/tags/k8s/"}]},{"title":"swift 离线图片识别文字(ocr)","slug":"macos-ocr-swift","date":"2024-01-02T02:09:19.000Z","updated":"2024-07-05T01:50:55.446Z","comments":true,"path":"2024/01/02/macos-ocr-swift/","permalink":"http://yelog.org/2024/01/02/macos-ocr-swift/","excerpt":"","text":"背景最近打算写一个 macos 翻译软件, 需要用到 ocr 图像识别, 并且因为速度问题, 一开始就考虑使用系统的自带能力来实现. 经过翻阅文档和 chatgpt 拉扯了一下午, 最终成功实现. 代码代码逻辑为, 接受参数: 图片路径, 然后获取图片, 通过 VNImageRequestHandler 对图片进行文字识别 如下代码可以直接放进一个 ocr.swift, 然后执行 swiftc -o ocr ocr.swift , 在执行 ./ocr /Users/yelog/Desktop/3.png 后面为你实际的有文字的图片路经 // // ocr.swift // Fast Translation // // Created by 杨玉杰 on 2023/12/31. // import SwiftUI import Vision func handleDetectedText(request: VNRequest?, error: Error?) { if let error = error { print("ERROR: \\(error)") return } guard let results = request?.results, results.count > 0 else { print("No text found") return } for result in results { if let observation = result as? VNRecognizedTextObservation { for text in observation.topCandidates(1) { let string = text.string print("识别: \\(string)") } } } } func ocrImage(path: String) { let cgImage = NSImage(byReferencingFile: path)?.ciImage()?.cgImage let requestHandler = VNImageRequestHandler(cgImage: cgImage!) let request = VNRecognizeTextRequest(completionHandler: handleDetectedText) // 设置文本识别的语言为英文 request.recognitionLanguages = ["en"] request.recognitionLevel = .accurate do { try requestHandler.perform([request]) } catch { print("Unable to perform the requests: \\(error).") } } extension NSImage { func ciImage() -> CIImage? { guard let data = self.tiffRepresentation, let bitmap = NSBitmapImageRep(data: data) else { return nil } let ci = CIImage(bitmapImageRep: bitmap) return ci } } // 执行函数,从命令行参数中获取图片的地址 ocrImage(path: CommandLine.arguments[1]) 然后准备待识别的有文字的图片 # 编译 swift 文件 swiftc -o ocr ocr.swift # 执行并且传递图片路径参数 ./ocr /Users/yelog/Desktop/3.png 最后最近打算着手写一些 macos 的小工具, 如果对 swift 或者 macos 感兴趣的可以关注或评论.","categories":[{"name":"开发","slug":"开发","permalink":"http://yelog.org/categories/%E5%BC%80%E5%8F%91/"},{"name":"swift","slug":"开发/swift","permalink":"http://yelog.org/categories/%E5%BC%80%E5%8F%91/swift/"}],"tags":[{"name":"swift","slug":"swift","permalink":"http://yelog.org/tags/swift/"},{"name":"macos","slug":"macos","permalink":"http://yelog.org/tags/macos/"},{"name":"ocr","slug":"ocr","permalink":"http://yelog.org/tags/ocr/"}]},{"title":"Caused by: java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher","slug":"Springboot-JarLauncher","date":"2023-12-11T08:58:00.000Z","updated":"2024-07-05T01:50:55.380Z","comments":true,"path":"2023/12/11/springboot-jarlauncher/","permalink":"http://yelog.org/2023/12/11/springboot-jarlauncher/","excerpt":"","text":"问题现象今天同事再升级框架后(spring-cloud 2022.0.4 -> 2023.0.0)(spring-boot 3.1.6 -> 3.2.0) 同时因为 spring-boot 的版本问题, 需要将 maven 升级到 3.6.3+ 升级后构建 jar 包和构建镜像都是正常的, 但是发布到测试环境就报错 Caused by: java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher 问题分析报错为 JarLauncher 找不到, 检查了 Jenkins 中的打包任务, 发现并没有编译报错, 同事直接使用打包任务中产生的 xx.jar, 可以正常运行. 说明在打包 Docker 镜像前都没有问题, 这时就想起来我们在打包镜像时, 先解压 xx.jar, 然后直接执行 org.springframework.boot.loader.JarLauncher, 所以很可能是升级后, 启动文件 JarLauncher 的路径变了. 为了验证我们的猜想, 我们得看一下实际容器内的文件结构, 但是这时容器一直报错导致无法启动, 不能直接通过 Rancher 查看文件结构, 我们可以通过文件拷贝的方式来解决, 如下: # 下载有问题的镜像, 并且创建容器(不启动) docker create -it --name dumy 10.188.132.123:5000/lemes-cloud/lemes-gateway:develop-202312111536 bash # 直接拷贝容器内的我们想要看的目录 docker cp dumy:/data . 到本地后, 就可以通过合适的工具查找 JarLauncher 文件, 我这里通过 vim 来寻找, 如下图: 发现比原来多了一层目录 launch, 所以问题就发生在这里了. 解决方案我们在打包脚本 JenkinsCommon.groovy 中根据当前打包的 JDK 版本来判断使用的启动类路径, 如下: 再次打包, 应用正常启动. ReferenceJarLauncher","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"},{"name":"spring-boot","slug":"spring-boot","permalink":"http://yelog.org/tags/spring-boot/"}]},{"title":"ideavim 使用百分号%支持xml的对应标签跳转","slug":"idea-tips-percent-match-xml","date":"2023-06-17T08:09:00.000Z","updated":"2024-07-05T01:50:55.202Z","comments":true,"path":"2023/06/17/idea-tips-percent-mach-xml/","permalink":"http://yelog.org/2023/06/17/idea-tips-percent-mach-xml/","excerpt":"","text":"前言由于最近几年使用 vim 的频率越来越高, 所以在 idea 中也大量开始使用 vim 技巧, 在一年多前碰到个问题, 终于在最近解决了。 问题描述在 idea 中, 在 normal 模式下, 使用 % 不能在匹配标签(xml/html等) 之间跳转 解决方案在 ~/.ideavimrc 中添加如下设置, 重启 idea 即可 set matchit 效果 最后最近会把一些加的 tips 分享出来,大家有什么建议和问题都可以在评论区讨论.","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"IDEA","slug":"工具/IDEA","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/IDEA/"}],"tags":[{"name":"efficiency","slug":"efficiency","permalink":"http://yelog.org/tags/efficiency/"},{"name":"vim","slug":"vim","permalink":"http://yelog.org/tags/vim/"},{"name":"IntellijIDEA","slug":"IntellijIDEA","permalink":"http://yelog.org/tags/IntellijIDEA/"}]},{"title":"Raycast 最强效率软件推荐","slug":"mac-raycast","date":"2023-05-17T09:45:00.000Z","updated":"2024-07-05T01:50:55.050Z","comments":true,"path":"2023/05/17/mac-raycast/","permalink":"http://yelog.org/2023/05/17/mac-raycast/","excerpt":"","text":"软件介绍 最近在很多平台上看到 Raycast 的推荐文章, 今天就尝试了一下, 发现确实不错, 完全可以替代我目前对 Alfred 的使用, 甚至很多地方做得更好, 所以本文就是介绍我使用 Raycast 的一些效果(多图预警), 方便那些还没有接触这个软件的人对它有个了解, 如果有插件推荐, 欢迎在评论区进行讨论。 Raycast 是 MacMac 平台独占的效率工具, 主要包含如下功能: 应用启动 文件查找 窗口管理 剪贴板历史 Snippets 应用菜单查询 插件扩展功能 翻译 斗图 结束进程 查询端口占用 查询ip 除此之外, Raycast提供的在线插件商店, 可以很方便的进行功能扩展 Raycast官网 核心功能应用启动 并且支持卸载应用, 找到应用, cmd+k 打开操作菜单, 下拉到最后 文件查找 窗口管理 剪贴板历史推荐使用 Clipboard History 这个扩展,和 Alfred 的一样, 并且有分类,效果如下 设置快捷键 cmd+shift+v Snippets通过 Snippets 可以保存自定义片段, 通过关键字快速查询并输出到光标处, 如常用语、 邮箱、手机号、税号、代码片段等等 创建 Snippets 可以通过搜索 Create Snippet, 搜索 Snippets 可以通过搜索Search Snippet 应用菜单查询查询当前激活应用的所有菜单, 不限层级. 可以通过搜索 Search Menu Items 来查询。 推荐插件Easydict(翻译软件)超强的翻译软件, 完美替代我在 Alfred 的 workflow 中配置的有道翻译, 我配置了如下功能 输入中文, 自动翻译成英文 输入英文, 自动翻译成中文 支持一键发音 Open AI 翻译长文本 Doutu表情包查询,选中回车就复制到剪贴板了, 就可以粘贴到 Discord/QQ/Wechat 斗图了, 非常方便。 Kill Process关键字查询, 并一键结束进程 Kill Port查询端口占用的进程, 并支持一键结束进程 MyIp查询当前ip","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"软件记录","slug":"工具/软件记录","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95/"}],"tags":[{"name":"mac","slug":"mac","permalink":"http://yelog.org/tags/mac/"},{"name":"efficiency","slug":"efficiency","permalink":"http://yelog.org/tags/efficiency/"}]},{"title":"[Java]通过 CompletableFuture 实现异步多线程优化请求处理速度","slug":"CompletableFuture","date":"2022-08-01T12:06:14.000Z","updated":"2024-07-05T01:50:55.423Z","comments":true,"path":"2022/08/01/[Java]optimize-request-processing-speed-by-completablefuture/","permalink":"http://yelog.org/2022/08/01/[Java]optimize-request-processing-speed-by-completablefuture/","excerpt":"","text":"零、背景我们在写后端请求的时候, 可能涉及多次 SQL 执行(或其他操作), 当这些请求相互不关联, 在顺序执行时就浪费了时间, 这些不需要先后顺序的操作可以通过多线程进行同时执行, 来加速整个逻辑的执行速度. 既然有了目标和大致思路, 如果有做过前端的小伙伴应该能想起来 Js 里面有个 Promise.all 来解决这个问题, 在 Java 里也有类似功能的类 CompletableFuture , 它可以实现多线程和线程阻塞, 这样能够保证等待多个线程执行完成后再继续操作. 一、CompletableFuture 是什么首先我们先了解一下 CompletableFuture 是干什么, 接下来我们通过简单的示例来介绍他的作用. long startTime = System.currentTimeMillis(); //生成几个任务 List<CompletableFuture<String>> futureList = new ArrayList<>(); futureList.add(CompletableFuture.supplyAsync(()->{ sleep(4000); System.out.println("任务1 完成"); return "任务1的数据"; })); futureList.add(CompletableFuture.supplyAsync(()->{ sleep(2000); System.out.println("任务2 完成"); return "任务2的数据"; })); futureList.add(CompletableFuture.supplyAsync(()->{ sleep(3000); System.out.println("任务3 完成"); return "任务3的数据"; })); //完成任务 CompletableFuture<Void> allTask = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])) .whenComplete((t, e) -> { System.out.println("所有任务都完成了, 返回结果集: " + futureList.stream().map(CompletableFuture::join).collect(Collectors.joining(","))); }); // 阻塞主线程 allTask.join(); System.out.println("main end, cost: " + (System.currentTimeMillis() - startTime)); 执行结果 任务2 完成 任务3 完成 任务1 完成 所有任务都完成了, 返回结果集: 任务1的数据,任务2的数据,任务3的数据 main end, cost: 4032 结果分析: 我们需要执行3个任务, 3个任务同时执行, 互不影响 其中任务2耗时最短,提前打印完成 其次是任务3 最后是执行1完成 当所有任务完成后, 触发 whenComplete 方法, 打印任务的返回结果 最后打印总耗时为 4.032s 结论: 多线程执行后, 耗时取决于最耗时的操作, 而单线程是所有操作耗时之和 二、封装工具类经过上面的测试, 通过 CompletableFuture 已经能够实现我们的预想, 为了操作方便, 我们将封装起来, 便于统一管理 package org.yelog.java.usage.concurrent; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; /** * 执行并发任务 * * @author yangyj13 * @date 11/7/22 9:49 PM */ public class MultiTask<T> { private List<CompletableFuture<T>> futureList; /** * 添加待执行的任务 * * @param completableFuture 任务 * @return 当前对象 */ public MultiTask<T> addTask(CompletableFuture<T> completableFuture) { if (futureList == null) { futureList = new ArrayList<>(); } futureList.add(completableFuture); return this; } /** * 添加待执行的任务(无返回) * * @param task 任务 * @return 当前对象 */ public MultiTask<T> addTask(Consumer<T> task) { addTask(CompletableFuture.supplyAsync(() -> { task.accept(null); return null; })); return this; } /** * 添加待执行的任务(有返回) * * @param task 任务 * @return 当前对象 */ public MultiTask<T> addTask(Function<Object, T> task) { addTask(CompletableFuture.supplyAsync(() -> task.apply(null))); return this; } /** * 开始执行任务 * * @param callback 当所有任务都完成后触发的回调方法 * @param waitTaskExecuteComplete 是否阻塞主线程 */ private void execute(Consumer<List<T>> callback, Boolean waitTaskExecuteComplete) { CompletableFuture<Void> allFuture = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])) .whenComplete((t, e) -> { if (callback != null) { List<T> objectList = new ArrayList<>(); futureList.forEach((future) -> { objectList.add(future.join()); }); callback.accept(objectList); } }); if (callback != null || waitTaskExecuteComplete == null || waitTaskExecuteComplete) { allFuture.join(); } } /** * 开始执行任务 * 等待所有任务完成(阻塞主线程) */ public void execute() { execute(null, true); } /** * 开始执行任务 * * @param waitTaskExecuteComplete 是否阻塞主线程 */ public void execute(Boolean waitTaskExecuteComplete) { execute(null, waitTaskExecuteComplete); } /** * 开始执行任务 * * @param callback 当所有任务都完成后触发的回调方法 */ public void execute(Consumer<List<T>> callback) { execute(callback, true); } } 那么上一步我们测试的流程转换成工具类后如下 long startTime = System.currentTimeMillis(); MultiTask<String> multiTask = new MultiTask<>(); multiTask.addTask(t -> { sleep(1000); System.out.println("任务1 完成"); }).addTask(t -> { sleep(3000); System.out.println("任务2 完成"); }).addTask(CompletableFuture.supplyAsync(()->{ sleep(2000); System.out.println("任务3 完成"); return "任务3的数据"; })).execute(resultList->{ System.out.println("all complete: " + resultList); }); System.out.println("main end, cost: " + (System.currentTimeMillis() - startTime)); 三、应用到实际的效果执行两次数据库的操作如下 public interface TestMapper { @Select("select count(*) from test_user where score < 1000 and user_id = #{userId}") int countScoreLess1000(Integer userId); @Select("select count(1) from test_log where success = true and user_id = #{userId}") int countSuccess(Integer userId); } 调用方法: long start = System.currentTimeMillis(); testMapper.countScoreLess1000(userId); long countScoreLess1000End = System.currentTimeMillis(); log.info("countScoreLess1000 cost: " + (countScoreLess1000End - start)); testMapper.countSuccess(userId); long countSuccessEnd = System.currentTimeMillis(); log.info("countSuccess cost: " + (countSuccessEnd - countScoreLess1000End)); log.info("all cost: " + (countSuccessEnd - start)); 顺序执行的平均时间如下 countScoreLess1000 cost: 368 countSuccess cost: 404 all cost: 772 当我们应用的上面的工具类后的调用方法 MultiTask multiTask = new MultiTask<>(); multiTask.addTask(t -> { testMapper.countScoreLess1000(userId); log.info("countScoreLess1000 cost: " + (System.currentTimeMillis() - start)); }).addTask(t -> { testMapper.countSuccess(userId); log.info("countSuccess cost: " + (System.currentTimeMillis() - start)); }).execute(); log.info("all cost: " + (System.currentTimeMillis() - start)); 效果如下 countScoreLess1000 cost: 433 countSuccess cost: 463 all cost: 464 可以看到各子任务执行时长是差不多的, 但是总耗时使用多线程后有了明显下降 四、总结通过使用 CompletableFuture 实现多线程阻塞执行后, 大幅降低这类请求, 并且当可以异步执行的子任务越多, 效果越明显.","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"concurrent","slug":"concurrent","permalink":"http://yelog.org/tags/concurrent/"}]},{"title":"基于 nacos/灰度发布 实现减少本地启动微服务数量的实践","slug":"reducing-local-springcloud-base-on-nacos-and-gray-release","date":"2022-08-01T12:06:14.000Z","updated":"2024-07-05T01:50:55.376Z","comments":true,"path":"2022/08/01/reducing-local-springcloud-base-on-nacos-and-gray-release/","permalink":"http://yelog.org/2022/08/01/reducing-local-springcloud-base-on-nacos-and-gray-release/","excerpt":"","text":"一、背景后台框架是基于 spring cloud 的微服务体系, 当开发同学在自己电脑上进行开发工作时, 比如开发订单模块, 除了需要启动订单模块外, 还需要启动网关模块、权限校验模块、公共服务模块等依赖模块, 非常消耗开发同学的本地电脑的资源, 也及其浪费时间. 二、解决方案2.1 目标和关键问题能不能开发同学本地只需要启动需要开发的模块:订单模块, 其他模块均适用测试环境中正在运行的服务. 既然要实现的目标有了, 我们就开始研究可行性和关键问题 开发环境和测试环境要在同一个 nacos 的 namespace 中, 这样才有可能让开发环境调用到测试环境的服务. 测试环境只能调用测试环境的微服务, 实现和开发环境的服务隔离 开发同学之间的微服务也要实现服务隔离 2.2 思路既要在同一个 namespace 下, 又要能够实现不同人访问不同的副本, 很容易想到可以利用灰度发布来实现: 测试环境设置 metadata lemes-env=product 来标识测试环境副本, 用于区分开发环境的微服务测测试环境的微服务 开发同学本地启动注册开发环境副本, 都会携带唯一IP, 则我们可以通过IP来区分不同开发同学的副本 假设我们需要开发的 API 的后台服务调用链条如下: 我们需要开发的 API 为 /addMo, 打算写在 Order 这个微服务里面, 并且他会调用 common 这个微服务的 /getDict 获取一个字典数据, /getDict 是现成的, 不需要开发, 如果是之前的情况, 开发本地至少需要启动5个微服务才能进行调试. 三、具体实现3.1 测试环境设置 metadata由于测试环境都是通过容器部署的, 那么启动方式就是下面容器中的 CMD, 我们在其中加入 -Dspring.cloud.nacos.discovery.metadata.lemes-env=product, 用于区分开发环境的微服务测测试环境的微服务 # 说明:Dockerfile 过程分为两部分。第一次用来解压 jar 包,并不会在目标镜像内产生 history/layer。第二部分将解压内容分 layer 拷贝到目标镜像内 # 目的:更新镜像时,只需要传输代码部分,依赖没有变动则不更新,节省发包时的网络传输量 # 原理:在第二部分中,每次 copy 就会在目标镜像内产生一层 layer,将依赖和代码分开, # 绝大部分更新都不会动到依赖,所以只需更新代码几十k左右的代码层即可 FROM 10.176.66.20:5000/library/amazoncorretto:11.0.11 as builder WORKDIR /build ARG ARTIFACT_ID COPY target/${ARTIFACT_ID}.jar app.jar RUN java -Djarmode=layertools -jar app.jar extract && rm app.jar FROM 10.176.66.20:5000/library/amazoncorretto:11.0.11 LABEL maintainer="yangyj13@lenovo.com" WORKDIR /data ARG ARTIFACT_ID ENV ARTIFACT_ID ${ARTIFACT_ID} # 依赖 COPY --from=builder /build/dependencies/ ./ COPY --from=builder /build/snapshot-dependencies/ ./ COPY --from=builder /build/spring-boot-loader/ ./ # 应用代码 COPY --from=builder /build/application/ ./ # 容器运行时启动命令 CMD echo "NACOS_ADDR: ${NACOS_ADDR}"; \\ echo "JAVA_OPTS: ${JAVA_OPTS}"; \\ echo "TZ: ${TZ}"; \\ echo "ARTIFACT_ID: ${ARTIFACT_ID}"; \\ # 去除了 server 的应用名 REAL_APP_NAME=${ARTIFACT_ID//-server/}; \\ echo "REAL_APP_NAME: ${REAL_APP_NAME}"; \\ # 获取当前时间 now=`date +%F+%T+%Z`; \\ # java 启动命令 java $JAVA_OPTS \\ -Dtingyun.app_name=${REAL_APP_NAME}-${TINGYUN_SUFFIX} \\ -Dspring.cloud.nacos.discovery.metadata.lemes-env=product \\ -Dspring.cloud.nacos.discovery.metadata.startup-time=${now} \\ -Dspring.cloud.nacos.discovery.server-addr=${NACOS_ADDR} \\ -Dspring.cloud.nacos.discovery.group=${NACOS_GROUP} \\ -Dspring.cloud.nacos.config.namespace=${NACOS_NAMESPACE} \\ -Dspring.cloud.nacos.discovery.namespace=${NACOS_NAMESPACE} \\ -Dspring.cloud.nacos.discovery.ip=${HOST_IP} \\ org.springframework.boot.loader.JarLauncher 3.2 开发前端传递开启智能连接const devIp = getLocalIP('10.') module.exports = { devServer: { proxy: { '/lemes-api': { target: 'http://10.176.66.58/lemes-api', ws: true, pathRewrite: { '^/lemes-api': '/' }, headers: { 'dev-ip': devIp, 'dev-sc': 'true' } } } }, } // 获取本机 IP function getLocalIP(prefix) { const excludeNets = ['docker', 'cni', 'flannel', 'vi', 've'] const os = require('os') const osType = os.type() // 系统类型 const netInfo = os.networkInterfaces() // 网络信息 const ipList = [] if (prefix) { for (const netInfoKey in netInfo) { if (excludeNets.filter(item => netInfoKey.startsWith(item)).length === 0) { for (let i = 0; i < netInfo[netInfoKey].length; i++) { const net = netInfo[netInfoKey][i] if (net.family === 'IPv4' && net.address.startsWith(prefix)) { ipList.push(net.address) } } } } } if (ipList.length === 0) { if (osType === 'Windows_NT') { for (const dev in netInfo) { // win7的网络信息中显示为本地连接,win10显示为以太网 if (dev === '本地连接' || dev === '以太网') { for (let j = 0; j < netInfo[dev].length; j++) { if (netInfo[dev][j].family === 'IPv4') { ipList.push(netInfo[dev][j].address) } } } } } else if (osType === 'Linux') { ipList.push(netInfo.eth0[0].address) } else if (osType === 'Darwin') { ipList.push(netInfo.en0[0].address) } } console.log('识别到的网卡信息', JSON.stringify(ipList)) return ipList.length > 0 ? ipList[0] : '' } 3.3 后端灰度处理不论是 gateway 还是 openfeign 都是通过 spring 的 loadbalancer 进行应用选择的, 那我们通过实现或者继承 ReactorServiceInstanceLoadBalancer 来重写选择的过程. @Log4j2 public class LemesLoadBalancer implements ReactorServiceInstanceLoadBalancer{ @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; final AtomicInteger position; // loadbalancer 提供的访问当前服务的名称 final String serviceId; // loadbalancer 提供的访问的服务列表 ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider; public LemesLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) { this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000)); } public LemesLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId, int seedPosition) { this.serviceId = serviceId; this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider; this.position = new AtomicInteger(seedPosition); } @Override public Mono<Response<ServiceInstance>> choose(Request request) { ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider .getIfAvailable(NoopServiceInstanceListSupplier::new); RequestDataContext context = (RequestDataContext) request.getContext(); RequestData clientRequest = context.getClientRequest(); return supplier.get(request).next() .map(serviceInstances -> processInstanceResponse(clientRequest,supplier, serviceInstances)); } private Response<ServiceInstance> processInstanceResponse(RequestData clientRequest,ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) { Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(clientRequest,serviceInstances); if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) { ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer()); } return serviceInstanceResponse; } private Response<ServiceInstance> getInstanceResponse(RequestData clientRequest, List<ServiceInstance> instances) { if (instances.isEmpty()) { if (log.isWarnEnabled()) { log.warn("No servers available for service: " + serviceId); } return new EmptyResponse(); } int pos = Math.abs(this.position.incrementAndGet()); // 筛选后的服务列表 List<ServiceInstance> filteredInstances; String devSmartConnect = clientRequest.getHeaders().getFirst(CommonConstants.DEV_SMART_CONNECT); if (StrUtil.equals(devSmartConnect, "true")) { String devIp = clientRequest.getHeaders().getFirst(CommonConstants.DEV_IP); // devIp 为空,为异常情况不处理,返回空实例集合 if (StrUtil.isBlank(devIp)) { log.warn("devIp is NULL,No servers available for service: " + serviceId); return new EmptyResponse(); } // 智能连接: 如果本地启动了服务,则优先访问本地服务,如果本地没有启动,则访问测试环境服务 // 优先调用本地自有服务 filteredInstances = instances.stream().filter(item -> StrUtil.equals(devIp, item.getHost())).collect(Collectors.toList()); // 如果本地服务没有开启,则调用生产/测试服务 if (CollUtil.isEmpty(filteredInstances)) { filteredInstances = instances.stream() .filter(item -> StrUtil.equals(CommonConstants.LEMES_ENV_PRODUCT, item.getMetadata().get("lemes-env"))) .collect(Collectors.toList()); // 解决开发环境无法访问 k8s 集群内 ip 的问题 String oneNacosIp = nacosDiscoveryProperties.getServerAddr().split(",")[0].replaceAll(":[\\\\s\\\\S]*", ""); filteredInstances.forEach(item -> { NacosServiceInstance instance = (NacosServiceInstance) item; // cloud 以 80 端口启动,认为是 k8s 内的应用 if (instance.getPort() == 80) { instance.setHost(oneNacosIp); instance.setPort(Integer.parseInt(item.getMetadata().get("port"))); } }); } } else { // 不是智能访问,则只访问一个环境 // 当前服务 ip String currentIp = nacosDiscoveryProperties.getIp(); String lemesEnv = nacosDiscoveryProperties.getMetadata().get("lemes-env"); filteredInstances = instances.stream() .filter(item -> StrUtil.equals(lemesEnv, CommonConstants.LEMES_ENV_PRODUCT) // 访问测试环境 ? StrUtil.equals(CommonConstants.LEMES_ENV_PRODUCT, item.getMetadata().get("lemes-env")) // 访问开发环境 : StrUtil.equals(currentIp, item.getHost())) .collect(Collectors.toList()); } if (filteredInstances.isEmpty()) { log.warn("No oneself servers and beta servers available for service: " + serviceId + ", use other instances"); // 找不到自己注册IP对应的服务和测试服务,则用nacos中其它的服务 filteredInstances = instances; } //最终的返回的 serviceInstance ServiceInstance instance = filteredInstances.get(pos % filteredInstances.size()); return new DefaultResponse(instance); } }","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"k8s","slug":"k8s","permalink":"http://yelog.org/tags/k8s/"},{"name":"nacos","slug":"nacos","permalink":"http://yelog.org/tags/nacos/"},{"name":"springcloud","slug":"springcloud","permalink":"http://yelog.org/tags/springcloud/"},{"name":"gray-release","slug":"gray-release","permalink":"http://yelog.org/tags/gray-release/"}]},{"title":"基于 nacos/springcloud/k8s 的不停机服务更新[graceful shutdown]","slug":"k8s 的不停机服务更新","date":"2022-07-27T07:35:39.000Z","updated":"2024-07-05T01:50:55.156Z","comments":true,"path":"2022/07/27/springboot-graceful-shutdown-based-on-nacos2-and-k8s/","permalink":"http://yelog.org/2022/07/27/springboot-graceful-shutdown-based-on-nacos2-and-k8s/","excerpt":"","text":"背景我们的 SpringCloud 是部署在 k8s 上的, 当通过 k8s 进行滚动升级时, 会有请求 500 的情况, 不利于用户体验, 严重的可能造成数据错误的问题 k8s 滚动更新策略介绍假设我们要升级的微服务在环境上为3个副本的集群, 升级应用时, 会先启动1个新版本的副本, 然后下线一个旧版本的副本, 之后再启动1个新版本的副本, 一次类推,直到所有旧副本都替换新副本. 通过链路追踪分析, 报错的原因分别由以下两种情况 SpringCloud 中的微服务在升级过程中, 当旧的微服务中还有没有处理完成的请求时, 就开始关闭动作, 造成请求中断 当旧应用执行关闭动作时, 已经开始拒绝请求, 但是 nacos 中的路由并没有及时更新, 造成 gateway/openfeign 在路由时仍会命中正在关闭的应用, 造成请求报错 为了解决这个问题, 我们将利用 springboot 的 graceful shutdown 功能和 nacos 的主动下线功能来解决这个问题. 具体思路如下: 比如当我们执行订单微服务(3个副本)滚动更新时 先启动一个新版本副本4 然后准备关闭副本1, 在关闭之前先通知 nacos 订单服务的副本1下线, 然后由 nacos 通知给其他应用(nacos2.x 是grpc, 所以通知速度比较快), 这样, 订单服务的副本1就不会再接收到请求, 然后执行 graceful shutdown(springboot 原生支持, 启用方法可以看后面代码), 所有请求处理完成后关闭应用. 这样就完成了 副本1 的关闭 启动新版本副本5 再优雅关闭副本2(参考第2点副本1的流程) 然后启动新版本副本6 再优雅关闭副本3 完成了服务不中断的应用升级 实现关键点为了实现上面背景中提到的思路, 主要从如下几个方面入手 创建从 nacos 中下线副本的API我们通过创建自定义名为 deregister 的 endpoint 来通知 nacos 下线副 import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.registry.NacosRegistration; import com.alibaba.cloud.nacos.registry.NacosServiceRegistry; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.stereotype.Component; @Component @Endpoint(id = "deregister") @Log4j2 public class LemesNacosServiceDeregisterEndpoint { @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; @Autowired private NacosRegistration nacosRegistration; @Autowired private NacosServiceRegistry nacosServiceRegistry; /** * 从 nacos 中主动下线,用于 k8s 滚动更新时,提前下线分流流量 * * @param * @return com.lenovo.lemes.framework.core.util.ResultData<java.lang.String> * @author Yujie Yang * @date 4/6/22 2:57 PM */ @ReadOperation public String endpoint() { String serviceName = nacosDiscoveryProperties.getService(); String groupName = nacosDiscoveryProperties.getGroup(); String clusterName = nacosDiscoveryProperties.getClusterName(); String ip = nacosDiscoveryProperties.getIp(); int port = nacosDiscoveryProperties.getPort(); log.info("deregister from nacos, serviceName:{}, groupName:{}, clusterName:{}, ip:{}, port:{}", serviceName, groupName, clusterName, ip, port); // 设置服务下线 nacosServiceRegistry.setStatus(nacosRegistration, "DOWN"); return "success"; } } 支持 Graceful Shutdown由于 springboot 原生支持, 我们只需要在 bootstrap.yaml 中添加如下配置即可 server: # 开启优雅下线 shutdown: graceful spring: lifecycle: # 优雅下线超时时间 timeout-per-shutdown-phase: 5m # 暴露 shutdown 接口 management: endpoint: shutdown: enabled: true endpoints: web: exposure: include: shutdown K8s 配置有了上面两个 API, 接下来就配置到 k8s 上 terminationGracePeriodSeconds 如果关闭应用的时间超过 10 分钟, 则向容器发送 TERM 信号, 防止应用长时间下线不了 preStop 先执行下线操作, 等待30s, 留够通知到其他应用的时间, 然后执行 graceful shutdown 关闭应用 --- apiVersion: apps/v1 kind: Deployment metadata: name: lemes-service-common labels: app: lemes-service-common spec: replicas: 2 selector: matchLabels: app: lemes-service-common # strategy: # type: RollingUpdate # rollingUpdate: ## replicas - maxUnavailable < running num < replicas + maxSurge # maxUnavailable: 1 # maxSurge: 1 template: metadata: labels: app: lemes-service-common spec: # 容器重启策略 Never Always OnFailure # restartPolicy: Never # 如果关闭时间超过10分钟, 则向容器发送 TERM 信号 terminationGracePeriodSeconds: 600 affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - podAffinityTerm: topologyKey: "kubernetes.io/hostname" labelSelector: matchExpressions: - key: app operator: In values: - lemes-service-common weight: 100 # requiredDuringSchedulingIgnoredDuringExecution: # - labelSelector: # matchExpressions: # - key: app # operator: In # values: # - lemes-service-common # topologyKey: "kubernetes.io/hostname" volumes: - name: lemes-host-path hostPath: path: /data/logs type: DirectoryOrCreate - name: sidecar emptyDir: { } containers: - name: lemes-service-common image: 10.176.66.20:5000/lemes-cloud/lemes-service-common-server:v0.1 imagePullPolicy: Always volumeMounts: - name: lemes-host-path mountPath: /data/logs - name: sidecar mountPath: /sidecar ports: - containerPort: 80 resources: # 资源通常情况下的占用 requests: memory: '2048Mi' # 资源占用上限 limits: memory: '4096Mi' livenessProbe: httpGet: path: /actuator/health/liveness port: 80 initialDelaySeconds: 5 # 探针可以连续失败的次数 failureThreshold: 10 # 探针超时时间 timeoutSeconds: 10 # 多久执行一次探针查询 periodSeconds: 10 startupProbe: httpGet: path: /actuator/health/liveness port: 80 failureThreshold: 30 timeoutSeconds: 10 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health/readiness port: 80 initialDelaySeconds: 5 timeoutSeconds: 10 periodSeconds: 10 lifecycle: preStop: exec: # 应用关闭操作:1. 从 nacos 下线,2. 等待30s, 保证 nacos 通知到其他应用 2.触发 springboot 的 graceful shutdown command: - sh - -c - curl http://127.0.0.1/actuator/deregister;sleep 30;curl -X POST http://127.0.0.1/actuator/shutdown;","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"k8s","slug":"k8s","permalink":"http://yelog.org/tags/k8s/"},{"name":"nacos","slug":"nacos","permalink":"http://yelog.org/tags/nacos/"},{"name":"springcloud","slug":"springcloud","permalink":"http://yelog.org/tags/springcloud/"}]},{"title":"el-drawer 实现鼠标拖拽宽度[ElementUI]","slug":"el-drawer-drag-width","date":"2022-06-24T11:38:00.000Z","updated":"2024-07-05T01:50:55.114Z","comments":true,"path":"2022/06/24/el-drawer-drag-width/","permalink":"http://yelog.org/2022/06/24/el-drawer-drag-width/","excerpt":"","text":"实现效果 实现思路通过指令的方式, 在 drawer 的左侧边缘, 添加一个触发拖拽的长条形区域, 监听鼠标左键按下时启动 document.onmousemove 的监听, 监听鼠标距离浏览器右边的距离, 设置为 drawer 的宽度, 并添加约束: 不能小于浏览器宽度的 20%, 不能大于浏览器宽度的 80%. 指令代码创建文件 src/directive/elment-ui/drawer-drag-width.js, 内容如下 import Vue from 'vue' /** * el-drawer 拖拽指令 */ Vue.directive('el-drawer-drag-width', { bind(el, binding, vnode, oldVnode) { const drawerEle = el.querySelector('.el-drawer') console.log(drawerEle) // 创建触发拖拽的元素 const dragItem = document.createElement('div') // 将元素放置到抽屉的左边边缘 dragItem.style.cssText = 'height: 100%;width: 5px;cursor: w-resize;position: absolute;left: 0;' drawerEle.append(dragItem) dragItem.onmousedown = (downEvent) => { // 拖拽时禁用文本选中 document.body.style.userSelect = 'none' document.onmousemove = function(moveEvent) { // 获取鼠标距离浏览器右边缘的距离 let realWidth = document.body.clientWidth - moveEvent.pageX const width30 = document.body.clientWidth * 0.2 const width80 = document.body.clientWidth * 0.8 // 宽度不能大于浏览器宽度 80%,不能小于宽度的 20% realWidth = realWidth > width80 ? width80 : realWidth < width30 ? width30 : realWidth drawerEle.style.width = realWidth + 'px' } document.onmouseup = function(e) { // 拖拽时结束时,取消禁用文本选中 document.body.style.userSelect = 'initial' document.onmousemove = null document.onmouseup = null } } } }) 然后在 main.js 中将其导入 import './directive/element-ui/drawer-drag-width' 指令使用在 el-drawer 上添加指令 v-el-drawer-drag-width 即可, 如下 <el-drawer v-el-drawer-drag-width :visible.sync="helpDrawer.show" direction="rtl" class="my-drawer" > <template #title> <div class="draw-title">{{ helpDrawer.title }}</div> </template> <Editor v-model="helpDrawer.html" v-loading="helpDrawer.loading" class="my-wang-editor" style="overflow-y: auto;" :default-config="helpDrawer.editorConfig" :mode="helpDrawer.mode" @onCreated="onCreatedHelp" /> </el-drawer>","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"ElementUI","slug":"ElementUI","permalink":"http://yelog.org/tags/ElementUI/"},{"name":"Vue","slug":"Vue","permalink":"http://yelog.org/tags/Vue/"}]},{"title":"SpringCloud系列之接入SkyWalking进行链路追踪和日志收集","slug":"springcloud-skywalking","date":"2021-09-26T10:08:00.000Z","updated":"2024-07-05T01:50:55.404Z","comments":true,"path":"2021/09/26/spring-cloud-skywalking/","permalink":"http://yelog.org/2021/09/26/spring-cloud-skywalking/","excerpt":"","text":"前言前一段时间一直在研究升级公司项目的架构,在不断学习和试错后,最终确定了一套基于 k8s 的高可用架构体系,未来几期会将这套架构体系的架设过程和注意事项以系列文章的形式分享出来,敬请期待! 由于集群和分布式规模的扩大,对微服务链路的监控和日志收集,越来越有必要性,所以在筛选了了一些方案后,发现 SkyWalking 完美符合我们的预期,对链路追踪和日志收集都有不错的实现。 SkyWalking 简介SkyWalking 是一款 APM(应用程序监控)系统,转为微服务、云原生、基于容器的架构而设计。主要包含了一下核心功能 对服务、运行实例、API进行指标分析 链路检测,检查缓慢的服务和API 对基础设施(VM、网络、磁盘、数据库)进行监控 对超出阈值的情况进行警报 等等 开源地址:apache/skywalking 官网:Apache SkyWalking SpringCloud 整合 SkyWalking1. 搭建 SkyWalking 服务在使用 SkyWalking 进行链路追踪和日志收集之前,需要先搭建起一套 SkyWalking 的服务,然后才能通过 agent 将 SpringCloud 的运行状态和日志发送给 SkyWalking 进行解析和展示。 SkyWalking 的搭建方式有很多中,我这里介绍两种 docker-compose(非高可用,快速启动,方便测试、学习) 和 k8s(高可用、生产级别) docker-compose 的方式docker 和 docker-compose 的安装不是本文的重点,所以有需要可以自行查询。 以下操作会启动三个容器 elasticsearch 作为 skywalking 的存储,保存链路和日志数据等 oap 数据接收和分析 Observability Analysis Platform ui web端的数据展示 # 创建配置文件保存的目录 mkdir -p /data/docker/admin/skywalking # 切换到刚创建的目录 cd /data/docker/admin/skywalking # 将下面的 docker-compose.yml 文件保存到这个目录 vi docker-compose.yml # 拉去镜像并启动 docker-compose up -d # 查看日志 docker-compose logs -f docker-compose.yml version: '3.8' services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:7.14.1 container_name: elasticsearch restart: always ports: - 9200:9200 healthcheck: test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 40s environment: - discovery.type=single-node - bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - TZ=Asia/Shanghai ulimits: memlock: soft: -1 hard: -1 oap: image: apache/skywalking-oap-server:8.7.0-es7 container_name: oap depends_on: - elasticsearch links: - elasticsearch restart: always ports: - 11800:11800 - 12800:12800 healthcheck: test: ["CMD-SHELL", "/skywalking/bin/swctl"] interval: 30s timeout: 10s retries: 3 start_period: 40s environment: TZ: Asia/Shanghai SW_STORAGE: elasticsearch7 SW_STORAGE_ES_CLUSTER_NODES: elasticsearch:9200 ui: image: apache/skywalking-ui:8.7.0 container_name: ui depends_on: - oap links: - oap restart: always ports: - 8088:8080 environment: TZ: Asia/Shanghai SW_OAP_ADDRESS: http://oap:12800 启动之后浏览器访问 服务ip:8080 即可 k8s等待更新。。 2. 下载 agent 代理包点击链接进行下载,skywalking-apm-8.7 其他版本可以看 apache 归档站,找到对应版本的 .tar.gz 后缀的包,进行下载 通过命令或者软件进行解压 tar -zxvf apache-skywalking-apm-8.7.0.tar.gz 3. java 命令使用代码启动 jar 包springcloud/springboot 一般是通过 java -jar xxx.jar 进行启动。我们只需要在其中加上 -javaagent 参数即可,如下 其中 自定义服务名 可以改为应用名 如 lemes-auth,服务ip 为第一步搭建的 SkyWalking 服务的ip,端口11800 为启动的 oap 这个容器的端口 java -javaagent:上一步解压目录/agent/skywalking-agent.jar=agent.service_name=自定义服务名,collector.backend_service=服务ip:11800 -jar xx.jar 执行命令启动后,访问以下接口,就可以在第一步 服务ip:8080 中看到访问的链接和调用链路。 4. 开启日志收集本文主要以 log4j2 来介绍,其他的大同小异,可以网上找教程。SpringCloud 集成 log4j2 不是本文重点,所以请自行 Google。 引入依赖要开启日志收集,必须要添加依赖,如下 <dependency> <groupId>org.apache.skywalking</groupId> <artifactId>apm-toolkit-log4j-2.x</artifactId> <version>8.7.0</version> </dependency> 修改 log4j2.xml需要修改 log4j2.xml 主要添加下面两个关键点 添加 %traceId 来打印 traceid 声明 GRPCLogClientAppender 完整内容如下 <?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!-- Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时, 你会看到log4j2内部各种详细输出。可以设置成OFF(关闭) 或 Error(只输出错误信息)。 --> <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数--> <configuration status="WARN" monitorInterval="30"> <Properties> <Property name="log.path">logs/lemes-auth</Property> <Property name="logging.lemes.pattern"> %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%traceId] [%logger{50}.%M:%L] - %msg%n </Property> </Properties> <Appenders> <!-- 输出控制台日志的配置 --> <Console name="Console" target="SYSTEM_OUT"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/> <!-- 输出日志的格式 --> <PatternLayout pattern="${logging.lemes.pattern}"/> </Console> <RollingRandomAccessFile name="debugRollingFile" fileName="${log.path}/debug.log" filePattern="${log.path}/debug/$${date:yyyy-MM}/debug.%d{yyyy-MM-dd}-%i.log.gz"> <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout charset="UTF-8" pattern="${logging.lemes.pattern}"/> <Policies> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="100 MB"/> </Policies> <DefaultRolloverStrategy max="30"/> </RollingRandomAccessFile> <GRPCLogClientAppender name="grpc-log"> <PatternLayout pattern="${logging.lemes.pattern}"/> </GRPCLogClientAppender> </Appenders> <Loggers> <!-- ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF --> <Logger name="com.lenovo.lemes" level="debug"/> <Logger name="org.apache.kafka" level="warn"/> <Root level="info"> <AppenderRef ref="Console"/> <AppenderRef ref="debugRollingFile"/> <AppenderRef ref="grpc-log"/> </Root> </Loggers> </configuration> 启动命令中声明上报日志在上一步的 agent 中添加上报日志的参数 plugin.toolkit.log.grpc.reporter.server_host=服务ip,plugin.toolkit.log.grpc.reporter.server_port=11800 完整如下 java -javaagent:上一步解压目录/agent/skywalking-agent.jar=agent.service_name=自定义服务名,collector.backend_service=服务ip:11800,plugin.toolkit.log.grpc.reporter.server_host=服务ip,plugin.toolkit.log.grpc.reporter.server_port=11800 -jar xx.jar 日志收集效果这样启动日志中就会打印 traceid , N/A 代表的是非请求的日志,有 traceid 的为 api 请求日志 在 skywalking 中就能看到我们上报的日志 重点:SkyWalking 可以在链路追踪中查看当前请求的所有日志(不同实例/模块) 5. 兼容 spring-cloud-gateway经过上面的步骤之后,链路已经搭建完成,查看发现了一个问题,gateway 模块的 traceId 和 业务模块的 traceId 不统一。 这是由于 SkyWalking 对于 spring-cloud-gateway 的支持不是默认的,所以需要将 agent/optional-plugins/apm-spring-cloud-gateway-2.1.x-plugin-8.7.0.jar 复制到 agent/plugins 下,然后重启即可。 最后SkyWalking 上面这两个功能就已经非常强大,能够有效帮助我们优化我们的程序,监控系统的问题,并及时报警。日志收集也解决的在大规模分布式集群下日志查询难的问题。 SkyWalking 还支持 VM、浏览器、k8s等监控,后续如果有实践,将会逐步更新。","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"SpringCloud","slug":"SpringCloud","permalink":"http://yelog.org/tags/SpringCloud/"},{"name":"SkyWalking","slug":"SkyWalking","permalink":"http://yelog.org/tags/SkyWalking/"}]},{"title":"3-hexo添加自定义图标","slug":"3-hexo-add-icon","date":"2020-12-28T14:00:00.000Z","updated":"2024-07-05T01:50:55.222Z","comments":true,"path":"2020/12/28/3-hexo-add-icon/","permalink":"http://yelog.org/2020/12/28/3-hexo-add-icon/","excerpt":"","text":"一、前言鉴于许多人问过如何添加自定义图标,这里就详细说明一下,以备后人乘凉。 这篇文章主要讲解是从 iconfont 添加图标。 二、添加彩色图标2.1 登录并添加图标访问 iconfont,点击如下图位置登录,可以使用 Github 账号登录。 登录成功后,搜索合适的图标,然后点击添加到购物车,如下图所示。 添加了多个后,可以点击右上角的“购物车”,添加到项目,点击加号创建项目,如下图所示。 添加完成后回到项目页面,找到自己刚刚创建的项目。 如果没有到项目页面,可以点击上面菜单进入:资源管理 -> 我的项目 2.2 引入 3-hexo 中点击下载到本地,解压并复制其中的 iconfont.js 到项目 3-hexo/source/js/ 下,并改名 custom-iconfont.js。 在文件 3-hexo/layout/_partial/meta.ejs 最后追加下面一行。 <script src="<%=theme.blog_path?theme.blog_path.lastIndexOf("/") === theme.blog_path.length-1?theme.blog_path.slice(0, theme.blog_path.length-1):theme.blog_path:'' %>/js/custom-iconfont.js?v=<%=theme.version%>" ></script> 2.3 在配置文件中添加生效修改 3-hexo/_config.yml 如下图所示 完成! 图标名如上面的 gitee 可以在 网站上修改,如下图所示 三、添加黑白图标link.theme=white 3.1 同 2.13.2 引入 3-hexo 中点击生成代码,如下图所示。 复制生成的代码,修改 font-family 的值为 custom-iconfont,添加到 3-hexo/source/css/_partial/font.styl 最后,并写入图标信息,content 可以移到图标上进行复制,注意前面斜杠转译和去掉后面的分号。 @font-face { font-family: 'custom-iconfont'; /* project id 2298064 */ src: url('//at.alicdn.com/t/font_2298064_34vkk4c9945.eot'); src: url('//at.alicdn.com/t/font_2298064_34vkk4c9945.eot?#iefix') format('embedded-opentype'), url('//at.alicdn.com/t/font_2298064_34vkk4c9945.woff2') format('woff2'), url('//at.alicdn.com/t/font_2298064_34vkk4c9945.woff') format('woff'), url('//at.alicdn.com/t/font_2298064_34vkk4c9945.ttf') format('truetype'), url('//at.alicdn.com/t/font_2298064_34vkk4c9945.svg#iconfont') format('svg'); } .icon-gitee:before { content: "\\e602"; } .icon-youtubeautored:before { content: "\\e649"; } 3.3 在配置文件中添加生效 同2.2结束!","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"一文看懂JavaScript中的Promise","slug":"一文看懂JavaScript中的Promise","date":"2020-10-20T11:43:44.000Z","updated":"2024-07-05T01:50:55.098Z","comments":true,"path":"2020/10/20/know-javascript-promise/","permalink":"http://yelog.org/2020/10/20/know-javascript-promise/","excerpt":"","text":"一、Promise 是什么Promise 是 ES6 提供的原生对象,用来处理异步操作 它有三种状态 pending: 初始状态,不是成功或失败状态。 fulfilled: 意味着操作成功完成。 rejected: 意味着操作失败。 二、使用2.1 创建 Promise通过 new Promise 来实例化,支持链式调用 new Promise((resolve, reject)=>{ // 逻辑 }).then(()=>{ //当上面"逻辑"中调用 resolve() 时触发此方法 }).catch(()=>{ //当上面"逻辑"中调用 reject() 时触发此方法 }) 2.2 执行顺序Promise一旦创建就立即执行,并且无法中途取消,执行逻辑和顺序可以从下面的示例中获得 如下,可修改 if 条件来改变异步结果,下面打印开始的数字是执行顺序 在线调试此示例 - jsbin console.log('1.开始创建并执行 Promise') new Promise(function(resolve, reject) { console.log('2.由于创建会立即执行,所以会立即执行到本行') setTimeout(()=>{ // 模拟异步请求 console.log('4. 1s之期已到,开始执行异步操作') if (true) { // 一般我们符合预期的结果时调用 resolve(),会在 .then 中继续执行 resolve('成功') } else { // 不符合预期时调用 reject(),会在 .catch 中继续执行 reject('不符合预期') } }, 1000) }).then((res)=>{ console.log('5.调用了then,接收数据:' + res) }).catch((error)=>{ console.log('5.调用了catch,错误信息:' + error) }) console.log('3.本行为同步操作,所以先于 Promise 内的异步操作(setTimeout)') 执行结果如下 "1.开始创建并执行 Promise" "2.由于创建会立即执行,所以会立即执行到本行" "3.本行为同步操作,所以先于 Promise 内的异步操作(setTimeout)" "4. 1s之期已到,开始执行异步操作" "5.调用了then,接收数据:成功" 2.3 用函数封装 Promise这是比较常用的方法,如下用 setTimeout 模拟异步请求,封装通用请求函数 在线调试此示例 - jsbin // 这是一个异步方法 function ajax(url){ return new Promise(resolve=>{ console.log('异步方法开始执行') setTimeout(()=>{ console.log('异步方法执行完成') resolve(url+'的结果集') }, 1000) }) } // 调用请求函数,并接受处理返回结果 ajax('/user/list').then((res)=>{ console.log(res) }) 执行结果 "异步方法开始执行" "异步方法执行完成" "/user/list的结果集" 三、高级用法3.1 同时支持Callback与Promise在线调试此示例 - jsbin function ajax(url, success, fail) { if (typeof success === 'function') { setTimeout(() => { if (true) { success({user: '羊'}) } else if (typeof fail === 'function') { console.log(typeof fail) fail('用户不存在') } }, 1000) } else { return new Promise((resolve, reject) => { this.ajax(url, resolve, reject) }) } } // callback 调用方式 ajax('/user/get', (res)=>{ console.log('Callback请求成功!返回结果:', res) }, (error)=>{ console.log('Callback请求失败!错误信息:', error) }) // Promise 调用方式 ajax('/user/get').then((res)=>{ console.log('Pormise请求成功!返回结果:', res) }).catch((error)=>{ console.log('Promise请求失败!返回结果:', error) }) 执行结果 Callback请求成功!返回结果: {user: "羊"} Pormise请求成功!返回结果: {user: "羊"} 3.2 链式调用.then 支持返回 Promise 对象进行链式调用 ajax('/user/info').then((res)=>{ // 用户信息查询成功后,可以根据返回结果查询后续信息 console.log('用户信息:', res) return ajax('/user/score') }).then((res)=>{ console.log('用户成绩:', res) return ajax('/user/friends') }).then((res)=>{ console.log('用户朋友:', res) }) 3.3 Promise.allPromise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。在线调试此示例 - jsbin // 生成一个Promise对象的数组 var promises = [2, 3, 5, 7, 11, 13].map(function(id){ return new Promise((resolve, reject)=>{ if (id % 3 === 0) { resolve(id) } else { reject(id) } }); }); Promise.all(promises).then(function(post) { console.log('全部通过') }).catch(function(reason){ console.log('未全部通过,有问题id:'+reason) }); 执行结果 未全部通过,有问题id:2 Referencemozilla web docs","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"javascript","slug":"javascript","permalink":"http://yelog.org/tags/javascript/"}]},{"title":"Docker 技术整理","slug":"Docker-技术整理","date":"2020-09-01T14:11:00.000Z","updated":"2024-07-05T01:50:54.938Z","comments":true,"path":"2020/09/01/Docker-summary/","permalink":"http://yelog.org/2020/09/01/Docker-summary/","excerpt":"","text":"一、概述1.1 什么是dockerDocker 诞生于 2013 年初,由 dotCloud 公司(后改名为 Docker Inc)基于 Go 语言实现并开源的项目。此项目后来加入 Linux基金会,遵从了 Apache 2.0 协议 Docker 项目的目标是实现轻量级的操作系统虚拟化解决方案。Docker 是在 Linux 容器技术(LXC)的基础上进行了封装,让用户可以快速并可靠的将应用程序从一台运行到另一台上。 使用容器部署应用被称为容器化,容器化技术的几大优势: 灵活:甚至复杂的应用也可以被容器化 轻量:容器利用和共享宿主机内核,从而在利用系统资源比虚拟机更加的有效 可移植:你可以在本地构建,在云端部署并在任何地方运行 松耦合:容器是高度封装和自给自足的,允许你在不破环其他容器的情况下替换或升级任何一个 可扩展:你可以通过数据中心来新增和自动分发容器 安全:容器依赖强约束和独立的进程 1.2 和传统虚拟机的区别容器在Linux上本地运行,并与其他容器共享主机的内核。它运行一个离散进程,不占用任何其他可执行文件更多的内存,从而使其轻巧。 1.3 相关链接官网:https://www.docker.com/ 文档:https://docs.docker.com/ 二、Image镜像2.1 介绍Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。 父镜像:每个镜像都可能依赖于有一个或多个下层组成的另一个镜像。下层那个镜像就是上层镜像的父镜像 基础镜像:一个没有任何父镜像的镜像,被称为基础镜像 镜像ID:所有镜像都是通过一个 64 位十六进制字符串(256 bit 的值)来标识的。为了简化使用,前 12 个自负可以组成一个短ID,可以在命令行中使用。短ID还是有一定的碰撞几率,所以服务器总是返回长ID 2.2 从仓库下载镜像可以通过 docker pull 命令从仓库获取所需要的镜像 docker pull [选项] [Docker Registry 地址]<镜像名>:<标签> 选项: –all-tags,-a : 拉去所有 tagged 镜像 –disable-content-trust:忽略镜像的校验,默认 –platform:如果服务器是开启多平台支持的,则需要设置平台 –quiet,-q:静默执行,不打印详细信息 标签: 下载指定标签的镜像,默认 latest 示例 # 从 Docker Hub 下载最新的 debian 镜像 docker pull debian # 从 Docker Hub 下载 jessie 版 debian 镜像 docker pull debian:jessie # 下载指定摘要(sha256)的镜像 docker pull ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2 2.3 列出本地镜像# 列出已下载的镜像 image_name: 指定列出某个镜像 docker images [选项] [image_name] 选项 参数 描述 –all, -a 展示所有镜像(包括 intermediate 镜像) –digests 展示摘要 –filter, -f 添加过滤条件 –format 使用 Go 模版更好的展示 –no-trunc 不删减输出 –quiet, -q 静默输出,仅仅展示 IDs 示例 # 展示本地所有下载的镜像 docker images # 在本地查找镜像名是 "java" 标签是 "8" 的 奖项 docker images: java:8 # 查找悬挂镜像 docker images --filter "dangling=true" # 过滤 lable 为 "com.example.version" 的值为 0.1 的镜像 docker images --filter "label=com.example.version=0.1" 2.4 Dockerfile创建镜像为了方便分享和快速部署,我们可以使用 docker build 来创建一个新的镜像,首先创建一个文件 Dockerfile,如下 # This is a comment FROM ubuntu:14.04 MAINTAINER Chris <jaytp@qq.com> RUN apt-get -qq update RUN apt-get -qqy install ruby ruby-dev RUN gem install sinatra 然后在此 Dockerfile 所在目录执行 docker build -t yelog/ubuntu:v1 . 来生成镜像,所属组织/镜像名:标签 2.5 上传镜像用户可以通过 docker push 命令,把自己创建的镜像上传到仓库中来共享。例如,用户在 Docker Hub 上完成注册后,可以推送自己的镜像到仓库中。 docker push yelog/ubuntu 2.6 导出和载入镜像docker 支持将镜像导出为文件,然后可以再从文件导入到本地镜像仓库 # 导出 docker load --input yelog_ubuntu_v1.tar # 载入 docker load < yelog_ubuntu_v1.tar 2.7 移除本地镜像# -f 强制删除 docker rmi [-f] yelog/ubuntu:v1 # 删除悬挂镜像 docker rmi $(docker images -f "dangling=true" -q) # 删除所有未被容器使用的镜像 docker image prune -a 三、容器3.1 介绍容器和镜像,就像面向对象中的 类 和 示例 一样,镜像是静态的定义,容器是镜像运行的实体,容器可以被创建、启动、停止、删除和暂停等 容器的实质是进城,耽于直接的宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的 root 文件系统、网络配置和进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。 3.2 创建容器我们可以通过命令 docker run 命令创建容器 如下,启动一个容器,执行命令输出 “Hello word”,之后终止容器 docker run ubuntu:14.04 /bin/echo 'Hello world' 下面的命令则是启动一个 bash 终端,允许用户进行交互 docker run -t -i ubuntu:14.04 /bin/bash -t 让 Dcoker 分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上 -i 责让容器的标准输入保持打开 更多参数可选 -a stdin 指定标准输入输出内容类型 -d 后台运行容器,并返回容器ID -i 以交互模式运行容器,通常与 -t 同时使用 -P 随机端口映射,容器端口内部随即映射到宿主机的端口上 -p 指定端口映射, -p 宿主机端口:容器端口 -t \b为容器重新分配一个伪输入终,通常与 -i 同时使用 –name=”gate” 为容器指定一个名称 –dns 8.8.8.8 指定容器的 DNS 服务器,默认与宿主机一致 –dns-search example.com 指定容器 DNS 搜索域名,默认与宿主机一致 -h “gate” 指定容器的 hostname -e username=’gate’ 设置环境变量 –env-file=[] 从指定文件读入环境变量 –cpuset=”0-2” or –cpuset=”0,1,2” 绑定容器到指定 CPU 运行 -m 设置容器使用内存最大值 –net=”bridge” 指定容器的网络连接类型支持 bridge/host/none/container –link=[] 添加链接到另一个容器 –expose=[] 开放一个端口或一组端口 –volume,-v 绑定一个卷 当利用 docker run 来创建容器时,Dcoker 在后台运行的标准操作包括: 检查本地是否存在指定的镜像,不存在就从公有仓库下载 利用镜像创建并启动一个容器 分配一个文件系统,并在只读的镜像外面挂在一层可读写层 从宿主主机配置的网桥接口中桥接一个虚拟借口到容器中去 从地址池配置一个 ip 地址给容器 执行用户指定的应用程序 执行用户指定的应用程序 执行完毕后容器被终止 3.3 启动容器# 创建一个名为 test 的容器,容器任务是:打印一行 Hello word docker run --name='test' ubuntu:14.04 /bin/echo 'Hello world' # 查看所有可用容器 [-a]包括终止在内的所有容器 docker ps -a # 启动指定 name 的容器 docker start test # 重启指定 name 的容器 docker restart test # 查看日志运行日志(每次启动的日志均被查询出来) $ docker logs test Hello world Hello world 3.4 守护态运行前面创建的容器都是执行任务(打印Hello world)后,容器就终止了。更多的时候,我们需要让 Docker 容器在后台以守护态(Daemonized)形式运行。此时,可以通过添加 -d 参数来实现 注意:docker是否会长久运行,和 docker run 指定的命令有关 # 创建 docker 后台守护进程的容器 docker run --name='test2' -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done" # 查看容器 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 237e555d4457 ubuntu:14.04 "/bin/sh -c 'while t…" 52 seconds ago Up 51 seconds test2 # 获取容器的输出信息 $ docker logs test2 hello world hello world hello world 3.5 进入容器上一步我们已经实现了容器守护态长久运行,某些时候需要进入容器进行操作,可以使用 attach 、exec 进入容器。 # 不安全的,ctrl+d 退出时容器也会终止 docker attach [容器Name] # 以交互式命令行进入,安全的,推荐使用 docker exec -it [容器Name] /bin/bash 命令优化 使用 docker exec 命令时,好用,但是命令过长,我们可以通过自定义命令来简化使用 创建文件 /user/bin/ctn 命令文件,内容如下 docker exec -it $1 /bin/bash 检查环境变量有没有配置目录 /usr/bin (一般是有配置在环境变量里面的,不过最好再确认一下) $PATH bash: /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games: No such file or directory 完成上面步骤后,就可以直接通过命令 ctn 来进入容器 注意:如果是使用非 root 账号创建的命令,而 docker 命令是 root 权限,可能存在权限问题,可以通过设置 chmod 777 /usr/bin/ctn 设置权限,使用 sudo ctn [容器Name] 即可进入容器 $ ctn [容器Name] 使用上面命令时,容器Name 需要手动输入,容器出错。我们可以借助 complete 命令来补全 容器Name,在 ~/.bashrc (作用于当前用户,如果想要所要用户上校,可以修改 /etc/bashrc)文件中添加一行,内容如下。保存后执行 source ~/.bashrc 使之生效,之后我们输入 ctn 后,按 tab 就会提示或自动补全容器名了了 # ctn auto complete complete -W "$(docker ps --format"{{.Names}}")" ctn 注意: 由于提示的 容器Name 是 ~/.bashrc 生效时的列表,所有如果之后 docker 容器列表有变动,需要重新执行 source ~/.bashrc 使之更新提示列表 3.6 终止容器通过 docker stop [容器Name] 来终止一个运行中的容器 # 终止容器名为 test2 的容器 docker stop test2 # 查看正在运行中的容器 docker ps # 查看所有容器(包括终止的) docker ps -a 3.7 将容器保存为镜像我们修改一个容器后,可以经当前容器状态打包成镜像,方便下次直接通过镜像仓库生成当前状态的容器。 # 创建容器 docker run -t -i training/sinatra /bin/bash # 添加两个应用 gem install json # 将修改后的容器打包成新的镜像 docker commit -m "Added json gem" -a "Docker Newbee" 0b2616b0e5a8 ouruser/sinatra:v2 3.8 导出/导入容器容器 ->导出> 容器快照文件 ->导入> 本地镜像仓库 ->新建> 容器 $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2a8bffa405c8 ubuntu:14.04 "/bin/sh -c 'while t…" About an hour ago Up 3 seconds test2 # 导出 $ docker export 2a8bffa405c8 > ubuntu.tar # 导入为镜像 $ docker ubuntu.tar | docker import - test/ubuntu:v1.0 # 从指定 URL 或者某个目录导入 $ docker import http://example.com/exampleimage.tgz example/imagerepo 注意:用户既可以通过 docker load 来导入镜像存储文件到本地镜像仓库,也可以使用 docker import 来导入一个容器快找到本地镜像仓库,两者的区别在于容器快照将丢失所有的历史记录和元数据信息,仅保存容器当时的状态,而镜像存储文件将保存完成的记录,体积要更大。所有容器快照文件导入时需要重新指定标签等元数据信息。 3.9 删除容器可以使用 docker rm [容器Name] 来删除一个终止状态的容器,如果容器还未终止,可以先使用 docker stop [容器Name] 来终止容器,再进行删除操作 docker rm test2 # 删除容器 -f: 强制删除,无视是否运行 $ docker [-f] rm myubuntu # 删除所有已关闭的容器 $ docker rm $(docker ps -a -q) 3.10 查看容器状态docker stats $(docker ps --format={{.Names}}) 四、数据卷4.1 介绍数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多特性: 数据卷可以在容器之间共享和重用 对数据卷的修改会立马生效 对数据卷的更新,不会影响镜像 卷会一直存在,直到没有容器使用 数据卷类似于 Linux 下对目录或文件进行 mount 4.2 创建数据卷在用 docker run 命令的时候,使用 -v 标记来创建一个数据卷并挂在在容器里,可同时挂在多个。 # 创建一个 web 容器,并加载一个数据卷到容器的 /webapp 目录 docker run -d -P --name web -v /webapp training/webapp python app.py # 挂载一个宿主机目录 /data/webapp 到容器中的 /opt/webapp docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py # 默认是读写权限,也可以指定为只读 docker run -d -P --name web -v /src/webapp:/opt/webapp:ro # 挂载单个文件 docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash 4.3 数据卷容器如果需要多个容器共享数据,最好创建数据卷容器,就是一个正常的容器,撰文用来提供数据卷供其他容器挂载的 # 创建一个数据卷容器 dbdata docker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres # 其他容器挂载 dbdata 容器的数据卷 docker run -d --volumes-from dbdata --name db1 training/postgres docker run -d --volumes-from dbdata --name db2 training/postgres 五、网络5.1 外部访问容器在容器内运行一些服务,需要外部可以访问到这些服务,可以通过 -P 或 -p 参数来指定端口映射。 当使用 -P 标记时,Docker 会随即映射一个 49000~49900 的端口到内部容器开放的网络端口。 使用 docker ps 可以查看端口映射情况 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7f43807dc042 training/webapp "python app.py" 3 seconds ago Up 2 seconds 0.0.0.0:32770->5000/tcp amazing_liskov -p 指定端口映射,支持格式 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort # 不限制ip访问 docker run -d -p 5000:5000 training/webapp python app.py # 只允许宿主机回环地址访问 docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py # 宿主机自动分配绑定端口 docker run -d -p 127.0.0.1::5000 training/webapp python app.py # 指定 udp 端口 docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py # 指定多个端口映射 docker run -d -p 5000:5000 -p 3000:80 training/webapp python app.py # 查看映射端口配置 $ docker port amazing_liskov 5000/tcp -> 0.0.0.0:32770 5.2 容器互联容器除了跟宿主机端口映射外,还有一种容器间交互的方式,可以在源/目标容器之间建立一个隧道,目标容器可以看到源容器指定的信息。 可以通过 --link name:alias 来连接容器,下面就是 “web容器连接db容器” 的例子 # 创建 容器db docker run -d --name db training/postgres # 创建 容器web 并连接到 容器db docker run -d -P --name web --link db:db training/webapp python app.py # 进入 容器web,测试连通性 $ ctn web $ ping db PING db (172.17.0.3) 56(84) bytes of data. 64 bytes from db (172.17.0.3): icmp_seq=1 ttl=64 time=0.254 ms 64 bytes from db (172.17.0.3): icmp_seq=2 ttl=64 time=0.190 ms 64 bytes from db (172.17.0.3): icmp_seq=3 ttl=64 time=0.389 ms 5.3 访问控制容器想要访问外部网络,需要宿主机的转发支持。在 Linux 系统中,通过以下命令检查是否打开 $ sysctl net.ipv4.ip_forward net.ipv4.ip_forward = 1 如果是 0,说明没有开启转发,则需要手动打开。 $ sysctl -w net.ipv4.ip_forward=1 5.4 配置 docker0 桥接Docker 服务默认会创建一个 docker0 网桥,他在内核层连通了其他物理或虚拟网卡,这就将容器和主机都放在同一个物理网络。 Docker 默认制定了 docker0 接口的IP地址和子网掩码,让主机和容器间可以通过网桥相互通信,他还给了 MTU(接口允许接收的最大单元),通常是 1500 Bytes,或宿主机网络路由上支持的默认值。这些都可以在服务启动的时候进行配置。 --bip=CIDR ip地址加子网掩码格式,如 192.168.1.5/24 --mtu=BYTES 覆盖默认的 Docker MTU 配置 可以通过 brctl show 来查看网桥和端口连接信息 5.5 网络配置文件Docker 1.2.0 开始支持在运行中的容器里编辑 /etc/hosts 、/etc/hostsname 和 /etc/resolve.conf 文件,修改都是临时的,重新容器将会丢失修改,通过 docker commit 也不会被提交。 六、Dockerfile6.1 介绍Dockerfile 是由一行行命令组成的命令集合,分为四个部分: 基础镜像信息 维护着信息 镜像操作指令 容器启动时执行指令 如下: # 最前面一般放这个 Dockerfile 的介绍、版本、作者及使用说明等 # This dockerfile uses the ubuntu image # VERSION 2 - EDITION 1 # Author: docker_user # Command format: Instruction [arguments / command] .. # 使用的基础镜像,必须放在非注释第一行 FROM ubuntu # 维护着信息信息: 名字 联系方式 MAINTAINER docker_user docker_user@email.com # 构建镜像的命令:对镜像做的调整都在这里 RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list RUN apt-get update && apt-get install -y nginx RUN echo "\\ndaemon off;" >> /etc/nginx/nginx.conf # 创建/运行 容器时的操作指令 # 可以理解为 docker run 后跟的运行指令 CMD /usr/sbin/nginx 6.2 指令指令一般格式为 INSTRUCTION args,包括 FORM 、 MAINTAINER 、RUN 等 FORM 第一条指令必须是 FORM 指令,并且如果在同一个Dockerfile 中创建多个镜像,可以使用多个 FROM 指令(每个镜像一次) FORM ubuntuFORM ubuntu:14.04 MAINTAINER 维护者信息 MAINTAINER Chris xx@gmail.com RUN 每条 RUN 指令在当前镜像基础上执行命令,并提交为新的镜像。当命令过长时可以使用 \\ 来换行 在 shell 终端中运行命令RUN apt-get update && apt-get install -y nginx在 exec 中执行:RUN ["/bin/bash", "-c", "echo hello"] CMD 指定启动容器时执行的命令,每个 Dockerfile 只能有一条 CMD 命令。如果指定了多条命令,只有最后一条会被执行。 CMD ["executable","param1","param2"] 使用 exec 执行,推荐方式;CMD command param1 param2 在 /bin/sh 中执行,提供给需要交互的应用;CMD ["param1","param2"] 提供给 ENTRYPOINT 的默认参数; EXPOSE 告诉服务端容器暴露的端口号, EXPOSE ENV 指定环境变量 ENV PG_MAJOR 9.3ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH ADD ADD 该命令将复制指定的 到容器中的 。其中 可以是 Dockerfile 所在目录的一个相对路径,也可以是一个URL;还可以是一个 tar文件(自动解压为目录) COPY 格式为 COPY 复制本地主机的 (为 Dockerfile 所在目录的相对路径)到容器中的 。当使用本地目录为源目录时,推荐使用 COPY ENTRYPOINT 配置容器启动执行的命令,并且不可被 docker run 提供的参数覆盖每个Docekrfile 中只能有一个 ENTRYPOINT ,当指定多个时,只有最后一个起效 两种格式ENTRYPOINT ["executable", "param1", "param2"]``ENTRYPOINT command param1 param2(shell中执行) VOLUME 创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。 VOLUME [“/data”] USER 指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户 USER daemon WORKDIR 为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录。可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。 格式为 WORKDIR /path/to/workdir。 WORKDIR /aWORKDIR bWORKDIR cRUN pwd最后的路径为 /a/b/c ONBUILD 配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。 格式为 ONBUILD [INSTRUCTION]。 6.3 创建镜像编写完成 Dockerfile 之后,可以通过 docker build 命令来创建镜像 docker build [选项] 路径 该命令江都区指定路径下(包括子目录)的Dockerfile,并将该路径下所有内容发送给 Docker 服务端,有服务端来创建镜像。可以通过 .dockerignore 文件来让 Docker 忽略路径下的目录与文件 # 使用 -t 指定镜像的标签信息 docker build -t myrepo/myimage . 七、Docker Compose7.1 介绍Docker Compose 是 Docker 官方编排项目之一,负责快速在集群中部署分布式应用。维护地址:https://github.com/docker/compose,由 Python 编写,实际调用 Docker提供的API实现。 Dockerfile 可以让用户管理一个单独的应用容器,而 Compose 则允许用户在一个模版(YAML格式)中定义一组相关联的应用容器(被称为一个project/项目),例如一个 web容器再加上数据库、redis等。 7.2 安装# 使用 pip 进行安装 pip install -U docker-compose # 查看用法 docker-ompose -h # 添加 bash 补全命令 curl -L https://raw.githubusercontent.com/docker/compose/1.2.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose 7.3 使用术语 服务/service: 一个应用容器,实际上可以运行多个相同镜像的实例 项目/project: 有一组关联的应用容器组成的完成业务单元 示例:创建一个 Haproxy 挂载三个 Web 容器 创建一个 compose-haproxy-web 目录,作为项目工作目录,并在其中分别创建两个子目录: haproxy 和 web 。 compose-haproxy-webcompose-haproxy-web git clone https://github.com/yelog/compose-haproxy-web.git 目录长这样: compose-haproxy-web ├── docker-compose.yml ├── haproxy │ └── haproxy.cfg └── web ├── Dockerfile ├── index.html └── index.py 在该目录执行 docker-compose up 命令,会整合输出所有容器的输出 $ docker-compose up Starting compose-haproxy-web_webb_1 ... done Starting compose-haproxy-web_webc_1 ... done Starting compose-haproxy-web_weba_1 ... done Recreating compose-haproxy-web_haproxy_1 ... done Attaching to compose-haproxy-web_webb_1, compose-haproxy-web_weba_1, compose-haproxy-web_webc_1, compose-haproxy-web_haproxy_1 haproxy_1 | [NOTICE] 244/131022 (1) : haproxy version is 2.2.2 haproxy_1 | [NOTICE] 244/131022 (1) : path to executable is /usr/local/sbin/haproxy haproxy_1 | [ALERT] 244/131022 (1) : parsing [/usr/local/etc/haproxy/haproxy.cfg:14] : 'listen' cannot handle unexpected argument ':70'. haproxy_1 | [ALERT] 244/131022 (1) : parsing [/usr/local/etc/haproxy/haproxy.cfg:14] : please use the 'bind' keyword for listening addresses. haproxy_1 | [ALERT] 244/131022 (1) : Error(s) found in configuration file : /usr/local/etc/haproxy/haproxy.cfg haproxy_1 | [ALERT] 244/131022 (1) : Fatal errors found in configuration. compose-haproxy-web_haproxy_1 exited with code 1 此时访问本地的 80 端口,会经过 haproxy 自动转发到后端的某个 web 容器上,刷新页面,可以观察到访问的容器地址的变化。 7.4 命令说明大部分命令都可以运行在一个或多个服务上。如果没有特别的说明,命令则应用在项目所有的服务上。 执行 docker-compose [COMMAND] --help 查看具体某个命令的使用说明 使用格式 docker-compose [options] [COMMAND] [ARGS...] build 构建/重建服务服务一旦构建后,将会带上一个标记名,例如 web_db可以随时在项目目录运行 docker-compose build 来重新构建服务 help 获得一个命令的信息 kill 通过发送 SIGKILL 信号来强制停止服务容器,支持通过参数来指定发送信号,例如docker-compose kill -s SIGINT logs 查看服务的输出 port 打印绑定的公共端口 ps 列出所有容器 pull 拉去服务镜像 rm 删除停止的服务容器 run 在一个服务上执行一个命令docker-compose run ubuntu ping docker.com scale 设置同一个服务运行的容器个数通过 service=num 的参数来设置数量docker-compose scale web=2 worker=3 start 启动一个已经存在的服务容器 stop 停止一个已经运行的容器,但不删除。可以通过 docker-compose start 再次启动 up 构建、创建、启动、链接一个服务相关的容器链接服务都将被启动,除非他们已经运行docker-compose up -d 将后台运行并启动docker-compose up 已存在容器将会重新创建docker-compose up --no-recreate 将不会重新创建容器 7.5 环境变量环境变量可以用来配置 Compose 的行为 以 Docker_ 开头的变量用来配置 Docker 命令行客户端使用的一样 COMPOSE_PROJECT_NAME 设置通过 Compose 启动的每一个容器前添加的项目名称,默认是当前工作目录的名字。 COMPOSE_FILE 设置要使用的 docker-compose.yml 的路径。默认路径是当前工作目录。 DOCKER_HOST 设置 Docker daemon 的地址。默认使用 unix:///var/run/docker.sock,与 Docker 客户端采用的默认值一致。 DOCKER_TLS_VERIFY 如果设置不为空,则与 Docker daemon 交互通过 TLS 进行。 DOCKER_CERT_PATH 配置 TLS 通信所需要的验证(ca.pem、cert.pem 和 key.pem)文件的路径,默认是 ~/.docker 。 7.6 docker-compose.yml默认模版文件是 docker-compose.yml ,启动定义了每个服务都必须经过 image 指令指定镜像或 build 指令(需要 Dockerfile) 来自动构建。 其他大部分指令跟 docker run 类似 如果使用 build 指令,在 Dockerfile 中设置的选项(如 CMD 、EXPOSE 等)将会被自动获取,无需在 docker-compose.yml 中再次设置。 **image** 指定镜像名称或镜像ID,如果本地仓库不存在,将尝试从远程仓库拉去此镜像 image: ubuntu image: orchardup/postgresql image: a4bc65fd **build** 指定 Dockerfile 所在文件的路径。Compose 将利用它自动构建这个镜像,然后使用这个镜像。 build: /path/to/build/dir **command** 覆盖容器启动默认执行命令 command: bundle exec thin -p 3000 **links** 链接到其他服务中的容器,使用服务名称或别名 links: - db - db:database - redis 别名会自动在服务器中的 /etc/hosts 里创建。例如: 172.17.2.186 db 172.17.2.186 database 172.17.2.187 redis **external_links** 连接到 docker-compose.yml 外部的容器,甚至并非 Compose 管理的容器。 external_links: - redis_1 - project_db_1:mysql - project_db_1:postgresql ports 暴露端口信息 HOST:CONTAINER 格式或者仅仅指定容器的端口(宿主机会随机分配端口) ports: - "3000" - "8000:8000" - "49100:22" - "127.0.0.1:8001:8001" 注:当使用 HOST:CONTAINER 格式来映射端口时,如果你使用的容器端口小于 60 你可能会得到错误得结果,因为 YAML 将会解析 xx:yy 这种数字格式为 60 进制。所以建议采用字符串格式。 **expose** 暴露端口,但不映射到宿主机,只被连接的服务访问 expose: - "3000" - "8000" volumes 卷挂载路径设置。可以设置宿主机路径 (HOST:CONTAINER) 或加上访问模式 (HOST:CONTAINER:ro)。 volumes: - /var/lib/mysql - cache/:/tmp/cache - ~/configs:/etc/configs/:ro **** volumes_from 从另一个服务或容器挂载它的所有卷。 volumes_from: - service_name - container_name environment 设置环境变量。你可以使用数组或字典两种格式。 只给定名称的变量会自动获取它在 Compose 主机上的值,可以用来防止泄露不必要的数据。 environment: RACK_ENV: development SESSION_SECRET: environment: - RACK_ENV=development - SESSION_SECRET env_file 从文件中获取环境变量,可以为单独的文件路径或列表。 如果通过 docker-compose -f FILE 指定了模板文件,则 env_file 中路径会基于模板文件路径。 如果有变量名称与 environment 指令冲突,则以后者为准。 env_file: .env env_file: - ./common.env - ./apps/web.env - /opt/secrets.env 环境变量文件中每一行必须符合格式,支持 # 开头的注释行。 # common.env: Set Rails/Rack environment RACK_ENV=development extends 基于已有的服务进行扩展。例如我们已经有了一个 webapp 服务,模板文件为 common.yml。 # common.yml webapp: build: ./webapp environment: - DEBUG=false - SEND_EMAILS=false 编写一个新的 development.yml 文件,使用 common.yml 中的 webapp 服务进行扩展。 # development.yml web: extends: file: common.yml service: webapp ports: - "8000:8000" links: - db environment: - DEBUG=true db: image: postgres 后者会自动继承 common.yml 中的 webapp 服务及相关环节变量。 **** net 设置网络模式。使用和 docker client 的 --net 参数一样的值。 net: "bridge" net: "none" net: "container:[name or id]" net: "host" **** pid 跟主机系统共享进程命名空间。打开该选项的容器可以相互通过进程 ID 来访问和操作。 pid: "host" dns 配置 DNS 服务器。可以是一个值,也可以是一个列表。 dns: 8.8.8.8 dns: - 8.8.8.8 - 9.9.9.9 cap_add, cap_drop 添加或放弃容器的 Linux 能力(Capabiliity)。 cap_add: - ALL cap_drop: - NET_ADMIN - SYS_ADMIN **** dns_search 配置 DNS 搜索域。可以是一个值,也可以是一个列表。 dns_search: example.com dns_search: - domain1.example.com - domain2.example.com **** working_dir, entrypoint, user, hostname, domainname, mem_limit, privileged, restart, stdin_open, tty, cpu_shares 这些都是和 docker run 支持的选项类似。 八、安全8.1 内核命名空间当使用 docker run 启动一个容器时,在后台 Docker 为容器创建一个独立的命名空间和控制集合。命名空间踢空了最基础的也是最直接的隔离,在容器中运行的进程不会被运行在主机上的进程和其他容器发现和作用。 8.2 控制组控制组是 Linux 容器机制的另一个关键组件,负责实现资源的审计和限制。 它提供了很多特性,确保哥哥容器可以公平地分享主机的内存、CPU、磁盘IO等资源;当然,更重要的是,控制组确保了当容器内的资源使用产生压力时不会连累主机系统。 8.3 内核能力机制能力机制是 Linux 内核的一个强大特性,可以提供细粒度的权限访问控制。 可以作用在进程上,也可以作用在文件上。 例如一个服务需要绑定低于 1024 的端口权限,并不需要 root 权限,那么它只需要被授权 net_bind_service 能力即可。 默认情况下, Docker 启动的容器被严格限制只允许使用内核的一部分能力。 使用能力机制加强 Docker 容器的安全有很多好处,可以按需分配给容器权限,这样,即便攻击者在容器中取得了 root 权限,也不能获取宿主机较高权限,能进行的破坏也是有限的。 参考资料https://docs.docker.com/engine/reference/commandline/images/ http://www.dockerinfo.net/","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://yelog.org/tags/linux/"},{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"3-hexo评论设置","slug":"3-hexo-comment","date":"2020-05-23T14:26:23.000Z","updated":"2024-07-05T01:50:55.231Z","comments":true,"path":"2020/05/23/3-hexo-comment/","permalink":"http://yelog.org/2020/05/23/3-hexo-comment/","excerpt":"","text":"前言目前 3-hexo 已经集成了评论系统有 gitalk 、gitment、 disqus 、来必力、utteranc 一、gitalkgitalk 是一款基于 Github Issue 和 Preact 开发的评论插件 官网: https://gitalk.github.io/ 1. 登录 github ,注册应用点击进行注册 ,如下 注册完后,可得到 Client ID 和 Client Secret 2. 新建存放评论的仓库因为 gitalk 是基于 Github 的 Issue 的,所以需要指定一个仓库,用来承接 gitalk 的评论,我们一般使用 Github Page 来做我们博客的评论,所以,新建仓库名为 xxx.github.io,其中 xxx 为你的 Github 用户名 3. 配置主题在主题下 _config.yml 中找到如下配置,启用评论,并使用 gitalk ##########评论设置############# comment: on: true type: gitalk 在主题下 _config.yml 中找到 gitalk 配置,将 第1步 得到的 Client ID 和 Client Secret 复制到如下位置 gitalk: githubID: # 填你的 github 用户名 repo: xxx.github.io # 承载评论的仓库,一般使用 Github Page 仓库 ClientID: # 第1步获得 Client ID ClientSecret: # 第1步获得 Client Secret adminUser: # Github 用户名 distractionFreeMode: true language: zh-CN perPage: 10 二、来必力1. 创建来必力账号,并选择 City 免费版官网http://livere.com/ ,创建账号,点击上面的安装,选择 City 免费版 复制获取到的代码中的 data-uid 2. 主题选择使用来必力评论在主题下 _config.yml 在找到来必力配置如下,第一步中复制的 data-uid 粘贴到下面 data_uid 处 livere: data_uid: xxxxxx 找到以下代码, 开启并选择 livere (来必力) ##########评论设置############# comment: on: true type: livere 三、utteranc官网地址:https://utteranc.es/ 1. 安装 utterances点我进行安装 2. 配置主题在主题下 _config.yml 中找到 utteranc 的配置 ,修改 repo 为自己的仓库名 utteranc: repo: xxx/xxx.github.io # 承载评论的仓库,填上自己的仓库 issue_term: pathname # Issue 与 博客文章 之间映射关系 label: utteranc # 创建的 Issue 添加的标签 theme: github-light # 主题,可选主题请查看官方文档 https://utteranc.es/#heading-theme # 官方文档 https://utteranc.es/ # 使用说明 https://yelog.org//2020/05/23/3-hexo-comment/ 在主题下 _config.yml 中找到如下配置,启用评论,并使用 utteranc comment: on: true type: utteranc","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"}]},{"title":"3-hexo支持mermaid图表","slug":"3-hexo-support-mermaid","date":"2019-11-12T01:55:37.000Z","updated":"2024-07-05T01:50:55.302Z","comments":true,"path":"2019/11/12/3-hexo-support-mermaid/","permalink":"http://yelog.org/2019/11/12/3-hexo-support-mermaid/","excerpt":"","text":"一、说明开启 安装hexo插件 npm install hexo-filter-mermaid-diagrams 修改themes/3-hexo/_config.yml 的 mermaid.on,开启主题支持 # Mermaid 支持 mermaid: on: true cdn: //cdn.jsdelivr.net/npm/mermaid@8.4.2/dist/mermaid.min.js #cdn: //cdnjs.cloudflare.com/ajax/libs/mermaid/8.3.1/mermaid.min.js options: # 更多配置信息可以参考 https://mermaidjs.github.io/#/mermaidAPI theme: 'default' startOnLoad: true flowchart: useMaxWidth: false htmlLabels: true 在markdown中,像写代码块一样写图表 二、示例以下示例源码可以在这边查看 本文源码更多示例可以查看官网:https://mermaidjs.github.io 1. flowchartgraph TD; A-->B; A-->C; B-->D; C-->D; graph TB c1-->a2 subgraph one a1-->a2 end subgraph two b1-->b2 end subgraph three c1-->c2 end 2.Sequence diagramssequenceDiagram participant Alice participant Bob Alice->>John: Hello John, how are you? loop Healthcheck John->>John: Fight against hypochondria end Note right of John: Rational thoughts prevail! John-->>Alice: Great! John->>Bob: How about you? Bob-->>John: Jolly good! 3.Class diagramsclassDiagram Animal NumLockOn : EvNumLockPressed NumLockOn --> NumLockOff : EvNumLockPressed -- [*] --> CapsLockOff CapsLockOff --> CapsLockOn : EvCapsLockPressed CapsLockOn --> CapsLockOff : EvCapsLockPressed -- [*] --> ScrollLockOff ScrollLockOff --> ScrollLockOn : EvCapsLockPressed ScrollLockOn --> ScrollLockOff : EvCapsLockPressed } 5.Gantt diagramsgantt dateFormat YYYY-MM-DD title Adding GANTT diagram functionality to mermaid section A section Completed task :done, des1, 2014-01-06,2014-01-08 Active task :active, des2, 2014-01-09, 3d Future task : des3, after des2, 5d Future task2 : des4, after des3, 5d section Critical tasks Completed task in the critical line :crit, done, 2014-01-06,24h Implement parser and jison :crit, done, after des1, 2d Create tests for parser :crit, active, 3d Future task in critical line :crit, 5d Create tests for renderer :2d Add to mermaid :1d section Documentation Describe gantt syntax :active, a1, after des1, 3d Add gantt diagram to demo page :after a1 , 20h Add another diagram to demo page :doc1, after a1 , 48h section Last section Describe gantt syntax :after doc1, 3d Add gantt diagram to demo page :20h Add another diagram to demo page :48h 6.Pie chart diagramspie \"Dogs\" : 386 \"Cats\" : 85 \"Rats\" : 15","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"3-hexo 添加音乐插件","slug":"3-hexo-add-music","date":"2019-10-08T02:44:30.000Z","updated":"2024-07-05T01:50:55.244Z","comments":true,"path":"2019/10/08/3-hexo-add-music/","permalink":"http://yelog.org/2019/10/08/3-hexo-add-music/","excerpt":"","text":"网易云音乐1. 复制网易云音乐插件代码前往网易云音乐官网,搜索一个作为背景音乐的歌曲,并进入播放页面,点击 生成外链播放器 设置好想要显示的样式后,复制 html 代码 最好外层在加一个 div,如下,可直接将上一步复制的 iframe 替换下方里面的 iframe <div id="musicMouseDrag" style="position:fixed; z-index: 9999; bottom: 0; right: 0;"> <div id="musicDragArea" style="position: absolute; top: 0; left: 0; width: 100%;height: 10px;cursor: move; z-index: 10;"></div> <iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=38592976&auto=1&height=66"></iframe> </div> 2. 将插件引入到主题中将上一步加过 div 的代码粘贴到主题下 layout/_partial/footer.ejs 的最后面 3. 调整位置默认给的样式是显示在右下角,可以通过调整上一步粘贴的 div 的 style 中 bottom 和 right 来调整位置。 4. 自由拖动如果需要自由拖动,在刚才添加的代码后面,再添加下面代码即可,鼠标就可以在音乐控件的 上边沿 点击拖动 <!--以下代码是为了支持随时拖动音乐控件的位置,如没有需求,可去掉下面代码--> <script> var $DOC = $(document) $('#musicMouseDrag').on('mousedown', function (e) { // 阻止文本选中 $DOC.bind("selectstart", function () { return false; }); $('#musicDragArea').css('height', '100%'); var $moveTarget = $('#musicMouseDrag'); $moveTarget.css('border', '1px dashed grey') var div_x = e.pageX - $moveTarget.offset().left; var div_y = e.pageY - $moveTarget.offset().top; $DOC.on('mousemove', function (e) { var targetX = e.pageX - div_x; var targetY = e.pageY - div_y; targetX = targetX < 0 ? 0 : (targetX + $moveTarget.outerWidth() >= window.innerWidth) ? window.innerWidth - $moveTarget.outerWidth() : targetX; targetY = targetY < 0 ? 0 : (targetY + $moveTarget.outerHeight() >= window.innerHeight) ? window.innerHeight - $moveTarget.outerHeight() : targetY; $moveTarget.css({'left': targetX + 'px', 'top': targetY + 'px', 'bottom': 'inherit', 'right': 'inherit'}) }).on('mouseup', function () { $DOC.unbind("selectstart"); $DOC.off('mousemove') $DOC.off('mouseup') $moveTarget.css('border', 'none') $('#musicDragArea').css('height', '10px'); }) }) </script>","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"3-hexo支持jsfiddle渲染","slug":"3-hexo-jsfiddle","date":"2019-09-24T01:59:37.000Z","updated":"2024-07-05T01:50:55.252Z","comments":true,"path":"2019/09/24/3-hexo-jsfiddle/","permalink":"http://yelog.org/2019/09/24/3-hexo-jsfiddle/","excerpt":"","text":"1. canvas 粒子效果 2. 复选框动画","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"3-hexo文章内toc生成","slug":"3-hexo-toc","date":"2019-09-24T01:48:20.000Z","updated":"2024-07-05T01:50:55.306Z","comments":true,"path":"2019/09/24/3-hexo-toc/","permalink":"http://yelog.org/2019/09/24/3-hexo-toc/","excerpt":"","text":"[toc] 1. 如何使用1.1 关键字只要在在文章中使用如下关键字,不区分大小写,便可以在相应位置显示目录导航,效果文章开头 1.2 小标题2jlksjdflksdjflksjdflksjdflkaj;sdfjka;lskdjfla;skjdf;lajsdflkjal;sdjkf;laskjdf占位占位 1.3 小标题占位占位占位 2. 标题二占位占位占位 2.1 小标题占位占位 2.2 小标题2占位占位占位占位占位 文末占位占位占位占位占位","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"[记]《知识分子的不幸》-王小波","slug":"misfortune-intellectual","date":"2019-06-09T13:31:09.000Z","updated":"2024-07-05T01:50:54.476Z","comments":true,"path":"2019/06/09/misfortune-intellectual/","permalink":"http://yelog.org/2019/06/09/misfortune-intellectual/","excerpt":"","text":"前言这篇文章发表于1996年第二期《东方》杂志,同样收录于《沉默的大多数》一书中。 所想文章一开头就抛出了一个问题:什么是知识分子最害怕的事?想起了高晓松在晓说中提到过这个问题,晓松肯定是看过这篇文章的。 王小波说:“知识分子最怕活在不理智的年代。”所谓不理智的年代,就是伽利略低头认罪,承认地球不转的年代,也是拉瓦斯上断头台的年代;是茨威格服毒自杀的年代,也是老舍跳太平湖的年代。“ 王小波和他的美国老师谈论了一个问题:”有信仰比无信仰要好。“,由于王小波是经历过文革的,所以王小波一开始是抵触这种思想的,尤其是 课间祷告 让王小波想起了文革中的 早请示。但老师最终说服了他,“不管是信神,还是自珍自重,人活在世界上总得有点信念才成。就我个人而言,虽是无神论者,我也有个人操守,从不逾越。” 国内的学者,只搞学术研究,不搞意识形态,这由不了自己。有朝一日它成了意识形态,你的话就是罪状。言论不自由,不理智,民族狂热,这不就是知识分子最怕的事情吗? 王小波崇拜墨子:其一,他思维缜密,其二,他敢赤裸裸地谈利害。(有了他,我也敢说自己是中华民族的赤诚分子,不怕国学家说我是全盘西化了。) 营造意识形态则是灭绝思想额丰饶。中国的人文知识分子,有种以天下为己任的使命感,总觉得自己该搞出些老百姓当信仰的东西。 国学,这种东西实在厉害。最可怕之处就在于那个“国”字。顶着这个字,谁敢有不同意见?抢到了这个制高点,就可以压制一切不同意见;所以很容易落入思想流氓的手中变成一种凶器。 目前正值 “中美贸易战”,各种自媒体为了点击量、关注度。煽动民族狂热情绪,导致民众根本容不得半点不同意见,不讲道理,“盲目爱国“。 认真思索,真诚的明辨是非,有这种态度大概就可算是善良了吧。 人活在世上,自会形成信念,一种学问、一本书,假如不对我的价值观发生变化,就不值得一学,不值得一看。","categories":[{"name":"读书","slug":"读书","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/"},{"name":"阅读笔记","slug":"读书/阅读笔记","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"reading","slug":"reading","permalink":"http://yelog.org/tags/reading/"}]},{"title":"人们在一本叫《活着》的书中纷纷死去","slug":"to-live","date":"2019-05-02T09:46:16.000Z","updated":"2024-07-05T01:50:54.484Z","comments":true,"path":"2019/05/02/to-live/","permalink":"http://yelog.org/2019/05/02/to-live/","excerpt":"","text":"有那么一个年代,离我们很近,它腥风血雨,连活着都是一件奢侈的事。 在富贵的一生中,每次出现看似被上天眷顾的福气后(如有庆长跑第一、凤霞嫁了人并怀了孩子),读者还在替富贵开心的时候,他们却以各种方式迅速死去,最终富贵亲手埋葬了他所有的亲人。 一本 12w 左右的小说,但是在没有华丽词藻的情况下,在顺畅流利的写作手法、跌宕起伏的剧情、第一人称的代入感下一口气读完了。期间多次痛哭流涕(一点儿没夸张),不得不放下书本,洗过脸后才能继续阅读。所以已经多年没写书评的我,还是忍不住为她写下书评。 人是为了活着本身而活着,而不是为了活着之外的任何事物所活着。 这是作者在中文序言中的一句话,在当今生活着的我,初读序言中的这句话,并无任何共鸣,甚至还行吐槽两句。随着富贵将他的”一生”娓娓道来,你就会明白在那样的时代背景下,活着已经是一件不容易的事。 所以作者在日文版序言中说到: 在旁人眼中富贵的一生是苦熬的一生;可是对于富贵自己,我相信他更多地感受到了幸福。 因为他相信自己的妻子是世上最好的妻子,他相信自己的子女也是世上最好的子女,还有他的女婿他的外孙,还有他的那头也叫富贵的牛,还有一起上火锅的朋友们,还有生活的点点滴滴…… 富贵的真是一路跌下去的一生,从”富家少爷”赌光了家产、气死了爹爹。由于母亲生病,为母亲求医路上被国民党抓壮丁,被俘虏后,放回家中。却发现母亲已死,女儿也由于生病变成了聋哑人。本想着大难之后必有后福,却只是悲惨一生的开端。儿子有庆由于和县长夫人血型匹配,遭抽血而亡、女儿凤霞产子大出血而亡、妻子家珍失去儿女后,失去了最后与病魔争斗的信念,也走了、女婿二喜在工地被水泥板拍死、外孙苦根难得吃到豆子,却被豆子撑死。最后只剩下自己和一个也叫作富贵的老牛。 春生想自杀前,找到富贵告别,在被家珍原谅,并答应不会自杀,在这种情况下坚持了一个月,最终还是自杀了。那种时代背景下的无奈,那种窒息感。。。 富贵的一生跨越了地主、解放战争、人民公社运动、大炼钢铁、自然灾害和文化大革命,从一个人的视角看到每个时代下的一个小小的缩影,但却比任何其他的描述更让人深刻了解到这些时代背景下人们的生活状态。 在那时,活着不仅是幸运,也更需要勇气。","categories":[{"name":"读书","slug":"读书","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/"},{"name":"阅读笔记","slug":"读书/阅读笔记","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"reading","slug":"reading","permalink":"http://yelog.org/tags/reading/"},{"name":"活着","slug":"活着","permalink":"http://yelog.org/tags/%E6%B4%BB%E7%9D%80/"}]},{"title":"shell速查表","slug":"shell速查表","date":"2018-09-08T03:48:24.000Z","updated":"2024-07-05T01:50:55.760Z","comments":true,"path":"2018/09/08/shell-command/","permalink":"http://yelog.org/2018/09/08/shell-command/","excerpt":"","text":"1. 变量#!/bin/bash msg="hello world" echo $msg 变量名的命名须遵循如下规则: 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。 中间不能有空格,可以使用下划线(_)。 不能使用标点符号。 不能使用bash里的关键字(可用help命令查看保留关键字)。 2. 传参#!/bin/bash echo "执行的文件名:$0"; echo "第一个参数为:$1"; echo "第二个参数为:$2"; echo "第三个参数为:$3"; 脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……另外,还有几个特殊字符用来处理参数: 参数 说明 $# 传递到脚本的参数个数 $* 以一个单字符串显示所有向脚本传递的参数。如"$*"用「”」括起来的情况、以”$1 $2 … $n”的形式输出所有参数。 $$ 脚本运行的当前进程ID号 $! 后台运行的最后一个进程的ID号 $@ 与$*相同,但是使用时加引号,并在引号中返回每个参数。如”$@”用「”」括起来的情况、以”$1” “$2” … “$n” 的形式输出所有参数。 $- 显示Shell使用的当前选项,与set命令功能相同。 $? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 3. 数组#!/bin/bash my_array=(A B "C" D) echo "第一个元素为: ${my_array[0]}" echo "第二个元素为: ${my_array[1]}" echo "第三个元素为: ${my_array[2]}" echo "第四个元素为: ${my_array[3]}" echo "数组的元素为: ${my_array[*]}" echo "数组的元素为: ${my_array[@]}" echo "数组元素个数为: ${#my_array[*]}" echo "数组元素个数为: ${#my_array[@]}" 执行结果如下: 第一个元素为: A 第二个元素为: B 第三个元素为: C 第四个元素为: D 数组的元素为: A B C D 数组的元素为: A B C D 数组元素个数为: 4 数组元素个数为: 4 4. 基本运算符 原生 bash 不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。 expr 是一款表达式计算工具,使用它能完成表达式的求值操作。 ① 算数运算符#!/bin/bash echo "2加2等于"`expr 2 + 2` echo "2减2等于"`expr 2 - 2` echo "2乘2等于"`expr 2 \\* 2` echo "2除2等于"`expr 2 / 2` echo "2除2取余"`expr 2 % 2` ② 关系运算符#!/bin/bash a=10 b=20 if [ $a -eq $b ] # 检测两个数是否相等,相等返回 true。 if [ $a -ne $b ] # 检测两个数是否不相等,不相等返回 true。 if [ $a -gt $b ] # 检测左边的数是否大于右边的,如果是,则返回 true。 if [ $a -lt $b ] # 检测左边的数是否小于右边的,如果是,则返回 true。 if [ $a -ge $b ] # 检测左边的数是否大于等于右边的,如果是,则返回 true。 if [ $a -le $b ] # 检测左边的数是否小于等于右边的,如果是,则返回 true。 ③ 布尔运算符#!/bin/bash if [ ! false ] # 非运算,返回 true if [ true -o false ] # 或运算,返回 true if [ true -a false ] # 与运算,返回 false ④ 逻辑运算符#!/bin/bash a=10 b=20 if [[ $a -lt $b && $a -gt $b ]] # 逻辑的 AND, 返回 false if [ $a -lt $b ] && [ $a -gt $b ] # 逻辑的 AND, 返回 false if [[ $a -lt $b || $a -gt $b ]] # 逻辑的 OR, 返回 true if [ $a -lt $b ] || [ $a -gt $b ] # 逻辑的 OR, 返回 true ⑤ 字符串运算符#!/bin/bash a="abc" b="efg" if [ $a = $b ] # 检测两个字符串是否相等,相等返回 true。 if [ $a != $b ] # 检测两个字符串是否相等,不相等返回 true。 if [ -z $a ] # 检测字符串长度是否为0,为0返回 true。 if [ -n "$a" ] # 检测字符串长度是否为0,不为0返回 true。 if [ $a ] # 检测字符串是否为空,不为空返回 true。 ⑥ 文件测试运算符文件测试运算符用于检测 Unix 文件的各种属性。 操作符 说明 -b file 检测文件是否是块设备文件,如果是,则返回 true。 -c file 检测文件是否是字符设备文件,如果是,则返回 true。 -d file 检测文件是否是目录,如果是,则返回 true。 -f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 -g file 检测文件是否设置了 SGID 位,如果是,则返回 true。 -k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 -p file 检测文件是否是有名管道,如果是,则返回 true。 -u file 检测文件是否设置了 SUID 位,如果是,则返回 true。 -r file 检测文件是否可读,如果是,则返回 true。 -w file 检测文件是否可写,如果是,则返回 true。 -x file 检测文件是否可执行,如果是,则返回 true。 -s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。 -e file 检测文件(包括目录)是否存在,如果是,则返回 true。 5. echo① 命令格式#!/bin/bash echo "It is a test" echo It is a test echo "\\"It is a test\\"" # 转义 name=Chris echo "$name is handsome" echo -e "OK! \\n" # 显示换行 -e 开启转义 echo "It is a test" > myfile # 显示结果定向至文件 echo '$name\\"' # 原样输入字符串,不进行转义或取变量(使用单引号) echo `date` # 显示命令执行结构 ② 颜色显示echo -e "\\033[字背景颜色;文字颜色m字符串\\033[0m" echo -e “\\033[30m 黑色字 \\033[0m” echo -e “\\033[31m 红色字 \\033[0m” echo -e “\\033[32m 绿色字 \\033[0m” echo -e “\\033[33m 黄色字 \\033[0m” echo -e “\\033[34m 蓝色字 \\033[0m” echo -e “\\033[35m 紫色字 \\033[0m” echo -e “\\033[36m 天蓝字 \\033[0m” echo -e “\\033[37m 白色字 \\033[0m” echo -e “\\033[40;37m 黑底白字 \\033[0m” echo -e “\\033[41;37m 红底白字 \\033[0m” echo -e “\\033[42;37m 绿底白字 \\033[0m” echo -e “\\033[43;37m 黄底白字 \\033[0m” echo -e “\\033[44;37m 蓝底白字 \\033[0m” echo -e “\\033[45;37m 紫底白字 \\033[0m” echo -e “\\033[46;37m 天蓝底白字 \\033[0m” echo -e “\\033[47;30m 白底黑字 \\033[0m” \\33[0m 关闭所有属性 \\33[1m 设置高亮度 \\33[4m 下划线 \\33[5m 闪烁 \\33[7m 反显 \\33[8m 消隐 \\33[30m — \\33[37m 设置前景色 \\33[40m — \\33[47m 设置背景色 \\33[nA 光标上移n行 \\33[nB 光标下移n行 \\33[nC 光标右移n行 \\33[nD 光标左移n行 \\33[y;xH设置光标位置 \\33[2J 清屏 \\33[K 清除从光标到行尾的内容 \\33[s 保存光标位置 \\33[u 恢复光标位置 \\33[?25l 隐藏光标 \\33[?25h 显示光标 6. sprintf#!/bin/bash printf "%-10s %-8s %-4s\\n" 姓名 性别 体重kg printf "%-10s %-8s %-4.2f\\n" 郭靖 男 66.1234 printf "%-10s %-8s %-4.2f\\n" 杨过 男 48.6543 printf "%-10s %-8s %-4.2f\\n" 郭芙 女 47.9876 结果: 姓名 性别 体重kg 郭靖 男 66.12 杨过 男 48.65 郭芙 女 47.99 %s %c %d %f 都是格式替代符d: Decimal 十进制整数 – 对应位置参数必须是十进制整数,否则报错!s: String 字符串 – 对应位置参数必须是字符串或者字符型,否则报错!c: Char 字符 – 对应位置参数必须是字符串或者字符型,否则报错!f: Float 浮点 – 对应位置参数必须是数字型,否则报错!%-10s 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。%-4.2f 指格式化为小数,其中.2指保留2位小数。 7. testShell中的 test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。 #!/bin/bash num1=100 num2=100 if test $[num1] -eq $[num2] 8. 流程控制① if-else#!/bin/bash a=10 b=20 if [ $a == $b ] then echo "a 等于 b" elif [ $a -gt $b ] then echo "a 大于 b" elif [ $a -lt $b ] then echo "a 小于 b" else echo "没有符合的条件" fi ② for#!/bin/bash for loop in 1 2 3 4 5 do echo "The value is: $loop" done ③ while#!/bin/bash int=1 while(( $int<=5 )) do echo $int let "int++" done ④ case#!/bin/bash echo '输入 1 到 4 之间的数字:' echo '你输入的数字为:' read aNum case $aNum in 1) echo '你选择了 1' ;; 2) echo '你选择了 2' ;; 3) echo '你选择了 3' ;; 4) echo '你选择了 4' ;; *) echo '你没有输入 1 到 4 之间的数字' ;; esac ⑤ breakbreak命令允许跳出所有循环(终止执行后面的所有循环)。 #!/bin/bash while : do echo -n "输入 1 到 5 之间的数字:" read aNum case $aNum in 1|2|3|4|5) echo "你输入的数字为 $aNum!" ;; *) echo "你输入的数字不是 1 到 5 之间的! 游戏结束" break ;; esac done ⑥ continue跳出当前循环。 #!/bin/bash while : do echo -n "输入 1 到 5 之间的数字: " read aNum case $aNum in 1|2|3|4|5) echo "你输入的数字为 $aNum!" ;; *) echo "你输入的数字不是 1 到 5 之间的!" continue echo "游戏结束" ;; esac done ⑦ until#!/bin/bash a=0 until [ ! $a -lt 10 ] do echo $a a=`expr $a + 1` done 9. 函数#!/bin/bash funWithParam(){ echo "第一个参数为 $1 !" echo "第二个参数为 $2 !" echo "第十个参数为 $10 !" echo "第十个参数为 ${10} !" echo "第十一个参数为 ${11} !" echo "参数总数有 $# 个!" echo "作为一个字符串输出所有参数 $* !" } funWithParam 1 2 3 4 5 6 7 8 9 34 73 结果: 第一个参数为 1 ! 第二个参数为 2 ! 第十个参数为 10 ! 第十个参数为 34 ! 第十一个参数为 73 ! 参数总数有 11 个! 作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 ! 10. 输入输出#!/bin/bash who > today.log # 执行结果覆盖到文件 today.log echo "菜鸟教程:www.runoob.com" >> today.log # 执行结果追加到文件 today.log wc -l < today.log # 统计 today.log 行数 wc -l << EOF 李白 苏轼 王勃 EOF 11. 文件包含test1.sh #!/bin/bash name="Chris" test2.sh #!/bin/bash #使用 . 号来引用test1.sh 文件 . ./test1.sh # 或者使用以下包含文件代码 # source ./test1.sh echo $name 注:被包含的文件 test1.sh 不需要可执行权限。 reference:[1] http://www.runoob.com/linux/linux-shell.html","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://yelog.org/tags/linux/"},{"name":"shell","slug":"shell","permalink":"http://yelog.org/tags/shell/"}]},{"title":"nginx配置记录","slug":"nginx配置记录","date":"2018-02-08T01:19:09.000Z","updated":"2024-07-05T01:50:54.981Z","comments":true,"path":"2018/02/08/nginx-config/","permalink":"http://yelog.org/2018/02/08/nginx-config/","excerpt":"","text":"启用https1.购买免费证书登录阿里云 -> 控制台 -> 安全(云盾) -> CA证书服务 -> 购买证书 2.补全证书信息点击补全,绑定域名 3.下载并配置选择下载 证书for nginx 上面这个页面有相关的配置信息,下面简单介绍: ① 将下载文件中的 *.pem、*.key, 拷贝到 nginx 目录下 的 cert , 当然也可以是其他目录② 修改 nginx.conf server { listen 443 ssl; server_name xiangzhangshugongyi.com; ssl_certificate cert/214487958220243.pem; ssl_certificate_key cert/214487958220243.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Proto https; proxy_redirect off; proxy_connect_timeout 240; proxy_send_timeout 240; proxy_read_timeout 240; # note, there is not SSL here! plain HTTP is used proxy_pass http://127.0.0.1:8080; } } ③ 重启 nginx,通过 证书绑定域名进行 https 访问到 服务器跑在 8080 的服务","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"nginx","slug":"nginx","permalink":"http://yelog.org/tags/nginx/"}]},{"title":"Mac神器-BTT(BetterTouchTool)不完全教程","slug":"Mac-BetterTouchTool","date":"2017-12-13T09:04:25.000Z","updated":"2024-07-05T01:50:55.036Z","comments":true,"path":"2017/12/13/Mac-BetterTouchTool/","permalink":"http://yelog.org/2017/12/13/Mac-BetterTouchTool/","excerpt":"","text":"介绍BetterTouchTool 是一款专为Mac用户开发的 窗口管理/Trackpad(触控板)/Magic Mouse(苹果鼠标)/Keyboard(键盘)/TouchBar 功能增强制作的软件。 这款软件不但可以设置全局的 手势/快捷键/TouchBar ,还可以给不同的应用定义不同的姿势,再配合上 Alfred 的 workflow,简直各种高难度姿势都能玩的出来。 本文主要介绍以下功能: 窗口管理 帮 Trackpad 定义各种姿势 帮 Magic Mouse 定义各种姿势 帮 Keyboard 定义各种姿势 帮任何应用自定义 TouchBar 本文以 macbook pro 2017 touchbar 版为例 1. 窗口管理这个功能无需过多配置,默认配置即可很好使用(和windows的理念相似) 将窗口移到左右边缘,最大化至半屏 将窗口移到上边缘,最大化至全屏 如果对默认配置不满意,也可以在如下图所示的位置来调整窗口展示: 2. 帮 Trackpad 定义各种姿势姿势选择在界面选择 Trackpad(触摸板) -> Add New Gesture(添加一个新姿势) 左边可以选择生效的范围:全局或者某个应用 如上图所示,姿势包括但不限于如: 单指:左下角单击、单指轻拍右上角、单指轻拍上边中点 双指:两个手指捏、张开两指以两指中心为圆轴逆时针、中指拍住中央食指轻拍面板、双指从上边缘下滑 三指:三指轻拍、三指拍顶端、三指点击并向上滑、两指轻拍住,拍左、右二指固定拍住,左一下滑 四指:四指双轻拍、中指无名小拍住,食单击、食中指无名拍住,小单击 五指:五手指轻拍、五手指上滑 上面只是列一些典型,更多姿势可以在上图中浏览。 绑定功能 选择过姿势之后,也可以选择在按住某个功能键的时候才能使用(左下角)。 右边是绑定功能:快捷键或动作。 绑定快捷键举例:比如 给chrome 设置 姿势(两指从触控板下边缘滑入),弹出开发者模式(快捷键绑定:command+option+i),如下图: 绑定动作举例:设置 在任何应用内,五指下滑 锁屏,如下图 3. 帮 Magic Mouse 定义各种姿势这个功能设置和 Trackpad 设置 大同小异,所以这边就不多讲,直接图示几个功能。 我快捷键设置了 option+E 鼠标取词翻译(欧陆词典),然后绑定到双指轻拍鼠标,即可触发翻译。 4. 帮 Keyboard 定义各种姿势这个功能比较简单,设置一些 键盘快捷键或录制案件序列 来触发 一些动作或者其他快捷键功能。 5. 帮任何应用自定义 TouchBar这个重磅功能,可以帮助不支持touchbar的软件定制 TouchBar,是不是有点厉害。 下面就以我给 IntelliJ IDEA 定制 TouchBar 为例 (没有F1 ~ F12 功能键,debug真的很痛苦,这个软件真的是雪中送炭),展示一下使用效果 如上图所示,我给 IntelliJ IDEA 添加了 四个功能 step over/step into/resume/evaluate 添加完之后,切到 IntelliJ IDEA 软件中时,TouchBar 就显示我们添加的四个功能键, 如下图所示 最后BTT还有其他很方便的功能,这盘就介绍到这里,等之后更新了 Alfred 的 workflow 开发指南之后,再一起更新一篇有意思的 BTT+Alfred 效率流。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"软件记录","slug":"工具/软件记录","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95/"}],"tags":[{"name":"mac","slug":"mac","permalink":"http://yelog.org/tags/mac/"},{"name":"efficiency","slug":"efficiency","permalink":"http://yelog.org/tags/efficiency/"}]},{"title":"[转]谈谈Java中的语法糖","slug":"转-谈谈Java中的语法糖","date":"2017-11-27T14:51:45.000Z","updated":"2024-07-05T01:50:55.359Z","comments":true,"path":"2017/11/27/java-grammatical-sugar/","permalink":"http://yelog.org/2017/11/27/java-grammatical-sugar/","excerpt":"","text":"语法糖(Syntactic Sugar),也称糖衣语法,指在计算机语言中添加的某种语法,这种语法对语言本身功能来说没有什么影响,只是为了方便程序员的开发,提高开发效率。说白了,语法糖就是对现有语法的一个封装。 Java作为一种与平台无关的高级语言,当然也含有语法糖,这些语法糖并不被虚拟机所支持,在编译成字节码阶段就自动转换成简单常用语法。一般来说Java中的语法糖主要有以下几种: 泛型与类型擦除 自动装箱与拆箱,变长参数、 增强for循环 内部类与枚举类 泛型与类型擦除Java语言并不是一开始就支持泛型的。在早期的JDK中,只能通过Object类是所有类型的父类和强制类型转换来实现泛型的功能。强制类型转换的缺点就是把编译期间的问题延迟到运行时,JVM并不能为我们提供编译期间的检查。 在JDK1.5中,Java语言引入了泛型机制。但是这种泛型机制是通过类型擦除来实现的,即Java中的泛型只在程序源代码中有效(源代码阶段提供类型检查),在编译后的字节码中自动用强制类型转换进行替代。也就是说,Java语言中的泛型机制其实就是一颗语法糖,相较与C++、C#相比,其泛型实现实在是不那么优雅。 /** * 在源代码中存在泛型 */ public static void main(String[] args) { Map<String,String> map = new HashMap<String,String>(); map.put("hello","你好"); String hello = map.get("hello"); System.out.println(hello); } 当上述源代码被编译为class文件后,泛型被擦除且引入强制类型转换 public static void main(String[] args) { HashMap map = new HashMap(); //类型擦除 map.put("hello", "你好"); String hello = (String)map.get("hello");//强制转换 System.out.println(hello); } 自动装箱与拆箱 Java中的自动装箱与拆箱指的是基本数据类型与他们的包装类型之间的相互转换。 我们知道Java是一门面向对象的语言,在Java世界中有一句话是这么说的:“万物皆对象”。但是Java中的基本数据类型却不是对象,他们不需要进行new操作,也不能调用任何方法,这在使用的时候有诸多不便。因此Java为这些基本类型提供了包装类,并且为了使用方便,提供了自动装箱与拆箱功能。自动装箱与拆箱在使用的过程中,其实是一个语法糖,内部还是调用了相应的函数进行转换。 下面代码演示了自动装箱和拆箱功能 public static void main(String[] args) { Integer a = 1; int b = 2; int c = a + b; System.out.println(c); } 经过编译后,代码如下 public static void main(String[] args) { Integer a = Integer.valueOf(1); // 自动装箱 byte b = 2; int c = a.intValue() + b;//自动拆箱 System.out.println(c); } 变长参数 所谓变长参数,就是方法可以接受长度不定确定的参数 变长参数特性是在JDK1.5中引入的,使用变长参数有两个条件,一是变长的那一部分参数具有相同的类型,二是变长参数必须位于方法参数列表的最后面。变长参数同样是Java中的语法糖,其内部实现是Java数组。 public class Varargs { public static void print(String... args) { for(String str : args){ System.out.println(str); } } public static void main(String[] args) { print("hello", "world"); } } 编译为class文件后如下,从中可以很明显的看出变长参数内部是通过数组实现的 public class Varargs { public Varargs() { } public static void print(String... args) { String[] var1 = args; int var2 = args.length; //增强for循环的数组实现方式 for(int var3 = 0; var3 < var2; ++var3) { String str = var1[var3]; System.out.println(str); } } public static void main(String[] args) { //变长参数转换为数组 print(new String[]{"hello", "world"}); } } 增强for循环 增强for循环与普通for循环相比,功能更强并且代码更简洁 增强for循环的对象要么是一个数组,要么实现了Iterable接口。这个语法糖主要用来对数组或者集合进行遍历,其在循环过程中不能改变集合的大小。 public static void main(String[] args) { String[] params = new String[]{"hello","world"}; //增强for循环对象为数组 for(String str : params){ System.out.println(str); } List<String> lists = Arrays.asList("hello","world"); //增强for循环对象实现Iterable接口 for(String str : lists){ System.out.println(str); } } 编译后的class文件为 public static void main(String[] args) { String[] params = new String[]{"hello", "world"}; String[] lists = params; int var3 = params.length; //数组形式的增强for退化为普通for for(int str = 0; str < var3; ++str) { String str1 = lists[str]; System.out.println(str1); } List var6 = Arrays.asList(new String[]{"hello", "world"}); Iterator var7 = var6.iterator(); //实现Iterable接口的增强for使用iterator接口进行遍历 while(var7.hasNext()) { String var8 = (String)var7.next(); System.out.println(var8); } } 内部类 内部类就是定义在一个类内部的类 Java语言中之所以引入内部类,是因为有些时候一个类只在另一个类中有用,我们不想让其在另外一个地方被使用。内部类之所以是语法糖,是因为其只是一个编译时的概念,一旦编译完成,编译器就会为内部类生成一个单独的class文件,名为outer$innter.class。 public class Outer { class Inner{ } } 使用javac编译后,生成两个class文件Outer.class和Outer$Inner.class,其中Outer$Inner.class的内容如下: class Outer$Inner { Outer$Inner(Outer var1) { this.this$0 = var1; } } 内部类分为四种:成员内部类、局部内部类、匿名内部类、静态内部类,每一种都有其用法,这里就不介绍了 枚举类型 枚举类型就是一些具有相同特性的类常量 java中类的定义使用class,枚举类的定义使用enum。在Java的字节码结构中,其实并没有枚举类型,枚举类型只是一个语法糖,在编译完成后被编译成一个普通的类。这个类继承java.lang.Enum,并被final关键字修饰。 public enum Fruit { APPLE,ORINGE } 使用jad对编译后的class文件进行反编译后得到 //继承java.lang.Enum并声明为final public final class Fruit extends Enum { public static Fruit[] values() { return (Fruit[])$VALUES.clone(); } public static Fruit valueOf(String s) { return (Fruit)Enum.valueOf(Fruit, s); } private Fruit(String s, int i) { super(s, i); } //枚举类型常量 public static final Fruit APPLE; public static final Fruit ORANGE; private static final Fruit $VALUES[];//使用数组进行维护 static { APPLE = new Fruit("APPLE", 0); ORANGE = new Fruit("ORANGE", 1); $VALUES = (new Fruit[] { APPLE, ORANGE }); } }","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"}]},{"title":"PostgreSQL事务及隔离级别","slug":"PostgreSQL事务及隔离级别","date":"2017-11-08T16:07:33.000Z","updated":"2024-07-05T01:50:54.998Z","comments":true,"path":"2017/11/09/PostgreSQL事物及隔离级别/","permalink":"http://yelog.org/2017/11/09/PostgreSQL%E4%BA%8B%E7%89%A9%E5%8F%8A%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB/","excerpt":"","text":"介绍PostgreSQL中提供了多种数据完整性的保证机制。如:约束、触发器、事务和锁管理等。 事务主要是为了保证一组相关数据库的操作能全部执行成功,从而保证数据的完整性。锁机制主要是控制多个用户对同一数据进行操作,使用锁机制可以解决并发问题。 事务事务是用户对一个数据库操作的一个序列,这些操作要么全做,要么全不做,是一个不可分割的单位。 事务管理的常用语句如下: BEGIN; SQL语句1; SQL语句2; ... COMMIT; 事务块是指包围在BEGIN和COMMIT之间的语句。在PostgreSQL9中,常用的事务块管理语句含义如下: START TRANSACTION:此命令表示开始一个新的事务块.BEGIN:初始化一个事务块。在BEGIN命令后的语句都将在一个事务里面执行,知道遇见COMMIT或ROLLBACK。它和START TRANSACTION是一样的。COMMIT:提交事务。ROLLBACK:事务失败时执行回滚操作。SET TRANSACTION:设置当前事务的特性。对后面的事务没有影响。 事务隔离及并发控制PostgreSQL是一个支持多用户的数据库,当多个用户操作同一数据库时,并发控制要保证所有用户可以高效的访问的同时不破坏数据的完整性。 数据库中数据的并发操作经常发生,而对数据的并发操作会带来下面的一些问题: 脏读一个事务读取了另一个未提交事务写入的数据。 不可重复读一个事务重新读取前面读取过的数据,发现该数据已经被另一个已经提交的事务修改。 幻读一个事务重新执行一个查询,返回符合查询条件的行的集合,发现满足查询条件的行的集合因为其它最近提交的事务而发生了改变。 SQL标准定义了四个级别的事务隔离。 | 隔离级别 | 脏读 | 幻读 | 不可重复性读取 || :- | :- ||读未提交 |可能 |可能 |可能||读已提交 |不可能| 可能 |可能||可重复读 |不可能 |可能 |不可能||可串行读 |不可能 |不可能 |不可能| 在PostgreSQL中,可以请求4种隔离级别中的任意一种。但是在内部,实际上只有两种独立的隔离级别,分别对应已提交和可串行化。如果选择了读未提交的级别,实际上使用的是读已提交,在选择可重复读级别的时候,实际上用的是可串行化,所以实际的隔离级别可能比选择的更严格。这是SQL标准允许的:4种隔离级别只定义了哪种现象不能发生,但是没有定义哪种现象一定发生。 PostgreSQL只提供两种隔离级别的原因是,这是把标准的隔离级别与多版本并发控制架构映射相关的唯一合理方法。 读已提交这是PostgreSQL中默认的隔离级别,当一个事务运行在这个隔离级别时,一个SELECT查询只能看到查询开始前已提交的数据,而无法看到未提交的数据或者在查询期间其他的事务已提交的数据。 可串行化可串行化提供最严格的事务隔离。这个级别模拟串行的事务执行,就好像事务是一个接着一个串行的执行。不过,这个级别的应用必须准备在串行化失败的时候重新启动事务。","categories":[{"name":"数据库","slug":"数据库","permalink":"http://yelog.org/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"}],"tags":[{"name":"PostgreSQL","slug":"PostgreSQL","permalink":"http://yelog.org/tags/PostgreSQL/"}]},{"title":"linux下修改按键ESC<=>CAPSLOCK和Control=>ALT_R","slug":"linux下修改按键ESC-CAPSLOCK和Control-ALT-R","date":"2017-10-20T09:38:49.000Z","updated":"2024-07-05T01:50:55.743Z","comments":true,"path":"2017/10/20/linux下修改按键ESC<=>CAPSLOCK和Control=>ALT_R/","permalink":"http://yelog.org/2017/10/20/linux%E4%B8%8B%E4%BF%AE%E6%94%B9%E6%8C%89%E9%94%AEESC%3C=%3ECAPSLOCK%E5%92%8CControl=%3EALT_R/","excerpt":"","text":"使用 vim 过程中发现 esc 和 ctrl 按键很难按,小拇指没有那么长啊~~,而 caps_lock 和 alt_r(右alt) 很少用。 本教程将 esc 和 caps_lock 两个按键交换, alt_r(右alt) 改为 ctrl。 一、 esc 与 caps_lock 按键交换①. 创建 .xmodmaprc 文件。②. 加入以下内容: remove Lock = Caps_Lock add Lock = Escape keysym Caps_Lock = Escape keysym Escape = Caps_Lock ③. 执行 xmodmap .xmodmaprc 使之生效。 二、 将 右alt 改为 ctrl①. 查看需要修改键位的 keysym通过 xev | grep keycode 获取右 alt 的 keysym 为 Alt_R。如下图所示: ②. 查看 Alt_R 是哪个 modifier 使用的通过 xmodmap -pm 查看,发现 Alt_R 是作为 modifier mod1 使用的。如下图所示: ③. 修改 modifier xmodmap -e 'remove mod1 = Alt_R' # 解除原来绑定 xmodmap -e 'add control = Alt_R' # 作为 control 使用","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://yelog.org/tags/linux/"},{"name":"vim","slug":"vim","permalink":"http://yelog.org/tags/vim/"},{"name":"keybord","slug":"keybord","permalink":"http://yelog.org/tags/keybord/"},{"name":"emacs","slug":"emacs","permalink":"http://yelog.org/tags/emacs/"}]},{"title":"[转]字符编解码的故事(ASCII,ANSI,Unicode,Utf-8区别)","slug":"转-字符编解码的故事(ASCII,ANSI,Unicode,Utf-8区别)","date":"2017-09-25T11:15:00.000Z","updated":"2024-07-05T01:50:55.784Z","comments":true,"path":"2017/09/25/ascii-ansi-unicode-utf-8/","permalink":"http://yelog.org/2017/09/25/ascii-ansi-unicode-utf-8/","excerpt":"","text":"很久很久以前,有一群人,他们决定用8个可以开合的晶体管来组合成不同的状态,以表示世界上的万物。他们认为8个开关状态作为原子单位很好,于是他们把这称为”字节”。 再后来,他们又做了一些可以处理这些字节的机器,机器开动了,可以用字节来组合出更多的状态,状态开始变来变去。他们看到这样是好的,于是它们就这机器称为”计算机”。 开始计算机只在美国用。八位的字节一共可以组合出256(2的8次方)种不同的状态。 他们把其中的编号从0开始的32种状态分别规定了特殊的用途,一但终端设备或者打印机遇上这些约定好的字节时,就要做一些约定的动作。遇上 00x10, 终端就换行,遇上0x07, 终端就向人们嘟嘟叫,例好遇上0x1b, 打印机就打印反白的字,对于终端就用彩色显示字母。他们看到这样很好,于是就把这些0x20(十进制32)以下的字节状态称为”控制码”。 他们又把所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第127号,这样计算机就可以用不同字节来存储英语的 文字了。大家看到这样,都感觉很好,于是大家都把这个方案叫做 ANSI 的”Ascii”编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都用同样的ASCII方案来保存英文文字。 后来,就像建造巴比伦塔一样,世界各地的都开始使用计算机,但是很多国家用的不是英文,他们用到的许多字母在ASCII中根本没有,为了也可以在计算机中保存他们的文字,他们决定采用127号之后的空位来表示这些新的字母、符号,还加入了很多画表格时需要用下到的横线、竖线、交叉等形状,一直把序号编到了最后一个状态255。从128到255这一页的字符集被称”扩展字符集”。从此之后,贪婪的人类再没有新的状态可以用了,美帝国主义可能没有想到还有第三世界国家的人们也希望可以用到计算机吧! 等中国人们得到计算机时,已经没有可以利用的字节状态来表示汉字,况且有6000多个常用汉字需要保存呢。但是这难不倒智慧的中国人民,我们不客气地把那些127号之后的奇异符号们直接取消掉,并且规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。 中国人民看到这样很不错,于是就把这种汉字方案叫做”GB2312”。GB2312 是对 ASCII 的中文扩展。 但是中国的汉字太多了,我们很快就就发现有许多人的人名没有办法在这里打出来,特别是某些很会麻烦别人的国家领导人(如朱镕基的“镕”字)。于是我们不得不继续把 GB2312 没有用到的码位找出来老实不客气地用上。 后来还是不够用,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为 GBK 标准,GBK 包括了 GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。 后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK 扩成了 GB18030。从此之后,中华民族的文化就可以在计算机时代中传承了。 中国的程序员们看到这一系列汉字编码的标准是好的,于是通称他们叫做 “DBCS”(Double Byte Charecter Set 双字节字符集)。在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。那时候凡是受过加持,会编程的计算机僧侣们都要每天念下面这个咒语数百遍: “一个汉字算两个英文字符!一个汉字算两个英文字符……” 因为当时各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码,连大陆和台湾这样只相隔了150海里,使用着同一种语言的兄弟地区,也分别采用了不同的 DBCS 编码方案——当时的中国人想让电脑显示汉字,就必须装上一个”汉字系统”,专门用来处理汉字的显示、输入的问题,但是那个台湾的愚昧封建人士写的算命程序就必须加装另一套支持 BIG5 编码的什么”倚天汉字系统”才可以用,装错了字符系统,显示就会乱了套!这怎么办?而且世界民族之林中还有那些一时用不上电脑的穷苦人民,他们的文字又怎么办? 真是计算机的巴比伦塔命题啊! 正在这时,大天使加百列及时出现了——一个叫 ISO (国际标谁化组织)的国际组织决定着手解决这个问题。他们采用的方法很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号的编码!他们打算叫它”Universal Multiple-Octet Coded Character Set”,简称 UCS, 俗称 “UNICODE”。 UNICODE 开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。于是 ISO 就直接规定必须用两个字节,也就是16位来统一表示所有的字符,对于ascii里的那些”半角”字符,UNICODE 包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于”半角”英文符号只需要用到低8位,所以其高 8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。 这时候,从旧社会里走过来的程序员开始发现一个奇怪的现象:他们的strlen函数靠不住了,一个汉字不再是相当于两个字符了,而是一个!是 的,从 UNICODE 开始,无论是半角的英文字母,还是全角的汉字,它们都是统一的”一个字符”!同时,也都是统一的”两个字节”,请注意”字符”和”字节”两个术语的不同, “字节”是一个8位的物理存贮单元,而”字符”则是一个文化相关的符号。在UNICODE 中,一个字符就是两个字节。一个汉字算两个英文字符的时代已经快过去了。 从前多种字符集存在时,那些做多语言软件的公司遇上过很大麻烦,他们为了在不同的国家销售同一套软件,就不得不在区域化软件时也加持那个双字节字符集咒语,不仅要处处小心不要搞错,还要把软件中的文字在不同的字符集中转来转去。UNICODE 对于他们来说是一个很好的一揽子解决方案,于是从 Windows NT 开始,MS 趁机把它们的操作系统改了一遍,把所有的核心代码都改成了用 UNICODE 方式工作的版本,从这时开始,WINDOWS 系统终于无需要加装各种本土语言系统,就可以显示全世界上所有文化的字符了。 但是,UNICODE 在制订时没有考虑与任何一种现有的编码方案保持兼容,这使得 GBK 与UNICODE 在汉字的内码编排上完全是不一样的,没有一种简单的算术方法可以把文本内容从UNICODE编码和另一种编码进行转换,这种转换必须通过查表来进行。 如前所述,UNICODE 是用两个字节来表示为一个字符,他总共可以组合出65535不同的字符,这大概已经可以覆盖世界上所有文化的符号。如果还不够也没有关系,ISO已经准备了UCS-4方案,说简单了就是四个字节来表示一个字符,这样我们就可以组合出21亿个不同的字符出来(最高位有其他用途),这大概可以用到银河联邦成立那一天吧! UNICODE 来到时,一起到来的还有计算机网络的兴起,UNICODE 如何在网络上传输也是一个必须考虑的问题,于是面向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF8就是每次8个位传输数据,而UTF16就是每次16个位,只不过为了传输时的可靠性,从UNICODE到 UTF时并不是直接的对应,而是要过一些算法和规则来转换。 受到过网络编程加持的计算机僧侣们都知道,在网络里传递信息时有一个很重要的问题,就是对于数据高低位的解读方式,一些计算机是采用低位先发送的方法,例如我们PC机采用的 INTEL 架构;而另一些是采用高位先发送的方式。在网络中交换数据时,为了核对双方对于高低位的认识是否是一致的,采用了一种很简便的方法,就是在文本流的开始时向对方发送一个标志符——如果之后的文本是高位在位,那就发送”FEFF”,反之,则发送”FFFE”。不信你可以用二进制方式打开一个UTF-X格式的文件,看看开头两个字节是不是这两个字节? 下面是Unicode和UTF-8转换的规则 Unicode UTF-8 0000 - 007F 0xxxxxxx 0080 - 07FF 110xxxxx 10xxxxxx 0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx 例如”汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以要用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 1100 0100 1001,将这个比特流按三字节模板的分段方法分为0110 110001 001001,依次代替模板中的x,得到:1110-0110 10-110001 10-001001,即E6 B1 89,这就是其UTF8的编码。 讲到这里,我们再顺便说说一个很著名的奇怪现象:当你在 windows 的记事本里新建一个文件,输入”联通”两个字之后,保存,关闭,然后再次打开,你会发现这两个字已经消失了,代之的是几个乱码!呵呵,有人说这就是联通之所以拼不过移动的原因。 其实这是因为GB2312编码与UTF8编码产生了编码冲撞的原因。 当一个软件打开一个文本时,它要做的第一件事是决定这个文本究竟是使用哪种字符集的哪种编码保存的。软件一般采用三种方式来决定文本的字符集和编码: 检测文件头标识,提示用户选择,根据一定的规则猜测 最标准的途径是检测文本最开头的几个字节,开头字节 Charset/encoding,如下表: EF BB BF UTF-8 FF FE UTF-16/UCS-2, little endian FE FF UTF-16/UCS-2, big endian FF FE 00 00 UTF-32/UCS-4, little endian. 00 00 FE FF UTF-32/UCS-4, big-endian. 当你新建一个文本文件时,记事本的编码默认是ANSI(代表系统默认编码,在中文系统中一般是GB系列编码), 如果你在ANSI的编码输入汉字,那么他实际就是GB系列的编码方式,在这种编码下,”联通”的内码是: c1 1100 0001 aa 1010 1010 cd 1100 1101 a8 1010 1000 注意到了吗?第一二个字节、第三四个字节的起始部分的都是”110”和”10”,正好与UTF8规则里的两字节模板是一致的, 于是当我们再次打开记事本时,记事本就误认为这是一个UTF8编码的文件,让我们把第一个字节的110和第二个字节的10去掉,我们就得到了”00001 101010”,再把各位对齐,补上前导的0,就得到了”0000 0000 0110 1010”,不好意思,这是UNICODE的006A,也就是小写的字母”j”,而之后的两字节用UTF8解码之后是0368,这个字符什么也不是。这就是只有”联通”两个字的文件没有办法在记事本里正常显示的原因。 而如果你在”联通”之后多输入几个字,其他的字的编码不见得又恰好是110和10开始的字节,这样再次打开时,记事本就不会坚持这是一个utf8编码的文件,而会用ANSI的方式解读之,这时乱码又不出现了。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"encoding","slug":"encoding","permalink":"http://yelog.org/tags/encoding/"}]},{"title":"搭建dubbo+zookeeper平台","slug":"搭建dubbo-zookeeper平台","date":"2017-09-25T08:29:07.000Z","updated":"2024-07-05T01:50:55.400Z","comments":true,"path":"2017/09/25/搭建dubbo+zookeeper平台/","permalink":"http://yelog.org/2017/09/25/%E6%90%AD%E5%BB%BAdubbo+zookeeper%E5%B9%B3%E5%8F%B0/","excerpt":"","text":"前言本文将介绍在SpringMVC+Spring+Mybatis项目中添加 dubbo 作为 rpc 服务。 文末有项目代码地址。 一.搭建zookeeper使用 docker 一句话创建: docker run -dit --name zookeeper --hostname zookeeper-host -v /data:/data -p 2181:2181 jplock/zookeeper:latest 二.安装zkui(非必须)这个项目为 zookeeper 提供一个 web 的管理界面。当然我们也可以直接在zookeeper中使用命令查看,所以此步骤可以忽略 在开始前需要安装 Java 环境、Maven 环境。 到 zkui 的项目中下载代码。 git clone https://github.com/DeemOpen/zkui.git 执行 mvn clean install 生成jar文件。 将config.cfg复制到上一步生成的jar文件所在目录,然后修改配置文件中的zookeeper地址。 执行 nohup java -jar zkui-2.0-SNAPSHOT-jar-with-dependencies.jar & 测试 http://localhost:9090,如果能看到如下页面,表示安装成功。 三.使用dubbo 在原来 SpringMVC+Spring+Mybatis 项目中,除了原来 spring 相关依赖外,还需要加入以下依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.5.5</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.9</version> </dependency> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.2</version> </dependency> 定义服务接口 public interface IPersonService { List<Person> listAll(); Person getById(Integer id); Integer delById(Person person); Integer updatePerson(Person person); } 定义服务实现类 @Service public class PersonService implements IPersonService { @Autowired PersonMapper personMapper; public List<Person> listAll() { return personMapper.findAll(); } public Person getById(Integer id) { return personMapper.findOneById(id); } public Integer delById(Person person) { return personMapper.del(person); } public Integer updatePerson(Person person) { return personMapper.update(person); } } 配置生产者,注册服务信息 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!--定义了提供方应用信息,用于计算依赖关系;--> <dubbo:application name="demotest-provider" /> <!-- 使用 zookeeper 注册中心暴露服务地址 --> <dubbo:registry address="zookeeper://192.168.0.86:2181"/> <!-- 用dubbo协议在20880端口暴露服务 --> <dubbo:protocol name="dubbo" port="20880"/> <!-- 和本地bean一样实现服务 --> <bean id="personService" class="com.ssm.service.PersonService"/> <!-- 声明需要暴露的服务接口 --> <dubbo:service interface="com.ssm.iservice.IPersonService" ref="personService"/> </beans> 配置消费者,订阅服务 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 --> <dubbo:application name="demo-consumer"/> <!-- 使用 zookeeper 注册中心暴露发现服务地址 --> <dubbo:registry address="zookeeper://192.168.0.86:2181"/> <!-- 生成远程服务代理,可以和本地bean一样使用demoService --> <dubbo:reference id="personService" check="false" interface="com.ssm.iservice.IPersonService"/> </beans> 调用远程服务配置完成后,我们就可以像使用本地 bean 一样,使用 rpc 的 service; @Controller public class IndexController { @Autowired IPersonService personService; @RequestMapping("/index.html") public String index(Model model) { RpcContext.getContext().setAttachment("index", "1");//测试ThreadLocal List<Person> list = personService.listAll(); model.addAttribute("command",list); return "index"; } } 最后至此,单机运行的 rpc 服务已搭建完成。 代码传送文 ssm","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"dubbo","slug":"dubbo","permalink":"http://yelog.org/tags/dubbo/"},{"name":"zookeeper","slug":"zookeeper","permalink":"http://yelog.org/tags/zookeeper/"}]},{"title":"docker报错集锦","slug":"docker报错集锦","date":"2017-09-25T02:03:50.000Z","updated":"2024-07-05T01:50:54.907Z","comments":true,"path":"2017/09/25/docker-errors/","permalink":"http://yelog.org/2017/09/25/docker-errors/","excerpt":"","text":"docker创建容器1. iptables failed创建 tale 容器时,如下命令: docker run -d --privileged --hostname tale --name tale \\ -v /etc/localtime:/etc/localtime:ro \\ -v /home/tale:/var/tale_home -p 127.0.0.1:234:9000 \\ -m 1024m --memory-swap -1 tale:1.0 然后就报了以下错误: docker: Error response from daemon: driver failed programming external connectivity on endpoint tale (263775ff559176224428ec44dcec416a1c20e6c69198d9760b38f35849914260): iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 127.0.0.1 --dport 234 -j DNAT --to-destination 172.17.0.4:9000 ! -i docker0: iptables: No chain/target/match by that name. (exit status 1). 解决办法:重启 docker 服务: $ service docker restart","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"Hexo加速渲染速度之fragment_cache","slug":"hexo-fragment_cache","date":"2017-09-21T11:34:37.000Z","updated":"2024-07-05T01:50:55.235Z","comments":true,"path":"2017/09/21/hexo-fragment_cache/","permalink":"http://yelog.org/2017/09/21/hexo-fragment_cache/","excerpt":"","text":"前文从开发 3-hexo 主题到现在已过去 9 个月时间了,累计在博客中写 132 篇文章了。 现在发现了严重的问题,hexo generate 渲染的速度越来越慢,现在132篇左右,每次渲染时间到达了 50+ s,相当不爽。 今日抽时间,查看了官方api,看到了 fragment_cache 局部缓存这个东西,解决了渲染速度的问题。 使用官方文档局部缓存。它储存局部内容,下次使用时就能直接使用缓存。 <%- fragment_cache(id, fn); %> 替换简单文本区域a. 我们可以将所有页面都一样的区域,如下所示,缓存下来。当下一篇文章在渲染到这个位置时,将不再渲染,直接拿缓存数据。 <%- fragment_cache('header', function(){ return partial('<head></head>'); }) %> b. 文章模块也可以使用,原来公共引用部分(没有和当前文章耦合的内容)使用下面的方式: <%- partial('_partial/header'); %> 改进为以下代码: <%- fragment_cache('header', function(){ return partial('_partial/header'); }) %> 最后这个语法只适用于所有页面都相同,不随文章内容变化的部分。 作者在 3-hexo 中加入了此语法,渲染132篇文章的速度已从 50+s 到现在 3s 左右了。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"},{"name":"fragment_cache","slug":"fragment-cache","permalink":"http://yelog.org/tags/fragment-cache/"}]},{"title":"[转]浏览器前进/后退缓存(BF Cache)","slug":"bf-cache","date":"2017-09-21T07:35:24.000Z","updated":"2024-07-05T01:50:55.146Z","comments":true,"path":"2017/09/21/bf-cache/","permalink":"http://yelog.org/2017/09/21/bf-cache/","excerpt":"","text":"[浏览器前进/后退缓存](https://developer.mozilla.org/en-US/docs/Working_with_BFCache)(Backward/Forward Cache,BF Cache)是指浏览器在前进后退过程中, 会应用更强的缓存策略,表现为 DOM、window、甚至 JavaScript 对象被缓存,以及同步 XHR 也被缓存。 这一现象在移动端浏览器尤为常见,除 Chrome for Android、Android Browser 之外的浏览器基本都会触发。 BF Cache 本来是一项浏览器优化,但在某些情况下(比如前端路由的 Web App)会引起困惑。 本文主要讨论 BF Cache 的行为、如何检测 BF Cache 缓存、以及如何 workaround。 缓存行为BF Cache 是一种浏览器优化,HTML 标准并未指定其如何进行缓存,因此缓存行为是与浏览器实现相关的。 User agents may discard the Document objects of entries other than the current entry that are not referenced from any script, reloading the pages afresh when the user or script navigates back to such pages. This specification does not specify when user agents should discard Document objects and when they should cache them. – Session history and navigation, WHATWG Desktop Chrome:阻塞的资源和同步发出的 XHR 都会被缓存,但不缓存渲染结果。因此可以看到明显的载入过程,此时脚本也会重新执行。 Chrome for Android:有些情况下不会缓存,缓存时与 Desktop Chrome 行为一致。 Desktop Firefox:页面会被 Frozen,定时器会被暂停,DOM、Window、JavaScript 对象会被缓存,返回时页面脚本重新开始运行。 iOS Safari:渲染结果也会被缓存,因此才能支持左右滑动手势来前进/后退。 Desktop Firefox 暂停计时器的行为非常有趣,以下 HTML 中显示一个每秒加一的数字。 当页面导航时就会暂停,返回时继续增加(因此直接使用 setInterval 倒计时不仅不精确,而且不可靠): <span id="timer-tick"></span> <a href="http://harttle.com">External Link</a> <script> var i = 0 setInterval(() => document.querySelector('#timer-tick').innerHTML = i++, 1000) </script> pagehide/pageshow 事件会话(Session)中的某一个页面显示/隐藏时,会触发 pagehide 和 pageshow 事件。 这两个事件都有一个 persisted 属性用来指示当前页面是否被 BF Cache 缓存。 因此可以通过 persisted 属性来达到禁用 BF Cache 的效果: window.onpageshow = function(event) { if (event.persisted) { window.location.reload() } }; 注意无论页面是否被缓存 pageshow 总会触发,因此需要检测器 persisted 属性。 另外 pageshow 的时机总是在 load 事件之后。 这一点很容易检测,下面的 pageshow 日志总在 load 之前: window.addEventListener('pageshow', function () { console.log('on pageshow') }) window.addEventListener('load', function () { console.log('load') }) XHR 缓存同步(阻塞加载的)脚本发出的 XMLHttpRequest 也会被 Chrome 强制缓存, 因此即使在断网的情况下后退到访问过的页面仍然是可以完美渲染的。 如果页面中有这样一段外部脚本: sendXHR(); function sendXHR () { var xhr = new XMLHttpRequest() xhr.open('GET', '/data.json') xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { console.log('xhr arrived', xhr.responseText) } } xhr.send() } 超链接跳转后回来,该 xhr 也会被缓存。注意下图中的 XHR 一项 size 为 “from disk cache”: 为了强制发送 xhr,可以将 xhr 改为异步发送,或者加一个不重要的 query。 setTimeout(sendXHR, 1000) 这样就能看到 xhr 真正发送出去了 :) 异步 xhr 缓存时机未经兼容性测试, 还是建议读者使用一个随机产生的 query。","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"浏览器","slug":"浏览器","permalink":"http://yelog.org/tags/%E6%B5%8F%E8%A7%88%E5%99%A8/"},{"name":"js","slug":"js","permalink":"http://yelog.org/tags/js/"}]},{"title":"解决iphone下后退不执行js的问题","slug":"解决iphone下后退不执行js的问题","date":"2017-09-21T07:25:32.000Z","updated":"2024-07-05T01:50:55.142Z","comments":true,"path":"2017/09/21/iphone-bf-no-run-js/","permalink":"http://yelog.org/2017/09/21/iphone-bf-no-run-js/","excerpt":"","text":"直接上解决方法不论页面是否被缓存,都会触发 pageshow,所以后退后需要执行的方法可以都放在下面事件内: window.addEventListener('pageshow', function () { console.log('on pageshow') }) 浏览器缓存行为 的详细介绍可以参考: [转]浏览器前进/后退缓存(BF Cache)","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"js","slug":"js","permalink":"http://yelog.org/tags/js/"}]},{"title":"CentOS7使用Firewalld","slug":"CentOS7使用Firewalld","date":"2017-09-19T01:53:53.000Z","updated":"2024-07-05T01:50:55.794Z","comments":true,"path":"2017/09/19/CentOS7使用Firewalld/","permalink":"http://yelog.org/2017/09/19/CentOS7%E4%BD%BF%E7%94%A8Firewalld/","excerpt":"","text":"介绍FirewallD 提供了支持网络/防火墙区域(zone)定义网络链接以及接口安全等级的动态防火墙管理工具。它支持 IPv4, IPv6 防火墙设置以及以太网桥接,并且拥有运行时配置和永久配置选项。它也支持允许服务或者应用程序直接添加防火墙规则的接口。 安装$ yum install firewalld # 如果需要图形界面的话,则再安装 $ yum install firewall-config zoneFirewall 能将不同的网络连接归类到不同的信任级别。 $ firewall-cmd --list-all-zones #查看所有zone信息 Zone 提供了以下几个级别: drop: 丢弃所有进入的包,而不给出任何响应 block: 拒绝所有外部发起的连接,允许内部发起的连接 public: 允许指定的进入连接 external: 同上,对伪装的进入连接,一般用于路由转发 dmz: 允许受限制的进入连接 work: 允许受信任的计算机被限制的进入连接,类似 workgroup home: 同上,类似 homegroup internal: 同上,范围针对所有互联网用户 trusted: 信任所有连接 过滤规则 source: 根据源地址过滤 interface: 根据网卡过滤 service: 根据服务名过滤 port: 根据端口过滤 icmp-block: icmp 报文过滤,按照 icmp 类型配置 masquerade: ip 地址伪装 forward-port: 端口转发 rule: 自定义规则 过滤规则的优先级遵循如下顺序 source interface firewalld.conf 使用$ systemctl start firewalld # 启动 $ systemctl stop firewalld # 关闭 $ systemctl enable firewalld # 开机启动 $ systemctl disable firewalld # 取消开机启动 具体的规则管理,可以使用 firewall-cmd,具体的使用方法 $ firewall-cmd --help --zone=NAME # 指定 zone --permanent # 永久修改,--reload 后生效 --timeout=seconds # 持续效果,到期后自动移除,用于调试,不能与 --permanent 同时使用 查看规则查看运行状态 $ firewall-cmd --state 查看已被激活的 Zone 信息 $ firewall-cmd --get-active-zones public interfaces: eth0 eth1 查看指定接口的 Zone 信息 $ firewall-cmd --get-zone-of-interface=eth0 public 查看指定级别的接口 $ firewall-cmd --zone=public --list-interfaces eth0 查看指定级别的所有信息,譬如 public $ firewall-cmd --zone=public --list-all public (default, active) interfaces: eth0 sources: services: dhcpv6-client http ssh ports: masquerade: no forward-ports: icmp-blocks: rich rules: 查看所有级别被允许的信息 $ firewall-cmd --get-service 查看重启后所有 Zones 级别中被允许的服务,即永久放行的服务 $ firewall-cmd --get-service --permanent 管理规则$ firewall-cmd --panic-on # 丢弃 $ firewall-cmd --panic-off # 取消丢弃 $ firewall-cmd --query-panic # 查看丢弃状态 $ firewall-cmd --reload # 更新规则,不重启服务 $ firewall-cmd --complete-reload # 更新规则,重启服务 添加某接口至某信任等级,譬如添加 eth0 至 public,永久修改 $ firewall-cmd --zone=public --add-interface=eth0 --permanent 设置 public 为默认的信任级别 $ firewall-cmd --set-default-zone=public a. 管理端口列出 dmz 级别的被允许的进入端口 $ firewall-cmd --zone=dmz --list-ports 允许 tcp 端口 8080 至 dmz 级别 $ firewall-cmd --zone=dmz --add-port=8080/tcp 允许某范围的 udp 端口至 public 级别,并永久生效 $ firewall-cmd --zone=public --add-port=5060-5059/udp --permanent b. 网卡接口列出 public zone 所有网卡 $ firewall-cmd --zone=public --list-interfaces 将 eth0 添加至 public zone,永久 $ firewall-cmd --zone=public --permanent --add-interface=eth0 eth0 存在与 public zone,将该网卡添加至 work zone,并将之从 public zone 中删除 $ firewall-cmd --zone=work --permanent --change-interface=eth0 删除 public zone 中的 eth0,永久 $ firewall-cmd --zone=public --permanent --remove-interface=eth0 c. 管理服务添加 smtp 服务至 work zone $ firewall-cmd --zone=work --add-service=smtp 移除 work zone 中的 smtp 服务 $ firewall-cmd --zone=work --remove-service=smtp d. 配置 external zone 中的 ip 地址伪装查看 $ firewall-cmd --zone=external --query-masquerade 打开伪装 $ firewall-cmd --zone=external --add-masquerade 关闭伪装 $ firewall-cmd --zone=external --remove-masquerade e. 配置 public zone 的端口转发要打开端口转发,则需要先 $ firewall-cmd --zone=public --add-masquerade 然后转发 tcp 22 端口至 3753 $ firewall-cmd --zone=public --add-forward-port=port=22:proto=tcp:toport=3753 转发 22 端口数据至另一个 ip 的相同端口上 $ firewall-cmd --zone=public --add-forward-port=port=22:proto=tcp:toaddr=192.168.1.100 转发 22 端口数据至另一 ip 的 2055 端口上 $ firewall-cmd --zone=public --add-forward-port=port=22:proto=tcp:toport=2055:toaddr=192.168.1.100 f. 配置 public zone 的 icmp查看所有支持的 icmp 类型 $ firewall-cmd --get-icmptypes destination-unreachable echo-reply echo-request parameter-problem redirect router-advertisement router-solicitation source-quench time-exceeded 列出 $ firewall-cmd --zone=public --list-icmp-blocks 添加 echo-request 屏蔽 $ firewall-cmd --zone=public --add-icmp-block=echo-request [--timeout=seconds] 移除 echo-reply 屏蔽 $ firewall-cmd --zone=public --remove-icmp-block=echo-reply g. IP 封禁 $ firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='222.222.222.222' reject" 当然,我们仍然可以通过 ipset 来封禁 ip 封禁 ip $ firewall-cmd --permanent --zone=public --new-ipset=blacklist --type=hash:ip $ firewall-cmd --permanent --zone=public --ipset=blacklist --add-entry=222.222.222.222 封禁网段 $ firewall-cmd --permanent --zone=public --new-ipset=blacklist --type=hash:net $ firewall-cmd --permanent --zone=public --ipset=blacklist --add-entry=222.222.222.0/24 倒入 ipset 规则 $ firewall-cmd --permanent --zone=public --new-ipset-from-file=/path/blacklist.xml 然后封禁 blacklist $ firewall-cmd --permanent --zone=public --add-rich-rule='rule source ipset=blacklist drop' 重新载入以生效 $ firewall-cmd --reload","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"firewall","slug":"firewall","permalink":"http://yelog.org/tags/firewall/"}]},{"title":"docker备份恢复之save与export","slug":"docker-save-与-docker-export","date":"2017-09-18T14:38:52.000Z","updated":"2024-07-05T01:50:54.947Z","comments":true,"path":"2017/09/18/docker-save-export/","permalink":"http://yelog.org/2017/09/18/docker-save-export/","excerpt":"","text":"docker save导出docker save 命令用于持久化 镜像,先获得镜像名称,再执行保存: # 通过此命令查出要持久化的镜像名称 $ docker images # 持久化镜像名为 image_name 的镜像, $ docker save image_name -o ~/save.tar 注意: 如果镜像是在远程仓库,执行保存镜像的时候可能会报 Cowardly refusing to save to a terminal. Use the -o flag or redirect. 的错,可以通过 docker save image_name > image_name.tar 将镜像从远程仓库持久化到本地。 导入# 导入 save.tar $ docker load < ~/save.tar # 查看镜像 $ docker images images docker export导出docker export 命令用于持久化 容器,先获取容器ID,再执行保存。 # 通过此命令查出要持久化的容器ID $ docker ps -a # 持久化容器id为 container_id 的容器 $ docker export container_id > ~/export.tar 导入# 从 export.tar 导入镜像 $ cat ~/export.tar | docker import - my-images:latest # 查看镜像 $ sudo docker images 不同通过 sudo docker images --tree 可以查看到镜像的所有层,就会发现, docker export 丢失了所有的历史,而docker save 则会保存所有历史。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"sudo命令免密码设置","slug":"sudo命令免密码设置","date":"2017-09-11T01:30:55.000Z","updated":"2024-07-05T01:50:55.738Z","comments":true,"path":"2017/09/11/sudo命令免密码设置/","permalink":"http://yelog.org/2017/09/11/sudo%E5%91%BD%E4%BB%A4%E5%85%8D%E5%AF%86%E7%A0%81%E8%AE%BE%E7%BD%AE/","excerpt":"","text":"如果某台linux只有自己在使用,比如个人系统,每次调用 sudo 时都需要输入密码,长期下来着实厌烦,因此本文介绍如何配置 sudo 命令,使其在运行时不需要输入密码。 步骤 执行命令 $ sudo visudo 添加以下两行, 下面的 sys 表示 sys 组成员不用密码使用sudo aaronkilik ALL=(ALL) NOPASSWD: ALL %sys ALL=(ALL) NOPASSWD: ALL 现在在使用 sudo 命令, 将不再需要输入密码。 扩展如果只允许用户使用 kill 和 rm 命令时,不需要输入密码,见如下配置 %sys ALL=(ALL) NOPASSWD: /bin/kill, /bin/rm","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://yelog.org/tags/linux/"}]},{"title":"搭建Maven私服-Nexus","slug":"搭建Maven私服-Nexus","date":"2017-09-06T15:01:31.000Z","updated":"2024-07-05T01:50:55.747Z","comments":true,"path":"2017/09/06/build-Maven-Nexus/","permalink":"http://yelog.org/2017/09/06/build-Maven-Nexus/","excerpt":"","text":"Maven 私服,可以代理远程仓库和部署自己或第三方构件。本文介绍使用最广泛搭建 Maven 私服的工具: Sonatype Nexus。 作者环境 本次搭建私服是在局域网的一台服务器上,操作系统为 CentOS 。 需要部署到私服的项目 soul ssm 项目需要引用 soul 安装Java 确保服务器已经安装了 java 环境,这个过程不是本文重点,安装过程自行百度。 安装Nexus 官网 pro 版本的是需要付费的。所以我们使用免费的 OSS 版本,下载地址 (https://www.sonatype.com/download-oss-sonatype) # 上传到服务器并解压 $ tar xvf nexus-3.5.1-02-unix.tar.gz 启动Nexus# 启动服务 $ cd /nexus-3.5.1-02/bin/ $ ./nexus start 验证打开网址:(http://192.168.0.86:8081/) , ip 为搭建私服的服务器 ip 。用户名/密码: admin/admin123出现一下画面,就说明安装成功了。 发布soul项目到私服创建仓库 创建yelog-release仓库(名字自定义), type选择 : release 创建yelog-snapshot仓库(名字自定义), type选择 : snapshot重复上面 ① 和 ② 步,根据下图选择类型: 两个都创建完成后,效果如下: pom中添加部署配置url 复制上图中新建的仓库的 copy 按钮,复制url。 <distributionManagement> <repository> <id>yelog-release</id> <name>Release Repository of yelog</name> <url>http://192.168.0.86:8081/repository/yelog-release/</url> </repository> <snapshotRepository> <id>yelog-snapshot</id> <name>Snapshot Repository of yelog</name> <url>http://192.168.0.86:8081/repository/yelog-snapshot/</url> </snapshotRepository> </distributionManagement> 在maven的 settings.xml 中配置这里配置 maven 的账号密码,id 要与 distributionManagement 中的id一致。默认账号/密码:admin/admin123 <servers> <server> <id>yelog-realease</id> <username>admin</username> <password>admin123</password> </server> <server> <id>yelog-snapshot</id> <username>admin</username> <password>admin123</password> </server> </servers> 执行maven命令部署项目到私服上我这里直接使用IDE的插件执行部署完成后,可以在 yelog-snapshot 仓库中,查看部署的情况,如下图所示 从私服拉去依赖库 上一步我们已经将项目 soul 部署到私服上了,这一步介绍项目 ssm 如何依赖引用 soul。私服中的 maven-central 可以链接远程仓库。这样,当有依赖在私服中找不到后,就可以通过远程仓库自动下载依赖。 pom 文件中添加如下配置 public库成员仓库中添加我们自定义的仓库 配置远程仓库为私服地址。 <repositories> <repository> <id>public</id> <name>public Repository</name> <url>http://192.168.0.86:8081/repository/maven-public/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>public</id> <name>Public Repositories</name> <url>http://192.168.0.86:8081/repository/maven-public/</url> </pluginRepository> </pluginRepositories> 引入依赖 <dependency> <groupId>org.soul</groupId> <artifactId>commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> ssm项目就可以引用到soul代码 本文结束。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"maven","slug":"maven","permalink":"http://yelog.org/tags/maven/"},{"name":"nexus","slug":"nexus","permalink":"http://yelog.org/tags/nexus/"}]},{"title":"Mybatis常用Mapper语句","slug":"mybatis-Mapper","date":"2017-08-04T07:54:47.000Z","updated":"2024-07-05T01:50:55.364Z","comments":true,"path":"2017/08/04/mybatis-Mapper/","permalink":"http://yelog.org/2017/08/04/mybatis-Mapper/","excerpt":"","text":"插入/* 简单插入 */ <insert id="insertOne" parameterType="Person"> insert into person (id, name, age) VALUES(#{id}, #{name}, #{age}); </insert> /* 插入并返回对象的主键(数据库序列) */ <insert id="insertOne" parameterType="Person" useGeneratedKeys="true" keyProperty="id"> insert into person (name, age) VALUES(#{name}, #{age}); </insert> 更新/* 简单更新 */ <update id="updateName"> update person set name = #{name} where id = #{id}; </update> /* 更新值并返回 */ <select id="updateAge" parameterType="Person"> update person set age = age + #{age} where id = #{id} returning age; </select> 插入或更新记录玩家在某种类型游戏下的统计记录: 如果没有记录,则从插入,count字段为1;如果有记录,则更新count字段+1; 方式一 <insert id="addCount" parameterType="CountRecord"> /*如果有记录,则更新;无记录,则noting*/ update count_record set "count" = "count"+1 where type_id = #{typeId} and user_id = #{userId}; /*如果有记录,则noting;无记录,则插入*/ insert into count_record(type_id, user_id, "count") select #{typeId}, #{userId}, 1 where not exists (select * from count where type_id = #{typeId} and user_id = #{userId}); </insert> 方式二 /* 利用 PostgreSQL 的 conflic 特性 */ <insert id="addCount" parameterType="CountRecord"> insert into count_record(type_id, user_id, "count") VALUES (#{typeId}, #{userId}, #{count}) on conflict(type_id,user_id) do update set "count" = count_record."count" + 1 </insert>","categories":[{"name":"数据库","slug":"数据库","permalink":"http://yelog.org/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"}],"tags":[{"name":"mybatis","slug":"mybatis","permalink":"http://yelog.org/tags/mybatis/"}]},{"title":"MathJax适配Pjax","slug":"MathJax-pjax","date":"2017-07-05T11:24:10.000Z","updated":"2024-07-05T01:50:55.123Z","comments":true,"path":"2017/07/05/MathJax-pjax/","permalink":"http://yelog.org/2017/07/05/MathJax-pjax/","excerpt":"","text":"hexo 添加 MathJax 的过程网上很多,这里就不细讲,这里贴一张写的不错的文章 Hexo博客(13)添加MathJax数学公式渲染 由于 3-hexo 这个主题使用了 pjax ,刷新和第一次加载没有问题,但是点到其他文章,再点回来,渲染就无效了。 这个问题和之前适配多说和高亮时,是同样的问题,只需要在下面配置即可。 $(document).on({ /*pjax请求回来页面后触发的事件*/ 'pjax:end': function () { /*渲染MathJax数学公式*/ $.getScript('//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML',function () { MathJax.Hub.Typeset(); }); } }); 这样就解决了pjax的适配问题。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"mathjax","slug":"mathjax","permalink":"http://yelog.org/tags/mathjax/"},{"name":"pjax","slug":"pjax","permalink":"http://yelog.org/tags/pjax/"}]},{"title":"3-hexo配置MathJax数学公式渲染","slug":"3-hexo-mathjax","date":"2017-07-05T07:09:42.000Z","updated":"2024-07-05T01:50:55.226Z","comments":true,"path":"2017/07/05/3-hexo-mathjax/","permalink":"http://yelog.org/2017/07/05/3-hexo-mathjax/","excerpt":"","text":"在用 markdown 写文档时,免不了碰到数学公式。 处理hexo的MarkDown渲染器与MathJax的冲突由于hexo的MarkDown渲染器与MathJax有冲突,所以在使用之前需要修改两个地方。 编辑 node_modules\\marked\\lib\\marked.js 脚本 将451行 ,这一步取消了对 \\\\,\\{,\\} 的转义(escape) escape: /^\\\\([\\\\`*{}\\[\\]()# +\\-.!_>])/, 改为 escape: /^\\\\([`*\\[\\]()# +\\-.!_>])/, 将459行,这一步取消了对斜体标记 _ 的转义 em: /^\\b_((?:[^_]|__)+?)_\\b|^\\*((?:\\*\\*|[\\s\\S])+?)\\*(?!\\*)/, 改为 em:/^\\*((?:\\*\\*|[\\s\\S])+?)\\*(?!\\*)/, 开启MathJax修改 3-hexo/_config.yml # MathJax 数学公式支持 mathjax: on: true #是否启用 per_page: false # 若只渲染单个页面,此选项设为false,页面内加入 mathjax: true 考虑到页面的加载速度,支持渲染单个页面。 设置 per_page: false ,在需要渲染的页面内 加入 mathjax: true 这样,就可以在页面内写MathJax公式了。 MathJax公式书写公式书写依然按照MarkDown语法来,基本上也和LaTeX相同,单 $ 符引住的是行内公式,双$符引住的是行间公式。 MathJax公式书写参考MathJax basic tutorial and quick reference 1.MathJax行内公式含有下划线 _ 的公式 $x_mu$ : $x_mu$ 希腊字符 $\\sigma$ : $\\sigma$ 双 \\\\ 公式内换行 $$ f(n) = \\begin{cases} n/2, & \\text{if $n$ is even} \\\\ 3n+1, & \\text{if $n$ is odd} \\end{cases} $$ $$f(n) =\\begin{cases}n/2, & \\text{if $n$ is even} \\3n+1, & \\text{if $n$ is odd}\\end{cases}$$ 行内公式 $y=ax+b$:$y=ax+b$ 行内公式 $\\cos 2\\theta = \\cos^2 \\theta - \\sin^2 \\theta = 2 \\cos^2 \\theta$:$\\cos 2\\theta = \\cos^2 \\theta - \\sin^2 \\theta = 2 \\cos^2 \\theta$ 行内公式 $M(\\beta^{\\ast}(D),D) \\subseteq C$ : $M(\\beta^{\\ast}(D),D) \\subseteq C$ 2.MathJax行间公式行间公式$$ \\sum_{i=0}^n i^2 = \\frac{(n^2+n)(2n+1)}{6} $$:$$ \\sum_{i=0}^n i^2 = \\frac{(n^2+n)(2n+1)}{6} $$ 行间公式$$ x = \\dfrac{-b \\pm \\sqrt{b^2 - 4ac}}{2a} $$:$$ x = \\dfrac{-b \\pm \\sqrt{b^2 - 4ac}}{2a} $$ 3.MathJax公式自动编号书写时使用 $$ \\begin{equation} \\end{equation} $$ 进行公式自动编号,同时会自动连续编号,例如: $$ \\begin{equation} \\sum_{i=0}^n F_i \\cdot \\phi (H, p_i) - \\sum_{i=1}^n a_i \\cdot ( \\tilde{x_i}, \\tilde{y_i}) + b_i \\cdot ( \\tilde{x_i}^2 , \\tilde{y_i}^2 ) \\end{equation} $$ $$ \\begin{equation} \\beta^*(D) = \\mathop{argmin} \\limits_{\\beta} \\lambda {||\\beta||}^2 + \\sum_{i=1}^n max(0, 1 - y_i f_{\\beta}(x_i)) \\end{equation} $$ $$\\begin{equation}\\sum_{i=0}^n F_i \\cdot \\phi (H, p_i) - \\sum_{i=1}^n a_i \\cdot ( \\tilde{x_i}, \\tilde{y_i}) + b_i \\cdot ( \\tilde{x_i}^2 , \\tilde{y_i}^2 )\\end{equation}$$$$\\begin{equation}\\beta^*(D) = \\mathop{argmin} \\limits_{\\beta} \\lambda {||\\beta||}^2 + \\sum_{i=1}^n max(0, 1 - y_i f_{\\beta}(x_i))\\end{equation}$$ MathJax公式手动编号可以在公式书写时使用 \\tag{手动编号} 添加手动编号,例如: $$ \\begin{equation} \\sum_{i=0}^n F_i \\cdot \\phi (H, p_i) - \\sum_{i=1}^n a_i \\cdot ( \\tilde{x_i}, \\tilde{y_i}) + b_i \\cdot ( \\tilde{x_i}^2 , \\tilde{y_i}^2 ) \\tag{1.2.3} \\end{equation} $$ $$\\begin{equation}\\sum_{i=0}^n F_i \\cdot \\phi (H, p_i) - \\sum_{i=1}^n a_i \\cdot ( \\tilde{x_i}, \\tilde{y_i}) + b_i \\cdot ( \\tilde{x_i}^2 , \\tilde{y_i}^2 ) \\tag{1.2.3}\\end{equation}$$ 不加 \\begin{equation} \\end{equation} 也可以,例如: $$ \\beta^*(D) = \\mathop{argmin} \\limits_{\\beta} \\lambda {||\\beta||}^2 + \\sum_{i=1}^n max(0, 1 - y_i f_{\\beta}(x_i)) \\tag{我的公式3} $$ $$\\beta^*(D) = \\mathop{argmin} \\limits_{\\beta} \\lambda {||\\beta||}^2 + \\sum_{i=1}^n max(0, 1 - y_i f_{\\beta}(x_i)) \\tag{我的公式3}$$ 行内公式加\\tag{}后会自动成为行间公式,例如: $z = (p_0, ..... , p_n) \\tag{公式21} $$z = (p_0, ….. , p_n) \\tag{公式21} $ 4.其他公式书写技巧如何将下标放到正下方?① 如果是数学符号,那么直接用 \\limits 命令放在正下方,如Max函数下面的取值范围,需要放在Max的正下方。可以如下实现:$ \\max \\limits_{a<x<b}\\{f(x)\\} $$ \\max \\limits_{a<x<b}{f(x)} $ ② 若是普通符号,那么要用 \\mathop 先转成数学符号再用 \\limits,如$ \\mathop{a}\\limits_{i=1} $$ \\mathop{a}\\limits_{i=1} $ MathJax矩阵输入无括号矩阵: $$ \\begin{matrix} 1 & x & x^2 \\\\ 1 & y & y^2 \\\\ 1 & z & z^2 \\\\ \\end{matrix} $$ $$\\begin{matrix}1 & x & x^2 \\1 & y & y^2 \\1 & z & z^2 \\\\end{matrix}$$ 有括号有竖线矩阵: $$ \\left[ \\begin{array}{cc|c} 1&2&3\\\\ 4&5&6 \\end{array} \\right] $$ $$\\left[ \\begin{array}{cc|c} 1&2&3\\ 4&5&6 \\end{array}\\right]$$ 行内小矩阵:$\\bigl( \\begin{smallmatrix} a & b \\\\ c & d \\end{smallmatrix} \\bigr)$$\\bigl( \\begin{smallmatrix} a & b \\ c & d \\end{smallmatrix} \\bigr)$ 这里有个问题,上面的写法在矩阵内没有换行,我看了下源码,双反斜杠\\又被MarkDown渲染引擎转义为单个反斜杠了,解决方法是写三个反斜杠\\\\或在双反斜杠后换行即可: $\\bigl( \\begin{smallmatrix} a & b \\\\\\ c & d \\end{smallmatrix} \\bigr)$$\\bigl( \\begin{smallmatrix} a & b \\\\ c & d \\end{smallmatrix} \\bigr)$ 参考Hexo博客(13)添加MathJax数学公式渲染在Hexo中渲染MathJax数学公式MathJax basic tutorial and quick reference","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"mathjax","slug":"mathjax","permalink":"http://yelog.org/tags/mathjax/"}]},{"title":"CentOS7安装配置匿名访问Samba","slug":"CentOS7-安装配置匿名访问Samba","date":"2017-07-03T11:40:14.000Z","updated":"2024-07-05T01:50:55.752Z","comments":true,"path":"2017/07/03/CentOS7-anonymous-Samba/","permalink":"http://yelog.org/2017/07/03/CentOS7-anonymous-Samba/","excerpt":"","text":"介绍 Samba,是种用来让UNIX系列的操作系统与微软Windows操作系统的SMB/CIFS(Server Message Block/Common Internet File System)网络协议做链接的自由软件 –wikipedia 本文就以 CentOS7 搭建 Samba 匿名完全访问(读/写)为目标,实现一个局域网内的文件共享平台。 1.安装Samba服务使用 yum 工具进行安装 $ yum install samba samba-client 2.检查是否安装成功$ rpm -qa | grep samba 3.防火墙开放端口在 /etc/sysconfig/iptables 中添加配置 -A INPUT -p tcp -m state --state NEW -m tcp --dport 137 -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 138 -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 139 -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 389 -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 445 -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 901 -j ACCEPT 重启 iptables 服务 $ service iptables restart 设置开机自启动 $ chkconfig --level 35 smb on 4.共享配置Samba Server的验证方式有四种: share:匿名访问共享,不需要提供用户名和口令, 安全性能较低。 user:共享目录只能被授权的用户访问,由Samba Server负责检查账号和密码的正确性。账号和密码要在本Samba Server中建立。 server:依靠其他Windows Server或Samba Server来验证用户的账号和密码,是一种代理验证。此种安全模式下,系统管理员可以把所有的Windows用户和口令集中到一个Server系统上,使用 Windows Server进行Samba认证, 远程服务器可以自动认证全部用户和口令,如果认证失败,Samba将使用用户级安全模式作为替代的方式。 domain:域安全级别,使用主域控制器(PDC)来完成认证。 创建一个匿名共享访问,需要使用share模式,但在CentOS安装的samba4中share 和 server验证方式已被弃用 配置如下: [global] workgroup = MYGROUP server string = Samba Server Version %v log file = /var/log/samba/log.%m max log size = 50 security = user map to guest = Bad User load printers = yes cups options = raw [share] comment = share path = /home/samba directory mask = 0777 create mask = 0777 #不可视目录 #browseable = yes guest ok=yes writable=yes 创建 /home/samba 共享目录 $ mkdir /home/samba 重启 smb 服务 $ service smb restart 检查服务是否在运行 $ pgrep smbd 检查配置参数 $ testparm Load smb config files from /etc/samba/smb.conf Processing section "[share]" Loaded services file OK. Server role: ROLE_STANDALONE Press enter to see a dump of your service definitions # Global parameters [global] server string = Samba Server Version %v workgroup = MYGROUP log file = /var/log/samba/log.%m max log size = 50 map to guest = Bad User security = USER idmap config * : backend = tdb cups options = raw [share] comment = share path = /home/samba create mask = 0777 directory mask = 0777 guest ok = Yes read only = No 访问以上就配置完成,如服务器地址为192.168.0.87 windows 系统访问,直接运行 \\\\192.168.0.87\\share linux 系统访问, smb://192.168.0.87/share 遇到的问题 linux 系统可以正常读写修改,但 windows 系统只可以读写,直接打开修改时就,就为只读文件了。解决办法:修改 /etc/samba/smb.conf ,在 [share] 中加入以下内容 create mask = 0777 访问部分文件可以正常访问,但部分文件无法访问。解决方法:修改文件访问权限 $ chmod -R 1777 /home/samba $ chown nobody:nobody 参考 CentOS7 安装Samba服务 CentOS7 安装配置匿名访问Samba","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"samba","slug":"samba","permalink":"http://yelog.org/tags/samba/"}]},{"title":"完美替代多说-gitment","slug":"gitment","date":"2017-06-26T04:08:13.000Z","updated":"2024-07-05T01:50:55.193Z","comments":true,"path":"2017/06/26/gitment/","permalink":"http://yelog.org/2017/06/26/gitment/","excerpt":"","text":"自从多说要停止服务时,就开始关注第三方评论系统,现在的评论系统都有这样或那样的问题,见 关于第三方评论系统 。忽然看到作者 孙士权 的一片文章 Gitment:使用 GitHub Issues 搭建评论系统 。 立即就将 gitment 集成到 3-hexo 主题内。本篇文章只讲在 3-hexo 内如何使用,如果想自定义,可以参考上面原文。 注册 OAuth Application点击此处 来注册一个新的 OAuth Application。其他内容可以随意填写,但要确保填入正确的 callback URL(一般是评论页面对应的域名,如 http://yelog.org)。 使用 gitment 评论系统修改主题 _config.yml gitment: on: true # 启用gitment评论系统 owner: yelog # 你的github账号 repo: yelog.github.io # 评论issue保存的仓库,我选择保存在blog仓库,也可以新建一个仓库 client_id: d64ceca0d8a4e8b1f5c9 # 上一步注册后生成的client_id client_secret: fb17d5f0aba31372f61a03df707bb20a39a73a06 # 上一步注册后生成的client_secret 部署并初始化1.发布 hexo $ hexo clear && hexo g && hexo d 2.打开发布的blog,登录github账号,并点击 Initialize Comments。 3.现在其他人就可以进行评论了 感受整体评论系统做的简洁,整体来说是个不错的系统。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"解决粘贴到vim缩进错乱问题","slug":"解决粘贴到vim缩进错乱问题","date":"2017-06-01T11:45:24.000Z","updated":"2024-07-05T01:50:54.879Z","comments":true,"path":"2017/06/01/vim-paste/","permalink":"http://yelog.org/2017/06/01/vim-paste/","excerpt":"","text":"遇见当我使用vim,想要粘贴下面这段脚本到 xx.sh 文件中 #!/bin/bash if [ $1 ] then if [ $1 == "help" ]; then echo -e "\\033[37m pay 参数1 [参数2] \\033[0m" else if [ $2 ]; then filename = $2 fi fi else echo -e "\\033[37m 缺少关键词,通过'pay help'查看帮助信息 \\033[0m" fi 却出现了错乱,如下图所示 分析vim 没有相应的程序来处理这个从其他应用复制粘贴的过程,所以Vim通过插入键盘输入的buffer来模拟这个粘贴的过程,这个时候Vim会以为这是用户输入的。 所以问题是:当上一行结束,光标进入下一行时Vim会自动以上一行的的缩进为初始位置。这样就会破坏原始文件的缩进。 解决问题经过一番google,发现vim提供了 paste 选项,进入 paste 模式后,就可以正常缩进了。 # 进入 paste 模式 :set paste # 退出 paste 模式 :set nopaste 如果不想每次都执行这个命令,可以在 ~/.vimrc 中添加一行配置 set pastetoggle=<F12> ,这样就可以通过F12快速在paste模式中切换。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"vim","slug":"vim","permalink":"http://yelog.org/tags/vim/"}]},{"title":"进入docker容器命令制作","slug":"进入docker容器命令制作","date":"2017-06-01T09:25:11.000Z","updated":"2024-07-05T01:50:55.756Z","comments":true,"path":"2017/06/01/entering-docker/","permalink":"http://yelog.org/2017/06/01/entering-docker/","excerpt":"","text":"通过attach进入容器# 进入容器(Docker自带的命令) $ sudo docker attach [name] 通过这命令进入容器后,执行ctrl+d退出容器后发现容器也停止了。所以可以通过 先按,ctrl+p 再按,ctrl+q 退出 制作进入容器的命令既然attach退出很麻烦,一不小心容器就down掉了 通过 docker exec 进入容器是安全的,但是命令过长 所以我们可以通过下面操作,简化命令 1.创建文件 /usr/bin/ctn,内容如下 docker exec -it $1 /bin/bash 2.检查环境变量有没有配置目录 /usr/bin $PATH bash: /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games: No such file or directory 配置环境变量的方式自行百度 3.完成上面两步即可通过命令 ctn 进入容器 $ ctn [name] 注意:如果是使用非root账号创建的命令,而docker命令是root权限,可能会存在权限问题可以设置 chmod 777 /usr/bin/ctn 设置权限使用 sudo ctn [name] 即可进入容器 4.自动补全docker名使用上面命令时,docker的名字都是手动输入,很麻烦,而且容易出错。 我们可以借助complete命令,来补全docker信息。 在~/.bashrc(作用于当前用户,如果所有用户,修改/etc/bashrc)文件中添加一行 # ctn auto complete complete -W "$(docker ps --format "{{.Names}}")" ctn 再执行 source .bashrc 使之生效。 这样我们输入 ctn 后,按 Tab 就会提示或自动补全了。 注意: 由于提示的docker名是 .bashrc 生效时的列表,所以如果之后docker列表有变动,需重新执行 source .bashrc 使之更新提示列表","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"tale博客搭建及体验","slug":"tale-build-experience","date":"2017-05-24T03:29:30.000Z","updated":"2024-07-05T01:50:55.188Z","comments":true,"path":"2017/05/24/tale-build-experience/","permalink":"http://yelog.org/2017/05/24/tale-build-experience/","excerpt":"","text":"不久之前在逛blog时,发现了这款tale,今天抽空搭建了一下,将搭建过程写于此。demo website:https://tale.biezhi.me 搭建思路看了tale作者的(github)[https://github.com/otale] 发现有建好docker,所以果断使用docker搭建tale的环境 构建docker镜像下载tale-docker到本地。 # 下载官方Dockerfile $ git clone https://github.com/otale/tale-docker.git # 构建 tale 镜像 $ docker build -t tale:1.0 . 下载tale博客文件# 下载压缩包 $ sudo wget http://7xls9k.dl1.z0.glb.clouddn.com/tale.zip # 讲解压出来的文件夹移入home目录 $ unzip tale.zip $ mv tale /home 构建tale镜像docker run -d --privileged --hostname tale --name tale \\ -v /etc/localtime:/etc/localtime:ro \\ -v /home/tale:/var/tale_home -p 80:9000 \\ -m 1024m --memory-swap -1 tale:1.0 访问浏览器进入 127.0.0.1 即可访问 体验管理后台 文章支持Markdown和富文本。 文章/评论/友链/标签管理/主题,设置简单,一目了然 支持插件扩展 博客 主题简洁(当然支持切换主题) 使用 instantclick ,页面切换流畅 评论系统,简洁易用 搜索只支持文章标题 整体 管理简单方便 使用docker后,迁移数据也方便 主题还不是很多 对于常年使用静态blog,手动渲染/发布,使用这个之后还有点小清新。 最后tale整体不错,值得入手。 不过目前没有笔者喜欢的主题(当然默认主题也不错),暂时不打算更换blog,笔者也打算过一段时间开发一个tale的主题,然后正式迁入tale。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"软件记录","slug":"工具/软件记录","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95/"}],"tags":[{"name":"tale","slug":"tale","permalink":"http://yelog.org/tags/tale/"}]},{"title":"docker数据管理","slug":"docker数据管理","date":"2017-05-23T13:43:06.000Z","updated":"2024-07-05T01:50:54.918Z","comments":true,"path":"2017/05/23/docker-data-manager/","permalink":"http://yelog.org/2017/05/23/docker-data-manager/","excerpt":"","text":"数据卷数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性: 数据卷可以在容器之间共享和重用 对数据卷的修改会立马生效 对数据卷的更新,不会影响镜像 数据卷默认会一直存在,即使容器被删除 注意:数据卷的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的数据卷。 创建一个数据卷在用 docker run 命令的时候,使用 -v 标记来创建一个数据卷并挂载到容器里。在一次 run 中多次使用可以挂载多个数据卷。 下面创建一个名为 web 的容器,并加载一个数据卷到容器的 /webapp 目录。 $ sudo docker run -d -P --name web -v /webapp training/webapp python app.py 注意:也可以在 Dockerfile 中使用 VOLUME 来添加一个或者多个新的卷到由该镜像创建的任意容器。 删除数据卷数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docker不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v 这个命令。无主的数据卷可能会占据很多空间,要清理会很麻烦。Docker官方正在试图解决这个问题,相关工作的进度可以查看这个PR。 挂载一个主机目录作为数据卷使用 -v 标记也可以指定挂载一个本地主机的目录到容器中去。 $ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py 上面的命令加载主机的 /src/webapp 目录到容器的 /opt/webapp 目录。这个功能在进行测试的时候十分方便,比如用户可以放置一些程序到本地目录中,来查看容器是否正常工作。本地目录的路径必须是绝对路径,如果目录不存在 Docker 会自动为你创建它。 注意:Dockerfile 中不支持这种用法,这是因为 Dockerfile 是为了移植和分享用的。然而,不同操作系统的路径格式不一样,所以目前还不能支持。 Docker 挂载数据卷的默认权限是读写,用户也可以通过 :ro 指定为只读。 $ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py 加了 :ro 之后,就挂载为只读了。 查看数据卷的具体信息在主机里使用以下命令可以查看指定容器的信息 $ docker inspect web 在输出的内容中找到其中和数据卷相关的部分,可以看到所有的数据卷都是创建在主机的/var/lib/docker/volumes/下面的 "Volumes": { "/webapp": "/var/lib/docker/volumes/fac362...80535" }, "VolumesRW": { "/webapp": true } ... 注:从Docker 1.8.0起,数据卷配置在”Mounts”Key下面,可以看到所有的数据卷都是创建在主机的/mnt/sda1/var/lib/docker/volumes/….下面了。 "Mounts": [ { "Name": "b53ebd40054dae599faf7c9666acfe205c3e922fc3e8bc3f2fd178ed788f1c29", "Source": "/mnt/sda1/var/lib/docker/volumes/b53ebd40054dae599faf7c9666acfe205c3e922fc3e8bc3f2fd178ed788f1c29/_data", "Destination": "/webapp", "Driver": "local", "Mode": "", "RW": true, "Propagation": "" } ] ... 挂载一个本地主机文件作为数据卷-v 标记也可以从主机挂载单个文件到容器中 $ sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash 这样就可以记录在容器输入过的命令了。 注意:如果直接挂载一个文件,很多文件编辑工具,包括 vi 或者 sed --in-place,可能会造成文件 inode 的改变,从 Docker 1.1 .0起,这会导致报错误信息。所以最简单的办法就直接挂载文件的父目录。 数据卷容器如果你有一些持续更新的数据需要在容器之间共享,最好创建数据卷容器。 数据卷容器,其实就是一个正常的容器,专门用来提供数据卷供其它容器挂载的。 首先,创建一个名为 dbdata 的数据卷容器: $ sudo docker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres 然后,在其他容器中使用 –volumes-from 来挂载 dbdata 容器中的数据卷。 $ sudo docker run -d --volumes-from dbdata --name db1 training/postgres $ sudo docker run -d --volumes-from dbdata --name db2 training/postgres 可以使用超过一个的 --volumes-from 参数来指定从多个容器挂载不同的数据卷。 也可以从其他已经挂载了数据卷的容器来级联挂载数据卷。 $ sudo docker run -d --name db3 --volumes-from db1 training/postgres 注意:使用 –volumes-from 参数所挂载数据卷的容器自己并不需要保持在运行状态。 如果删除了挂载的容器(包括 dbdata、db1 和 db2),数据卷并不会被自动删除。如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时使用 docker rm -v 命令来指定同时删除关联的容器。 这可以让用户在容器之间升级和移动数据卷。具体的操作将在下一节中进行讲解。 利用数据卷容器来备份、恢复、迁移数据卷可以利用数据卷对其中的数据进行进行备份、恢复和迁移。 备份首先使用 –volumes-from 标记来创建一个加载 dbdata 容器卷的容器,并从主机挂载当前目录到容器的 /backup 目录。命令如下: $ sudo docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata 容器启动后,使用了 tar 命令来将 dbdata 卷备份为容器中 /backup/backup.tar 文件,也就是主机当前目录下的名为 backup.tar 的文件。 恢复如果要恢复数据到一个容器,首先创建一个带有空数据卷的容器 dbdata2。 $ sudo docker run -v /dbdata --name dbdata2 ubuntu /bin/bash 然后创建另一个容器,挂载 dbdata2 容器卷中的数据卷,并使用 untar 解压备份文件到挂载的容器卷中。 $ sudo docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar 为了查看/验证恢复的数据,可以再启动一个容器挂载同样的容器卷来查看 $ sudo docker run --volumes-from dbdata2 busybox /bin/ls /dbdata","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"docker容器","slug":"docker容器","date":"2017-05-23T13:29:38.000Z","updated":"2024-07-05T01:50:54.923Z","comments":true,"path":"2017/05/23/docker-container/","permalink":"http://yelog.org/2017/05/23/docker-container/","excerpt":"","text":"容器镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。 容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。 命令# 创建一个名为myubuntu的容器 # -t:分配一个伪终端 -i:让容器的标准输入保持打开 $ docker run --name=myubuntu -t -i ubuntu /bin/bash # 创建一个名为webserver 的nginx容器,使用卷映射本机/home/faker/myspace/nginx目录到docker目录/usr/share/nginx/html $ docker run --name=webserver -d -v /home/faker/myspace/nginx:/usr/share/nginx/html -p 80:80 nginx # 查看容器的输出信息(打印信息,如 echo) # run的时候,使用-d将会不展示在宿主机上,可通过下面命令查看打印信息 $ docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done" $ docker logs [container ID or NAMES] # 启动容器 myubuntu $ docker start myubuntu # 关闭容器 myubuntu $ docker stop myubuntu # 查看已启动的容器 -a:查看包括未启动的容器在内的所有容器 $ docker ps [-a] # 进入容器(Docker自带的命令) $ docker attach [name] # 进入容器(通过exec) $ docker exec -it [name] /bin/bash # 导出容器快照到本地文件 $ docker export [container id] > ubuntu.tar # 将容器快照导入为镜像 $ cat ubuntu.tar | docker import - test/ubuntu:v1.0 # 从制定 URL 或者某个目录导入 $ docker import http://example.com/exampleimage.tgz example/imagerepo # 删除容器 -f:删除正在运行的容器 $ docker [-f] rm myubuntu # 删除所有已关闭的容器 $ docker rm $(docker ps -a -q) # 查询各容器资源使用情况 $ docker stats $(docker ps --format={{.Names}})","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"docker仓库","slug":"docker仓库","date":"2017-05-23T13:15:52.000Z","updated":"2024-07-05T01:50:54.903Z","comments":true,"path":"2017/05/23/docker-registry/","permalink":"http://yelog.org/2017/05/23/docker-registry/","excerpt":"","text":"Docker Hub目前 Docker 官方维护了一个公共仓库 Docker Hub,其中已经包括了超过 15,000 的镜像。大部分需求,都可以通过在 Docker Hub 中直接下载镜像来实现。 登录可以通过执行 docker login 命令来输入用户名、密码和邮箱来完成注册和登录。 注册成功后,本地用户目录的 .dockercfg 中将保存用户的认证信息。 基本操作用户无需登录即可通过 docker search 命令来查找官方仓库中的镜像,并利用 docker pull 命令来将它下载到本地。 例如以 centos 为关键词进行搜索: $ sudo docker search centos NAME DESCRIPTION STARS OFFICIAL AUTOMATED centos The official build of CentOS. 465 [OK] tianon/centos CentOS 5 and 6, created using rinse instea... 28 blalor/centos Bare-bones base CentOS 6.5 image 6 [OK] saltstack/centos-6-minimal 6 [OK] tutum/centos-6.4 DEPRECATED. Use tutum/centos:6.4 instead. ... 5 [OK] ... 可以看到返回了很多包含关键字的镜像,其中包括镜像名字、描述、星级(表示该镜像的受欢迎程度)、是否官方创建、是否自动创建。 官方的镜像说明是官方项目组创建和维护的,automated 资源允许用户验证镜像的来源和内容。 根据是否是官方提供,可将镜像资源分为两类。 一种是类似 centos 这样的基础镜像,被称为基础或根镜像。这些基础镜像是由 Docker 公司创建、验证、支持、提供。这样的镜像往往使用单个单词作为名字。 还有一种类型,比如 tianon/centos 镜像,它是由 Docker 的用户创建并维护的,往往带有用户名称前缀。可以通过前缀 user_name/ 来指定使用某个用户提供的镜像,比如 tianon 用户。 另外,在查找的时候通过 -s N 参数可以指定仅显示评价为 N 星以上的镜像。 下载官方 centos 镜像到本地。 $ sudo docker pull centos Pulling repository centos 0b443ba03958: Download complete 539c0211cd76: Download complete 511136ea3c5a: Download complete 7064731afe90: Download complete 用户也可以在登录后通过 docker push 命令来将镜像推送到 Docker Hub。 私有仓库安装 docker-registry容器运行在安装了 Docker 后,可以通过获取官方 registry 镜像来运行。 $ sudo docker run -d -p 5000:5000 registry 这将使用官方的 registry 镜像来启动本地的私有仓库。 用户可以通过指定参数来配置私有仓库位置,例如配置镜像存储到 Amazon S3 服务。 $ sudo docker run \\ -e SETTINGS_FLAVOR=s3 \\ -e AWS_BUCKET=acme-docker \\ -e STORAGE_PATH=/registry \\ -e AWS_KEY=AKIAHSHB43HS3J92MXZ \\ -e AWS_SECRET=xdDowwlK7TJajV1Y7EoOZrmuPEJlHYcNP2k4j49T \\ -e SEARCH_BACKEND=sqlalchemy \\ -p 5000:5000 \\ registry 此外,还可以指定本地路径(如 /home/user/registry-conf )下的配置文件。 $ sudo docker run -d -p 5000:5000 -v /home/user/registry-conf:/registry-conf -e DOCKER_REGISTRY_CONFIG=/registry-conf/config.yml registry 默认情况下,仓库会被创建在容器的 /tmp/registry 下。可以通过 -v 参数来将镜像文件存放在本地的指定路径。 例如下面的例子将上传的镜像放到 /opt/data/registry 目录。 $ sudo docker run -d -p 5000:5000 -v /opt/data/registry:/tmp/registry registry 本地安装对于 Ubuntu 或 CentOS 等发行版,可以直接通过源安装。1.Ubuntu $ sudo apt-get install -y build-essential python-dev libevent-dev python-pip liblzma-dev $ sudo pip install docker-registry 2.CentOS $ sudo yum install -y python-devel libevent-devel python-pip gcc xz-devel $ sudo python-pip install docker-registry 3.源码安装 $ sudo apt-get install build-essential python-dev libevent-dev python-pip libssl-dev liblzma-dev libffi-dev $ git clone https://github.com/docker/docker-registry.git $ cd docker-registry $ sudo python setup.py install 然后修改配置文件,主要修改 dev 模板段的 storage_path 到本地的存储仓库的路径。 $ cp config/config_sample.yml config/config.yml 之后启动 Web 服务。 $ sudo gunicorn -c contrib/gunicorn.py docker_registry.wsgi:application 或者 $ sudo gunicorn --access-logfile - --error-logfile - -k gevent -b 0.0.0.0:5000 -w 4 --max-requests 100 docker_registry.wsgi:application 此时使用 curl 访问本地的 5000 端口,看到输出 docker-registry 的版本信息说明运行成功。 注:config/config_sample.yml 文件是示例配置文件。 在私有仓库上传、下载、搜索镜像创建好私有仓库之后,就可以使用 docker tag 来标记一个镜像,然后推送它到仓库,别的机器上就可以下载下来了。例如私有仓库地址为 192.168.7.26:5000。 先在本机查看已有的镜像。 $ sudo docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE ubuntu latest ba5877dc9bec 6 weeks ago 192.7 MB ubuntu 14.04 ba5877dc9bec 6 weeks ago 192.7 MB 使用docker tag 将 ba58 这个镜像标记为 192.168.7.26:5000/test(格式为 docker tag IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG])。 $ sudo docker tag ba58 192.168.7.26:5000/test root ~ # docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE ubuntu 14.04 ba5877dc9bec 6 weeks ago 192.7 MB ubuntu latest ba5877dc9bec 6 weeks ago 192.7 MB 192.168.7.26:5000/test latest ba5877dc9bec 6 weeks ago 192.7 MB 使用 docker push 上传标记的镜像。 $ sudo docker push 192.168.7.26:5000/test The push refers to a repository [192.168.7.26:5000/test] (len: 1) Sending image list Pushing repository 192.168.7.26:5000/test (1 tags) Image 511136ea3c5a already pushed, skipping Image 9bad880da3d2 already pushed, skipping Image 25f11f5fb0cb already pushed, skipping Image ebc34468f71d already pushed, skipping Image 2318d26665ef already pushed, skipping Image ba5877dc9bec already pushed, skipping Pushing tag for rev [ba5877dc9bec] on {http://192.168.7.26:5000/v1/repositories/test/tags/latest} 用 curl 查看仓库中的镜像。 $ curl http://192.168.7.26:5000/v1/search {"num_results": 7, "query": "", "results": [{"description": "", "name": "library/miaxis_j2ee"}, {"description": "", "name": "library/tomcat"}, {"description": "", "name": "library/ubuntu"}, {"description": "", "name": "library/ubuntu_office"}, {"description": "", "name": "library/desktop_ubu"}, {"description": "", "name": "dockerfile/ubuntu"}, {"description": "", "name": "library/test"}]} 这里可以看到 {“description”: “”, “name”: “library/test”},表明镜像已经被成功上传了。 现在可以到另外一台机器去下载这个镜像。 $ sudo docker pull 192.168.7.26:5000/test Pulling repository 192.168.7.26:5000/test ba5877dc9bec: Download complete 511136ea3c5a: Download complete 9bad880da3d2: Download complete 25f11f5fb0cb: Download complete ebc34468f71d: Download complete 2318d26665ef: Download complete $ sudo docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE 192.168.7.26:5000/test latest ba5877dc9bec 6 weeks ago 192.7 MB 可以使用 这个脚本 批量上传本地的镜像到注册服务器中,默认是本地注册服务器 127.0.0.1:5000。例如: $ wget https://github.com/yeasy/docker_practice/raw/master/_local/push_images.sh; sudo chmod a+x push_images.sh $ ./push_images.sh ubuntu:latest centos:centos7 The registry server is 127.0.0.1 Uploading ubuntu:latest... The push refers to a repository [127.0.0.1:5000/ubuntu] (len: 1) Sending image list Pushing repository 127.0.0.1:5000/ubuntu (1 tags) Image 511136ea3c5a already pushed, skipping Image bfb8b5a2ad34 already pushed, skipping Image c1f3bdbd8355 already pushed, skipping Image 897578f527ae already pushed, skipping Image 9387bcc9826e already pushed, skipping Image 809ed259f845 already pushed, skipping Image 96864a7d2df3 already pushed, skipping Pushing tag for rev [96864a7d2df3] on {http://127.0.0.1:5000/v1/repositories/ubuntu/tags/latest} Untagged: 127.0.0.1:5000/ubuntu:latest Done Uploading centos:centos7... The push refers to a repository [127.0.0.1:5000/centos] (len: 1) Sending image list Pushing repository 127.0.0.1:5000/centos (1 tags) Image 511136ea3c5a already pushed, skipping 34e94e67e63a: Image successfully pushed 70214e5d0a90: Image successfully pushed Pushing tag for rev [70214e5d0a90] on {http://127.0.0.1:5000/v1/repositories/centos/tags/centos7} Untagged: 127.0.0.1:5000/centos:centos7 Done 仓库配置文件Docker 的 Registry 利用配置文件提供了一些仓库的模板(flavor),用户可以直接使用它们来进行开发或生产部署。 模板在 config_sample.yml 文件中,可以看到一些现成的模板段: common:基础配置 local:存储数据到本地文件系统 s3:存储数据到 AWS S3 中 dev:使用 local 模板的基本配置 test:单元测试使用 prod:生产环境配置(基本上跟s3配置类似) gcs:存储数据到 Google 的云存储 swift:存储数据到 OpenStack Swift 服务 glance:存储数据到 OpenStack Glance 服务,本地文件系统为后备 glance-swift:存储数据到 OpenStack Glance 服务,Swift 为后备 elliptics:存储数据到 Elliptics key/value 存储 用户也可以添加自定义的模版段。 默认情况下使用的模板是 dev,要使用某个模板作为默认值,可以添加 SETTINGS_FLAVOR 到环境变量中,例如 export SETTINGS_FLAVOR=dev 另外,配置文件中支持从环境变量中加载值,语法格式为 _env:VARIABLENAME[:DEFAULT]。 示例配置common: loglevel: info search_backend: "_env:SEARCH_BACKEND:" sqlalchemy_index_database: "_env:SQLALCHEMY_INDEX_DATABASE:sqlite:////tmp/docker-registry.db" prod: loglevel: warn storage: s3 s3_access_key: _env:AWS_S3_ACCESS_KEY s3_secret_key: _env:AWS_S3_SECRET_KEY s3_bucket: _env:AWS_S3_BUCKET boto_bucket: _env:AWS_S3_BUCKET storage_path: /srv/docker smtp_host: localhost from_addr: docker@myself.com to_addr: my@myself.com dev: loglevel: debug storage: local storage_path: /home/myself/docker test: storage: local storage_path: /tmp/tmpdockertmp","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"},{"name":"docker仓库","slug":"docker仓库","permalink":"http://yelog.org/tags/docker%E4%BB%93%E5%BA%93/"}]},{"title":"每天一个linux命令(58): sort","slug":"linux-command-58-sort","date":"2017-05-23T03:44:12.000Z","updated":"2024-07-05T01:50:55.734Z","comments":true,"path":"2017/05/23/linux-command-57-sort/","permalink":"http://yelog.org/2017/05/23/linux-command-57-sort/","excerpt":"","text":"sort是在Linux里非常常用的一个命令,管排序的。 1.工作原理sort将文件的每一行作为一个单位,相互比较,比较原则是从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出。 $ cat seq.txt apple allow photo bowl peal check cheese $ sort seq.txt allow apple bowl check cheese peal photo 2.sort的-u选项它的作用很简单,就是在输出行中去除重复行。 3.sort的-r选项sort默认的排序方式是升序,如果想改成降序,就加个-r就搞定了。 4.sort的-o选项由于sort默认是把结果输出到标准输出,所以需要用重定向才能将结果写入文件,形如sort filename > newfile。 但是,如果你想把排序结果输出到原文件中,用重定向可就不行了。 [rocrocket@rocrocket programming]$ sort -r number.txt > number.txt [rocrocket@rocrocket programming]$ cat number.txt [rocrocket@rocrocket programming]$ 看,竟然将number清空了。 就在这个时候,-o选项出现了,它成功的解决了这个问题,让你放心的将结果写入原文件。这或许也是-o比重定向的唯一优势所在。 5.sort的-n选项你有没有遇到过10比2小的情况。我反正遇到过。出现这种情况是由于排序程序将这些数字按字符来排序了,排序程序会先比较1和2,显然1小,所以就将10放在2前面喽。这也是sort的一贯作风。 我们如果想改变这种现状,就要使用-n选项,来告诉sort,“要以数值来排序”! 6.sort的-t选项和-k选项如果有一个文件的内容是这样: [rocrocket@rocrocket programming]$ cat facebook.txt banana:30:5.5 apple:10:2.5 pear:90:2.3 orange:20:3.4 这个文件有三列,列与列之间用冒号隔开了,第一列表示水果类型,第二列表示水果数量,第三列表示水果价格。 那么我想以水果数量来排序,也就是以第二列来排序,如何利用sort实现? 幸好,sort提供了-t选项,后面可以设定间隔符。(是不是想起了cut和paste的-d选项,共鸣~~) 指定了间隔符之后,就可以用-k来指定列数了。 [rocrocket@rocrocket programming]$ sort -n -k 2 -t : facebook.txt apple:10:2.5 orange:20:3.4 banana:30:5.5 pear:90:2.3 我们使用冒号作为间隔符,并针对第二列来进行数值升序排序,结果很令人满意。 7.其他的sort常用选项-f会将小写字母都转换为大写字母来进行比较,亦即忽略大小写 -c会检查文件是否已排好序,如果乱序,则输出第一个乱序的行的相关信息,最后返回1 -C会检查文件是否已排好序,如果乱序,不输出内容,仅返回1 -M会以月份来排序,比如JAN小于FEB等等 -b会忽略每一行前面的所有空白部分,从第一个可见字符开始比较。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"CentOS修改DNS/GW/IP","slug":"CentOS修改DNS-GW-IP","date":"2017-05-23T01:53:52.000Z","updated":"2024-07-05T01:50:55.769Z","comments":true,"path":"2017/05/23/CentOS-DNS-GW-IP/","permalink":"http://yelog.org/2017/05/23/CentOS-DNS-GW-IP/","excerpt":"","text":"1.修改DNS解决方案一:修改网卡的DNS的配置文件 $ vim /etc/resolv.conf 添加以下内容,设置两条dns nameserver 8.8.8.8 #google域名服务器 nameserver 8.8.4.4 #google域名服务器 若未生效,可执行 chattr +i /etc/resolv.conf 设置文件属性只有root用户才能修改然后执行 service NetworkManager restart 解决方案二:对接口添加dns信息;编辑/etc/sysconfig/network-scripts/ifcfg-xxx,xxx为你的网卡名,但一般是ifcfg-eth0的,具体的xxx根据你的网卡确定,在最下面添加: DNS1=8.8.8.8 #google dns服务器, 根据实际情况更换 DNS2=8.8.4.4 #google dns服务器, 根据实际情况更换 保存后重启网络 $ service network restart 2.修改网关修改网关的配置文件(第3部分也可以设置) $ vim /etc/sysconfig/network 修改为一下内容 NETWORKING=yes(表示系统是否使用网络,一般设置为yes。如果设为no,则不能使用网络,而且很多系统服务程序将无法启动) HOSTNAME=centos(设置本机的主机名,这里设置的主机名要和/etc/hosts中设置的主机名对应) GATEWAY=192.168.1.1(设置本机连接的网关的IP地址。例如,网关为10.0.0.2) 3.修改ip修改对应的网卡的IP地址的配置文件 $ vim /etc/sysconfig/network-scripts/ifcfg-eth0 修改为一下内容 DEVICE=eth0 #描述网卡对应的设备别名,例如ifcfg-eth0的文件中它为eth0 BOOTPROTO=static #设置网卡获得ip地址的方式,可能的选项为static,dhcp或bootp,分别对应静态指定的 ip地址,通过dhcp协议获得的ip地址,通过bootp协议获得的ip地址 BROADCAST=192.168.0.255 #对应的子网广播地址 HWADDR=00:07:E9:05:E8:B4 #对应的网卡物理地址 IPADDR=12.168.1.2 #如果设置网卡获得 ip地址的方式为静态指定,此字段就指定了网卡对应的ip地址 IPV6INIT=no IPV6_AUTOCONF=no NETMASK=255.255.255.0 #网卡对应的网络掩码 NETWORK=192.168.1.0 #网卡对应的网络地址 ONBOOT=yes #系统启动时是否设置此网络接口,设置为yes时,系统启动时激活此设备","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/tags/%E8%BF%90%E7%BB%B4/"},{"name":"centos","slug":"centos","permalink":"http://yelog.org/tags/centos/"}]},{"title":"Dockerfile指令详解","slug":"Dockerfile指令详解","date":"2017-05-22T11:11:42.000Z","updated":"2024-07-05T01:50:54.912Z","comments":true,"path":"2017/05/22/Dockerfile/","permalink":"http://yelog.org/2017/05/22/Dockerfile/","excerpt":"","text":"Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。 构建镜像命令格式: $ docker build [选项] <上下文路径/URL/-> 示例: # 构建一个名为 nginx:v3 的镜像 $ docker build -t nginx:v3 . RUN 执行命令 shell格式:RUN <命令>,就像直接在命令行中输入的命令一样。刚才写的 Dockrfile 中的 RUN 指令就是这种格式。 RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html exec格式:RUN [“可执行文件”, “参数1”, “参数2”],这更像是函数调用中的格式。 COPY 复制文件格式: COPY <源路径>... <目标路径> COPY ["<源路径1>",... "<目标路径>"]和 RUN 指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。比如: COPY package.json /usr/src/app/ <源路径> 可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match 规则,如: COPY hom* /mydir/ COPY hom?.txt /mydir/ <目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。 此外,还需要注意一点,使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。 ADD更高级的复制文件ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。 比如 <源路径> 可以是一个 URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600,如果这并不是想要的权限,那么还需要增加额外的一层 RUN 进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN 指令进行解压缩。所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。 如果 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去。 在 Docker 官方的最佳实践文档中要求,尽可能的使用 COPY 。 因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。 CMD 容器启动命令CMD 指令的格式和 RUN 相似,也是两种格式:1) shell 格式:CMD <命令>2) exec 格式:CMD ["可执行文件", "参数1", "参数2"...]3) 参数列表格式:CMD ["参数1", "参数2"...]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。 Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器主进程的启动命令的。 在运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如,ubuntu 镜像默认的 CMD 是 /bin/bash,如果我们直接 docker run -it ubuntu 的话,会直接进入 bash。我们也可以在运行时指定运行别的命令,如 docker run -it ubuntu cat /etc/os-release。这就是用 cat /etc/os-release 命令替换了默认的 /bin/bash 命令了,输出了系统版本信息。 在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 “,而不要使用单引号。 ENTRYPOINT 入口点ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 –entrypoint 来指定。 当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为: <ENTRYPOINT> "<CMD>" 场景一:让镜像变成像命令一样使用 FROM ubuntu:16.04 RUN apt-get update \\ && apt-get install -y curl \\ && rm -rf /var/lib/apt/lists/* ENTRYPOINT [ "curl", "-s", "http://ip.cn" ] $ docker run myip -i 这是因为当存在 ENTRYPOINT 后,CMD 的内容将会作为参数传给 ENTRYPOINT,而这里 -i 就是新的 CMD,因此会作为参数传给 curl,从而达到了我们预期的效果。 场景二:应用运行前的准备工作可以写一个脚本,然后放入 ENTRYPOINT 中去执行,而这个脚本会将接到的参数(也就是 )作为命令,在脚本最后执行。比如官方镜像 redis 中就是这么做的: FROM alpine:3.4 ... RUN addgroup -S redis && adduser -S -G redis redis ... ENTRYPOINT ["docker-entrypoint.sh"] EXPOSE 6379 CMD [ "redis-server" ] 可以看到其中为了 redis 服务创建了 redis 用户,并在最后指定了 ENTRYPOINT 为 docker-entrypoint.sh 脚本。 #!/bin/sh ... # allow the container to be started with `--user` if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then chown -R redis . exec su-exec redis "$0" "$@" fi exec "$@" 该脚本的内容就是根据 CMD 的内容来判断,如果是 redis-server 的话,则切换到 redis 用户身份启动服务器,否则依旧使用 root 身份执行。比如: $ docker run -it redis id uid=0(root) gid=0(root) groups=0(root) ENV 设置环境变量格式有两种: ENV <key> <value> ENV <key1>=<value1> <key2>=<value2>... 这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,如 RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。 ENV VERSION=1.0 DEBUG=on \\ NAME="Happy Feet" 这个例子中演示了如何换行,以及对含有空格的值用双引号括起来的办法,这和 Shell 下的行为是一致的。 定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。比如在官方 node 镜像 Dockerfile 中,就有类似这样的代码: ENV NODE_VERSION 7.2.0 RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \\ && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \\ && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \\ && grep " node-v$NODE_VERSION-linux-x64.tar.xz\\$" SHASUMS256.txt | sha256sum -c - \\ && tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \\ && rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \\ && ln -s /usr/local/bin/node /usr/local/bin/nodejs 在这里先定义了环境变量 NODE_VERSION,其后的 RUN 这层里,多次使用 $NODE_VERSION 来进行操作定制。可以看到,将来升级镜像构建版本的时候,只需要更新 7.2.0 即可,Dockerfile 构建维护变得更轻松了。 下列指令可以支持环境变量展开: ADD、COPY、ENV、EXPOSE、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD。 可以从这个指令列表里感觉到,环境变量可以使用的地方很多,很强大。通过环境变量,我们可以让一份 Dockerfile 制作更多的镜像,只需使用不同的环境变量即可。 ARG 构建参数格式:ARG <参数名>[=<默认值>] 构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。 Dockerfile 中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build 中用 –build-arg <参数名>=<值> 来覆盖。 在 1.13 之前的版本,要求 –build-arg 中的参数名,必须在 Dockerfile 中用 ARG 定义过了,换句话说,就是 –build-arg 指定的参数,必须在 Dockerfile 中使用了。如果对应参数没有被使用,则会报错退出构建。从 1.13 开始,这种严格的限制被放开,不再报错退出,而是显示警告信息,并继续构建。这对于使用 CI 系统,用同样的构建流程构建不同的 Dockerfile 的时候比较有帮助,避免构建命令必须根据每个 Dockerfile 的内容修改。 VOLUME 定义匿名卷格式为: VOLUME ["<路径1>", "<路径2>"...] VOLUME <路径> 之前我们说过,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,后面的章节我们会进一步介绍 Docker 卷的概念。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。 VOLUME /data 这里的 /data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如: $ docker run -d -v mydata:/data xxxx 在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。 EXPOSE 声明端口格式为 EXPOSE <端口1> [<端口2>...]。 EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。 此外,在早期 Docker 版本中还有一个特殊的用处。以前所有容器都运行于默认桥接网络中,因此所有容器互相之间都可以直接访问,这样存在一定的安全性问题。于是有了一个 Docker 引擎参数 –icc=false,当指定该参数后,容器间将默认无法互访,除非互相间使用了 –links 参数的容器才可以互通,并且只有镜像中 EXPOSE 所声明的端口才可以被访问。这个 –icc=false 的用法,在引入了 docker network 后已经基本不用了,通过自定义网络可以很轻松的实现容器间的互联与隔离。 要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。-p,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。 WORKDIR 指定工作目录格式为 WORKDIR <工作目录路径>。 使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。 之前提到一些初学者常犯的错误是把 Dockerfile 等同于 Shell 脚本来书写,这种错误的理解还可能会导致出现下面这样的错误: RUN cd /app RUN echo "hello" > world.txt 如果将这个 Dockerfile 进行构建镜像运行后,会发现找不到 /app/world.txt 文件,或者其内容不是 hello。原因其实很简单,在 Shell 中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令;而在 Dockerfile 中,这两行 RUN 命令的执行环境根本不同,是两个完全不同的容器。这就是对 Dokerfile 构建分层存储的概念不了解所导致的错误。 之前说过每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。第一层 RUN cd /app 的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。 因此如果需要改变以后各层的工作目录的位置,那么应该使用 WORKDIR 指令。 USER 指定当前用户格式:USER <用户名> USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。 当然,和 WORKDIR 一样,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。 RUN groupadd -r redis && useradd -r -g redis redis USER redis RUN [ "redis-server" ] 如果以 root 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用 su 或者 sudo,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu,可以从其项目网站看到进一步的信息:https://github.com/tianon/gosu # 建立 redis 用户,并使用 gosu 换另一个用户执行命令 RUN groupadd -r redis && useradd -r -g redis redis # 下载 gosu RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" \\ && chmod +x /usr/local/bin/gosu \\ && gosu nobody true # 设置 CMD,并以另外的用户执行 CMD [ "exec", "gosu", "redis", "redis-server" ] HEALTHCHECK 健康检查格式: HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令 HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令 HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12 引入的新指令。 在没有 HEALTHCHECK 指令前,Docker 引擎只可以通过容器内主进程是否退出来判断容器是否状态异常。很多情况下这没问题,但是如果程序进入死锁状态,或者死循环状态,应用进程并不退出,但是该容器已经无法提供服务了。在 1.12 以前,Docker 不会检测到容器的这种状态,从而不会重新调度,导致可能会有部分容器已经无法提供服务了却还在接受用户请求。 而自 1.12 之后,Docker 提供了 HEALTHCHECK 指令,通过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。 当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting,在 HEALTHCHECK 指令检查成功后变为 healthy,如果连续一定次数失败,则会变为 unhealthy。 HEALTHCHECK 支持下列选项: --interval=<间隔>:两次健康检查的间隔,默认为 30 秒; --timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;3)--retries=<次数>:当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次。 和 CMD, ENTRYPOINT 一样,HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。 在 HEALTHCHECK [选项] CMD 后面的命令,格式和 ENTRYPOINT 一样,分为 shell 格式,和 exec 格式。命令的返回值决定了该次健康检查的成功与否:0:成功;1:失败;2:保留,不要使用这个值。 假设我们有个镜像是个最简单的 Web 服务,我们希望增加健康检查来判断其 Web 服务是否在正常工作,我们可以用 curl 来帮助判断,其 Dockerfile 的 HEALTHCHECK 可以这么写: FROM nginx RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* HEALTHCHECK --interval=5s --timeout=3s \\ CMD curl -fs http://localhost/ || exit 1 这里我们设置了每 5 秒检查一次(这里为了试验所以间隔非常短,实际应该相对较长),如果健康检查命令超过 3 秒没响应就视为失败,并且使用 curl -fs http://localhost/ || exit 1 作为健康检查命令。 使用 docker build 来构建这个镜像: $ docker build -t myweb:v1 . 构建好了后,我们启动一个容器: $ docker run -d --name web -p 80:80 myweb:v1 当运行该镜像后,可以通过 docker ps 看到最初的状态为 (health: starting): $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 3 seconds ago Up 2 seconds (health: starting) 80/tcp, 443/tcp web 在等待几秒钟后,再次 docker ps,就会看到健康状态变化为了 (healthy): $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 18 seconds ago Up 16 seconds (healthy) 80/tcp, 443/tcp web 如果健康检查连续失败超过了重试次数,状态就会变为 (unhealthy)。 为了帮助排障,健康检查命令的输出(包括 stdout 以及 stderr)都会被存储于健康状态里,可以用 docker inspect 来查看。 $ docker inspect --format '{{json .State.Health}}' web | python -m json.tool { "FailingStreak": 0, "Log": [ { "End": "2016-11-25T14:35:37.940957051Z", "ExitCode": 0, "Output": "<!DOCTYPE html>\\n<html>\\n<head>\\n<title>Welcome to nginx!</title>\\n<style>\\n body {\\n width: 35em;\\n margin: 0 auto;\\n font-family: Tahoma, Verdana, Arial, sans-serif;\\n }\\n</style>\\n</head>\\n<body>\\n<h1>Welcome to nginx!</h1>\\n<p>If you see this page, the nginx web server is successfully installed and\\nworking. Further configuration is required.</p>\\n\\n<p>For online documentation and support please refer to\\n<a href=\\"http://nginx.org/\\">nginx.org</a>.<br/>\\nCommercial support is available at\\n<a href=\\"http://nginx.com/\\">nginx.com</a>.</p>\\n\\n<p><em>Thank you for using nginx.</em></p>\\n</body>\\n</html>\\n", "Start": "2016-11-25T14:35:37.780192565Z" } ], "Status": "healthy" } ONBUILD 为他人做嫁衣裳格式:ONBUILD <其它指令>。 ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。 Dockerfile 中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD 是为了帮助别人定制自己而准备的。 假设我们要制作 Node.js 所写的应用的镜像。我们都知道 Node.js 使用 npm 进行包管理,所有依赖、配置、启动信息等会放到 package.json 文件里。在拿到程序代码后,需要先进行 npm install 才可以获得所有需要的依赖。然后就可以通过 npm start 来启动应用。因此,一般来说会这样写 Dockerfile: FROM node:slim RUN "mkdir /app" WORKDIR /app COPY ./package.json /app RUN [ "npm", "install" ] COPY . /app/ CMD [ "npm", "start" ] 把这个 Dockerfile 放到 Node.js 项目的根目录,构建好镜像后,就可以直接拿来启动容器运行。但是如果我们还有第二个 Node.js 项目也差不多呢?好吧,那就再把这个 Dockerfile 复制到第二个项目里。那如果有第三个项目呢?再复制么?文件的副本越多,版本控制就越困难,让我们继续看这样的场景维护的问题。 如果第一个 Node.js 项目在开发过程中,发现这个 Dockerfile 里存在问题,比如敲错字了、或者需要安装额外的包,然后开发人员修复了这个 Dockerfile,再次构建,问题解决。第一个项目没问题了,但是第二个项目呢?虽然最初 Dockerfile 是复制、粘贴自第一个项目的,但是并不会因为第一个项目修复了他们的 Dockerfile,而第二个项目的 Dockerfile 就会被自动修复。 那么我们可不可以做一个基础镜像,然后各个项目使用这个基础镜像呢?这样基础镜像更新,各个项目不用同步 Dockerfile 的变化,重新构建后就继承了基础镜像的更新?好吧,可以,让我们看看这样的结果。那么上面的这个 Dockerfile 就会变为: FROM node:slim RUN "mkdir /app" WORKDIR /app CMD [ "npm", "start" ] 这里我们把项目相关的构建指令拿出来,放到子项目里去。假设这个基础镜像的名字为 my-node 的话,各个项目内的自己的 Dockerfile 就变为: FROM my-node COPY ./package.json /app RUN [ "npm", "install" ] COPY . /app/ 基础镜像变化后,各个项目都用这个 Dockerfile 重新构建镜像,会继承基础镜像的更新。 那么,问题解决了么?没有。准确说,只解决了一半。如果这个 Dockerfile 里面有些东西需要调整呢?比如 npm install 都需要加一些参数,那怎么办?这一行 RUN 是不可能放入基础镜像的,因为涉及到了当前项目的 ./package.json,难道又要一个个修改么?所以说,这样制作基础镜像,只解决了原来的 Dockerfile 的前4条指令的变化问题,而后面三条指令的变化则完全没办法处理。 ONBUILD 可以解决这个问题。让我们用 ONBUILD 重新写一下基础镜像的 Dockerfile: FROM node:slim RUN "mkdir /app" WORKDIR /app ONBUILD COPY ./package.json /app ONBUILD RUN [ "npm", "install" ] ONBUILD COPY . /app/ CMD [ "npm", "start" ] 这次我们回到原始的 Dockerfile,但是这次将项目相关的指令加上 ONBUILD,这样在构建基础镜像的时候,这三行并不会被执行。然后各个项目的 Dockerfile 就变成了简单地: FROM my-node 是的,只有这么一行。当在各个项目目录中,用这个只有一行的 Dockerfile 构建镜像时,之前基础镜像的那三行 ONBUILD 就会开始执行,成功的将当前项目的代码复制进镜像、并且针对本项目执行 npm install,生成应用镜像。 ReferenceDocker–从入门到实践: https://yeasy.gitbooks.io/docker_practice/content/Dockerfie 官方文档:https://docs.docker.com/engine/reference/builder/Dockerfile 最佳实践文档:https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"docker镜像","slug":"docker镜像","date":"2017-05-19T08:47:33.000Z","updated":"2024-07-05T01:50:54.928Z","comments":true,"path":"2017/05/19/docker-image/","permalink":"http://yelog.org/2017/05/19/docker-image/","excerpt":"","text":"WhatDocker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。 因为镜像包含操作系统完整的 root 文件系统,其体积往往是庞大的,因此在 Docker 设计时,就充分利用 Union FS 的技术,将其设计为分层存储的架构。所以严格来说,镜像并非是像一个 ISO 那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。 安装# 官方 的安装脚本 $ curl -sSL https://get.docker.com/ | sh # 阿里云 的安装脚本 $ curl -sSL http://acs-public-mirror.oss-cn-hangzhou.aliyuncs.com/docker-engine/internet | sh - # DaoCloud 的安装脚本 $ curl -sSL https://get.daocloud.io/docker | sh 镜像# 获取镜像,registry为空默认从Docker Hub上获取 docker pull [选项] [Docker Registry地址]<仓库名>:<标签> # 交互式运行,退出删除: -i:交互式 ,-t:终端,--rm 退出删除 ,bash 启动bash窗口 $ docker run -it --rm ubuntu:14.04 bash # 列出已下载的镜像(只显示顶层镜像) -a:显示所有镜像 image_name:指定列出某个镜像 $ docker images [-a] [image_name] # 只显示虚悬镜像(dangling image) -f:--filter 过滤 $ docker images -f dangling=true # 过滤从mongo:3.2建立之后的镜像 $ docker images -f since=mongo:3.2 # 通过label过滤 $ docker images -f label=com.example.version=0.1 # 只显示镜像id $ docker images -q # 只包含镜像ID和仓库名 $ docker images --format "{{.ID}}: {{.Repository}}" # 以表格等距显示 有标题行,和默认一样,不过自己定义列 $ docker images --format "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}" # 删除镜像ID为image_id的镜像 $ docker rmi <image_id> # 删除虚悬镜像 $ docker rmi $(docker images -q -f dangling=true) # 将容器保存为镜像 $ docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]] # 将容器保存为镜像 $ docker commit \\ --author "Tao Wang <twang2218@gmail.com>" \\ --message "修改了默认网页" \\ webserver \\ nginx:v2 $ docker history nginx:v2","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"docker初体验","slug":"docker初体验","date":"2017-05-19T08:32:23.000Z","updated":"2024-07-05T01:50:54.932Z","comments":true,"path":"2017/05/19/docker-first/","permalink":"http://yelog.org/2017/05/19/docker-first/","excerpt":"","text":"安装笔者环境操作系统:deepin 15.4 Desktop 64Bit 安装# 官方 的安装脚本 $ curl -sSL https://get.docker.com/ | sh # 阿里云 的安装脚本 $ curl -sSL http://acs-public-mirror.oss-cn-hangzhou.aliyuncs.com/docker-engine/internet | sh - # DaoCloud 的安装脚本 $ curl -sSL https://get.daocloud.io/docker | sh 获取镜像Docker Hub 上有大量的高质量的镜像可以用,这里我们就说一下怎么获取这些镜像并运行。从 Docker Registry 获取镜像的命令是 docker pull。其命令格式为: $ docker pull [选项] [Docker Registry地址]<仓库名>:<标签> 具体的选项可以通过 docker pull --help 命令看到,这里我们说一下镜像名称的格式。 Docker Registry地址:地址的格式一般是 <域名/IP>[:端口号]。默认地址是 Docker Hub。 仓库名:如之前所说,这里的仓库名是两段式名称,既 <用户名>/<软件名>。对于 Docker Hub,如果不给出用户名,则默认为 library,也就是官方镜像。 $ sudo docker pull ubuntu 运行有了镜像后,我们就可以以这个镜像为基础启动一个容器来运行。以上面的 ubuntu 为例,如果我们打算启动里面的 bash 并且进行交互式操作的话,可以执行下面的命令。 $ sudo docker run -it --rm ubuntu root@0ae011f7b5be:/# cat /etc/os-release NAME="Ubuntu" VERSION="16.04.2 LTS (Xenial Xerus)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 16.04.2 LTS" VERSION_ID="16.04" HOME_URL="http://www.ubuntu.com/" SUPPORT_URL="http://help.ubuntu.com/" BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/" VERSION_CODENAME=xenial UBUNTU_CODENAME=xenial docker run 就是运行容器的命令 -it:这是两个参数,一个是 -i:交互式操作,一个是 -t 终端。我们这里打算进入 bash 执行一些命令并查看返回结果,因此我们需要交互式终端。 --rm:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 –rm 可以避免浪费空间。 ubuntu:这是指用 ubuntu 镜像为基础来启动容器。 bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 bash。 进入容器后,我们可以在 Shell 下操作,执行任何所需的命令。这里,我们执行了 cat /etc/os-release,这是 Linux 常用的查看当前系统版本的命令,从返回的结果可以看到容器内是 Ubuntu 16.04.2 LTS 系统。 最后通过 exit 退出了这个容器。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"linux无损调整分区大小","slug":"linux无损调整分区大小","date":"2017-05-17T14:00:42.000Z","updated":"2024-07-05T01:50:55.765Z","comments":true,"path":"2017/05/17/linux无损调整分区大小/","permalink":"http://yelog.org/2017/05/17/linux%E6%97%A0%E6%8D%9F%E8%B0%83%E6%95%B4%E5%88%86%E5%8C%BA%E5%A4%A7%E5%B0%8F/","excerpt":"","text":"summary 系统环境: Red Hat 4.8.5-11 情况: home:500G root:50G root分区不够用 思路:把home分区的空间划一部分到root分区 # 设置home分区大小为200G,释放300G空间 $ lvreduce -L 200G /dev/centos/home # 将空闲空间扩展到root分区 $ lvextend -l +100%FREE /dev/centos/root # 使用XFS文件系统自带的命令集增加分区空间 $ xfs_growfs /dev/mapper/centos-root 实例situation挂载在根目录的分区 /dev/mapper/centos-root 爆满,占用100% $ df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/centos-root 50G 50G 19M 100% / devtmpfs 32G 0 32G 0% /dev tmpfs 32G 0 32G 0% /dev/shm tmpfs 32G 2.5G 29G 8% /run tmpfs 32G 0 32G 0% /sys/fs/cgroup /dev/mapper/centos-home 476G 33M 476G 1% /home /dev/sda1 497M 238M 259M 48% /boot tmpfs 6.3G 0 6.3G 0% /run/user/0 analyze挂载在根目录的分区空间太小,只有50G,而服务器 home 目录为非常用目录,挂在了近500G的空间。 思路:从 centos-home 分区划出300G空间到 centos-root 分区。 operation1.查看各分区信息$ lvdisplay --- Logical volume --- LV Path /dev/centos/home LV Name home VG Name centos LV UUID 1fAt1E-bQsa-1HXR-MCE2-5VZ1-xzBz-iI1SLv LV Write Access read/write LV Creation host, time localhost, 2016-10-26 17:23:47 +0800 LV Status available # open 0 LV Size 475.70 GiB Current LE 121778 Segments 1 Allocation inherit Read ahead sectors auto - currently set to 256 Block device 253:2 --- Logical volume --- LV Path /dev/centos/root LV Name root VG Name centos LV UUID lD64zY-yc3Z-SZaB-dAjK-03YM-2gM8-pfj4oo LV Write Access read/write LV Creation host, time localhost, 2016-10-26 17:23:48 +0800 LV Status available # open 1 LV Size 50.00 GiB Current LE 12800 Segments 1 Allocation inherit Read ahead sectors auto - currently set to 256 Block device 253:0 2.减少/home分区空间# 释放 /dev/centos/home 分区 300G 的空间 # 命令设置 /dev/centos/home 分区 200G空间 $ lvreduce -L 200G /dev/centos/home WARNING: Reducing active logical volume to 200.00 GiB. THIS MAY DESTROY YOUR DATA (filesystem etc.) Do you really want to reduce centos/home? [y/n]: y Size of logical volume centos/home changed from 475.70 GiB (121778 extents) to 200.00 GiB (51200 extents). Logical volume centos/home successfully resized. 3.增加/root分区空间$ lvextend -l +100%FREE /dev/centos/root Size of logical volume centos/root changed from 50.06 GiB (12816 extents) to 325.76 GiB (83394 extents). Logical volume centos/root successfully resized. 4.扩展XFS文件空间大小$ xfs_growfs /dev/mapper/centos-root meta-data=/dev/mapper/centos-root isize=256 agcount=4, agsize=3276800 blks = sectsz=512 attr=2, projid32bit=1 = crc=0 finobt=0 spinodes=0 data = bsize=4096 blocks=13107200, imaxpct=25 = sunit=0 swidth=0 blks naming =version 2 bsize=4096 ascii-ci=0 ftype=0 log =internal bsize=4096 blocks=6400, version=2 = sectsz=512 sunit=0 blks, lazy-count=1 realtime =none extsz=4096 blocks=0, rtextents=0 data blocks changed from 13107200 to 85395456 完成","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/tags/%E8%BF%90%E7%BB%B4/"},{"name":"磁盘分区","slug":"磁盘分区","permalink":"http://yelog.org/tags/%E7%A3%81%E7%9B%98%E5%88%86%E5%8C%BA/"}]},{"title":"Git统计操作","slug":"git-log","date":"2017-05-16T01:26:11.000Z","updated":"2024-07-05T01:50:55.337Z","comments":true,"path":"2017/05/16/git-log/","permalink":"http://yelog.org/2017/05/16/git-log/","excerpt":"","text":"按commit统计# 统计当前作者今天(从凌晨1点开始)提交次数 $ git log --author="$(git config --get user.name)" --no-merges --since=1am --stat # 按提交作者统计,按提交次数排序 $ git shortlog -sn $ git shortlog --numbered --summary # 只看某作者提交的commit数 $ git log --author="faker" --oneline --shortstat # 按提交作者统计,提交数量排名前5(看全部,去掉head管道即可) $ git log --pretty='%aN' | sort | uniq -c | sort -k1 -n -r | head -n 5 # 按提交者邮箱统计,提交数量排名前5 $ git log --pretty=format:%ae | gawk -- '{ ++c[$0]; } END { for(cc in c) printf "%5d %s\\n",c[cc],cc; }' | sort -u -n -r | head -n 5 # 统计贡献者数量 $ git log --pretty='%aN' | sort -u | wc -l # 统计提交数量 $ git log --oneline | wc -l 按代码行数统计# 统计指定作者增删行数 $ git log --author="faker" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\\n", add, subs, loc }' - # 统计当前作者增删行数 $ git log --author="$(git config --get user.name)" --pretty=tformat: --numstat | gawk '{ add += $1 ; subs += $2 ; loc += $1 - $2 } END { printf "added lines: %s removed lines : %s total lines: %s\\n",add,subs,loc }' - # 统计所有邮箱前缀的增删行数 -英文版 $ git log --shortstat --pretty="%cE" | sed 's/\\(.*\\)@.*/\\1/' | grep -v "^$" | awk 'BEGIN { line=""; } !/^ / { if (line=="" || !match(line, $0)) {line = $0 "," line }} /^ / { print line " # " $0; line=""}' | sort | sed -E 's/# //;s/ files? changed,//;s/([0-9]+) ([0-9]+ deletion)/\\1 0 insertions\\(+\\), \\2/;s/\\(\\+\\)$/\\(\\+\\), 0 deletions\\(-\\)/;s/insertions?\\(\\+\\), //;s/ deletions?\\(-\\)//' | awk 'BEGIN {name=""; files=0; insertions=0; deletions=0;} {if ($1 != name && name != "") { print name ": " files " files changed, " insertions " insertions(+), " deletions " deletions(-), " insertions-deletions " net"; files=0; insertions=0; deletions=0; name=$1; } name=$1; files+=$2; insertions+=$3; deletions+=$4} END {print name ": " files " files changed, " insertions " insertions(+), " deletions " deletions(-), " insertions-deletions " net";}' # 统计所有邮箱前缀的增删行数 -中文版 $ git log --shortstat --pretty="%cE" | sed 's/\\(.*\\)@.*/\\1/' | grep -v "^$" | awk 'BEGIN { line=""; } !/^ / { if (line=="" || !match(line, $0)) {line = $0 "," line }} /^ / { print line " # " $0; line=""}' | sort | sed -E 's/# //;s/ files? changed,//;s/([0-9]+) ([0-9]+ deletion)/\\1 0 insertions\\(+\\), \\2/;s/\\(\\+\\)$/\\(\\+\\), 0 deletions\\(-\\)/;s/insertions?\\(\\+\\), //;s/ deletions?\\(-\\)//' | awk 'BEGIN {name=""; files=0; insertions=0; deletions=0;} {if ($1 != name && name != "") { print name ": " files " 个文件被改变, " insertions " 行被插入(+), " deletions " 行被删除(-), " insertions-deletions " 行剩余"; files=0; insertions=0; deletions=0; name=$1; } name=$1; files+=$2; insertions+=$3; deletions+=$4} END {print name ": " files " 个文件被改变, " insertions " 行被插入(+), " deletions " 行被删除(-), " insertions-deletions " 行剩余";}' # 统计所有作者增删行数 --英文版 $ git log --format='%aN' | sort -u | while read name; do echo -en "$name\\t"; git log --author="$name" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\\n", add, subs, loc }' -; done # 统计所有作者增删行数 --中文版 $ git log --format='%aN' | sort -u | while read name; do echo -en "$name\\t"; git log --author="$name" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "添加行数: %s, 删除行数: %s, 总行数: %s\\n", add, subs, loc }' -; done git log 说明 git log 参数说明:--author 指定作者--stat 显示每次更新的文件修改统计信息,会列出具体文件列表--shortstat 统计每个commit 的文件修改行数,包括增加,删除,但不列出文件列表:--numstat 统计每个commit 的文件修改行数,包括增加,删除,并列出文件列表: -p 选项展开显示每次提交的内容差异,用 -2 则仅显示最近的两次更新 例如:git log -p -2--name-only 仅在提交信息后显示已修改的文件清单--name-status 显示新增、修改、删除的文件清单--abbrev-commit 仅显示 SHA-1 的前几个字符,而非所有的 40 个字符--relative-date 使用较短的相对时间显示(比如,“2 weeks ago”)--graph 显示 ASCII 图形表示的分支合并历史--pretty 使用其他格式显示历史提交信息。可用的选项包括 oneline,short,full,fuller 和 format(后跟指定格式) 例如: git log --pretty=oneline ; git log --pretty=short ; git log --pretty=full ; git log –pretty=fuller--pretty=tformat: 可以定制要显示的记录格式,这样的输出便于后期编程提取分析 例如: git log --pretty=format:""%h - %an, %ar : %s"" 下面列出了常用的格式占位符写法及其代表的意义。 选项 说明 %H 提交对象(commit)的完整哈希字串 %h 提交对象的简短哈希字串 %T 树对象(tree)的完整哈希字串 %t 树对象的简短哈希字串 %P 父对象(parent)的完整哈希字串 %p 父对象的简短哈希字串 %an 作者(author)的名字 %ae 作者的电子邮件地址 %ad 作者修订日期(可以用 -date= 选项定制格式) %ar 作者修订日期,按多久以前的方式显示 %cn 提交者(committer)的名字 %ce 提交者的电子邮件地址 %cd 提交日期 %cr 提交日期,按多久以前的方式显示 %s 提交说明--since 限制显示输出的范围, 例如: git log --since=2.weeks 显示最近两周的提交 选项 说明 -(n) 仅显示最近的 n 条提交 --since, --after 仅显示指定时间之后的提交。 --until, --before 仅显示指定时间之前的提交。 --author 仅显示指定作者相关的提交。 --committer 仅显示指定提交者相关的提交。 一些例子:git log --until=1.minute.ago // 一分钟之前的所有 loggit log --since=1.day.ago //一天之内的loggit log --since=1.hour.ago //一个小时之内的 loggit log --since=1.month.ago --until=2.weeks.ago //一个月之前到半个月之前的loggit log --since ==2013-08.01 --until=2013-09-07 //某个时间段的 loggit blame 看看某一个文件的相关历史记录例如:git blame index.html --date short","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"}],"tags":[{"name":"git","slug":"git","permalink":"http://yelog.org/tags/git/"}]},{"title":"解决linux下zip文件解压乱码","slug":"解决linux下zip文件解压乱码","date":"2017-04-25T01:10:40.000Z","updated":"2024-07-05T01:50:55.778Z","comments":true,"path":"2017/04/25/解决linux下zip文件解压乱码/","permalink":"http://yelog.org/2017/04/25/%E8%A7%A3%E5%86%B3linux%E4%B8%8Bzip%E6%96%87%E4%BB%B6%E8%A7%A3%E5%8E%8B%E4%B9%B1%E7%A0%81/","excerpt":"","text":"原因由于zip格式并没有指定编码格式,Windows下生成的zip文件中的编码是GBK/GB2312等,因此,导致这些zip文件在Linux下解压时出现乱码问题,因为Linux下的默认编码是UTF8。 解决方案使用7z解压。 安装p7zip和convmv # fedora $ su -c 'yum install p7zip convmv' # ubuntu $ sudo apt-get install p7zip convmv 执行一下命令解压缩 # 使用7z解压缩 $ LANG=C 7za x your-zip-file.zip # 递归转码 $ convmv -f GBK -t utf8 --notest -r .","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"加密算法简介","slug":"加密算法简介","date":"2017-04-22T01:30:41.000Z","updated":"2024-07-05T01:50:55.388Z","comments":true,"path":"2017/04/22/encryption-algorithm/","permalink":"http://yelog.org/2017/04/22/encryption-algorithm/","excerpt":"","text":"一、对称密钥算法概述对称加密(Symmetric-key algorithm)是指加解密用同一个密钥的算法,根据具体实现分为流加密和分组加密两种类型: 流加密(Stream cipher)是对称加密常用的一种实现方法,加密和解密双方使用相同伪随机加密数据流,一般都是逐位异或随机密码本的内容。 分组加密加密(Block cipher),也叫块加密,将明文分成多个等长的模块(block),使用确定的算法和对称密钥对每组分别加密解密。现代分组加密建立在迭代的思想上产生密文。迭代产生的密文在每一轮加密中使用不同的子密钥,而子密钥生成自原始密钥。 对称加密普遍比非对称加密速度要快,实现更简单,适合大量内容的加密 DESDES (Data Encryption Standard) 是一种分组加密算法 DES算法的入口参数有三个:Key,Data,Mode,Key是密钥密钥占7个字节56位(64位里另外8位是用来校验的),Data是加密内容,占8个字节64位,Mode是加密还是解密。 DES算法于1976被确定,现在已经被认为不够安全,主要原因是56位的密钥过短。据说这个算法因为包含一些机密设计元素,被怀疑内含美国国家安全局(NSA)的后门。 DES算法有个拓展算法叫3DES,就是对数据块进行三次DES加密,增加爆破成本,但本质上也不够安全。 RC4RC4 (Rivest Cipher 4) 是一种流加密算法 RC4起源于1987年,现在已经被认为不够安全。RC4由伪随机数生成器和异或运算组成。RC4的密钥长度可变,范围是[1,255]。RC4一个字节一个字节地加解密。给定一个密钥,伪随机数生成器接受密钥并产生一个S盒。S盒用来加密数据,而且在加密过程中S盒会变化。 由于异或运算的对合性,RC4加密解密使用同一套算法。这个算法实现起来很简单,只用了最基本的加、异或、循环,话说我大学时某个课程设计的做的加密算法就是简化版的RC4。 之后还出现了RC5、RC6加密算法,但RC5和RC6都是分组加密,和RC4原理并不一样。 RC5RC5 (Rivest Cipher 5) 是一种分组加密算法,它和RC2,RC4,RC6都是同一个叫Ronald Rivest的人设计的。 相比RC4,RC5的密钥成了128位,但RC5仍然只需要基础的加、异或、循环运算,可以在很多硬件上实现。RC5有三个参数:字的大小,循环轮数(round),密钥中的8位字节个数,所以可以说RC5是一种可变加密算法。实际上循环轮数12轮以下的RC5都被认为是不安全的,会被差分分析法(Differential cryptanalysis)攻击,18-20轮才足够安全。 目前来说,RC5还是挺安全的,因为实现简单,消耗资源少,在一些传感器、嵌入式设备上使用很合适。 RC6RC6 (Rivest Cipher 6) 是RC5的加强版,也属于分组加密算法。 RC6算法在RC5算法基础之上针对RC5算法中的漏洞,主要是循环移位的位移量并不取决于要移动次数的所有比特,通过采用引入乘法运算来决定循环移位次数的方法,对RC5算法进行了改进,从而大大提高了RC6算法的安全性。 RC6曾作为AES(高级加密标准)备选算法之一,但最终AES选择了Rijndael算法。 AES最后压轴出场的是最著名的单密钥对称加密算法AES (Rijndael),AES是Advanced Encryption Standard的缩写,是美国国家标准与技术研究院2001年发布的新加密标准。 AES现在就是指的限定了区块长度和密钥长度的Rijndael算法,同样属于分组加密算法,该算法是两位比利时学者1998年发布的。起初还有很多算法参与了AES甄选,最终Rijndael凭借高安全性和清晰的数学结构而被选用。 AES将Rijndael算法的区块长度固定为128位,密钥长度可选128,192或256比特(Rijndael原版支持128-256,n*32的区块长度和密钥长度)。 AES算法包括4个步骤: AddRoundKey—矩阵中的每一个字节都与该次回合密钥(round key)做XOR运算;每个子密钥由密钥生成方案产生。 SubBytes—通过一个非线性的替换函数,用查找表的方式把每个字节替换成对应的字节。 ShiftRows—将矩阵中的每个横列进行循环式移位。 MixColumns—为了充分混合矩阵中各个直行的操作。这个步骤使用线性转换来混合每内联的四个字节。最后一个加密循环中省略MixColumns步骤,而以另一个AddRoundKey替换。 截止现在(2016),AES在算法层面上是安全的。2005年有人公布过一种缓存时序攻击法,但使用场景非常极端。 二、非对称秘钥算法概述公钥加密的思想于1974年被提出,相比对称加密无需共享密钥,更加安全。但是没法加密大量数据,一般用来加密对称加密的密钥,而用对称加密加密大量数据。非对称加密的原理如下: 消息发送方A在本地构建密钥对,公钥和私钥; 消息发送方A将产生的公钥发送给消息接收方B; B向A发送数据时,通过公钥进行加密,A接收到数据后通过私钥进行解密,完成一次通信; 反之,A向B发送数据时,通过私钥对数据进行加密,B接收到数据后通过公钥进行解密。 RSARSA算法是最著名的非对称加密算法。RSA是1977年提出的,名字来源于Rivest、Shmir和Adleman三位作者。我们平时用到的SSL协议,TLS协议都采用了该算法加密,SSH(Secure Shell)也是基于RSA实现的。 RSA的数学基础是极大整数的因数分解,具体实现过程如下: 随意选择两个大的质数p和q,p不等于q,计算N=pq。 根据欧拉函数,求得r=varphi (N) = varphi(p) * varphi(q)=(p-1)(q-1) 选择一个小于r的整数e,使e与r互质。并求得e关于r的模反元素,命名为d。 (N,e)是公钥,(N,d)是私钥。 加密时,加密的块 n^e ≡ c(MOD N),得到的c就是密文。解密时,c^d ≡ n(MOD N)。 要破解RSA要解决怎么把一个极大数分解为两个质数p和q,然后通过欧拉函数再得到公钥和私钥。但极大数因数分解目前还没什么好办法,所以只要N足够大,RSA在算法层面上就是安全的。 当N的长度为256时,用普通电脑花几小时即可以分解,当N长度为512时需要花数月时间分解,1024时需要大型分布式系统才能分解,长度到2046则可以确保是完全安全的。目前已有记录里,被分解的极大数最大位数是768位,于2009年被分解。 RSA也常被用来做数字签名,在消息内附加一个私钥加密过的散列值(Message digest),以此来确保消息发送人是可靠的。公钥私钥对生成 # 1.该命令会生成1024位的私钥,此时我们就可以在当前路径下看到rsa_private_key.pem文件了. genrsa -out rsa_private_key.pem 1024 # 2.生成的密钥不是pcs8格式,我们需要转成pkcs8格式 pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt # 3.生成 rsa 公钥 rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem 椭圆曲线算法椭圆曲线算法(Elliptic curve cryptography)也是一种非对称加密算法,于1985年被提出,以下简称ECC。相比RSA,同等破解难度时ECC的秘钥更短。另外,ECC可定义椭圆曲线群的双线性映射,该特性可能将来被用来实现身份基加密体制(Identity-Based Encryption,IBE)。 ECC的数学基础是求椭圆曲线离散对数问题。实现比较复杂我就不写了,因为我也看不懂(⊙﹏⊙)b。 也正因为实现复杂,ECC的加解密速度慢,消耗资源也更多。 ECC也同样可以实现数字签名,叫做ECDSA。 ECC的秘钥长度最小要求是160位,建议是163位。目前已有的破解记录是109位,一万台机器破解了一年半。所以ECC在算法层面是可以保证安全的。 ElGamalElGamal加密算法是一种用于对采用Diff-Hellman方式进行交换的公钥进行加密,常被用于数字签名和密钥加密的算法,ElGamal的数学基础是有限域上的离散对数问题。 选择一个素数p和两个随机数g 、x (g、 x < p ),计算 y ≡ g^x( mod p ) ,则其公钥为 y, g 和p ,私钥是x ,g和p可由一组用户共享。 ElGamal方法中一个明文对应两个加密结果(g^a和g^b),因此密文空间的大小是明文空间大小的两倍,也就是说纵观整个通信过程,收发密文的大小是实际明文大小的两倍。 三、哈希算法概述我们经常说MD5加密,但追根究底的话,MD5应该是哈希函数(Hash Function),而哈希函数并不等同于加密(Encrypt),不过我们平常也把哈希叫做加密。哈希函数也叫散列函数,散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值(hash values,hash codes,hash sums,或hashes)的指纹。散列值通常用来代表一个短的随机字母和数字组成的字符串。 说人话就是哈希(Hash)是将目标文本转换成具有相同长度的、不可逆的杂凑字符串,而加密(Encrypt)是将目标文本转换成具有不同长度的、可逆的密文。 哈希主要用来校验身份,错误检查,完整性检查。 MD5MD5(Message-Digest5 Algorithm)即消息摘要算法,是最著名、应用最为广泛的一种哈希算法,于1992年被公开。MD5之前还有MD4、MD3、MD2等哥哥算法,MD5是最终的改进版。 MD5输入不定长度信息,输出固定长度为128-bits的散列 未完 待补充REFERENCE常见加密算法简介","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"encryption","slug":"encryption","permalink":"http://yelog.org/tags/encryption/"}]},{"title":"JSP操作记录","slug":"JSP操作记录","date":"2017-04-21T03:22:12.000Z","updated":"2024-07-05T01:50:55.088Z","comments":true,"path":"2017/04/21/jsp-use-record/","permalink":"http://yelog.org/2017/04/21/jsp-use-record/","excerpt":"","text":"问题EL表达式失效<!-- jsp渲染器不识别el表达式,结果页面展示效果如下 --> {person.id} {person.name} 解决方法:在页面内加入下面代码即可 <%@ page isELIgnored="false" %> Map遍历<c:forEach items="${map}" var="entry"> <c:out value="${entry.key}" /> <c:out value="${entry.value}" /> </c:forEach> 取值<c:out value="${map[key]}" />","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"jsp","slug":"jsp","permalink":"http://yelog.org/tags/jsp/"},{"name":"jstl","slug":"jstl","permalink":"http://yelog.org/tags/jstl/"}]},{"title":"[转]SpringMVC执行流程及源码解析","slug":"转-SpringMVC执行流程及源码解析","date":"2017-04-15T02:22:05.000Z","updated":"2024-07-05T01:50:55.392Z","comments":true,"path":"2017/04/15/SpringMVC-implementation-process/","permalink":"http://yelog.org/2017/04/15/SpringMVC-implementation-process/","excerpt":"","text":"在SpringMVC中主要是围绕着DispatcherServlet来设计,可以把它当做指挥中心。这里先说明一下SpringMVC文档给出的执行流程,然后是我们稍微具体的执行流程,最后是流程大致的源码跟踪。关于很很很详细的源码解析,这里暂先不做。 官方文档中的流程首先看下SpringMVC文档上给的流程图:这张图片给了我们大概的执行流程: 用户请求首先发送到前端控制器DispatcherServlet,DispatcherServlet根据请求的信息来决定使用哪个页面控制器Controller(也就是我们通常编写的Controller)来处理该请求。找到控制器之后,DispatcherServlet将请求委托给控制器去处理。 接下来页面控制器开始处理用户请求,页面控制器会根据请求信息进行处理,调用业务层等等,处理完成之后,会把结果封装成一个ModelAndView返回给DispatcherServlet。 前端控制器DispatcherServlet接到页面控制器的返回结果后,根据返回的视图名选择相应的试图模板,并根据返回的数据进行渲染。 最后前端控制器DispatcherServlet将结果返回给用户。 更具体的流程上面只是总体流程,接下来我们稍微深入一点,看下更具体的流程,这里没有图,只有步骤解析: 用户请求发送到前端控制器DispatcherServlet。 前端控制器DispatcherServlet接收到请求后,DispatcherServlet会使用HandlerMapping来处理,HandlerMapping会查找到具体进行处理请求的Handler对象。 HandlerMapping找到对应的Handler之后,并不是返回一个Handler原始对象,而是一个Handler执行链,在这个执行链中包括了拦截器和处理请求的Handler。HandlerMapping返回一个执行链给DispatcherServlet。 DispatcherServlet接收到执行链之后,会调用Handler适配器去执行Handler。 Handler适配器执行完成Handler(也就是我们写的Controller)之后会得到一个ModelAndView,并返回给DispatcherServlet。 DispatcherServlet接收到Handler适配器返回的ModelAndView之后,会根据其中的视图名调用视图解析器。 视图解析器根据逻辑视图名解析成一个真正的View视图,并返回给DispatcherServlet。 DispatcherServlet接收到视图之后,会根据上面的ModelAndView中的model来进行视图中数据的填充,也就是所谓的视图渲染。 渲染完成之后,DispatcherServlet就可以将结果返回给用户了。 源码DispatcherServlet是一个Servlet,我们知道在Servlet在处理一个请求的时候会交给service方法进行处理,这里也不例外,DispatcherServlet继承了FrameworkServlet,首先进入FrameworkServlet的service方法: protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //请求方法 String method = request.getMethod(); //PATCH方法单独处理 if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) { processRequest(request, response); } else {//其他的请求类型的方法经由父类,也就是HttpServlet处理 super.service(request, response); } } HttpServlet中会根据请求类型的不同分别调用doGet或者doPost等方法,FrameworkServlet中已经重写了这些方法,在这些方法中会调用processRequest进行处理,在processRequest中会调用doService方法,这个doService方法就是在DispatcherServlet中实现的。下面就看下DispatcherServlet中的doService方法的实现。 请求到达DispatcherServletdoService方法: protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { //给request中的属性做一份快照 Map<String, Object> attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { logger.debug("Taking snapshot of request attributes before include"); attributesSnapshot = new HashMap<String, Object>(); Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } //如果我们没有配置类似本地化或者主题的处理器之类的 //SpringMVC会使用默认的值 //默认配置文件是DispatcherServlet.properties request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); try { //开始处理 doDispatch(request, response); } finally { if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { return; } // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } DispatcherServlet开始真正的处理,doDispatch方法: protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; //SpringMVC中异步请求的相关知识,暂先不解释 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { //先检查是不是Multipart类型的,比如上传等 //如果是Multipart类型的,则转换为MultipartHttpServletRequest类型 processedRequest = checkMultipart(request); multipartRequestParsed = processedRequest != request; //获取当前请求的Handler mappedHandler = getHandler(processedRequest, false); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } //获取当前请求的Handler适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 对于header中last-modified的处理 String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } //拦截器的preHandle方法进行处理 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } try { //真正调用Handler的地方 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } //处理成默认视图名,就是添加前缀和后缀等 applyDefaultViewName(request, mv); //拦截器postHandle方法进行处理 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } //处理最后的结果,渲染之类的都在这里 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Error err) { triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); return; } // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } 可以看到大概的步骤还是按照我们上面分析的走的。 查找请求对应的Handler对象对应着这句代码 mappedHandler = getHandler(processedRequest, false);,看下具体的getHandler方法: protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception { return getHandler(request); } 继续往下看getHandler: protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //遍历所有的handlerMappings进行处理 //handlerMappings是在启动的时候预先注册好的 for (HandlerMapping hm : this.handlerMappings) { HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } return null; } 继续往下看getHandler,在AbstractHandlerMapping类中: public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //根据request获取handler Object handler = getHandlerInternal(request); if (handler == null) { //如果没有找到就使用默认的handler handler = getDefaultHandler(); } if (handler == null) { return null; } //如果Handler是String,表明是一个bean名称 //需要超照对应bean if (handler instanceof String) { String handlerName = (String) handler; handler = getApplicationContext().getBean(handlerName); } //封装Handler执行链 return getHandlerExecutionChain(handler, request); } 根据requrst获取handler首先看下根据requrst获取handler步骤getHandlerInternal方法,在AbstractHandlerMethodMapping中: protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { //获取request中的url,用来匹配handler String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); //根据路径寻找Handler HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); //根据handlerMethod中的bean来实例化Handler并添加进HandlerMethod return (handlerMethod != null) ? handlerMethod.createWithResolvedBean() : null; } 看下根据路径寻找handler的方法lookupHandlerMethod: protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<Match>(); //直接匹配 List<T> directPathMatches = this.urlMap.get(lookupPath); //如果有匹配的,就添加进匹配列表中 if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } //还没有匹配的,就遍历所有的处理方法查找 if (matches.isEmpty()) { // No choice but to go through all mappings addMatchingMappings(this.handlerMethods.keySet(), matches, request); } //找到了匹配的 if (!matches.isEmpty()) { Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); Collections.sort(matches, comparator); //排序之后,获取第一个 Match bestMatch = matches.get(0); //如果有多个匹配的,会找到第二个最合适的进行比较一下 if (matches.size() > 1) { Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.handlerMethod.getMethod(); Method m2 = secondBestMatch.handlerMethod.getMethod(); throw new IllegalStateException( "Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" + m1 + ", " + m2 + "}"); } } //设置request参数 handleMatch(bestMatch.mapping, lookupPath, request); //返回匹配的url的处理的方法 return bestMatch.handlerMethod; } else {//最后还没有找到,返回null return handleNoMatch(handlerMethods.keySet(), lookupPath, request); } } 获取默认Handler如果上面没有获取到Handler,就会获取默认的Handler。如果还获取不到就返回null。 处理String类型的Handler如果上面处理完的Handler是String类型的,就会根据这个handlerName获取bean。 封装Handler执行链上面获取完Handler,就开始封装执行链了,就是将我们配置的拦截器加入到执行链中去,getHandlerExecutionChain: protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { //如果当前Handler不是执行链类型,就使用一个新的执行链实例封装起来 HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain) ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler); //先获取适配类型的拦截器添加进去拦截器链 chain.addInterceptors(getAdaptedInterceptors()); //当前的url String lookupPath = urlPathHelper.getLookupPathForRequest(request); //遍历拦截器,找到跟当前url对应的,添加进执行链中去 for (MappedInterceptor mappedInterceptor : mappedInterceptors) { if (mappedInterceptor.matches(lookupPath, pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); } } return chain; } 获取对应请求的Handler适配器getHandlerAdapter: protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { //遍历所有的HandlerAdapter,找到和当前Handler匹配的就返回 //我们这里会匹配到RequestMappingHandlerAdapter for (HandlerAdapter ha : this.handlerAdapters) { if (ha.supports(handler)) { return ha; } } } 缓存的处理也就是对last-modified的处理 执行拦截器的preHandle方法就是遍历所有的我们定义的interceptor,执行preHandle方法 使用Handler适配器执行当前的Handlerha.handle执行当前Handler,我们这里使用的是RequestMappingHandlerAdapter,首先会进入AbstractHandlerMethodAdapter的handle方法: public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return handleInternal(request, response, (HandlerMethod) handler); } handleInternal方法,在RequestMappingHandlerAdapter中: protected final ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { // Always prevent caching in case of session attribute management. checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true); } else { // Uses configured default cacheSeconds setting. checkAndPrepare(request, response, true); } // Execute invokeHandlerMethod in synchronized block if required. if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { return invokeHandleMethod(request, response, handlerMethod); } } } //执行方法,封装ModelAndView return invokeHandleMethod(request, response, handlerMethod); } 组装默认视图名称前缀和后缀名都加上 执行拦截器的postHandle方法遍历intercepter的postHandle方法。 处理最后的结果,渲染之类的processDispatchResult方法: private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { boolean errorView = false; if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } // Did the handler return a view to render? if (mv != null && !mv.wasCleared()) { //渲染 render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; } if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, null); } } 重点看下render方法,进行渲染: protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { //设置本地化 Locale locale = this.localeResolver.resolveLocale(request); response.setLocale(locale); View view; if (mv.isReference()) { //解析视图名,得到视图 view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); } else { // No need to lookup: the ModelAndView object contains the actual View object. view = mv.getView(); if (view == null) { throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'"); } } //委托给视图进行渲染 view.render(mv.getModelInternal(), request, response); } view.render就是进行视图的渲染,然后跳转页面等处理。 到这里大概的流程就走完了。其中涉及到的东西还有很多,暂先不做详细处理。 原文:SpringMVC执行流程及源码解析","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"spring","slug":"spring","permalink":"http://yelog.org/tags/spring/"},{"name":"springmvc","slug":"springmvc","permalink":"http://yelog.org/tags/springmvc/"}]},{"title":"PostgreSQL常用SQL操作","slug":"PostgreSQL常用SQL操作","date":"2017-04-14T08:37:24.000Z","updated":"2024-07-05T01:50:54.990Z","comments":true,"path":"2017/04/14/postgres-sql-use/","permalink":"http://yelog.org/2017/04/14/postgres-sql-use/","excerpt":"","text":"说明:文章中实例均在 PostgreSQL 环境操作。 DDL数据定义语言数据库/角色/schema-- 创建一个数据库用户 create role "sp-boss" createdb createrole login password 'sp-boss'; -- 使用上面角色登录 postgres 数据库 psql -U sp-boss -d postgres -- 创建自己的数据库 create database "sp-boss" -- 登录自己的数据库 psql -U sp-boss -- 创建一个其他用户 create role "sp-manager" login password 'sp-manager'; -- 赋予 create 权限 grant create on database "sp-boss" to "sp-manager"; -- 使用 新用户 登录数据库 psql -U sp-manager -d sp-boss -- 创建自己的 schema create schema "sp-manager"; 表--创建表 create table user_info ( id serial primary key, name varchar(20), age integer, create_time timestamp, type integer, display boolean default true, unique (name, type) ); --删除表 drop table exists user_info; --重命名表 alter table user_info rename to user_infos; 字段(列)--添加一列 alter table user_info add [column] username varchar(50); --删除一列 alter table user_info drop [column] username; --重命名列 alter table user_info rename [column] username to name; --修改结构 alter table user_info alter [column] username set not null; -- 唯一约束-- 添加名为 uk_name 的联合唯一约束,组合列为column1和column2 alter table sys_theme add constraint uk_name unique(column1,column2); -- 删除名为 uk_name 的约束 alter table sys_theme drop constraint uk_name; DML数据库操作语言SELECT查询包含json格式的text类型的数据postgres=# select * from person; id | name | other ----+--------+---------------------------------------------------------- 1 | faker | {"gender":"male","address":"xiamen","college":"xmut"} 2 | watson | {"gender":"male","address":"shenzhen","college":"szu"} 3 | lance | {"gender":"male","address":"shenzhen","college":"xmut"} 4 | jine | {"gender":"female","address":"xiamen","college":"xmut"} 5 | jobs | {"gender":"male","address":"beijing","college":"xmu"} 6 | yak | {"gender":"female","address":"xiamen","college":"xmut"} 7 | alice | {"gender":"female","address":"shanghai","college":"thu"} 8 | anita | {"gender":"female","address":"xiongan","college":"hku"} (8 行记录) -- 查询深圳学生的高校分部情况 select other::json->>'college' college, count(1) from person where other::json->>'address'='shenzhen' group by other::json->>'college'; ___________________________ college | count ---------+------- szu | 1 xmut | 1 (1 行记录) --- 结果可得深圳一共有两个学生, --- 在深圳大学和厦门理工学院各一个。","categories":[{"name":"数据库","slug":"数据库","permalink":"http://yelog.org/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"}],"tags":[{"name":"postgres","slug":"postgres","permalink":"http://yelog.org/tags/postgres/"},{"name":"sql","slug":"sql","permalink":"http://yelog.org/tags/sql/"}]},{"title":"deepin系统使用记录","slug":"deepin-linux","date":"2017-03-30T10:58:50.000Z","updated":"2024-07-05T01:50:55.774Z","comments":true,"path":"2017/03/30/deepin-linux/","permalink":"http://yelog.org/2017/03/30/deepin-linux/","excerpt":"","text":"传送门 官网 论坛 deepin’wiki Deepin应用列表 输入法安装输入法,除了商店下载(好多输入法没有被收录进deepin商店),可以使用fcitx安装。如安装google拼音输入法: $ sudo aptitude install fcitx fcitx-googlepinyin 如果当前在使用ibus,而不是fcitx的话,看下面1)安装fcitx,并安装google拼音 $ sudo apt-get install fcitx fcitx-googlepinyin im-config 2)打开输入法配置 $ im-config 依次:ok->yes,选择fcitx为默认输入法框架,ok->ok 制作启动器图标以创建 atom 这款编辑器的启动器图标为例。1)进入 /usr/share/applications/ 目录,创建 atom.desktop 文件2)编辑 atom.desktop 文件 [Desktop Entry] Name=Atom Comment=A hackable text editor for the 21st century Exec=/opt/atom/atom %F Icon=/opt/atom/atom.png Type=Application StartupNotify=true Categories=TextEditor;Development;Utility; MimeType=text/plain; 解释:Name:创建的图标名称Comment:备注,随便填Exec:启动文件的位置Icon:图标位置Type:类型,启动程序就填ApplicationStartupNotify: 启动通知,填true就行了。详细可查 Startup notificationCategories: 分类,随便填,比如:Application;MimeType: 打开文件类型 修改apt源修改 /etc/apt/sources.list默认的源 deb [by-hash=force] http://packages.deepin.com/deepin/ unstable main contrib non-free 阿里云的源 deb [by-hash=force] http://mirrors.aliyun.com/deepin unstable main contrib non-free 更换文件管理器Nautilus 深度商店下载安装 Nautilus2)卸载深度任务管理器 $ sudo apt remove dde-file-manager Nautilus 常用的快捷键 快捷键 作用 F2 重命名 Ctrl + 1 图标视图 Ctrl + 2 列表视图 Ctrl + T 新建标签页 Ctrl + W 关闭标签页 Alt + 数字 切换到指定标签页 Ctrl + D 收藏到当前文件夹到书签 Shift + F10 打开鼠标右键菜单 Alt + 左方向键 后退 Alt + 右方向键 前进 Ctrl + Q 退出","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"软件记录","slug":"工具/软件记录","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95/"}],"tags":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/tags/%E8%BF%90%E7%BB%B4/"},{"name":"deepin","slug":"deepin","permalink":"http://yelog.org/tags/deepin/"}]},{"title":"hexo报错合集","slug":"hexo-error-collection","date":"2017-03-27T11:40:42.000Z","updated":"2024-07-05T01:50:55.279Z","comments":true,"path":"2017/03/27/hexo-error-collection/","permalink":"http://yelog.org/2017/03/27/hexo-error-collection/","excerpt":"","text":"hexo server时报错FATAL watch … ENOSPC日志:2017-03-27 执行 hexo server 后报错。如图:分析问题:node.js 中 watch 的文件数是有限制的。解决问题: $ echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"3-hexo快捷键说明","slug":"3-hexo-shortcuts","date":"2017-03-24T08:45:31.000Z","updated":"2024-07-05T01:50:55.266Z","comments":true,"path":"2017/03/24/3-hexo-shortcuts/","permalink":"http://yelog.org/2017/03/24/3-hexo-shortcuts/","excerpt":"","text":"今日公司断网了半个小时,就利用这段时间给主题添加了快捷键操作,方便使用。 快捷键为vim风格的。按键可能与vimium(chrome插件)的快捷键有冲突,插件设置屏蔽掉此站的快捷键即可 如果有比较好的建议,欢迎骚扰。 说明全局 Key Descption s/S 全屏/取消全屏 w/W 打开/关闭文章目录 i/I 获取搜索框焦点 j/J 向下滑动 k/K 向上滑动 gg/GG 到最顶端 shift+G/g 到最下端 搜索框 Key Descption ESC 1.如果输入框有内容,清除内容2.如果输入框无内容,失去焦点 下 向下选择文章 上 向上选择文章 回车 打开当前选中的文章,若没有,则默认打开第一个 关闭快捷键在主题下 _config.yml 中 找到 shortcutKey 设为 false shortcutKey: false","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"3-hexo使用说明","slug":"3-hexo-instruction","date":"2017-03-23T07:13:47.000Z","updated":"2024-07-05T01:50:55.239Z","comments":true,"path":"2017/03/23/3-hexo-instruction/","permalink":"http://yelog.org/2017/03/23/3-hexo-instruction/","excerpt":"","text":"下面如果没有特殊说明, _config.yml 都指主题配置文件,即 3-hexo 目录下 一、初始化博客下 _config.yml1.1 国际化language: zh-CN #支持 zh-CN、en 1.2 关掉 hexo 自带的代码高亮主题内置了主题高亮,所以需要禁用 hexo 自带的高亮 根据 hexo 版本, 禁用的方式发生了变化 禁用高亮 below v7.0.0 # _config.yaml highlight: enable: false 7.0.0+ # _config.yml syntax_highlighter: # empty 二、功能相关2.1 自定义首页可查看这篇文章: 3-hexo配置首页 2.3 blog快捷键可查看这篇文章: 3-hexo快捷键说明 2.4 多作者模式可查看这篇文章: 3-hexo多作者模式 2.5 开启关于页面 在 hexo 根目录执行以下,创建 关于 页面 hexo new page "about" 位置: source/aoubt/index.md ,根据需要进行编辑。 在主题中开启显示:修改主题根目录 _config.yml 中的 about 的 on 为 true,如下所示 menu: about: # '关于' 按钮 on: true # 是否显示 url: /about # 跳转链接 type: 1 # 跳转类型 1:站内异步跳转 2:当前页面跳转 3:打开新的tab页 2.6 添加音乐插件3-hexo 添加音乐插件 2.7 配置评论系统3-hexo评论设置 三、样式设置3.1 代码高亮首先要关闭hexo根目录下_config.yml中的高亮设置: highlight: enable: false 配置主题下_config.yml中的高亮设置:可以根据提示,配置喜欢的高亮主题 highlight: on: true # true开启代码高亮 lineNum: true # true显示行号 theme: darcula # 代码高亮主题,效果可以查看 https://highlightjs.org/static/demo/ # 支持主题: # sublime : 参考sublime的高亮主题 # darcula : 参考idea中的darcula的主题 # atom-dark : 参考Atom的dark主题 # atom-light : 参考Atom的light主题 # github : 参考GitHub版的高亮主题 # github-gist : GitHub-Gist主题 # brown-paper : 牛皮纸效果 # gruvbox-light : gruvbox的light主题 # gruvbox-dark : gruvbox的dark主题 # rainbow : # railscasts : # sunburst : # kimbie-dark : # kimbie-light : # school-book : 纸张效果 3.2 MathJax数学公式修改 _config.yml # MathJax 数学公式支持 mathjax: on: true #是否启用 per_page: false # 若只渲染单个页面,此选项设为false,页面内加入 mathjax: true 考虑到页面的加载速度,支持渲染单个页面。设置 per_page: false ,在需要渲染的页面内 加入 mathjax: true 注意: 由于hexo的MarkDown渲染器与MathJax有冲突,可能会造成矩阵等使用不正常。所以在使用之前需要修改两个地方编辑 node_modules\\marked\\lib\\marked.js 脚本 将451行 ,这一步取消了对 \\\\,\\{,\\} 的转义(escape) escape: /^\\\\([\\\\`*{}\\[\\]()# +\\-.!_>])/, 改为 escape: /^\\\\([`*\\[\\]()# +\\-.!_>])/, 将459行,这一步取消了对斜体标记 _ 的转义 em: /^\\b_((?:[^_]|__)+?)_\\b|^\\*((?:\\*\\*|[\\s\\S])+?)\\*(?!\\*)/, 改为 em:/^\\*((?:\\*\\*|[\\s\\S])+?)\\*(?!\\*)/, 3.3 表格样式目前提供了3中样式,修改 _config.yml table: green_title # table 的样式 # 为空时类似github的table样式 # green 绿色样式 # green_title 头部为青色的table样式 3.4 文章列表的hover样式鼠标移入的背景色和文字颜色变动,设置 _config.yml #文章列表 鼠标移上去的样式, 为空时使用默认效果 article_list: hover: background: '#e2e0e0' # 背景色:提供几种:'#c1bfc1' '#fbf4a8' color: # 文字颜色 提供几种:'#ffffff' # 注意:由于颜色如果包含#,使用单引号 ' 引起来 3.5 开启字数统计 开启此功能需先安装插件,在 hexo根目录 执行 npm i hexo-wordcount --save 修改 _config.yml word_count: true 3.6 更换头像两种方式: 替换 source/img/avatar.jpg 图片。 修改 _config.yml 中头像的配置记录 # 你的头像url avatar: /img/avatar.jpg favicon: /img/avatar.jpg 3.7 设置链接图标 如果需要自定义图标可以看这篇文章 3-hexo添加自定义图标 如下,如果没有连接,则不展示图标。 #链接图标,链接为空则不显示 link: rss: /atom.xml github: https://github.com/yelog facebook: https://www.facebook.com/faker.tops twitter: linkedin: instagram: reddit: https://www.reddit.com/user/yelog/ weibo: http://weibo.com/u/2307534817 email: jaytp@qq.com 四、排序及置顶4.1 分类排序默认按照首字母正序排序,由于中文在 nodejs 环境下不能按照拼音首字母排序,所以添加了自定义排序方式,在主题下 _config.yml 中找到如下配置,category.sort则是定义分类顺序的。 规则:在 sort中定义的 category 比 没有在 sort 中定义的更靠前 # 文章分类设置 category: num: true # 分类显示文章数 sub: true # 开启多级分类 sort: - 读书 - 大前端 - 后端 - 数据库 - 工具 - 运维 4.2 文章排序 2020-05-20 更新:无需安装插件或修改源码,主题已经内置排序算法 文章列表默认按照创建时间(如下文章内定义的date)倒序。 使用 top 将会置顶文章,多个置顶文章时,top 定义的值越大,越靠前。 --- title: 每天一个linux命令 date: 2017-01-23 11:41:48 top: 1 categories: - 运维 tags: - linux命令 --- 五、关于写文章5.1 如何写每篇文章最好写上文集和标签,方便筛选和查看。一般推荐一篇文章设置一个文集,一个或多个标签categories:文集,为左侧列表tags:标签,通过#来筛选例如 本篇文章的设置 --- title: 3-hexo使用说明 date: 2017-03-23 15:13:47 categories: - 工具 tags: - hexo - 3-hexo --- 5.2 写作1.设置模板,blog根目录 scaffolds/post.md加入categories,tags等,这样以后通过 hexo new 生成的模板就不用写这两个单词了。当然,你也可以写入任何你每个文章中都会有的部分。 --- title: {{ title }} date: {{ date }} categories: tags: --- 六、技巧6.1 快捷命令其实就通过alias,触发一些命令的集合在 ~/.bashrc 文件中添加 alias hs='hexo clean && hexo g && hexo s' #启动本地服务 alias hd='hexo clean && hexo g && hexo d' #部署博客 甚至你也可以加入备份文章的命令,可以自由发挥。 6.3 博客备份(快捷命令升级版)为了保证我们写的文章不丢失、快速迁移博客,都需要备份我们的blog。 博客根目录,执行 git init 创建 git 仓库。 在 github(或其他托管平台、自建远程仓库等) 创建仓库并和本地仓库建立联系。 在 ~/.bashrc 文件中添加 alias hs='hexo clean && hexo g && hexo s' alias hd='hexo clean && hexo g && hexo d && git add . && git commit -m "update" && git push -f' 这样,我们在执行 hd 进行部署时,就一同将博客进行备份了","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"关于第三方评论系统","slug":"the-third-comment","date":"2017-03-23T03:59:50.000Z","updated":"2024-07-05T01:50:55.197Z","comments":true,"path":"2017/03/23/the-third-comment/","permalink":"http://yelog.org/2017/03/23/the-third-comment/","excerpt":"","text":"前言昨天登陆blog看到了多说的通知:将于2017年6月1日正式关停服务,其实并没有太大的意外。 自从去年9月份disqus被GFW认证后,被迫转移到多说,一看就是很久没有维护了,感觉关闭就是迟早的事,没想到刚用5个月。。 不能用disqus不开心,然后就又开始调查第三方评论系统。 友言特点 界面挺像disqus的 查询最近的评论,需要打开新的页面 支持表情,不支持图片 支持自定义界面 注册即用 网易云跟帖特点 界面简洁 网易作为后台,不容易倒 不支持表情,不支持图片 支持自定义界面 我在国外加载巨慢,不过查了国内的延迟,平均20ms左右。 注册即用 畅言特点 界面简介 打印记的功能 支持表情和图片 支持获取评论数 需要ICP备案 最后想要尝试一下畅言,功能符合我的预期,不过要ICP备案,最近由于想要开启国内CDN加速,也需要备案。 蛋四!! 我在国外,等回国的时候再备案吧,暂时现使用 disqus,如果想评论,国内只能翻墙了。╮(╯_╰)╭ 最后,多说一路走好。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"windows环境下使用hexo搭建blog平台","slug":"windows-hexo","date":"2017-03-17T06:02:55.000Z","updated":"2024-07-05T01:50:55.248Z","comments":true,"path":"2017/03/17/windows-hexo/","permalink":"http://yelog.org/2017/03/17/windows-hexo/","excerpt":"","text":"之前已经出过几期搭建 hexo 的文章,但是有不少朋友私信说系统是windows的,希望出一期windows环境下的hexo搭建文章。 为此,确保可用性,笔者在linux(笔者的系统环境)环境下安装了windows虚拟机重新演练了一边,确保没有什么漏洞,才写下了此文。 本文会非常详细,以确保没有计算机背景的小白也可以轻松上手 环境搭建安装git1).下载:从官网下载windows版本的git,地址在下方。https://git-scm.com/download/win2).安装:双击安装,一直点击下一步即可 安装node.js1).下载:从官网下载windows版本的node.js安装包(.msi后缀),地址下方https://nodejs.org/zh-cn/download/2).安装:双击安装,一直点击下一步即可 安装hexo在任意目录如桌面,点击鼠标右键,选择Git Bash Here这一项,打开git bash命令框(前提是git安装成功),如下图在打开的命令窗内输入下面的命令进行安装 npm install hexo-cli -g 安装过后,输入 hexo -v,出现下面信息,则表示安装成功 $ hexo -v hexo-cli: 1.0.2 os: Windows_NT 6.1.7601 win32 x64 http_parser: 2.7.0 node: 6.10.0 v8: 5.1.281.93 uv: 1.9.1 zlib: 1.2.8 ares: 1.10.1-DEV icu: 58.2 modules: 48 openssl: 1.0.2k 初始化blog进入准备创建blog的目录,同样点击鼠标右键,打开git bash命令框,执行一下命令进行初始化 $ hexo init myblog 就会自动创建一个名字为myblog目录,这时本地blog就已经创建好了。进入blog目录,启动 blog $ cd myblog $ hexo server 在浏览器输入 127.0.0.1:4000就可以访问到刚刚创建好的blog了。 换皮肤如果觉的自带的皮肤太难看。可以根据以下步奏更换皮肤在官网 可以查看各种各样的皮肤,挑选自己喜欢的皮肤 这里以 3-hexo 这款皮肤为例(这款皮肤是笔者写的,效果可查看 yelog.org)1)进入皮肤的 github 官网,如3-hexo的网址找到 clone or download ,复制它的url:https://github.com/yelog/hexo-theme-3-hexo.git 2)进入 myblog 目录,打开 git bash 命令框,执行以下命令将皮肤下载到themes目录下 $ git clone https://github.com/yelog/hexo-theme-3-hexo.git themes/3-hexo 修改 myblog/_config.yml 中的 theme: landscape 为 theme: 3-hexo 如果使用 3-hexo 主题的话,还需要注意两点①因为主题使用了自己的高亮效果,还需要修改 highlight enable: true 的 true 改为 false。②由于主题启用了文章字数统计功能,需要安装一个插件,在 myblog 目录下,打开 git bash 命令框,执行 npm i --save hexo-wordcount 即可 重新渲染,启动服务器 $ hexo clean && hexo g && hexo s 打开浏览器查看效果,换肤成功 如何写文章文章在 myblog/source/_posts/ 下,以markdown格式写成,笔者推荐使用atom作为写作工具。可以通过 hexo new 文章名 来创建一篇文章,当然也可以直接在 _posts 目录下直接新建.md文件。执行命令 仍是在 myblog 目录下,打开 git bash 命令框。以下是常用命令,其他可以查阅官网。 # 创建一个标题为“git教程”的文章 $ hexo new "git教程" # 清除所有渲染的页面 $ hexo clean # 将markdown渲染成页面 $ hexo g # 启动hexo $ hexo s 发布到网上如果想要在github上搭建blog,或者在自己的购买的服务器上搭建blog可以查看笔者的往期文章 今天的教程就到这里,有什么问题可以在评论区交流。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"npm使用介绍","slug":"npm使用介绍","date":"2017-03-16T12:09:48.000Z","updated":"2024-07-05T01:50:55.110Z","comments":true,"path":"2017/03/16/npm/","permalink":"http://yelog.org/2017/03/16/npm/","excerpt":"","text":"Whatnpm(全称Node Package Manager,即node包管理器)是Node.js默认的、以JavaScript编写的软件包管理系统。作者:艾萨克·施吕特(Isaac Z. Schlueter) 安装npm 是随同node.js一起安装的,所以安装node.js即可。 使用# 查看版本 $ npm -v # 升级 $ sudo npm install npm -g # 安装模块 $ npm install <Module Name> #本地安装 # 本地安装:安装到./node_modules(命令运行目录) $ npm install <Module Name> -g #全局安装 # 全局安装:放在 /usr/local 下或者你 node 的安装目录。 # 卸载模块 $ npm uninstall <Module Name> # 更新模块 $ npm update <Module Name> # 查看所有安装的模块 $ npm ls #所有本地模块 $ npm ls -g #所有全局模块 # 搜索模块 $ npm search <Module Name>","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"node","slug":"node","permalink":"http://yelog.org/tags/node/"}]},{"title":"为Hexo添加RSS和Sitemap","slug":"Hexo-RSS-Sitemap","date":"2017-03-14T01:44:29.000Z","updated":"2024-07-05T01:50:55.293Z","comments":true,"path":"2017/03/14/Hexo-RSS-Sitemap/","permalink":"http://yelog.org/2017/03/14/Hexo-RSS-Sitemap/","excerpt":"","text":"添加RSS使用RSS是为自己的blog提供订阅功能。 1.用npm安装插件$ npm install hexo-generator-feed --save 2.配置根目录_config.yml# Extensions ## Plugins: http://hexo.io/plugins/ #RSS订阅 plugin: - hexo-generator-feed #Feed Atom feed: type: atom path: atom.xml limit: 20 3.验证配置是否成功执行 hexo g,查看一下public目录下,如果有 atom.xml 文件,则表明配置成功。 4.显示RSS图标这里以3-hexo主题为例,给rss添加链接/atom.xml修改/themes/3-hexo/_config.yml link: rss: /atom.xml 5.效果链接图标:链接地址效果 添加SitemapSitemap,网站地图,是网站优化中重要的一环,无论是对于访问者还是对于搜索引擎。 1.用npm安装插件$ npm install hexo-generator-sitemap --save 2.配置根目录_config.ymlplugin: - hexo-generator-feed - hexo-generator-sitemap 3.验证配置是否成功执行 hexo g,查看一下public目录下,如果有 sitemap.xml 文件,则表明配置成功。 4.效果访问 /sitemap.xml 就能看到生成的站点地图了","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"3-hexo开发日志-持续更新","slug":"3-hexo-logs","date":"2017-03-13T07:44:57.000Z","updated":"2024-07-05T01:50:55.257Z","comments":true,"path":"2017/03/13/3-hexo-logs/","permalink":"http://yelog.org/2017/03/13/3-hexo-logs/","excerpt":"","text":"2024年1月01-05 fix 修复代码块的行号错位问题 2023年11月11-22 fix 修复代码块, 复制时丢失缩进和空格问题 环境 - 改为实例 2023年6月06-16 fix 修复文章分类层级不能无限递归的问题 06-02 fix 修复由于层级不连续,导致的 toc 获取不完整的问题 2023年5月05-23 fix 修复设置宽度后,背景不全 fixes #123 05-19 fix 修复隐藏文章后, 左侧分类上的文章数量不对的问题 2022年6月06-27 add 添加隐藏文章参数 hidden, 设置为 true 将文章从列表中隐藏 2022年2月02-22 fix jquery dom selector contains special symbols cause errors fix Script could run successfully in the case of errors synchronizing TOC 02-16 fix 修复首页使用自定义 html 标题时,大纲和文章关联关系丢失报错的问题 2021年9月09-26 fix 修复 gitalk 代理节点的问题,并升级为官方最新的代码 2021年2月02-22 enhance 按标题搜索支持字符级别模糊查询,并高亮显示 2020年12月12-28 fix 修复了右下角按钮错位问题 enhance 优化图标样式,支持自定义图标引入 change 分类超出隐藏 2020年8月08-09 fix 修复了切换大纲时,未关闭全局搜索框(in:) 的问题2. add 代码块新增显示代码类型和复制代码功能 fix分类支持 \\/#.[]() 特殊字符 fix 大纲支持URI 编码,兼容 hexo5+ 2020年5月05-29 fix 修复了旧版 hexo 报错的问题 05-23 change 由于 cloudflare 国内访问不稳定,故 cdn 切换到 jsdelivr fix 修复文章大纲为空时,同步大纲报错的问题 change 文章内 toc 生成从 @【toc】 改为 【toc】 enhance 快捷键支持关闭 shortcutKey: false add 添加第三方评论 来必力 和 utteranc remove 移除网易云评论 05-21 add 添加备案号配置 05-20 enhance 内置文章排序,无需再引入排序插件或修改源代码 enhance 支持自定义分类的顺序, 具体可以查看 3-hexo使用说明 中的排序相关内容 05-19 change 重做了文章大纲 change 重做了搜索/标签页 style 优化了整体界面风格 fix修复了诸如 分类选中动画闪烁 等细节问题 2020年2月02-041.fix: 修复 gitalk 使用 app API query parameter 弃用的问题 2019年11月11-121.fix: 修复 hexo4.0 版本链接外跳的问题2.add: 支持 mermaid 3-hexo支持mermaid图表 09-241.add: 支持文章内 toc 生成 3-hexo文章内toc生成 09-051.fix: pjax 兼容 jsfiddle 的渲染 3-hexo支持jsfiddle渲染 2019年8月08-201.fix: 左下角菜单个数为一个时,在移动端出现的位置错乱的问题 08-13 add 添加自定义左侧分类栏宽度 category.width,详情见 _config.yml 08-01 fix 修复 友链 区域超出不滚动的问题 2019年7月07-22 add 新增修改文章列表颜色的参数 07-15 fix 修复 gitalk 由于作者停止了跨域的服务,借用其他人的跨域服务解决问题 07-12 add 新增图标:qq、酷狗、网易云音乐 fix 修复文末声明跨行的问题 2019年6月06-09 fix 修复了标签按钮在某些分辨率下错位问题。 fix 修复了代码行数超百行时,行号溢出的问题 2019年5月05-21 add 文章分类可以显示文章数 category>num add 文章分类支持多级显示 category>sub fix 修复gitalk显示评论数错误的问题 添加背景图设置: ① _config.xml 配置默认背景图片 首页背景图: index_bg_img: xxx.jpg 文章页面背景图:other_bg_img: xxx.jpg ② 我们还可以单独给某篇文章设置背景图(优先级最高) title: 3-hexo开发日志-持续更新 bgImg: xxx.jpg #设置这篇文章的背景图 05-05 add 添加代码段高亮样式配置,对应 markdown 语法 `` ,位置:_config.yml 关键字 code 2019年1月01-05 fix 修复Firefox下,Tags 图标失效问题 2018年11月11-30 add 应用彩色图标,新增简书、知乎、csdn、oschina等图标 fix 修复 gitment 登录报错的问题 enhance 升级gitalk插件,并跟随官方版本 2018年8月08-08 修复 fix : 修复左侧栏出现滚动条的问题 2018年4月04-18 修复 fix : 调整原文地址key,解决和 encrypt 的冲突 2017年12月12-31 优化 enhance : 关闭打赏,则屏蔽相关代码。 12-28 添加自定义菜单功能 add : 添加自定义菜单功能,见配置文件 menu: 相关 fix : 修复 photoSwipe 一些问题:手机端右上角图标遮挡、首页图片渲染失败、CDN引入配置文件 12-27 插件添加 add : 引入 photoSwipe 图片相册,可在 _config 中 配置 img_resize: photoSwipe fix : 左侧分类列表过多,则显示滚动条 change : 由于cdnjs最近在国内网络波动较大,将默认CDN改到bootcdn 12-26 功能优化 enhance : 全文检索支持通过方向键选择文章,回车跳转 12-24 功能添加 add : 添加全文检索功能, 输入in:开头即可开始检索 2017年10月10-21 样式调整 enhance : 优化了高亮样式 atom-light 10-20 样式调整与添加 add : 添加列表样式 thread add : 添加引用块样式 bracket add : 文章列表可加入背景图 10-07 add : 加入关于/友链页面 2017年9月09-21 polish : 引入fragment_cache局部缓存,大幅缩减渲染(hexo g)的时间 Hexo加速渲染速度之fragment_cache enhance : 加入 SEO ,tag转keywords , title转description add : 添加文末说明参数 lit : 头像跳转首页的请求也处理为 pjax 2017年7月07-05 添加MathJax数学公式支持 add : 添加MathJax数学公式支持 3-hexo配置MathJax数学公式渲染 2017年6月06-26 添加gitment评论系统 add : 添加gitment评论系统,具体可参考 完美替代多说-gitment 2017年4月04-27 调整样式 change:调整引用块内p的样式。 04-19 调整样式和修复bug change:调整文章目录间距 fix:修复可能出现的访问量不显示的问题 04-17 调整样式及修复bug change:调整大屏下文章最大宽度(从780调到900),代码字号调整为比文字小3px fix:修复在过滤条件下,鼠标上下键不能正常在第一个,最后一个进行跳转的问题 fix:修复首页可能出现的错误渲染,导致没有样式的情况。 2017年3月03-29 增强文章列表上下键功能 enhance:增强列表跟随选择的文章上下滚动 enhance:文章列表上下循环 03-24 修复搜索及添加快捷键 fix:修复title关键字和标签关键字冲突的情况 fix:修复前进后退时对图片的错误处理 enhance:在搜索时,可以键盘上下键来选择文章 enhance:添加了一些快捷键,详情查看 3-hexo快捷键说明 03-22 添加评论系统 enhance: 添加网易云跟帖评论系统 03-21 图片放大动画 enhance:增加图片放大动画,增加过度感,最大放大至原图大小(若尺寸超过屏幕,按屏幕大小限制) 03-20 评论添加锚点 enhance:添加文章meta(标题下)中的评论数点击事件,滑动到评论区,评论区若隐藏,则自动打开 03-19 修复firfox错位问题 fix:修复firefox错位问题 enhance:文章标题下的分类、标签、作者在文章列表隐藏的情况下(包括移动端)点击,自动呼出文章列表 03-18 动画优化及修复bug enhance:给所有锚点添加动画 fix:修复文章列表页自适应宽度,解决由于firefox不支持自定义滚动条导致的错位 change:改动页面内站点访问量统计的标签,改动查看 — 3-hexo配置首页 03-17 评论调整优化 修复预加载时的评论数 首页添加评论框 03-16 文章meta样式修改 将文章meta(包括文集、标签、时间、字数等)改到文章title下方 站点版权信息(@2017 Yelog),自定义,在主题 _config.yml 中配置 文章meta中添加评论数 03-15 增加样式 扩展了文章列表的移入样式,位置 _config.yml 中 article_list 03-14 打赏优化 加入切换二维码动画 移动端样式调整 03-13 首页重构 将首页改写为md格式,方便博主更改,位置:/layout/index.md 03-09 修复多说、添加流量统计 修复多说在pjax中的使用 添加字数统计功能 添加文章版权信息 03-06 添加打赏功能 添加打赏功能 添加table样式 03-04 添加作者和标签的提示 输入#或@显示下拉提示 03-03 修复多作者模式 修复开启多作者模式时,文章没有作者引起的异常 03-02 图片样式改动 alt显示在图片下方 图片放大功能 移动端图片宽度100% 2017年2月02-28 多作者模式 添加多作者模式 02-27 调整样式 修改a的样式 调整移动端宽度 02-25 修改github仓库名 修改github仓库名:从 3-hexo 到 hexo-theme-3-hexo 添加回到顶端功能:小火箭 修复safari滑动bug 02-24 站点版权、ICON和置顶 添加站点版权信息 更换icon到icoMoon 添加置顶功能 02-20 代码块高亮主题 添加十几种代码高亮主题 移动端适配(ipad、手机) 02-19 移动端适配 添加文章加载动画进度条 开始进行移动端适配 02-08 评论系统 添加多说评论 添加disqus评论 02-07 样式及动画新增修改 添加标题和时间的title 添加头像下外链图标(facebook等) 添加目录显示动画 02-06 功能和样式开发 添加全屏和目录功能 搜索框下添加tags的显示,并支持使用#搜索 CDN改为在_config.yml中配置 02-05 开源3-hexo主题 设计主题页面结构 分类过滤和标题关键字搜索 使用pjax方式加载页面 添加引用、表格等样式 使用highlight.js来处理代码块高亮 命名为3-hexo并在github上开源e","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"3-hexo配置首页","slug":"3-hexo-homepage","date":"2017-03-13T01:56:07.000Z","updated":"2024-07-05T01:50:55.311Z","comments":true,"path":"2017/03/13/3-hexo-homepage/","permalink":"http://yelog.org/2017/03/13/3-hexo-homepage/","excerpt":"","text":"今日将首页提到md文件中了,方便大家的更改。 首页文件位置 /layout/indexs.md ,既然是md格式,要怎么写大家应该都熟门熟路了,阿杰就不赘述了。 如果需要使用以下信息,可以按照下面的方式使用(以下内容不限首页使用) 文章数统计/字数统计加入含有 class="article_number"的html标签可显示文章数量。加入含有 class="site_word_count"的html标签可显示站点总字数。 <!-- 我这里是借用了code的样式,所以直接使用code标签。 自定义样式,可加入style属性设置--> <code class="article_number"></code> <code class="site_word_count"></code> 上面代码的效果:文章:篇;总字数:字; 流量统计 日志: 2017-03-18改动,由原来的 id 改为现在的 class,可在页面添加多个同类标签 加入含有 class="site_uv"的html标签可显示站点访问人次。加入含有 class="site_pv"的html标签可显示站点访问量。 <!-- 我这里是借用了code的样式,所以直接使用code标签。 自定义样式,可加入style属性设置--> <code class="site_uv"></code> <code class="site_pv"></code> 上面代码的效果:访问人数:人,访问量:次。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"不蒜子适配pjax","slug":"不蒜子适配pjax","date":"2017-03-09T12:15:51.000Z","updated":"2024-07-05T01:50:55.132Z","comments":true,"path":"2017/03/09/busuanzi-pjax/","permalink":"http://yelog.org/2017/03/09/busuanzi-pjax/","excerpt":"","text":"不蒜子一般配置加入脚本 <script async src="//dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js"></script> <!--pv方式 --> <span id="busuanzi_container_site_pv"> 本站总访问量<span id="busuanzi_value_site_pv"></span>次 </span> <!--uv方式 --> <span id="busuanzi_container_site_uv"> 本站访客数<span id="busuanzi_value_site_uv"></span>人次 </span> <!--pv方式 --> <span id="busuanzi_container_page_pv"> 本文总阅读量<span id="busuanzi_value_page_pv"></span>次 </span> 只安装脚本,不安装标签代码,即可实现只记数,不显示。 适配pjax最近开发3-hexo主题,由于主题使用的pjax,异步加载页面时不蒜子会出现加载不到多说js的问题。在pjax:end加载下面js代替标签即可 $.getScript("//dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js");","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"},{"name":"jQuery","slug":"jQuery","permalink":"http://yelog.org/tags/jQuery/"}]},{"title":"多说适配pjax","slug":"多说适配pjax","date":"2017-03-09T11:50:45.000Z","updated":"2024-07-05T01:50:55.075Z","comments":true,"path":"2017/03/09/duoshuo-pjax/","permalink":"http://yelog.org/2017/03/09/duoshuo-pjax/","excerpt":"","text":"最近开发3-hexo主题,由于主题使用的pjax,异步加载页面时多说会出现加载不到多说js的问题。 多说加载代码如下: //加载多说 function loadComment() { duoshuoQuery = {short_name: $(".theme_duoshuo_domain").val()}; var d = document, s = d.createElement('script'); s.src = 'https://static.duoshuo.com/embed.js?t='+new Date().getTime(); s.async = true; s.charset = 'UTF-8'; (d.head || d.body).appendChild(s); } 当局部加载页面时,就会无法加载多说。需要编写一个js方法,参考文档:(http://dev.duoshuo.com/docs/50b344447f32d30066000147) /** * pjax后需要回调函数.加载多说 */ function pajx_loadDuodsuo(){ if(typeof duoshuoQuery =="undefined"){ loadComment(); } else { var dus=$(".ds-thread"); if($(dus).length==1){ var el = document.createElement('div'); el.setAttribute('data-thread-key',$(dus).attr("data-thread-key"));//必选参数 el.setAttribute('data-url',$(dus).attr("data-url")); DUOSHUO.EmbedThread(el); $(dus).html(el); } } } 在pjax:end中调用此方法即可。","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"},{"name":"jQuery","slug":"jQuery","permalink":"http://yelog.org/tags/jQuery/"}]},{"title":"Hexo加入字数统计WordCount","slug":"hexo-wordcount","date":"2017-03-09T08:57:10.000Z","updated":"2024-07-05T01:50:55.274Z","comments":true,"path":"2017/03/09/hexo-wordcount/","permalink":"http://yelog.org/2017/03/09/hexo-wordcount/","excerpt":"","text":"只需要安装一个插件 WordCount 安装$ npm i hexo-wordcount --save 使用单篇文章字数 <%=wordcount(post.content) %> 所有文章的总字数 <%=totalcount(site) %> 日志2017年3月9日,给3-hexo添加字数统计功能","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"前端页面开发规范","slug":"font-develop-rule","date":"2017-03-08T00:58:21.000Z","updated":"2024-07-05T01:50:55.128Z","comments":true,"path":"2017/03/08/font-develop-rule/","permalink":"http://yelog.org/2017/03/08/font-develop-rule/","excerpt":"","text":"一、前言随着开发人员的不断增加,在没有规范的情况下,就会导致开发的页面不统一,不像是一个系统。为了解决这个问题,就有了此规范的出现,当然为了不影响各个功能的灵活性,此规范要求不高, 请耐心阅读,并应用到日常开发中。 当然,如果你有更好的建议,可以通过邮件联系 yangyj13@lenovo.com,进行沟通来完善此篇规范。 二、编程规范2.1 命名规范2.1.1 文件命名全部采用小写方式,以横杠分割。 正例: resource.vue、user-info.vue 反例: basic_data.vue、EventLog.vue 2.1.2 目录命名全部采用小写方式,以横杠分割。 正例: system、ship-support 反例: errorPage、Components 2.1.3 JS、CSS、SCSS、HTML、PNG文件命名全部采用小写方式,以横杠分割。 正例: btn.scss、element-ui.scss、lenovo-logo.png 反例: leftSearch.scss、LeGrid.js 2.1.4 命名规范性代码中命名严禁使用拼音和英文混合的方式,更不允许直接使用中文的方式。说明: 正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音的命名方式也要避免采用。 正例: loading、searchForm、tableHeight、dmsLoading、rmb 专有名词缩写,视同英文反例: getLiaoPanName、DMSLoading 2.2 插件使用2.2.1 eslint 代码规范注意:前端的代码格式化已经在 eslint 中声明了,所以确保自己已经启用了 eslint,并使 eslint 进行代码格式化。 2.2.2 i18n 国际化所有展示的内容都要支持国际化。国际化内容写到 /src/lang/ 下的对应模块,通过 this.$t('xx.xx.xx') 来使用。 英文国际化的列或标签,请使用开头字母大写的方式,如: UserId、Status、UserName。 2.3 组件使用2.3.1 table 表格表格组件推荐使用 vxe-table,功能更加全面,之后也会主力优化此表格。比如可编辑表格的样式经过优化:可编辑表格 2.3.2 dialog 弹窗弹窗组件推荐使用 vxe-modal,代码设计更加合理,功能也更加全面。 2.3.2 element-ui除 table 和 modal 外,其他组件比如 form、button、DateTimePicker 优先使用 element-ui 。 2.3.2.1 icon 图标图标优先使用 element-ui 的图标。如果没有合适的,可以在 iconfont 上寻找到合适的图标后,找 yangyj13@lenovo.com 进行添加。 2.3.2.2 button 按钮按钮大小:除了在表格中的按钮要使用 size="mini" 外,其他情况使用默认大小即可。 按钮颜色:普通的 查询/修改/操作 等按钮使用蓝色 type="primary",新增使用绿色 type="success",删除等“危险”操作使用红色 type="danger"。推荐给按钮添加图标,可在 element-ui-icon 寻找合适的图标。 2.3.3 其他组件如果上述组件并不能满足业务需求,可以优先在网上找到合适的组件后,与 yangyj13@lenoov.com 联系后添加。 2.4 页面布局2.4.1 新增/修改表单普通的表单,采用中间对其的方案,也就是整个表单的 label-width 设置为一样的。 注意:一般的,新增修改使用弹窗的方式,展示表单。新增/修改可以共用代码,具体可以参考 common/system/va-config.vue <el-form ref="dialogForm" v-loading="edit.loading" :model="edit.form" :rules="edit.formRules" label-width="150px" style="padding-right: 30px;" > ... </el-form> 2.4.2 查询表单+表格这种应该是最长间的需求方案了,可以参考 /common/system/user.vue,在写的时候注意以下几点: label-width 不要设置,保证标签文字开头和表格对齐。 el-form 使用 :inline="true" 设置表单内容行内显示。 设置 vxe-table 的 height 属性,保证表格底部贴住网页底部,又不会有滚动条(表格内允许有滚动条) 按钮也放到表单中,不要单独一行。 最终效果如下:","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"translation","slug":"translation","permalink":"http://yelog.org/tags/translation/"}]},{"title":"[译]理解浏览器关键渲染路径","slug":"理解浏览器关键渲染路径-译","date":"2017-03-08T00:58:21.000Z","updated":"2024-07-05T01:50:55.093Z","comments":true,"path":"2017/03/08/understanding-the-critical-rendering-path/","permalink":"http://yelog.org/2017/03/08/understanding-the-critical-rendering-path/","excerpt":"","text":"当一个浏览器接收到从服务器发来的html页面,在渲染并呈现到屏幕上之前,有很多步骤要做。浏览器渲染页面需要做的一系列行为被称作“关键渲染路径(Critical Rendering Path 简称CRP)”。 CRP 的知识对于如何提升网站性能是相当有用的。CRP有6个步骤: 构建DOM树 构建CSSOM树 运行JavaScript 创建渲染树 生成布局 绘制页面 构建DOM树DOM(Document Object Model)树是一个表示整个解析过的HTML页面的对象,从根节点<html>开始,会创建页面中的每个 元素/文本 节点。嵌套在其他元素中的元素作为字节点,每个节点都包含了其所有的元素属性,例如: 一个<a>节点将有 href 属性与其关联。 举个例子 <html> <head> <title>Understanding the Critical Rendering Path</title> <link rel="stylesheet" href="style.css"> </head> <body> <header> <h1>Understanding the Critical Rendering Path</h1> </header> <main> <h2>Introduction</h2> <p>Lorem ipsum dolor sit amet</p> </main> <footer> <small>Copyright 2017</small> </footer> </body> </html> 上面的 HTML 将会被解析成下面的DOM树HTML的优点在于它不必等待整个页面加载完成才呈现页面,可以解析一部分,显示一部分。但是像CSS、JavaScript等其他资源会阻止页面渲染。 构建CSSOM树CSSOM(CSS Object Model) 是一个跟DOM相关的样式对象。它跟DOM的表示方法是相似的,但是不论显式声明还是隐式继承,每个节点都存在关联样式。 In the style.css file from the document mentioned above, we have the folowing styles在上面提到的html页面的style.css中的样式如下 body { font-size: 18px; } header { color: plum; } h1 { font-size: 28px; } main { color: firebrick; } h2 { font-size: 20px; } footer { display: none; } 它会被构建成下面的CSSOM树CSS 被认为是 “渲染阻塞资源”,它意味着如果不首先完全解析资源,渲染树是无法构建的。CSS由于它的层叠继承的性质,不能像HTML一样解析一部分,显示一部分。定义在文档后面的样式会覆盖或改写之前定义的样式,因为在整个样式表都被解析之前,如果我们使用了在样式表中较早定义的样式,那错误的样式将被应用。这意味着CSS必须被全部解析之后,才能开始下一步。 如果CSS文件适用于当前设备的话,仅仅只是会阻塞渲染。<link rel="stylesheet">标签可以使用media属性,用来指定特定样式宽度的特定媒体查询。 举个例子,如果我们有一个包含媒体属性orientation:landscape的样式,我们使用纵向模式(portrait mode)查看页面,这个资源将不会阻塞渲染。 CSS 也会导致脚本阻塞。这是因为JavaScript文件必须等待CSSOM被构建后才能运行。 运行JavaScriptJavaScript被认为是解析阻塞资源,这意味着HTML的解析会被JavaScript阻塞。 当解析器解析到 <script> 标签时,无论该资源是内部还是外链的都会停止解析,先去下载资源。这也是为什么,当页面内有引用JavaScript文件时,引用标签要放到可视元素之后了。 为避免JavaScript解析阻塞,它可以通过设定 async 属性来要求其异步加载。 <script async src="script.js"> 创建渲染树渲染树是DOM和CSSOM的结合体,它代表最终会渲染在页面上的元素的结构对象。这意味着它只关注可见内容,对于被隐藏或者CSS属性 display:none 的属性,不会被包含在结构内。 使用上面例子的DOM和CSSOM,渲染树被创建如下: 生成布局布局决定了浏览器视窗的大小,它提供了上下文依赖的CSS样式,如百分比或窗口的单位。视窗尺寸通常通过 <head> 标签中的 <meta> 中的 viewport 设定来决定。如果不存在该标签,则通常默认为 980px 例如,最常用的 meta veiwport 的值将会被设置为和设备宽度相符: <meta name="viewport" content="width=device-width,initial-scale=1"> 如果用户访问网页的设备宽度为1000px。然后整体视窗尺寸就会基于这个宽度值了,比如 50% 就是500px, 10vw 就是100px 等等。 绘制页面最后,在绘制页面步骤。页面上的所有可见内容都会被转换为像素并呈现在屏幕上。 具体的绘制时间跟DOM数以及应用的样式有关。有些样式会花费更多的执行时间,比如复杂的渐变背景图片所需要的计算时间远超过简单固定背景色。 整合所有想要看到关键渲染路径的执行流程,可以使用DevTools,在Chrome中,它是根据时间轴展示的。 举个例子, 上面的页面加入<script>标签 <html> <head> <title>Understanding the Critical Rendering Path</title> <link rel="stylesheet" href="style.css"> </head> <body> <header> <h1>Understanding the Critical Rendering Path</h1> </header> <main> <h2>Introduction</h2> <p>Lorem ipsum dolor sit amet</p> </main> <footer> <small>Copyright 2017</small> </footer> <script src="main.js"></script> </body> </html> 可以看关于页面加载时的事件日志,以下是我们获得的: Send Request - 发送到index.html的GET请求 Parse HTML and Send Request - 开始解析HTML并构建DOM,然后发送 GET 请求style.css和main.js Parse Stylesheet - 根据 style.css 创建的CSSOM Evaluate Script - 执行 main.js Layout - 基于HTML的元视窗标签,生成布局 Paint - 绘制网页基于这些信息,我们可以知道如何优化关键渲染路径。 原文: Understanding the Critical Rendering Path","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"translation","slug":"translation","permalink":"http://yelog.org/tags/translation/"}]},{"title":"Hexo主题3-hexo","slug":"3-hexo","date":"2017-03-07T03:15:50.000Z","updated":"2024-07-05T01:50:55.284Z","comments":true,"path":"2017/03/07/3-hexo/","permalink":"http://yelog.org/2017/03/07/3-hexo/","excerpt":"","text":"阮一峰曾言:喜欢写blog的人,会经历三个阶段 第一阶段,刚接触Blog,觉得很新鲜,试着选择一个免费空间来写。第二阶段,发现免费空间限制太多,就自己购买域名和空间,搭建独立博客。第三阶段,觉得独立博客的管理太麻烦,最好在保留控制权的前提下,让别人来管,自己只负责写文章。 有对搭建个人blog有兴趣的朋友,可以翻看我往期文章。 笔者从去年开始通过hexo写blog,使用了yilia主题,但是随着文章数量的上升,检索等操作就显得特别笨重。 在遍寻无果的情况下,就写下了3-hexo主题。Demo:http://yelog.org 多图预警 ↓↓↓ 设计思路整体设计三段式设计: 通过分类过滤 通过标题关键字搜索 通过作者搜索若开启了多作者模式,则可以通过输入@,进行作者搜索,如下所示 通过标签搜索输入#,就会出现标签提示 评论功能 打赏功能 文章置顶 返回头部 使用1.安装$ git clone https://github.com/yelog/hexo-theme-3-hexo.git themes/3-hexo 2.配置1) 修改hexo根目录的_config.yml的两处,如下 theme: 3-hexo highlight: enable: false #关闭hexo渲染高亮,使用主题代码块高亮 2) 在hexo 根目录source下添加avatar.jpg文件,作为头像 安装字数统计(由于主题使用这个插件,必须安装,否则会报错) $ $ npm i --save hexo-wordcount 注意: 如果没有安装会在 hexo g 的时候报错 3.更新$ cd themes/3-hexo $ git pull","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"Vim命令速查表","slug":"Vim命令速查表","date":"2017-03-06T03:23:36.000Z","updated":"2024-07-05T01:50:54.859Z","comments":true,"path":"2017/03/06/Vim-command/","permalink":"http://yelog.org/2017/03/06/Vim-command/","excerpt":"","text":"去年上半年开始全面使用linux进行开发和娱乐了,现在已经回不去windows了。 话归正传,在linux上一直使用vim,慢慢熟悉了它的命令,才终于领悟了什么是编辑器之神。 最近抽空整理了这份速查表,收获颇丰,并分享给大家。 进入vim 命令 描述 vim filename 打开或新建文件,并将光标置于第一行首 vim +n filename 打开文件,并将光标置于第n行首 vim + filename 打开文件,并将光标置于最后一行首 vim +/pattern filename 打开文件,并将光标置于第一个与pattern匹配的串处 vim -r filename 在上次正用vim编辑时发生系统崩溃,恢复filename vim filename….filename 打开多个文件,依次编辑 vim配置 命令 描述 all 列出所有选项设置情况 term 设置终端类型 ignorance 在搜索中忽略大小写 list 显示制表位(Ctrl+I)和行尾标志($) number 显示行号 report 显示由面向行的命令修改过的数目 terse 显示简短的警告信息 warn 在转到别的文件时若没保存当前文件则显示NO write信息 nomagic 允许在搜索模式中,使用前面不带“\\”的特殊字符 nowrapscan 禁止vi在搜索到达文件两端时,又从另一端开始 mesg 允许vi显示其他用户用write写到自己终端上的信息 :set number / set nonumber 显示/不显示行号 :set ruler /set noruler 显示/不显示标尺 :set hlsearch 高亮显示查找到的单词 :set nohlsearch 关闭高亮显示 :syntax on 语法高亮 :set nu 显示行号 :set ignorecase 搜索时忽略大小写 :set smartcase 搜索时匹配大小写 :set ruler 显示光标位置坐标 :set hlsearch 搜索匹配全高亮 :set tabstop=8 设置tab大小,8为最常用最普遍的设置 :set softtabstop=8 4:4个空格,8:正常的制表符,12:一个制表符4个空格,16:两个制表符 :set autoindent 自动缩进 :set cindent C语言格式里面的自动缩进 移动光标 命令 描述 k nk 上 向上移动n行 j nj 下 向下移动n行 h nh 左 向左移动n行 l nl 右 向右移动n行 Space 光标右移一个字符 Backspace 光标左移一个字符 Enter 光标下移一行 w/W 光标右移一个字至字首 b/B 光标左移一个字至字首 e或E 光标右移一个字至字尾 ) 光标移至句尾 ( 光标移至句首 } 光标移至段落开头 { 光标移至段落结尾 n$ 光标移至第n行尾 H 光标移至屏幕顶行 M 光标移至屏幕中间行 L 光标移至屏幕最后行 0 (注意是数字零)光标移至当前行首 ^ 移动光标到行首第一个非空字符上去 $ 光标移至当前行尾 gg 移到第一行 G 移到最后一行 f 移动光标到当前行的字符a上 F 相反 % 移动到与制匹配的括号上去(),{},[],<>等 nG 移动到第n行上 G 到最后一行 屏幕滚动 命令 描述 Ctrl+e 向文件首翻一行 Ctrl+y 向文件尾翻一行 Ctrl+u 向文件首翻半屏 Ctrl+d 向文件尾翻半屏 Ctrl+f 向文件尾翻一屏 Ctrl+b 向文件首翻一屏 nz 将第n行滚至屏幕顶部,不指定n时将当前行滚至屏幕顶部 插入文本类 命令 描述 i 在光标前 I 在当前行首 a 光标后 A 在当前行尾 o 在当前行之下新开一行 O 在当前行之上新开一行 r 替换当前字符 R 替换当前字符及其后的字符,直至按ESC键 s 从当前光标位置处开始,以输入的文本替代指定数目的字符 S 删除指定数目的行,并以所输入文本代替之 ncw/nCW 修改指定数目的字 nCC 修改指定数目的行 删除命令 命令 描述 x/X 删除一个字符,x删除光标后的,而X删除光标前的 dw 删除一个单词(删除光标位置到下一个单词开始的位置) dnw 删除n个单词 dne 也可,只是删除到单词尾 do 删至行首 d$ 删至行尾 dd 删除一行 ndd 删除当前行及其后n-1行 dnl 向右删除n个字母 dnh 向左删除n个字母 dnj 向下删除n行,当前行+其上n行 dnk 向上删除n行,当期行+其下n行 cnw[word] 将n个word改变为word C$ 改变到行尾 cc 改变整行 shift+j 删除行尾的换行符,下一行接上来了 复制粘贴 命令 描述 p 粘贴用x或d删除的文本 ynw 复制n个单词 yy 复制一行 ynl 复制n个字符 y$ 复制当前光标至行尾处 nyy 拷贝n行 撤销 命令 描述 u 撤销前一次的操作 shif+u(U) 撤销对该行的所有操作 搜索及替换 命令 描述 /pattern 从光标开始处向文件尾搜索pattern ?pattern 从光标开始处向文件首搜索pattern n 在同一方向重复上一次搜索命令 N 在反方向上重复上一次搜索命令 cw newword 替换为newword n 继续查找 . 执行替换 :s/p1/p2/g 将当前行中所有p1均用p2替代,g表示执行 用c表示需要确认 :n1,n2 s/p1/p2/g 将第n1至n2行中所有p1均用p2替代 :g/p1/s//p2/g 将文件中所有p1均用p2替换 :1,$ s/string1/string2/g 在全文中将string1替换为string2 书签 命令 描述 m[a-z] 在文中做标记,标记号可为a-z的26个字母 `a 移动到标记a处 visual模式 命令 描述 v 进入visual 模式 V 进入行的visual 模式 ctrl+v 进如块操作模式用o和O改变选择的边的大小 在所有行插入相同的内容如include< 将光标移到开始插入的位置,按CTRL+V进入VISUAL模式,选择好模块后按I(shift+i),后插入要插入的文本,按[ESC]完成 行方式命令 命令 描述 :n1,n2 co n3 将n1行到n2行之间的内容拷贝到第n3行下 :n1,n2 m n3 将n1行到n2行之间的内容移至到第n3行下 :n1,n2 d 将n1行到n2行之间的内容删除 :n1,n2 w!command 将文件中n1行至n2行的内容作为command的输入并执行之若不指定n1,n2,则表示将整个文件内容作为command的输入 宏 命令 描述 q[a-z] 开始记录但前开始的操作为宏,名称可为【a-z】,然后用q终止录制宏 reg 显示当前定义的所有的宏,用@[a-z]来在当前光标处执行宏[a-z] 窗口操作 命令 描述 :split 分割一个窗口 :split file.c 为另一个文件file.c分隔窗口 :nsplit file.c 为另一个文件file.c分隔窗口,并指定其行数 ctrl+w 在窗口中切换 :close 关闭当前窗口 文件及其他 命令 描述 :q 退出vi :q! 不保存文件并退出vi :e filename 打开文件filename进行编辑 :e! 放弃修改文件内容,重新载入该文件编辑 :w 保存当前文件 :wq 存盘退出 :ZZ 保存当前文档并退出VIM :!command 执行shell命令command :r!command 将命令command的输出结果放到当前行 :n1,n2 write temp.c :read file.c 将文件file.c的内容插入到当前光标所在的下面","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"vim","slug":"vim","permalink":"http://yelog.org/tags/vim/"}]},{"title":"如何在linux中搭建ftp服务","slug":"如何在linux中搭建ftp服务","date":"2017-03-06T00:47:48.000Z","updated":"2024-07-05T01:50:55.788Z","comments":true,"path":"2017/03/06/linux-ftp/","permalink":"http://yelog.org/2017/03/06/linux-ftp/","excerpt":"","text":"什么是 FTPFTP 是文件传输协议File Transfer Protocol的缩写。顾名思义,FTP用于计算机之间通过网络进行文件传输。你可以通过FTP在计算机账户间进行文件传输,也可以在账户和桌面计算机之间传输文件,或者访问在线软件归档。但是,需要注意的是多数的FTP站点的使用率非常高,可能需要多次重连才能连接上。 FTP地址和HTTP地址(即网页地址)非常相似,只是FTP地址使用 ftp://前缀而不是http:// FTP 服务器是什么通常,拥有FTP地址的计算机是专用于接收FTP连接请求的。一台专用于接收FTP连接请求的计算机即为FTP服务器或者FTP站点。 现在,我们来开始一个特别的冒险,我们将会搭建一个FTP服务用于和家人、朋友进行文件共享。在本教程,我们将以vsftpd作为ftp服务。 VSFTPD是一个自称为最安全的FTP服务端软件。事实上VSFTPD的前两个字母表示“非常安全的very secure”。该软件的构建绕开了FTP协议的漏洞。 尽管如此,你应该知道还有更安全的方法进行文件管理和传输,如:SFTP(使用OpenSSH)。FTP协议对于共享非敏感数据是非常有用和可靠的。 安装 VSFTP#使用 rpm 安装 $ dnf -y install vsftpd #使用 deb 安装 $ sudo apt-get install vsftpd #在 Arch 中安装 $ sudo pacman -S vsftpd 配置 FTP 服务多数的VSFTPD配置项都在/etc/vsftpd.conf配置文件中。这个文件本身已经有非常良好的文档说明了,因此,在本节中,我只强调一些你可能进行修改的重要选项。使用man页面查看所有可用的选项和基本的 文档说明: $ man vsftpd.conf 根据文件系统层级标准,FTP共享文件默认位于/srv/ftp目录中。允许上传:为了允许ftp用户可以修改文件系统的内容,如上传文件等,“write_enable”标志必须设置为 YES write_enable=YES 允许本地(系统)用户登录:为了允许文件/etc/passwd中记录的用户可以登录ftp服务,“local_enable”标记必须设置为YES。 local_enable=YES 匿名用户登录下面配置内容控制匿名用户是否允许登录: # 允许匿名用户登录 anonymous_enable=YES # 匿名登录不需要密码(可选) no_anon_password=YES # 匿名登录的最大传输速率,Bytes/second(可选) anon_max_rate=30000 # 匿名登录的目录(可选) anon_root=/example/directory/ 根目录限制(Chroot Jail)( LCTT 译注:chroot jail是类unix系统中的一种安全机制,用于修改进程运行的根目录环境,限制该线程不能感知到其根目录树以外的其他目录结构和文件的存在。详情参看chroot jail) 有时我们需要设置根目录(chroot)环境来禁止用户离开他们的家(home)目录。在配置文件中增加/修改下面配置开启根目录限制(Chroot Jail): chroot_list_enable=YES chroot_list_file=/etc/vsftpd.chroot_list “chroot_list_file”变量指定根目录限制所包含的文件/目录( LCTT 译注:即用户只能访问这些文件/目录) 最后你必须重启ftp服务,在命令行中输入以下命令: $ sudo systemctl restart vsftpd 到此为止,你的ftp服务已经搭建完成并且启动了。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"ftp","slug":"ftp","permalink":"http://yelog.org/tags/ftp/"}]},{"title":"每天一个linux命令(57): sftp","slug":"linux-command-57-sftp","date":"2017-03-05T08:29:38.000Z","updated":"2024-07-05T01:50:55.679Z","comments":true,"path":"2017/03/05/linux-command-57-sftp/","permalink":"http://yelog.org/2017/03/05/linux-command-57-sftp/","excerpt":"","text":"sFTP(安全文件传输程序)是一种安全的交互式文件传输程序,其工作方式与 FTP(文件传输协议)类似。 然而,sFTP 比 FTP 更安全;它通过加密 SSH 传输处理所有操作。 它可以配置使用几个有用的 SSH 功能,如公钥认证和压缩。 它连接并登录到指定的远程机器,然后切换到交互式命令模式,在该模式下用户可以执行各种命令。 在本文中,我们将向你展示如何使用 sFTP 上传/下载整个目录(包括其子目录和子文件)。 How to use默认情况下,SFTP 协议采用和 SSH 传输协议一样的方式建立到远程服务器的安全连接。虽然,用户验证使用类似于 SSH 默认设置的密码方式,但是,建议创建和使用 SSH 无密码登录,以简化和更安全地连接到远程主机。 要连接到远程 sftp 服务器,如下建立一个安全 SSH 连接并创建 SFTP 会话: $ sftp root@server 登录到远程主机后,你可以如下运行交互式的 sFTP 命令: sftp> ls #列出服务器文件列表 sftp> lls #列出本地文件列表 sftp> pwd #当前服务器上路径 sftp> lpwd #当前本地路径 sftp> cd img #切换服务器路径 sftp> lcd img #切换本地路径 sftp> mkdir img #在服务器上创建一个目录 sftp> lmkdir img #在本地创建一个目录 上传文件sftp> put readme.md #上传单个文件 sftp> mput *.xls #上传多个文件 下载文件sftp> get readme.md #下载单个文件 sftp> mget *.xls #下载多个文件 上传文件夹使用put -r .但是远程服务器要提前创建一个相同名称的目录; -r 递归复制子目录和子文件 sftp> mkdir img sftp> put -r img 要保留修改时间、访问时间以及被传输的文件的模式,可使用 -p 。 sftp> put -pr img 下载文件夹sftp> get -r img 退出sftp> bye 或 sftp> exit 或 ctrl + d","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"AngularJs快速入门","slug":"AngularJs快速入门","date":"2017-03-04T00:40:58.000Z","updated":"2024-07-05T01:50:55.080Z","comments":true,"path":"2017/03/04/AngularJs/","permalink":"http://yelog.org/2017/03/04/AngularJs/","excerpt":"","text":"简介 AngularJS是一个JavaScript框架,为了克服HTML在构建应用上的不足而设计的。 AngularJS通过使用我们称为标识符(directives)的结构,让浏览器能够识别新的语法。 AngularJS 使得开发现代的单一页面应用程序(SPAs:Single Page Applications)变得更加容易。 表达式AngularJS 使用 表达式 把数据绑定到 HTML。 表达式AngularJS 表达式写在双大括号内:{{ expression }} 。AngularJS 表达式把数据绑定到 HTML,这与 ng-bind 指令有异曲同工之妙。AngularJS 将在表达式书写的位置”输出”数据。AngularJS 表达式 很像 JavaScript 表达式:它们可以包含文字、运算符和变量。实例: {{ 5 + 5 }} 或 {{ firstName + \" \" + lastName }} <div ng-app=""> <p>我的第一个表达式: {{ 5 + 5 }}</p> </div> 效果 数字<div ng-app="" ng-init="quantity=1;cost=5"> <p>总价: {{ quantity * cost }}</p> </div>","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"AngularJs","slug":"AngularJs","permalink":"http://yelog.org/tags/AngularJs/"}]},{"title":"3-hexo多作者模式","slug":"3-hexo-multiple-author","date":"2017-02-28T02:55:31.000Z","updated":"2024-07-05T01:50:55.288Z","comments":true,"path":"2017/02/28/3-hexo-multiple-author/","permalink":"http://yelog.org/2017/02/28/3-hexo-multiple-author/","excerpt":"","text":"尽管hexo是为个人blog而生的工具,但是有时也可能会有多作者需求,比如他人投稿等等,为此笔者在写3-hexo主题时,顺便添加了此功能 。 1.修改配置文件修改 3-hexo/_config.yml,开启多作者模式,并添加blog中出现的作者,为搜索提供数据 author: on: true #true:开启多作者模式 authors: author1: yelog #添加两个作者yelog、小马哥 author2: 小马哥 2.修改文章头部信息添加 author: yelog ,表示这篇文章的作者为yelog```xmltitle: reading-listdate: 2017-01-31 15:29:32author: yelogtop: 2categories:- 读书tags:- reading**效果:** ![](http://img.saodiyang.com/Fjq0M7pBzl6fsnC3ivpqMsdLdXc0.png) ## 搜索某个作者的所有文章 在搜索栏中输入`@小马哥`就可以显示出所有小马哥的文章。 如果你在_config.xml中配置了作者名,就可以出现`提示`,具体看第一部分 **效果如下:** ![](http://img.saodiyang.com/Fm2PK5W9Rd6ojYq055zZoWcbioAn.gif)","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"Hexo创建404页面","slug":"hexo-create-404-page","date":"2017-02-25T07:18:39.000Z","updated":"2024-07-05T01:50:55.261Z","comments":true,"path":"2017/02/25/hexo-create-404-page/","permalink":"http://yelog.org/2017/02/25/hexo-create-404-page/","excerpt":"","text":"对于github page来说,只要在根目录又404.html,当页面找不到时,就会被转发到/404.html页面,所以我们只要更改这个页面,就可以实现自定义404页面了。 但是我们通常会需要与本主题相符的404页面。那我们就需要以下操作 新建404页面 进入 Hexo 所在文件夹,输入 hexo new page 404 ; 打开刚新建的页面文件,默认在 Hexo 文件夹根目录下 /source/404/index.md; 在顶部插入一行,写上 enlink: /404,这表示指定该页固定链接为 http://"主页"/404.html --- title: 404 enlink: /404 date: 2016-09-27 11:31:01 --- --- ## 页面未找到! 效果 http://yelog.org/举个404例子","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"Hexo置顶及排序问题","slug":"hexo-top-sort","date":"2017-02-24T07:50:38.000Z","updated":"2024-07-05T01:50:55.270Z","comments":true,"path":"2017/02/24/hexo-top-sort/","permalink":"http://yelog.org/2017/02/24/hexo-top-sort/","excerpt":"","text":"近期在写3-hexo主题时,发现文章(site.posts)排序按照.md文件的创建时间排序,而没有按照文章中的date排序。 这就导致了一个问题,我重装了一次电脑,.md文件通过git备份了,还原回来的时候,md的创建时间都是一样的,所以文章列表就按照文章标题排序了 随后就想起了以前使用yilia主题时,设置过置顶文章。所以做了排序,顺便做了置顶的功能。 @牵猪的松鼠根据这篇文章写了一个npm插件 hexo-generator-topindex安装插件命令: npm install hexo-generator-topindex --save如果安装插件,可跳过第一部分 #修改hexo的js代码,直接看第二部分 #设置置顶 修改hexo的js代码直接上操作,修改node_modules/hexo-generator-index/lib/generator.js 'use strict'; var pagination = require('hexo-pagination'); module.exports = function(locals){ var config = this.config; var posts = locals.posts; posts.data = posts.data.sort(function(a, b) { if(a.top && b.top) { // 两篇文章top都有定义 if(a.top == b.top) return b.date - a.date; // 若top值一样则按照文章日期降序排 else return b.top - a.top; // 否则按照top值降序排 } else if(a.top && !b.top) { // 以下是只有一篇文章top有定义,那么将有top的排在前面(这里用异或操作居然不行233) return -1; } else if(!a.top && b.top) { return 1; } else return b.date - a.date; // 都没定义按照文章日期降序排 }); var paginationDir = config.pagination_dir || 'page'; return pagination('', posts, { perPage: config.index_generator.per_page, layout: ['index', 'archive'], format: paginationDir + '/%d/', data: { __index: true } }); }; 设置置顶给需要置顶的文章加入top参数,如下```xmltitle: 每天一个linux命令date: 2017-01-23 11:41:48top: 1categories:- 运维tags:- linux命令如果存在多个置顶文章,top后的参数越大,越靠前。 ## 2020-05-20 更新 3-hexo 主题已经内置排序算法,无需上面下载插件或修改源码,可以直接使用,具体可看 {% post_link 3-hexo-instruction %} 中的排序相关内容 ## References Netcan 的 [解决Hexo置顶问题](http://www.netcan666.com/2015/11/22/%E8%A7%A3%E5%86%B3Hexo%E7%BD%AE%E9%A1%B6%E9%97%AE%E9%A2%98/)","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"[译]Java内存泄露介绍","slug":"Java内存泄露介绍","date":"2017-02-21T07:05:39.000Z","updated":"2024-07-05T01:50:55.412Z","comments":true,"path":"2017/02/21/the-introduction-of-memory-leak-what-why-and-how/","permalink":"http://yelog.org/2017/02/21/the-introduction-of-memory-leak-what-why-and-how/","excerpt":"","text":"内存管理是Java最大的优势之一;你可以很简单的创建一个对象,内存的分配和释放则交给Java垃圾收集器处理;然而实际情况并非如此简单,因为在Java应用程序中会频繁的发生内存泄露。 这个教程将会说明内存泄露是什么?它为什么会发生?我们如何防止它? 内存泄露是什么内存泄露的定义:对象不再被应用程序使用,但是由于它们还在被引用,垃圾收集器不能清除掉它们。 为了理解这个定义,我们需要理解对象在内存中的状态;下面的图表说明什么是未被使用和未被引用。 图表中,有被引用的对象和未被引用的对象;未被引用的对象将会被当做垃圾回收,而被引用的对象将不会被当做垃圾回收;未被引用的对象由于没有被其他对象引用,它当然也是不被使用的对象,然而,不被使用的对象不全是不被引用的,它们中的一些是被引用的!这就是内存泄露的来源。 内存泄露为什么会发生让我们来看一下下面这个例子,它说明了内存泄露为什么会发生。在下面这个列子中,对象A引用了对象B,A的生命周期(t1t4)是比B(t2t3)的长;当B不再被应用程序使用时,A仍然在引用它;在这种情况下,垃圾收集器不能从内存中移除B;如果A引用了很多类似B这样的对象,它们不能被回收,又消耗着内存空间的资源,这样很有可能造成内存不足的问题。 还有一种可能的事情,B又引用了一些对象,这些被B引用的对象也不能被回收,那所有这些不被使用的对象将消耗大量宝贵的内存空间。 如何防止内存泄露下面有一些防止内存泄露的快速实践技巧 注意集合类,如:HashMap、ArrayList等等,因为它们是在常见的地方发生内存泄露;当它们被static声明时,它们和应用程序的生命周期是一样长的。 注意事件监听和回调,当一个监听事件被注册,而这个类再也没有被使用时可能会发生内存泄露。 “如果一个类管理自己的内存,程序员应该被提醒内存泄漏了”,通常,一个对象的指向其他对象的成员变量需要被置为null。 References:[1] Program Creek :The Introduction of Java Memory Leaks","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"translation","slug":"translation","permalink":"http://yelog.org/tags/translation/"}]},{"title":"每天一个linux命令(56): tailf","slug":"linux-command-56-tailf","date":"2017-02-20T07:11:06.000Z","updated":"2024-07-05T01:50:55.596Z","comments":true,"path":"2017/02/20/linux-command-56-tailf/","permalink":"http://yelog.org/2017/02/20/linux-command-56-tailf/","excerpt":"tailf 一个实时监听文件或日志的强大的命令","text":"tailf 一个实时监听文件或日志的强大的命令 命令格式$ tailf [option] file 命令描述 tailf 将会打印出一个文件的最后10行,等待并持续输出此文件的增长,它和tail -f相似,不同之处是当文件没有增长时,是不访问此文件的;但这会有一个副作用:不会更新文件的访问时间。当没有发生日志活动时,文件系统的冲洗(flush)不会定期发生。 tailf 对于打印日志不频繁,而又在使用笔记本电脑时是非常有用的,这样用户就能降低磁盘转速从而增加笔记本续航。 命令参数 参数 描述 -n,–lines=N,-N 输出最后N行,而不是默认的最后10行 命令实例例一:展示一个文件的最后5行并监听文件的新行(新增加的内容) $ tailf -n 5 myfile.txt $ tailf -5 myfile.txt $ tailf --lines=5 myfile.txt 注:这是一个实时监听文件或日志的强大的命令 例二:实时新增日志内容,并通过管道过滤出自己想要的内容 # 实时监听ip地址为24.10.160.10的访问日志 $ tailf access.log | grep 24.10.160.10","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"pjax用法","slug":"pjax用法","date":"2017-02-08T12:18:54.000Z","updated":"2024-07-05T01:50:55.106Z","comments":true,"path":"2017/02/08/pjax/","permalink":"http://yelog.org/2017/02/08/pjax/","excerpt":"最近在开发一款hexo主题3-hexo,其中使用了pjax大大提高了用户体验和加载速度,在此简单介绍一下pjax的用法github链接","text":"最近在开发一款hexo主题3-hexo,其中使用了pjax大大提高了用户体验和加载速度,在此简单介绍一下pjax的用法github链接 pjax是什么 pjax是一款jQuery插件,使用了ajax和pushState的技术,在保留真正永久链接,网页标题和可用的返回功能的情况下,带来一种快速的浏览体验。 –官方介绍 用人话说,就是当跳转过去的网页和当前网页的一部分是一样的,这时可以通过pjax就会从响应页面中取出 不同的那部分 (需指定),替换掉原来的内容。 如果在服务端判断处理,直接返回 不同的那部分内容,这样就可以减少带宽占用,提升加载速度。 这样做的优势: 由于从服务器取回的数据量变少,加载速度将会提升。 并且采用异步刷新页面中的不一样的地方,用户体验也是满满的。 保留了浏览器回退的功能(解决了ajax的尴尬) 好了,开始操作。 Demo第一步:引入jQuery和jQuery.pjax <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery.pjax/1.9.6/jquery.pjax.min.js"></script> 第二步:将指定的a的链接,转为pjax风格 /*将#menu中的a的链接的页面,只取回class=pjax元素中的内容,替换掉当前页面class=pjax元素中的内容*/ $(document).pjax('.#menu a', '.pjax', {fragment:'.pjax', timeout:8000}); 第三步:如果需要在请求的过程中做一些自定义的事件,可以使用下面的方法 $(document).on({ 'pjax:click': function() { //点击链接时,需要触发的事件写到这里 }, 'pjax:start': function() { //当开始获取请求时,需要触发的事件写在这里 }, 'pjax:end': function() { //当请求完成后,需要触发的事件写在这里 } }); 结束。 详细文档翻译于官方 参数$(document).pjax(selector, [container], options) selector 触发点击事件的选择器,String类型 container 一个选择器,为唯一的pjax容器 options 一个可以包含下面这些选项的对象 pjax options key default description timeout 650 ajax超时时间,单位毫秒,超时后将请求整个页面进行刷新 push true 使用 pushState 添加一个浏览器历史导航条目 replace false 替换URL,而不添加浏览器历史条目 maxCacheLength 20 历史内容 cache 的最大size version string : 当前pjax版本 scrollTo 0 垂直位置滚动,为了避免改变滚动条位置 type “GET” 可以查看jQuery.ajax() dataType “html” 可以查看jQuery.ajax() container css选择器,此元素内容将被替换 url link.href string: ajax 请求的URL target link eventually the relatedTarget value for pjax events fragment 从ajax响应的页面中抽取的‘片段’ Events除了pjax:click和pjax:clicked外的所有pjax事件从pjax容器中触发,是不需要点击链接的。所有事件的生命周期在通过pjax请求链接的过程中 event cancel arguments notes pjax:click ✔︎ options 在一个链接被激活(点击)时触发此事件,可以在此取消阻止pjax pjax:beforeSend ✔︎ xhr, options 可以设置 XHR 头 pjax:start xhr, options pjax:send xhr, options pjax:clicked options 当链接被点击,并且已经开始pjax请求后触发 pjax:beforeReplace contents, options 从服务器已经加载到HTML内容,在替换HTML内容之前触发 pjax:success data, status, xhr, options 从服务器已经加载到HTML内容,在替换HTML内容之后触发 pjax:timeout ✔︎ xhr, options 页面将会在options.timeout之后直接发起请求刷新页面,除非取消pjax pjax:error ✔︎ xhr, textStatus, error, options ajax 错误,将会请求刷新页面,除非取消pjax pjax:complete xhr, textStatus, options 不管结果是什么,在ajax后,都触发 pjax:end xhr, options 生命周期在浏览器返回或前进时触发 event cancel arguments notes pjax:popstate 事件方向(前进,后退)属性: “back”/“forward” pjax:start null, options 替换内容前 pjax:beforeReplace contents, options 从cache中读取内容后,替换html前 pjax:end null, options 替换内容后","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"jQuery","slug":"jQuery","permalink":"http://yelog.org/tags/jQuery/"}]},{"title":"reading-list","slug":"reading-list","date":"2017-01-31T07:29:32.000Z","updated":"2024-07-05T01:50:54.498Z","comments":true,"path":"2017/01/31/reading-list/","permalink":"http://yelog.org/2017/01/31/reading-list/","excerpt":"下面是一些我读过的书","text":"下面是一些我读过的书 ★ ★ ★ ★ ☆ ☆ ☆ :推荐指数,7星制。此乃余之私见,或显偏薄。 文学小说 《棋王 树王 孩子王》 by 阿城 2015年9月 ★ ★ ★ ★ ☆ ☆ ☆ 《达芬奇密码》 by 丹.布朗 2015年9月 ★ ★ ★ ★ ★ ★ ☆ 《追风筝的人》 by 卡勒德·胡赛尼 2016年6月 ★ ★ ★ ★ ★ ☆ ☆ 《霍乱时期的爱情》 by 加西亚·马尔克斯 2016年8月 ★ ★ ★ ★ ★ ☆ ☆ 《查令十字街84号》 by 海莲·汉芙 2016年9月 ★ ★ ★ ★ ☆ ☆ ☆ 《围城》 by 钱钟书 2017年1月 ★ ★ ★ ★ ★ ☆ ☆ 《一个陌生女人的来信》 by 茨威格 2017年1月 ★ ★ ★ ★ ★ ★ ☆ 《一颗心的沦亡》 by 茨威格 2017年1月 ★ ★ ★ ★ ☆ ☆ ☆ 《情感的迷茫》 by 茨威格 2017年1月 ★ ★ ★ ★ ★ ☆ ☆ 《一个女人一生中的二十四个小时》 by 茨威格 2017年1月 ★ ★ ★ ★ ☆ ☆ ☆ 《摆渡人》 by 克莱尔·麦克福尔 2019年3月28 ★ ☆ ☆ ☆ ☆ ☆ ☆ 《悟空传》 by 今何在 2016年8月 ★ ★ ★ ★ ★ ☆ ☆ 《岛上书店》 by 加·泽文 2019年4月 ★ ★ ★ ★ ☆ ☆ ☆ 《月亮与六便士》 by 毛姆 2019年4月 ★ ★ ★ ★ ★ ☆ ☆ 《活着》 by 余华 2019年5月★ ★ ★ ★ ★ ★ ☆ 《白夜行》 by 东野圭吾 2019年12月 ★ ★ ★ ★ ★ ☆ ☆ 旅行 《不去会死》 by 石田裕辅 2017年2月 ★ ★ ★ ☆ ☆ ☆ ☆ 历史 《秦迷·秦始皇的秘密》 by 李开元 2016年3月 ★ ★ ★ ★ ☆ ☆ ☆ 《鱼羊野史·第1卷》 by 高晓松 2016年9月 ★ ★ ★ ★ ★ ☆ ☆ 心理学 《天才在左,疯子在右》 by 高铭 2016年3月 ★ ★ ★ ★ ★ ★ ☆ 经济 《历代经济变革得失》 by 吴晓波 2017年2月 ★ ★ ★ ★ ★ ★ ☆ 科幻 《球状闪电》 by 刘慈欣 2016年3月 ★ ★ ★ ★ ☆ ☆ ☆ 《三体·“地球往事”三部曲之一》 by 刘慈欣 2016年4月 ★ ★ ★ ★ ★ ☆ ☆ 《流浪地球》 by 刘慈欣 2019年3月 ★ ★ ★ ★ ☆ ☆ ☆ 创业 《从零到一》 by 彼得·蒂尔 2015年6月 ★ ★ ★ ★ ☆ ☆ ☆ 《创业维艰》 by 本·霍洛维茨 2015年6月 ★ ★ ★ ★ ★ ☆ ☆ 《餐巾纸上的创业课》 by 神田昌典 2016年6月 ★ ★ ★ ★ ☆ ☆ ☆ 方法论 《如何高效学习》 by 斯科特·扬 2017年1月 ★ ★ ★ ★ ★ ☆ ☆ 《DISCover自我探索》 by 李海峰 2017年1月 ★ ★ ★ ★ ★ ★ ☆ 计算机 《淘宝技术这十年》 by 子柳 2015年6月 ★ ★ ★ ★ ★ ☆ ☆ 《写给大家看的设计书》 by Robin Williams 2023年6月 ★ ★ ★ ★ ★ ☆ ☆ 《黑客与画家》 by 保罗·格雷姆 2023年10月 ★ ★ ★ ★ ★ ☆ ☆ 待读/在读 《基督山伯爵》 by 大仲马 《程序员修炼之道》 by Andrew Hunt & David Thomas","categories":[{"name":"读书","slug":"读书","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/"},{"name":"书单","slug":"读书/书单","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/%E4%B9%A6%E5%8D%95/"}],"tags":[{"name":"reading","slug":"reading","permalink":"http://yelog.org/tags/reading/"}]},{"title":"每天一个linux命令","slug":"linux-command-list","date":"2017-01-23T03:41:48.000Z","updated":"2024-07-05T01:50:55.463Z","comments":true,"path":"2017/01/23/linux-command/","permalink":"http://yelog.org/2017/01/23/linux-command/","excerpt":"开始详细的系统的学习linux命令,坚持每天一个命令。","text":"开始详细的系统的学习linux命令,坚持每天一个命令。 此系列最初参考peida的“ 每天一个linux命令”,之后根据自己的见闻逐渐添加整理。 文件目录操作命令 每天一个linux命令(1): ls 每天一个linux命令(2): cd 每天一个linux命令(3): pwd 每天一个linux命令(4): mkdir 每天一个linux命令(5): rm 每天一个linux命令(6): rmdir 每天一个linux命令(7): mv 每天一个linux命令(8): cp 每天一个linux命令(9): touch 每天一个linux命令(10): cat 每天一个linux命令(11): nl 每天一个linux命令(12): more 每天一个linux命令(13): less 每天一个linux命令(14): head 每天一个linux命令(15): tail 每天一个linux命令(56): tailf 文件查找命令 每天一个linux命令(16): which 每天一个linux命令(17): whereis 每天一个linux命令(18): locate 每天一个linux命令(19): find命令概览 每天一个linux命令(20): find命令之exec 每天一个linux命令(21): find命令之xargs 每天一个linux命令(22): find命令的参数详解 文件打包上传和下载 每天一个linux命令(23): 用SecureCRT来上传和下载文件 每天一个linux命令(24): tar 每天一个linux命令(25): gzip linux文件权限设置 每天一个linux命令(26): chmod 每天一个linux命令(27): chgrp 每天一个linux命令(28): chown 每天一个linux命令(29): /etc/group文件详解 磁盘存储相关 每天一个linux命令(30): df 每天一个linux命令(31): du 性能监控和优化命令 每天一个linux命令(32): top 每天一个linux命令(33): free 每天一个linux命令(34): vmstat 每天一个linux命令(35): iostat 每天一个linux命令(36): lsof 网络命令 每天一个linux命令(37): ifconfig 每天一个linux命令(38): route 每天一个linux命令(39): ping 每天一个linux命令(40): traceroute 每天一个linux命令(41): netstat 每天一个linux命令(42): ss 每天一个linux命令(43): telnet 每天一个linux命令(44): rcp 每天一个linux命令(45): scp 其他命令 每天一个linux命令(46): ln 每天一个linux命令(47): diff 每天一个linux命令(48): date 每天一个linux命令(49): cal 每天一个linux命令(50): grep 每天一个linux命令(51): wc 每天一个linux命令(52): ps 每天一个linux命令(53): watch 每天一个linux命令(54): at 每天一个linux命令(55): crontab 每天一个linux命令(56): tailf 每天一个linux命令(57): sftp 每天一个linux命令(58): sort","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(55): crontab","slug":"linux-command-55-crontab","date":"2017-01-23T02:44:50.000Z","updated":"2024-07-05T01:50:55.537Z","comments":true,"path":"2017/01/23/linux-command-55-crontab/","permalink":"http://yelog.org/2017/01/23/linux-command-55-crontab/","excerpt":"前一天学习了 at 命令是针对仅运行一次的任务,循环运行的例行性计划任务,linux系统则是由 cron (crond) 这个系统服务来控制的。Linux 系统上面原本就有非常多的计划性工作,因此这个系统服务是默认启动的。另外, 由于使用者自己也可以设置计划任务,所以, Linux 系统也提供了使用者控制计划任务的命令 :crontab 命令。","text":"前一天学习了 at 命令是针对仅运行一次的任务,循环运行的例行性计划任务,linux系统则是由 cron (crond) 这个系统服务来控制的。Linux 系统上面原本就有非常多的计划性工作,因此这个系统服务是默认启动的。另外, 由于使用者自己也可以设置计划任务,所以, Linux 系统也提供了使用者控制计划任务的命令 :crontab 命令。 crond简介 crond是linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程,与windows下的计划任务类似,当安装完成操作系统后,默认会安装此服务工具,并且会自动启动crond进程,crond进程每分钟会定期检查是否有要执行的任务,如果有要执行的任务,则自动执行该任务。 Linux下的任务调度分为两类,系统任务调度和用户任务调度。 系统任务调度:系统周期性所要执行的工作,比如写缓存数据到硬盘、日志清理等。在/etc目录下有一个crontab文件,这个就是系统任务调度的配置文件。 /etc/crontab文件包括下面几行: # /etc/crontab: system-wide crontab # Unlike any other crontab you don't have to run the `crontab' # command to install the new version when you edit this file # and files in /etc/cron.d. These files also have username fields, # that none of the other crontabs do. SHELL=/bin/sh PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin # m h dom mon dow user command 17 * * * * root cd / && run-parts --report /etc/cron.hourly 25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily ) 47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly ) 52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly ) 前四行是用来配置crond任务运行的环境变量,第一行SHELL变量指定了系统要使用哪个shell,这里是bash,第二行PATH变量指定了系统执行命令的路径,第三行MAILTO变量指定了crond的任务执行信息将通过电子邮件发送给root用户,如果MAILTO变量的值为空,则表示不发送任务执行信息给用户,第四行的HOME变量指定了在执行命令或者脚本时使用的主目录。第六至九行表示的含义将在下个小节详细讲述。这里不在多说。 用户任务调度:用户定期要执行的工作,比如用户数据备份、定时邮件提醒等。用户可以使用 crontab 工具来定制自己的计划任务。所有用户定义的crontab 文件都被保存在 /var/spool/cron目录中。其文件名与用户名一致。 使用者权限文件文件:/etc/cron.deny说明:该文件中所列用户不允许使用crontab命令 文件:/etc/cron.allow说明:该文件中所列用户允许使用crontab命令 文件:/var/spool/cron/说明:所有用户crontab文件存放的目录,以用户名命名 crontab文件的含义 用户所建立的crontab文件中,每一行都代表一项任务,每行的每个字段代表一项设置,它的格式共分为六个字段,前五段是时间设定段,第六段是要执行的命令段,格式如下:minute hour day month week command其中: minute: 表示分钟,可以是从0到59之间的任何整数。 hour:表示小时,可以是从0到23之间的任何整数。 day:表示日期,可以是从1到31之间的任何整数。 month:表示月份,可以是从1到12之间的任何整数。 week:表示星期几,可以是从0到7之间的任何整数,这里的0或7代表星期日。 command:要执行的命令,可以是系统命令,也可以是自己编写的脚本文件。 在以上各个字段中,还可以使用以下特殊字符:星号(*):代表所有可能的值,例如month字段如果是星号,则表示在满足其它字段的制约条件后每月都执行该命令操作。逗号(,):可以用逗号隔开的值指定一个列表范围,例如,“1,2,5,7,8,9”中杠(-):可以用整数之间的中杠表示一个整数范围,例如“2-6”表示“2,3,4,5,6”正斜线(/):可以用正斜线指定时间的间隔频率,例如“0-23/2”表示每两小时执行一次。同时正斜线可以和星号一起使用,例如*/10,如果用在minute字段,表示每十分钟执行一次。 crond服务安装crontab: $ yum install crontabs 服务操作说明: $ /sbin/service crond start //启动服务 $ /sbin/service crond stop //关闭服务 $ /sbin/service crond restart //重启服务 $ /sbin/service crond reload //重新载入配置 查看crontab服务状态: $ service crond status 手动启动crontab服务: $ service crond start 查看crontab服务是否已设置为开机启动,执行命令: $ ntsysv 加入开机自动启动: $ chkconfig –level 35 crond on contab 命令详解命令格式$ crontab [-u user] file $ crontab [-u user] [ -e | -l | -r ] 命令功能 通过crontab 命令,我们可以在固定的间隔时间执行指定的系统指令或 shell script脚本。时间间隔的单位可以是分钟、小时、日、月、周及以上的任意组合。这个命令非常设合周期性的日志分析或数据备份等工作。 命令参数 参数 描述 -u user 用来设定某个用户的crontab服务,例如,“-u ixdba”表示设定ixdba用户的crontab服务,此参数一般有root用户来运行 file file是命令文件的名字,表示将file做为crontab的任务列表文件并载入crontab。如果在命令行中没有指定这个文件,crontab命令将接受标准输入(键盘)上键入的命令,并将它们载入crontab。 -e 编辑某个用户的crontab文件内容。如果不指定用户,则表示编辑当前用户的crontab文件 -l 显示某个用户的crontab文件内容,如果不指定用户,则表示显示当前用户的crontab文件内容 -r 从/var/spool/cron目录中删除某个用户的crontab文件,如果不指定用户,则默认删除当前用户的crontab文件 -i 在删除用户的crontab文件时给确认提示 常用方法例一:创建一个新的crontab文件在考虑向cron进程提交一个crontab文件之前,首先要做的一件事情就是设置环境变量EDITOR。cron进程根据它来确定使用哪个编辑器编辑crontab文件。9 9 %的UNIX和LINUX用户都使用vi,如果你也是这样,那么你就编辑$ HOME目录下的. profile文件,在其中加入这样一行:EDITOR=vi; export EDITOR然后保存并退出。不妨创建一个名为 cron的文件,其中是用户名,例如, davecron。在该文件中加入如下的内容。 # (put your own initials here)echo the date to the console every # 15minutes between 6pm and 6am 0,15,30,45 18-06 * * * /bin/echo ‘date’ > /dev/console 保存并退出。确信前面5个域用空格分隔。在上面的例子中,系统将每隔1 5分钟向控制台输出一次当前时间。如果系统崩溃或挂起,从最后所显示的时间就可以一眼看出系统是什么时间停止工作的。在有些系统中,用tty1来表示控制台,可以根据实际情况对上面的例子进行相应的修改。为了提交你刚刚创建的crontab文件,可以把这个新创建的文件作为cron命令的参数: $ crontab davecron现在该文件已经提交给cron进程,它将每隔1 5分钟运行一次。同时,新创建文件的一个副本已经被放在/var/spool/cron目录中,文件名就是用户名(即dave)。例二:列出crontab文件 $ crontab -l 说明:你将会看到和上面类似的内容。可以使用这种方法在$ H O M E目录中对crontab文件做一备份: $ crontab -l > $HOME/mycron这样,一旦不小心误删了crontab文件,可以用上一节所讲述的方法迅速恢复。 例三:编辑crontab文件 $ crontab -e 说明:可以像使用v i编辑其他任何文件那样修改crontab文件并退出。如果修改了某些条目或添加了新的条目,那么在保存该文件时, c r o n会对其进行必要的完整性检查。如果其中的某个域出现了超出允许范围的值,它会提示你。我们在编辑crontab文件时,没准会加入新的条目。例如,加入下面的一条: # DT:delete core files,at 3.30am on 1,7,14,21,26,26 days of each month 30 3 1,7,14,21,26 * * /bin/find -name “core’ -exec rm {} ;现在保存并退出。最好在crontab文件的每一个条目之上加入一条注释,这样就可以知道它的功能、运行时间,更为重要的是,知道这是哪位用户的作业。现在让我们使用前面讲过的crontab -l命令列出它的全部信息: $ crontab -l # (crondave installed on Tue May 4 13:07:43 1999) # DT:ech the date to the console every 30 minites 0,15,30,45 18-06 * * * /bin/echo date > /dev/tty1 # DT:delete core files,at 3.30am on 1,7,14,21,26,26 days of each month 30 3 1,7,14,21,26 * * /bin/find -name “core’ -exec rm {} ; 例四:删除crontab文件 $ crontab -r 例五:恢复丢失的crontab文件如果不小心误删了crontab文件,假设你在自己的$ H O M E目录下还有一个备份,那么可以将其拷贝到/var/spool/cron/,其中是用户名。如果由于权限问题无法完成拷贝,可以用: $ crontab 其中,是你在$ H O M E目录中副本的文件名。我建议你在自己的$ H O M E目录中保存一个该文件的副本。我就有过类似的经历,有数次误删了crontab文件(因为r键紧挨在e键的右边)。这就是为什么有些系统文档建议不要直接编辑crontab文件,而是编辑该文件的一个副本,然后重新提交新的文件。有些crontab的变体有些怪异,所以在使用crontab命令时要格外小心。如果遗漏了任何选项,crontab可能会打开一个空文件,或者看起来像是个空文件。这时敲delete键退出,不要按,否则你将丢失crontab文件。 使用实例例一:每1分钟执行一次command * * * * * command 例二:每小时的第3和第15分钟执行 3,15 * * * * command 例三:在上午8点到11点的第3和第15分钟执行 3,15 8-11 * * * command 例四:每隔两天的上午8点到11点的第3和第15分钟执行 3,15 8-11 */2 * * command 例五:每个星期一的上午8点到11点的第3和第15分钟执行 3,15 8-11 * * 1 command 例六:每晚的21:30重启smb 30 21 * * * /etc/init.d/smb restart 例七:每月1、10、22日的4 : 45重启smb 45 4 1,10,22 * * /etc/init.d/smb restart 例八:每周六、周日的1 : 10重启smb 10 1 * * 6,0 /etc/init.d/smb restart 例九:每天18 : 00至23 : 00之间每隔30分钟重启smb 0,30 18-23 * * * /etc/init.d/smb restart 例十:每星期六的晚上11 : 00 pm重启smb 0 23 * * 6 /etc/init.d/smb restart 例十一:每一小时重启smb * */1 * * * /etc/init.d/smb restart 例十二:晚上11点到早上7点之间,每隔一小时重启smb * 23-7/1 * * * /etc/init.d/smb restart 例十三:每月的4号与每周一到周三的11点重启smb 0 11 4 * mon-wed /etc/init.d/smb restart 例十四:一月一号的4点重启smb 0 4 1 jan * /etc/init.d/smb restart 例十五:每小时执行/etc/cron.hourly目录内的脚本 01 * * * * root run-parts /etc/cron.hourly 说明:run-parts这个参数了,如果去掉这个参数的话,后面就可以写要运行的某个脚本名,而不是目录名了 注意事项注意环境变量问题 有时我们创建了一个crontab,但是这个任务却无法自动执行,而手动执行这个任务却没有问题,这种情况一般是由于在crontab文件中没有配置环境变量引起的。 在crontab文件中定义多个调度任务时,需要特别注意的一个问题就是环境变量的设置,因为我们手动执行某个任务时,是在当前shell环境下进行的,程序当然能找到环境变量,而系统自动执行任务调度时,是不会加载任何环境变量的,因此,就需要在crontab文件中指定任务运行所需的所有环境变量,这样,系统执行任务调度时就没有问题了。 不要假定cron知道所需要的特殊环境,它其实并不知道。所以你要保证在shelll脚本中提供所有必要的路径和环境变量,除了一些自动设置的全局变量。所以注意如下3点: 1)脚本中涉及文件路径时写全局路径; 2)脚本执行要用到java或其他环境变量时,通过source命令引入环境变量,如: $ cat start_cbp.sh #!/bin/sh source /etc/profile export RUN_CONF=/home/d139/conf/platform/cbp/cbp_jboss.conf /usr/local/jboss-4.0.5/bin/run.sh -c mev & 3)当手动执行脚本OK,但是crontab死活不执行时。这时必须大胆怀疑是环境变量惹的祸,并可以尝试在crontab中直接引入环境变量解决问题。如: 0 * * * * . /etc/profile;/bin/sh /var/www/java/audit_no_count/bin/restart_audit.sh 注意清理系统用户的邮件日志 每条任务调度执行完毕,系统都会将任务输出信息通过电子邮件的形式发送给当前系统用户,这样日积月累,日志信息会非常大,可能会影响系统的正常运行,因此,将每条任务进行重定向处理非常重要。 例如,可以在crontab文件中设置如下形式,忽略日志输出: 0 */3 * * * /usr/local/apache2/apachectl restart >/dev/null 2>&1 “/dev/null 2>&1”表示先将标准输出重定向到/dev/null,然后将标准错误重定向到标准输出,由于标准输出已经重定向到了/dev/null,因此标准错误也会重定向到/dev/null,这样日志输出问题就解决了。 系统级任务调度与用户级任务调度 系统级任务调度主要完成系统的一些维护操作,用户级任务调度主要完成用户自定义的一些任务,可以将用户级任务调度放到系统级任务调度来完成(不建议这么做),但是反过来却不行,root用户的任务调度操作可以通过“crontab –uroot –e”来设置,也可以将调度任务直接写入/etc/crontab文件,需要注意的是,如果要定义一个定时重启系统的任务,就必须将任务放到/etc/crontab文件,即使在root用户下创建一个定时重启系统的任务也是无效的。 其他注意事项 新创建的cron job,不会马上执行,至少要过2分钟才执行。如果重启cron则马上执行。 当crontab突然失效时,可以尝试/etc/init.d/crond restart解决问题。或者查看日志看某个job有没有执行/报错tail -f /var/log/cron。 千万别乱运行crontab -r。它从Crontab目录(/var/spool/cron)中删除用户的Crontab文件。删除了该用户的所有crontab都没了。 在crontab中%是有特殊含义的,表示换行的意思。如果要用的话必须进行转义%,如经常用的date ‘+%Y%m%d’在crontab里是不会执行的,应该换成date ‘+%Y%m%d’。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(54): at","slug":"linux-command-54-at","date":"2017-01-22T02:23:44.000Z","updated":"2024-07-05T01:50:55.476Z","comments":true,"path":"2017/01/22/linux-command-54-at/","permalink":"http://yelog.org/2017/01/22/linux-command-54-at/","excerpt":"在windows系统中,windows提供了计划任务这一功能,在控制面板 -> 性能与维护 -> 任务计划, 它的功能就是安排自动运行的任务。 通过’添加任务计划’的一步步引导,则可建立一个定时执行的任务。","text":"在windows系统中,windows提供了计划任务这一功能,在控制面板 -> 性能与维护 -> 任务计划, 它的功能就是安排自动运行的任务。 通过’添加任务计划’的一步步引导,则可建立一个定时执行的任务。 在linux系统中你可能已经发现了为什么系统常常会自动的进行一些任务?这些任务到底是谁在支配他们工作的?在linux系统如果你想要让自己设计的备份程序可以自动在某个时间点开始在系统底下运行,而不需要手动来启动它,又该如何处置呢? 这些例行的工作可能又分为一次性定时工作与循环定时工作,在系统内又是哪些服务在负责? 还有,如果你想要每年在老婆的生日前一天就发出一封信件提醒自己不要忘记,linux系统下该怎么做呢? 今天我们主要学习一下一次性定时计划任务的at命令的用法! 命令格式$ at [参数] [时间] 命令功能 在一个指定的时间执行一个指定任务,只能执行一次,且需要开启atd进程(ps -ef | grep atd查看, 开启用/etc/init.d/atd start or restart; 开机即启动则需要运行 chkconfig –level 2345 atd on)。 命令参数 参数 描述 -m 当指定的任务被完成之后,将给用户发送邮件,即使没有标准输出 -I atq的别名 -d atrm的别名 -v 显示任务将被执行的时间 -c 打印任务的内容到标准输出 -V 显示版本信息 -q<列队> 使用指定的列队 -f<文件> 从指定文件读入任务而不是从标准输入读入 -t<时间参数> 以时间参数的形式提交要运行的任务at允许使用一套相当复杂的指定时间的方法。他能够接受在当天的hh:mm(小时:分钟)式的时间指定。假如该时间已过去,那么就放在第二天执行。当然也能够使用midnight(深夜),noon(中午),teatime(饮茶时间,一般是下午4点)等比较模糊的 词语来指定时间。用户还能够采用12小时计时制,即在时间后面加上AM(上午)或PM(下午)来说明是上午还是下午。 也能够指定命令执行的具体日期,指定格式为month day(月 日)或mm/dd/yy(月/日/年)或dd.mm.yy(日.月.年)。指定的日期必须跟在指定时间的后面。 上面介绍的都是绝对计时法,其实还能够使用相对计时法,这对于安排不久就要执行的命令是很有好处的。指定格式为:now + count time-units ,now就是当前时间,time-units是时间单位,这里能够是minutes(分钟)、hours(小时)、days(天)、weeks(星期)。count是时间的数量,究竟是几天,还是几小时,等等。 更有一种计时方法就是直接使用today(今天)、tomorrow(明天)来指定完成命令的时间。 TIME 时间格式,这里可以定义出什么时候要进行 at 这项任务的时间 TIME的格式: HH:MM ex> 04:00 在今日的 HH:MM 时刻进行,若该时刻已超过,则明天的 HH:MM 进行此任务。 HH:MM YYYY-MM-DDex> 04:00 2009-03-17强制规定在某年某月的某一天的特殊时刻进行该项任务 HH:MM[am|pm] [Month] [Date]ex> 04pm March 17也是一样,强制在某年某月某日的某时刻进行该项任务 HH:MM[am|pm] + number [minutes|hours|days|weeks]ex> now + 5 minutesex> 04pm + 3 days就是说,在某个时间点再加几个时间后才进行该项任务。 使用实例例一:三天后的下午 5 点锺执行 /bin/ls $ at 5pm+3 days at> /bin/ls at> <EOT> # 按一下Ctrl+d就会出现<EOT>结束符 job 2 at Thu Feb 2 17:00:00 2017 例二:明天17点钟,输出时间到指定文件内 $ at 17:20 tomorrow at> date >/root/2013.log at> <EOT> 例三:计划任务设定后,在没有执行之前我们可以用atq命令来查看系统没有执行工作任务 $ atq 2 Thu Feb 2 17:00:00 2017 a faker 例四:删除已经设置的任务 # 2 为atq查出来的最前面的任务id $ atrm 2 例五:显示已经设置的任务内容 $ at -c 2 #!/bin/sh # atrun uid=1000 gid=1000 # mail faker 0 umask 22 此处省略n个字符 /bin/ls atd 的启动与 at 运行的方式atd 的启动 要使用一次性计划任务时,我们的 Linux 系统上面必须要有负责这个计划任务的服务,那就是 atd 服务。 不过并非所有的 Linux distributions 都默认会把他打开的,所以,某些时刻我们需要手动将atd 服务激活才行。 激活的方法很简单,就是这样:命令: $ /etc/init.d/atd start $ /etc/init.d/atd restart 配置一下启动时就启动这个服务,免得每次重新启动都得再来一次 $ chkconfig atd on at 的运行方式 既然是计划任务,那么应该会有任务执行的方式,并且将这些任务排进行程表中。那么产生计划任务的方式是怎么进行的? 事实上,我们使用 at 这个命令来产生所要运行的计划任务,并将这个计划任务以文字档的方式写入 /var/spool/at/ 目录内,该工作便能等待 atd 这个服务的取用与运行了。就这么简单。 不过,并不是所有的人都可以进行 at 计划任务。为什么? 因为系统安全的原因。很多主机被所谓的攻击破解后,最常发现的就是他们的系统当中多了很多的黑客程序, 这些程序非常可能运用一些计划任务来运行或搜集你的系统运行信息,并定时的发送给黑客。 所以,除非是你认可的帐号,否则先不要让他们使用 at 命令。那怎么达到使用 at 的可控呢? 我们可以利用 /etc/at.allow 与 /etc/at.deny 这两个文件来进行 at 的使用限制。加上这两个文件后, at 的工作情况是这样的: 先找寻 /etc/at.allow 这个文件,写在这个文件中的使用者才能使用 at ,没有在这个文件中的使用者则不能使用 at (即使没有写在 at.deny 当中); 如果 /etc/at.allow 不存在,就寻找 /etc/at.deny 这个文件,若写在这个 at.deny 的使用者则不能使用 at ,而没有在这个 at.deny 文件中的使用者,就可以使用 at 命令了。 如果两个文件都不存在,那么只有 root 可以使用 at 这个命令。 透过这个说明,我们知道 /etc/at.allow 是管理较为严格的方式,而 /etc/at.deny 则较为松散 (因为帐号没有在该文件中,就能够运行 at 了)。在一般的 distributions 当中,由于假设系统上的所有用户都是可信任的, 因此系统通常会保留一个空的 /etc/at.deny 文件,意思是允许所有人使用 at 命令的意思 (您可以自行检查一下该文件)。 不过,万一你不希望有某些使用者使用 at 的话,将那个使用者的帐号写入 /etc/at.deny 即可! 一个帐号写一行。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(53): watch","slug":"linux-command-53-watch","date":"2017-01-21T02:12:30.000Z","updated":"2024-07-05T01:50:55.529Z","comments":true,"path":"2017/01/21/linux-command-53-watch/","permalink":"http://yelog.org/2017/01/21/linux-command-53-watch/","excerpt":"watch是一个非常实用的命令,基本所有的Linux发行版都带有这个小工具,如同名字一样,watch可以帮你监测一个命令的运行结果,省得你一遍遍的手动运行。在Linux下,watch是周期性的执行下个程序,并全屏显示执行结果。你可以拿他来监测你想要的一切命令的结果变化,比如 tail 一个 log 文件,ls 监测某个文件的大小变化,看你的想象力了!","text":"watch是一个非常实用的命令,基本所有的Linux发行版都带有这个小工具,如同名字一样,watch可以帮你监测一个命令的运行结果,省得你一遍遍的手动运行。在Linux下,watch是周期性的执行下个程序,并全屏显示执行结果。你可以拿他来监测你想要的一切命令的结果变化,比如 tail 一个 log 文件,ls 监测某个文件的大小变化,看你的想象力了! 命令格式$ watch[参数][命令] 命令功能 可以将命令的输出结果输出到标准输出设备,多用于周期性执行命令/定时执行命令 命令参数 参数 描述 -n或–interval watch缺省每2秒运行一下程序,可以用-n或-interval来指定间隔的时间 -d或–differences watch 会高亮显示变化的区域 -d=cumulative 会把变动过的地方(不管最近的那次有没有变动)都高亮显示出来 -t 或-no-title 会关闭watch命令在顶部的时间间隔,命令,当前时间的输出 -h, –help 查看帮助文档 使用实例例一:每隔一秒高亮显示网络链接数的变化情况 $ watch -n 1 -d netstat -ant 说明:其它操作:切换终端: Ctrl+x退出watch:Ctrl+g (deepin系统没效果,只能使用Ctrl+c退出了) 例二:每隔一秒高亮显示http链接数的变化情况 # 每隔一秒高亮显示http链接数的变化情况。 后面接的命令若带有管道符,需要加''将命令区域归整。 $ watch -n 1 -d 'pstree|grep http' 例三:实时查看模拟攻击客户机建立起来的连接数 $ watch 'netstat -an | grep:21 | \\ grep<模拟攻击客户机的IP>| wc -l' 例四:监测当前目录中 scf’ 的文件的变化 $ watch -d 'ls -l|grep scf' 例五:10秒一次输出系统的平均负载 $ watch -n 10 'cat /proc/loadavg'","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(52): ps","slug":"linux-command-52-ps","date":"2017-01-20T01:46:16.000Z","updated":"2024-07-05T01:50:55.702Z","comments":true,"path":"2017/01/20/linux-command-52-ps/","permalink":"http://yelog.org/2017/01/20/linux-command-52-ps/","excerpt":"Linux中的ps命令是Process Status的缩写。ps命令用来列出系统中当前运行的那些进程。ps命令列出的是当前那些进程的快照,就是执行ps命令的那个时刻的那些进程,如果想要动态的显示进程信息,就可以使用top命令。","text":"Linux中的ps命令是Process Status的缩写。ps命令用来列出系统中当前运行的那些进程。ps命令列出的是当前那些进程的快照,就是执行ps命令的那个时刻的那些进程,如果想要动态的显示进程信息,就可以使用top命令。 要对进程进行监测和控制,首先必须要了解当前进程的情况,也就是需要查看当前进程,而 ps 命令就是最基本同时也是非常强大的进程查看命令。使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等。总之大部分信息都是可以通过执行该命令得到的。 ps 为我们提供了进程的一次性的查看,它所提供的查看结果并不动态连续的;如果想对进程时间监控,应该用 top 工具。 kill 命令用于杀死进程。linux上进程有5种状态: 运行(正在运行或在运行队列中等待) 中断(休眠中, 受阻, 在等待某个条件的形成或接受到信号) 不可中断(收到信号不唤醒和不可运行, 进程必须等待直到有中断发生) 僵死(进程已终止, 但进程描述符存在, 直到父进程调用wait4()系统调用后释放) 停止(进程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信号后停止运行运行) ps工具标识进程的5种状态码:D 不可中断 uninterruptible sleep (usually IO)R 运行 runnable (on run queue)S 中断 sleepingT 停止 traced or stoppedZ 僵死 a defunct (”zombie”) process 命令格式$ ps [参数] 命令功能用来现实当前进程的状态 命令参数 参数 描述 a 显示所有进程 -a 显示同一终端下的所有程序 -A 显示所有进程 c 显示进程的真实名称 -N 反向选择 -e 等于“-A” e 显示环境变量 f 显示程序间的关系 -H 显示树状结构 r 显示当前终端的进程 T 显示当前终端的所有程序 u 指定用户的所有进程 -au 显示较详细的资讯 -aux 显示所有包含其他使用者的行程 -C<命令> 列出指定命令的状况 –lines<行数> 每页显示的行数 –width<字符数> 每页显示的字符数 –help 显示帮助信息 –version 显示版本显示 使用实例例一:显示所有进程信息 $ ps -A 例二:显示指定用户的进程信息 $ ps -u faker 例三:显示所有进程信息,连同命令行 $ ps -ef 例四:ps 与grep 常用组合用法,查找特定进程 $ ps -ef|grep ssh 例五:将目前属于您自己这次登入的 PID 与相关信息列示出来 $ ps -l 说明:各相关信息的意义: F 代表这个程序的旗标 (flag), 4 代表使用者为 super user S 代表这个程序的状态 (STAT),关于各 STAT 的意义将在内文介绍 UID 程序被该 UID 所拥有 PID 就是这个程序的 ID ! PPID 则是其上级父程序的ID C CPU 使用的资源百分比 PRI 这个是 Priority (优先执行序) 的缩写,详细后面介绍 NI 这个是 Nice 值,在下一小节我们会持续介绍 ADDR 这个是 kernel function,指出该程序在内存的那个部分。如果是个 running的程序,一般就是 “-“ SZ 使用掉的内存大小 WCHAN 目前这个程序是否正在运作当中,若为 - 表示正在运作 TTY 登入者的终端机位置 TIME 使用掉的 CPU 时间。 CMD 所下达的指令为何 在预设的情况下, ps 仅会列出与目前所在的 bash shell 有关的 PID 而已,所以, 当我使用 ps -l 的时候,只有三个 PID。 例六:列出目前所有的正在内存当中的程序 $ ps aux 说明: USER:该 process 属于那个使用者账号的 PID :该 process 的号码 %CPU:该 process 使用掉的 CPU 资源百分比 %MEM:该 process 所占用的物理内存百分比 VSZ :该 process 使用掉的虚拟内存量 (Kbytes) RSS :该 process 占用的固定的内存量 (Kbytes) TTY :该 process 是在那个终端机上面运作,若与终端机无关,则显示 ?,另外, tty1-tty6 是本机上面的登入者程序,若为 pts/0 等等的,则表示为由网络连接进主机的程序。 STAT:该程序目前的状态,主要的状态有 R :该程序目前正在运作,或者是可被运作 S :该程序目前正在睡眠当中 (可说是 idle 状态),但可被某些讯号 (signal) 唤醒。 T :该程序目前正在侦测或者是停止了 Z :该程序应该已经终止,但是其父程序却无法正常的终止他,造成 zombie (疆尸) 程序的状态 START:该 process 被触发启动的时间 TIME :该 process 实际使用 CPU 运作的时间 COMMAND:该程序的实际指令 例七:列出类似程序树的程序显示 $ ps -axjf 例八:找出与 cron 与 syslog 这两个服务有关的 PID 号码 $ ps aux | egrep '(cron|syslog)' 其他 # 可以用 | 管道和 more 连接起来分页查看 $ ps -aux |more # 把所有进程显示出来,并输出到ps001.txt文件 $ ps -aux > ps001.txt # 输出指定的字段 $ ps -o pid,ppid,pgrp,session,tpgid,comm","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(51): wc","slug":"linux-command-51-wc","date":"2017-01-19T01:33:59.000Z","updated":"2024-07-05T01:50:55.496Z","comments":true,"path":"2017/01/19/linux-command-51-wc/","permalink":"http://yelog.org/2017/01/19/linux-command-51-wc/","excerpt":"Linux系统中的wc(Word Count)命令的功能为统计指定文件中的字节数、字数、行数,并将统计结果显示输出。","text":"Linux系统中的wc(Word Count)命令的功能为统计指定文件中的字节数、字数、行数,并将统计结果显示输出。 命令格式$ wc [选项]文件... 命令功能 统计指定文件中的字节数、字数、行数,并将统计结果显示输出。该命令统计指定文件中的字节数、字数、行数。如果没有给出文件名,则从标准输入读取。wc同时也给出所指定文件的总统计数。 命令参数 参数 描述 -c 统计字节数 -l 统计行数 -m 统计字符数。这个标志不能与 -c 标志一起使用 -w 统计字数。一个字被定义为由空白、跳格或换行字符分隔的字符串 -L 打印最长行的长度 -help 显示帮助信息 –version 显示版本信息 使用实例例一:查看文件的字节数、字数、行数 $ wc 1.txt 5 19 105 1.txt 行数 单词数 字节数 文件名 例二:用wc命令怎么做到只打印统计数字不打印文件名 $ wc -l 1.txt 5 1.txt # 5行 $ cat 1.txt | wc -l 5 # 值输出数字 例三:用来统计当前目录下的文件和文件夹总数 # 数量中包含当前目录 $ ls -l | wc -l 10 # 7个文件 + 2个文件夹 + 1个当前目录","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(50): grep","slug":"linux-command-50-grep","date":"2017-01-18T02:12:46.000Z","updated":"2024-07-05T01:50:55.720Z","comments":true,"path":"2017/01/18/linux-command-50-grep/","permalink":"http://yelog.org/2017/01/18/linux-command-50-grep/","excerpt":"Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来。grep全称是Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户。","text":"Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来。grep全称是Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户。 grep的工作方式是这样的,它在一个或多个文件中搜索字符串模板。如果模板包括空格,则必须被引用,模板后的所有字符串被看作文件名。搜索的结果被送到标准输出,不影响原文件内容。 grep可用于shell脚本,因为grep通过返回一个状态值来说明搜索的状态,如果模板搜索成功,则返回0,如果搜索不成功,则返回1,如果搜索的文件不存在,则返回2。我们利用这些返回值就可进行一些自动化的文本处理工作。 命令格式$ grep [option] pattern file 命令功能 用于过滤/搜索的特定字符。可使用正则表达式能多种命令配合使用,使用上十分灵活。 命令参数 参数 描述 -a –text 不要忽略二进制的数据 -A<显示行数> –after-context=<显示行数> 除了显示符合范本样式的那一列之外,并显示该行之后的内容 -b –byte-offset 在显示符合样式的那一行之前,标示出该行第一个字符的编号 -B<显示行数> –before-context=<显示行数> 除了显示符合样式的那一行之外,并显示该行之前的内容 -c –count 计算符合样式的列数 -C<显示行数> –context=<显示行数>或-<显示行数> 显示上下文n行 -d <动作> –directories=<动作> 当指定要查找的是目录而非文件时,必须使用这项参数,否则grep指令将回报信息并停止动作 -e<范本样式> –regexp=<范本样式> 指定字符串做为查找文件内容的样式 -E –extended-regexp 将样式为延伸的普通表示法来使用 -f<规则文件> –file=<规则文件> 指定规则文件,其内容含有一个或多个规则样式,让grep查找符合规则条件的文件内容,格式为每行一个规则样式 -F –fixed-regexp 将样式视为固定字符串的列表 -G –basic-regexp 样式视为普通的表示法来使用 -h –no-filename 在显示符合样式的那一行之前,不标示该行所属的文件名称 -H –with-filename 在显示符合样式的那一行之前,表示该行所属的文件名称 -i –ignore-case 忽略字符的大小写 -l –file-with-matches 只列出匹配的文件名 -L –files-without-match 列出不匹配的文件名 -n –line-number 显示行号 -q –quiet或–silent 不显示任何信息 -r –recursive 递归查询, 此参数的效果和指定“-d recurse”参数相同 -s –no-messages 不显示错误信息 -v –revert-match 显示不包含匹配文本的所有行 -V –version 显示版本信息 -w –word-regexp 只显示全字符合的列 -x –line-regexp 只显示全列符合的列 -y 此参数的效果和指定“-i”参数相同 规则表达式grep的规则表达式 ^ #锚定行的开始 如:'^grep'匹配所有以grep开头的行。 $ #锚定行的结束 如:'grep$'匹配所有以grep结尾的行。 . #匹配一个非换行符的字符 如:'gr.p'匹配gr后接一个任意字符,然后是p。 * #匹配零个或多个先前字符 如:'*grep'匹配所有一个或多个空格后紧跟grep的行。 .* #一起用代表任意字符。 [] #匹配一个指定范围内的字符,如'[Gg]rep'匹配Grep和grep。 [^] #匹配一个不在指定范围内的字符,如:'[^A-FH-Z]rep'匹配不包含A-R和T-Z的一个字母开头,紧跟rep的行。 \\(..\\) #标记匹配字符,如'\\(love\\)',love被标记为1。 \\< #锚定单词的开始,如:'\\<grep'匹配包含以grep开头的单词的行。 \\> #锚定单词的结束,如'grep\\>'匹配包含以grep结尾的单词的行。 x\\{m\\} #重复字符x,m次,如:'o\\{5\\}'匹配包含5个o的行。 x\\{m,\\} #重复字符x,至少m次,如:'o\\{5,\\}'匹配至少有5个o的行。 x\\{m,n\\} #重复字符x,至少m次,不多于n次,如:'o\\{5,10\\}'匹配5–10个o的行。 \\w #匹配文字和数字字符,也就是[A-Za-z0-9],如:'G\\w*p'匹配以G后跟零个或多个文字或数字字符,然后是p。 \\W #\\w的反置形式,匹配一个或多个非单词字符,如点号句号等。 \\b #单词锁定符,如: '\\bgrep\\b'只匹配grep。POSIX字符 为了在不同国家的字符编码中保持一至,POSIX(The Portable Operating System Interface)增加了特殊的字符类,如[:alnum:]是[A-Za-z0-9]的另一个写法。要把它们放到[]号内才能成为正则表达式,如[A-Za-z0-9]或[[:alnum:]]。在linux下的grep除fgrep外,都支持POSIX的字符类。 [:alnum:] #文字数字字符 [:alpha:] #文字字符 [:digit:] #数字字符 [:graph:] #非空字符(非空格、控制字符) [:lower:] #小写字符 [:cntrl:] #控制字符 [:print:] #非空字符(包括空格) [:punct:] #标点符号 [:space:] #所有空白字符(新行,空格,制表符) [:upper:] #大写字符 [:xdigit:] #十六进制数字(0-9,a-f,A-F) 使用实例例一:查找指定进程 $ ps -ef|grep hexo faker 13401 19030 0 09:51 pts/2 00:00:15 hexo faker 15465 15449 0 10:34 pts/3 00:00:00 grep hexo 说明:第一条记录是查找出的进程;第二条结果是grep进程本身,并非真正要找的进程。 例二:查找指定进程数 $ ps -ef|grep hexo -c $ ps -ef|grep -c hexo 2 例三:从2.txt中读取关键词在1.txt中进行搜索 # -n显示行号 $ cat 1.txt | grep -nf 2.txt 1:If you please draw me a sheep! 2:What! 例四:从文件中查找关键词 $ grep 'jump' 1.txt I jumped to my feet,completely thunderstruck. 例五:从多个文件中查找关键词 $ grep 'jump' 1.txt 2.txt 1.txt:I jumped to my feet,completely thunderstruck. 2.txt:I jump 说明:多文件时,输出查询到的信息内容行时,会把文件的命名在行最前面输出并且加上”:”作为标示符 例六:grep不显示本身进程 $ ps aux|grep \\[s]sh $ ps aux | grep ssh | grep -v "grep" 例七:找出以u开头的行内容 $ cat 1.txt |grep ^u If you please draw me a sheep! I jumped to my feet,completely thunderstruck. 例八:输出非u开头的行内容 $ cat 1.txt | grep ^[^I] What! Draw me a sheep! 例九:输出以!结尾的行内容 $ cat 1.txt |grep \\!$ If you please draw me a sheep! What! Draw me a sheep! 例十:显示包含sh或者at字符的内容行 $ cat 1.txt |grep -E "sh|at" If you please draw me a sheep! What! Draw me a sheep! 例十一:显示当前目录下面以.txt 结尾的文件中的所有包含每个字符串至少有7个连续小写字符的字符串的行 $ grep '[a-z]\\{7\\}' *.txt 1.txt:I jumped to my feet,completely thunderstruck. 3.txt:kdfkksjdf112123 4.txt:kisdfsf","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(49): cal","slug":"linux-command-49-cal","date":"2017-01-17T01:38:32.000Z","updated":"2024-07-05T01:50:55.615Z","comments":true,"path":"2017/01/17/linux-command-49-cal/","permalink":"http://yelog.org/2017/01/17/linux-command-49-cal/","excerpt":"cal命令可以用来显示公历(阳历)日历。公历是现在国际通用的历法,又称格列历,通称阳历。“阳历”又名“太阳历”,系以地球绕行太阳一周为一年,为西方各国所通用,故又名“西历”。","text":"cal命令可以用来显示公历(阳历)日历。公历是现在国际通用的历法,又称格列历,通称阳历。“阳历”又名“太阳历”,系以地球绕行太阳一周为一年,为西方各国所通用,故又名“西历”。 命令格式$ cal [参数][月份][年份] 命令功能用于查看日历等时间信息,如只有一个参数,则表示年份(1-9999),如有两个参数,则表示月份和年份 命令参数 参数 描述 -1 显示一个月的月历 -3 显示系统前一个月,当前月,下一个月的月历 -s 显示星期天为一个星期的第一天,默认的格式 -m 显示星期一为一个星期的第一天 -j 显示在当年中的第几天(一年日期按天算,从1月1号算起,默认显示当前月在一年中的天数) -y 显示当前年份的日历 使用实例例一:显示当前月份日历 $ cal 例二:显示指定月份的日历 $ cal 6 2016 例三:显示2016年的日历 $ cal -y 2016 $ cal 2016 例四:显示自1月1日的天数 $ cal -j 例五:星期一显示在第一列 $ cal -m","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(48): date","slug":"linux-command-48-date","date":"2017-01-16T06:39:27.000Z","updated":"2024-07-05T01:50:55.542Z","comments":true,"path":"2017/01/16/linux-command-48-date/","permalink":"http://yelog.org/2017/01/16/linux-command-48-date/","excerpt":"在linux环境中,不管是编程还是其他维护,时间是必不可少的,也经常会用到时间的运算,熟练运用date命令来表示自己想要表示的时间,肯定可以给自己的工作带来诸多方便。","text":"在linux环境中,不管是编程还是其他维护,时间是必不可少的,也经常会用到时间的运算,熟练运用date命令来表示自己想要表示的时间,肯定可以给自己的工作带来诸多方便。 命令格式$ date [参数]... [+格式] 命令功能 date 可以用来显示或设定系统的日期与时间。 命令参数命令参数 参数 描述 %H 小时(以00-23来表示) %I 小时(以01-12来表示) %K 小时(以0-23来表示) %l 小时(以0-12来表示) %M 分钟(以00-59来表示) %P AM或PM %r 时间(含时分秒,小时以12小时AM/PM来表示) %s 总秒数。起算时间为1970-01-01 00:00:00 UTC %S 秒(以本地的惯用法来表示) %T 时间(含时分秒,小时以24小时制来表示) %X 时间(以本地的惯用法来表示) %Z 市区 %a 星期的缩写 %A 星期的完整名称 %b 月份英文名的缩写 %B 月份的完整英文名称 %c 日期与时间。只输入date指令也会显示同样的结果 %d 日期(以01-31来表示) %D 日期(含年月日) %j 该年中的第几天 %m 月份(以01-12来表示) %U 该年中的周数 %w 该周的天数,0代表周日,1代表周一,异词类推 %x 日期(以本地的惯用法来表示) %y 年份(以00-99来表示) %Y 年份(以四位数来表示) %n 在显示时,插入新的一行 %t 在显示时,插入tab MM 月份(必要) DD 日期(必要) hh 小时(必要) mm 分钟(必要) ss 秒(选择性) 选择参数 参数 描述 -d<字符串> 显示字符串所指的日期与时间。字符串前后必须加上双引号 -s<字符串> 根据字符串来设置日期与时间。字符串前后必须加上双引号 -u 显示GMT –help 在线帮助 –version 显示版本信息 使用说明1.在显示方面,使用者可以设定欲显示的格式,格式设定为一个加号后接数个标记,其中可用的标记列表如下: % : 打印出 %%n : 下一行%t : 跳格%H : 小时(00..23)%I : 小时(01..12)%k : 小时(0..23)%l : 小时(1..12)%M : 分钟(00..59)%p : 显示本地 AM 或 PM%r : 直接显示时间 (12 小时制,格式为 hh:mm:ss [AP]M)%s : 从 1970 年 1 月 1 日 00:00:00 UTC 到目前为止的秒数%S : 秒(00..61)%T : 直接显示时间 (24 小时制)%X : 相当于 %H:%M:%S%Z : 显示时区 %a : 星期几 (Sun..Sat)%A : 星期几 (Sunday..Saturday)%b : 月份 (Jan..Dec)%B : 月份 (January..December)%c : 直接显示日期与时间%d : 日 (01..31)%D : 直接显示日期 (mm/dd/yy)%h : 同 %b%j : 一年中的第几天 (001..366)%m : 月份 (01..12)%U : 一年中的第几周 (00..53) (以 Sunday 为一周的第一天的情形)%w : 一周中的第几天 (0..6)%W : 一年中的第几周 (00..53) (以 Monday 为一周的第一天的情形)%x : 直接显示日期 (mm/dd/yy)%y : 年份的最后两位数字 (00.99)%Y : 完整年份 (0000..9999)2.在设定时间方面date -s //设置当前时间,只有root权限才能设置,其他只能查看。date -s 20080523 //设置成20080523,这样会把具体时间设置成空00:00:00date -s 01:01:01 //设置具体时间,不会对日期做更改date -s “01:01:01 2008-05-23″ //这样可以设置全部时间date -s “01:01:01 20080523″ //这样可以设置全部时间date -s “2008-05-23 01:01:01″ //这样可以设置全部时间date -s “20080523 01:01:01″ //这样可以设置全部时间3.加减date +%Y%m%d //显示前天年月日date +%Y%m%d –date=”+1 day” //显示前一天的日期date +%Y%m%d –date=”-1 day” //显示后一天的日期date +%Y%m%d –date=”-1 month” //显示上一月的日期date +%Y%m%d –date=”+1 month” //显示下一月的日期date +%Y%m%d –date=”-1 year” //显示前一年的日期date +%Y%m%d –date=”+1 year” //显示下一年的日期 使用实例例一:显示当前时间 $ date 2017年 01月 28日 星期六 14:51:10 CST $ date '+%c' 2017年01月28日 星期六 14时51分35秒 $ date '+%D' 01/28/17 $ date '+%x' 2017年01月28日 $ date '+%T' 14:52:02 $ date '+%X' 14时52分06秒 例二:显示日期和设定时间 $ date --date 08:42:00 例三:date -d参数使用 $ date -d "nov 22" 2012年 11月 22日 星期四 00:00:00 CST $ date -d '2 weeks' 2012年 12月 22日 星期六 08:50:21 CST $ date -d 'next monday' 2012年 12月 10日 星期一 00:00:00 CST $ date -d next-day +%Y%m%d 20121209 $ date -d tomorrow +%Y%m%d 20121209 $ date -d last-day +%Y%m%d 20121207 $ date -d yesterday +%Y%m%d 20121207 $ date -d last-month +%Y%m 201211 $ date -d next-month +%Y%m 201301 $ date -d '30 days ago' 2012年 11月 08日 星期四 08:51:37 CST $ date -d '-100 days' 2012年 08月 30日 星期四 08:52:03 CST $ date -d 'dec 14 -2 weeks' 2012年 11月 30日 星期五 00:00:00 CST $ date -d '50 days' 2013年 01月 27日 星期日 08:52:27 CST 说明: date 命令的另一个扩展是 -d 选项,该选项非常有用。使用这个功能强大的选项,通过将日期作为引号括起来的参数提供,您可以快速地查明一个特定的日期。-d 选项还可以告诉您,相对于当前日期若干天的究竟是哪一天,从现在开始的若干天或若干星期以后,或者以前(过去)。通过将这个相对偏移使用引号括起来,作为 -d 选项的参数,就可以完成这项任务。 具体说明如下: date -d “nov 22” 今年的 11 月 22 日是星期三 date -d ‘2 weeks’ 2周后的日期 date -d ‘next monday’ (下周一的日期) date -d next-day +%Y%m%d(明天的日期)或者:date -d tomorrow +%Y%m%d date -d last-day +%Y%m%d(昨天的日期) 或者:date -d yesterday +%Y%m%d date -d last-month +%Y%m(上个月是几月) date -d next-month +%Y%m(下个月是几月) 使用 ago 指令,您可以得到过去的日期: date -d ‘30 days ago’ (30天前的日期) 使用负数以得到相反的日期: date -d ‘dec 14 -2 weeks’ (相对:dec 14这个日期的两周前的日期) date -d ‘-100 days’ (100天以前的日期) date -d ‘50 days’(50天后的日期) 例四:显示月份和日数 $ date '+%B %d' 一月 28 例五:显示时间后跳行,再显示目前日期 $ date '+%T%n%D' 14:58:23 01/28/17","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(47): diff","slug":"linux-command-47-diff","date":"2017-01-15T06:08:08.000Z","updated":"2024-07-05T01:50:55.664Z","comments":true,"path":"2017/01/15/linux-command-47-diff/","permalink":"http://yelog.org/2017/01/15/linux-command-47-diff/","excerpt":"diff 命令是 linux上非常重要的工具,用于比较文件的内容,特别是比较两个版本不同的文件以找到改动的地方。diff在命令行中打印每一个行的改动。最新版本的diff还支持二进制文件。diff程序的输出被称为补丁 (patch),因为Linux系统中还有一个patch程序,可以根据diff的输出将a.c的文件内容更新为b.c。diff是svn、cvs、git等版本控制工具不可或缺的一部分。","text":"diff 命令是 linux上非常重要的工具,用于比较文件的内容,特别是比较两个版本不同的文件以找到改动的地方。diff在命令行中打印每一个行的改动。最新版本的diff还支持二进制文件。diff程序的输出被称为补丁 (patch),因为Linux系统中还有一个patch程序,可以根据diff的输出将a.c的文件内容更新为b.c。diff是svn、cvs、git等版本控制工具不可或缺的一部分。 命令格式$ diff [参数] [文件1或目录1] [文件2或目录2] 命令功能 diff命令能比较单个文件或者目录内容。如果指定比较的是文件,则只有当输入为文本文件时才有效。以逐行的方式,比较文本文件的异同处。如果指定比较的是目录的的时候,diff 命令会比较两个目录下名字相同的文本文件。列出不同的二进制文件、公共子目录和只在一个目录出现的文件。 命令参数 参数 描述 - 指定要显示多少行的文本。此参数必须与-c或-u参数一并使用 -a或–text diff预设只会逐行比较文本文件 -b或–ignore-space-change 不检查空格字符的不同 -B或–ignore-blank-lines 不检查空白行 -c 显示全部内文,并标出不同之处 -C或–context 与执行”-c”指令相同 -d或–minimal 使用不同的演算法,以较小的单位来做比较 -D或ifdef 此参数的输出格式可用于前置处理器巨集 -e或–ed 此参数的输出格式可用于ed的script文件 -f或-forward-ed 输出的格式类似ed的script文件,但按照原来文件的顺序来显示不同处 -H或–speed-large-files 比较大文件时,可加快速度 -l或–ignore-matching-lines 若两个文件在某几行有所不同,而这几行同时都包含了选项中指定的字符或字符串,则不显示这两个文件的差异 -i或–ignore-case 不检查大小写的不同 -l或–paginate 将结果交由pr程序来分页 -n或–rcs 将比较结果以RCS的格式来显示 -N或–new-file 在比较目录时,若文件A仅出现在某个目录中,预设会显示:Only in目录:文件A若使用-N参数,则diff会将文件A与一个空白的文件比较 -p 若比较的文件为C语言的程序码文件时,显示差异所在的函数名称 -P或–unidirectional-new-file 与-N类似,但只有当第二个目录包含了一个第一个目录所没有的文件时,才会将这个文件与空白的文件做比较 -q或–brief 仅显示有无差异,不显示详细的信息 -r或–recursive 比较子目录中的文件 -s或–report-identical-files 若没有发现任何差异,仍然显示信息 -S或–starting-file 在比较目录时,从指定的文件开始比较 -t或–expand-tabs 在输出时,将tab字符展开 -T或–initial-tab 在每行前面加上tab字符以便对齐 -u,-U或–unified= 以合并的方式来显示文件内容的不同 -v或–version 显示版本信息 -w或–ignore-all-space 忽略全部的空格字符 -W或–width 在使用-y参数时,指定栏宽 -x或–exclude 不比较选项中所指定的文件或目录 -X或–exclude-from 您可以将文件或目录类型存成文本文件,然后在=中指定此文本文件 -y或–side-by-side 以并列的方式显示文件的异同之处 –left-column 在使用-y参数时,若两个文件某一行内容相同,则仅在左侧的栏位显示该行内容 –suppress-common-lines 在使用-y参数时,仅显示不同之处 –help 显示帮助 使用实例例一:比较两个文件```bash$ diff 1.txt 2.txt1c1< ii iii >**说明:** 上面的“1c1”表示第一个文件和第二个文件的第1行内容有所不同; diff 的normal 显示格式有三种提示: a - add c - change d - delete **`例二`:并排格式输出** ```bash $ diff 1.txt 2.txt -y -W 50 ii | iii iii iii iiii iiii iiiii iiiii 说明: “|”表示前后2个文件内容有不同 “<”表示后面文件比前面文件少了1行内容 “>”表示后面文件比前面文件多了1行内容 例三:上下文输出格式 $ diff 1.txt 2.txt -c *** 1.txt 2017-01-28 14:24:13.744538252 +0800 --- 2.txt 2017-01-28 14:24:59.096124066 +0800 *************** *** 1,4 **** ! ii iii iiii iiiii --- 1,4 ---- ! iii iii iiii iiiii 说明: 这种方式在开头两行作了比较文件的说明,这里有三中特殊字符: “+” 比较的文件的后者比前着多一行 “-” 比较的文件的后者比前着少一行 “!” 比较的文件两者有差别的行 例四:统一格式输出 $ diff 1.txt 2.txt -u --- 1.txt 2017-01-28 14:24:13.744538252 +0800 +++ 2.txt 2017-01-28 14:24:59.096124066 +0800 @@ -1,4 +1,4 @@ -ii +iii iii iiii iiiii 例五:比较文件夹不同 $ diff test3 test6 例六:比较两个文件不同,并生产补丁 $ diff -ruN 1.txt 2.txt >patch.log 例七:打补丁 $ 1.txt patch","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(46): ln","slug":"linux-command-46-ln","date":"2017-01-14T03:00:33.000Z","updated":"2024-07-05T01:50:55.655Z","comments":true,"path":"2017/01/14/linux-command-46-ln/","permalink":"http://yelog.org/2017/01/14/linux-command-46-ln/","excerpt":"ln是linux中又一个非常重要命令,它的功能是为某一个文件在另外一个位置建立一个同步的链接.当我们需要在不同的目录,用到相同的文件时,我们不需要在每一个需要的目录下都放一个必须相同的文件,我们只要在某个固定的目录,放上该文件,然后在 其它的目录下用ln命令链接(link)它就可以,不必重复的占用磁盘空间。","text":"ln是linux中又一个非常重要命令,它的功能是为某一个文件在另外一个位置建立一个同步的链接.当我们需要在不同的目录,用到相同的文件时,我们不需要在每一个需要的目录下都放一个必须相同的文件,我们只要在某个固定的目录,放上该文件,然后在 其它的目录下用ln命令链接(link)它就可以,不必重复的占用磁盘空间。 命令格式$ ln [参数][源文件或目录][目标文件或目录] 命令功能 Linux文件系统中,有所谓的链接(link),我们可以将其视为档案的别名,而链接又可分为两种 : 硬链接(hard link)与软链接(symbolic link),硬链接的意思是一个档案可以有多个名称,而软链接的方式则是产生一个特殊的档案,该档案的内容是指向另一个档案的位置。硬链接是存在同一个文件系统中,而软链接却可以跨越不同的文件系统。软连接 1.软链接,以路径的形式存在。类似于Windows操作系统中的快捷方式 2.软链接可以 跨文件系统 ,硬链接不可以 3.软链接可以对一个不存在的文件名进行链接 4.软链接可以对目录进行链接硬链接 1.硬链接,以文件副本的形式存在。但不占用实际空间。 2.不允许给目录创建硬链接 3.硬链接只有在同一个文件系统中才能创建两点注意 第一,ln命令会保持每一处链接文件的同步性,也就是说,不论你改动了哪一处,其它的文件都会发生相同的变化; 第二,ln的链接又分软链接和硬链接两种,软链接就是ln –s 源文件 目标文件,它只会在你选定的位置上生成一个文件的镜像,不会占用磁盘空间,硬链接 ln 源文件 目标文件,没有参数-s, 它会在你选定的位置上生成一个和源文件大小相同的文件,无论是软链接还是硬链接,文件都保持同步变化。 ln指令用在链接文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录,则会把前面指定的所有文件或目录复制到该目录中。若同时指定多个文件或目录,且最后的目的地并非是一个已存在的目录,则会出现错误信息。 命令参数必要参数 参数 描述 -b 删除,覆盖以前建立的链接 -d 允许超级用户制作目录的硬链接 -f 强制执行 -i 交互模式,文件存在则提示用户是否覆盖 -n 把符号链接视为一般目录 -s 软链接(符号链接) -v 显示详细的处理过程 选择参数 参数 描述 -S “-S<字尾备份字符串> ”或 “–suffix=<字尾备份字符串>” -V “-V<备份方式>”或“–version-control=<备份方式>” –help 显示帮助信息 –version 显示版本信息 使用实例例一:给文件创建软链接 # 为2.txt文件创建软链接2,如果2.txt丢失,2将失效 $ ln -s 2.txt 2 例二:给文件创建硬链接 # 为1.txt创建硬链接1,1.txt与1的各项属性相同,删除1.txt,1仍能使用 $ ln 1.txt 1 例三:接上面两实例,链接完毕后,删除和重建链接原文件 $ ll -rw-r--r-- 2 faker faker 10 1月 22 11:28 1 -rw-r--r-- 2 faker faker 10 1月 22 11:28 1.txt lrwxrwxrwx 1 faker faker 5 1月 28 11:15 2 -> 2.txt -rwxrwxrwx 1 faker faker 14 1月 18 10:06 2.txt $ rm 1.txt 2.txt -rw-r--r-- 1 faker faker 10 1月 22 11:28 1 lrwxrwxrwx 1 faker faker 5 1月 28 11:15 2 -> 2.txt $ cat 1 sdfiskdlf $ cat 2 cat: 2: 没有那个文件或目录 说明: 1.源文件被删除后,并没有影响硬链接文件;软链接文件在centos系统下不断的闪烁,提示源文件已经不存在 2.重建源文件后,软链接不在闪烁提示,说明已经链接成功,找到了链接文件系统;重建后,硬链接文件并没有受到源文件影响,硬链接文件的内容还是保留了删除前源文件的内容,说明硬链接已经失效 例三:将文件链接为另一个目录中的相同名字 # 在ig文件夹中创建一个1.txt的链接 $ ln 1.txt ig/ $ ll ig -rw-r--r-- 2 faker faker 10 1月 28 13:40 1.txt 例五:给目录创建软连接 $ ln -s ig gi 说明: 1.目录只能创建软链接 2.目录创建链接必须用绝对路径,相对路径创建会不成功,会提示:符号连接的层数过多 这样的错误(测试并不会出现这样的问题) 3.在链接目标目录中修改文件都会在源文件目录中同步变化","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(45): scp","slug":"linux-command-45-scp","date":"2017-01-13T02:53:48.000Z","updated":"2024-07-05T01:50:55.688Z","comments":true,"path":"2017/01/13/linux-command-45-scp/","permalink":"http://yelog.org/2017/01/13/linux-command-45-scp/","excerpt":"scp是secure copy的简写,用于在Linux下进行远程拷贝文件的命令,和它类似的命令有cp,不过cp只是在本机进行拷贝不能跨服务器,而且scp传输是加密的。可能会稍微影响一下速度。当你服务器硬盘变为只读 read only system时,用scp可以帮你把文件移出来。另外,scp还非常不占资源,不会提高多少系统负荷,在这一点上,rsync就远远不及它了。虽然 rsync比scp会快一点,但当小文件众多的情况下,rsync会导致硬盘I/O非常高,而scp基本不影响系统正常使用。","text":"scp是secure copy的简写,用于在Linux下进行远程拷贝文件的命令,和它类似的命令有cp,不过cp只是在本机进行拷贝不能跨服务器,而且scp传输是加密的。可能会稍微影响一下速度。当你服务器硬盘变为只读 read only system时,用scp可以帮你把文件移出来。另外,scp还非常不占资源,不会提高多少系统负荷,在这一点上,rsync就远远不及它了。虽然 rsync比scp会快一点,但当小文件众多的情况下,rsync会导致硬盘I/O非常高,而scp基本不影响系统正常使用。 命令格式$ scp [参数] [原路径] [目标路径] 命令功能 scp是 secure copy的缩写, scp是linux系统下基于ssh登陆进行安全的远程文件拷贝命令。linux的scp命令可以在linux服务器之间复制文件和目录。 命令参数 参数 描述 -1 强制scp命令使用协议ssh1 -2 强制scp命令使用协议ssh2 -4 强制scp命令只使用IPv4寻址 -6 强制scp命令只使用IPv6寻址 -B 使用批处理模式(传输过程中不询问传输口令或短语) -C 允许压缩。(将-C标志传递给ssh,从而打开压缩功能) -p 保留原文件的修改时间,访问时间和访问权限 -q 不显示传输进度条 -r 递归复制整个目录 -v 详细方式显示输出。scp和ssh(1)会显示出整个过程的调试信息。这些信息用于调试连接,验证和配置问题 -c cipher 以cipher将数据传输进行加密,这个选项将直接传递给ssh -F ssh_config 指定一个替代的ssh配置文件,此参数直接传递给ssh -i identity_file 从指定文件中读取传输时使用的密钥文件,此参数直接传递给ssh -l limit 限定用户所能使用的带宽,以Kbit/s为单位 -o ssh_option 如果习惯于使用ssh_config(5)中的参数传递方式 -P port 注意是大写的P, port是指定数据传输用到的端口号 -S program 指定加密传输时所使用的程序。此程序必须能够理解ssh(1)的选项 使用实例从本地服务器复制到远程服务器: # 指定了用户名,命令执行后需输入密码 $ scp -r img/* root@server:/var/project/img/ # 没有指定用户名,命令执行后需要输入用户名密码 $ scp -r img/* server:/var/project/img/ 从远程服务器复制到本地当前目录: $ scp -r server:/var/project/img/* .","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(44): rcp","slug":"linux-command-44-rcp","date":"2017-01-12T02:36:06.000Z","updated":"2024-07-05T01:50:55.584Z","comments":true,"path":"2017/01/12/linux-command-44-rcp/","permalink":"http://yelog.org/2017/01/12/linux-command-44-rcp/","excerpt":"rcp代表“remote file copy”(远程文件拷贝)。该命令用于在计算机之间拷贝文件。rcp命令有两种格式。第一种格式用于文件到文件的拷贝;第二种格式用于把文件或目录拷贝到另一个目录中。","text":"rcp代表“remote file copy”(远程文件拷贝)。该命令用于在计算机之间拷贝文件。rcp命令有两种格式。第一种格式用于文件到文件的拷贝;第二种格式用于把文件或目录拷贝到另一个目录中。 命令格式$ rcp [参数] [源文件] [目标文件] 命令功能 rcp命令用在远端复制文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录,则它会把前面指定的所有文件或目录复制到该目录中。 命令参数 命令 描述 -r 递归地把源目录中的所有内容拷贝到目的目录中。要使用这个选项,目的必须是一个目录 -p 试图保留源文件的修改时间和模式,忽略umask -k 请求rcp获得在指定区域内的远程主机的Kerberos 许可,而不是获得由krb_relmofhost⑶确定的远程主机区域内的远程主机的Kerberos许可。 -x 为传送的所有数据打开DES加密。这会影响响应时间和CPU利用率,但是可以提高安全性。如果在文件名中指定的路径不是完整的路径名,那么这个路径被解释为相对远程机上同名用户的主目录。如果没有给出远程用户名,就使用当前用户名。如果远程机上的路径包含特殊shell字符,需要用反斜线(\\)、双引号(”)或单引号(’)括起来,使所有的shell元字符都能被远程地解释。需要说明的是,rcp不提示输入口令,它通过rsh命令来执行拷贝。 directory 每个文件或目录参数既可以是远程文件名也可以是本地文件名。远程文件名具有如下形式:rname@rhost:path,其中rname是远程用户名,rhost是远程计算机名,path是这个文件的路径。 使用实例使用rcp,需要具备的条件 如果系统中有 /etc/hosts 文件,系统管理员应确保该文件包含要与之进行通信的远程主机的项。 /etc/hosts 文件中有一行文字,其中包含每个远程系统的以下信息: internet_address official_name alias 例如: 9.186.10.*** webserver1.com.58.webserver.rhosts 文件 .rhosts 文件位于远程系统的主目录下,其中包含本地系统的名称和本地登录名。 例如,远程系统的 .rhosts 文件中的项可能是: webserver1 root 其中,webserver1 是本地系统的名称,root 是本地登录名。这样,webserver1 上的 root 即可在包含.rhosts 文件的远程系统中来回复制文件。配置过程:只对root用户生效 在双方root用户根目录下建立.rhosts文件,并将双方的hostname加进去.在此之前应在双方的 /etc/hosts文件中加入对方的IP和hostname 把rsh服务启动起来,redhat默认是不启动的。方法:用执行ntsysv命令,在rsh选项前用空格键选中,确定退出。然后执行:service xinetd restart即可。 3.到/etc/pam.d/目录下,把rsh文件中的auth required /lib/security/pam_securetty.so一行用“#”注释掉即可。(只有注释掉这一行,才能用root用户登录) 例一:将本地img文件夹内的所有内容 复制到服务器相应的img目录下 # -r 递归子目录 $ rcp -r img/* webserver1:/var/project/img/ 例二:将服务器的img文件夹内的所有内容 复制到本地目录下 # -r 递归子目录 $ rcp -r webserver1:/var/project/img/* img/ 例三:将目录复制到远程系统:要将本地目录及其文件和子目录复制到远程系统 # 将本地的img目录复制到服务器的project目录下 $ rcp -r img/ webserver1:/var/project/","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(43): telnet","slug":"linux-command-43-telnet","date":"2017-01-12T01:24:01.000Z","updated":"2024-07-05T01:50:55.610Z","comments":true,"path":"2017/01/12/linux-command-43-telnet/","permalink":"http://yelog.org/2017/01/12/linux-command-43-telnet/","excerpt":"telnet命令通常用来远程登录。telnet程序是基于TELNET协议的远程登录客户端程序。Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的 能力。在终端使用者的电脑上使用telnet程序,用它连接到服务器。终端使用者可以在telnet程序中输入命令,这些命令会在服务器上运行,就像直接在服务器的控制台上输入一样。可以在本地就能控制服务器。要开始一个 telnet会话,必须输入用户名和密码来登录服务器。Telnet是常用的远程控制Web服务器的方法。","text":"telnet命令通常用来远程登录。telnet程序是基于TELNET协议的远程登录客户端程序。Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的 能力。在终端使用者的电脑上使用telnet程序,用它连接到服务器。终端使用者可以在telnet程序中输入命令,这些命令会在服务器上运行,就像直接在服务器的控制台上输入一样。可以在本地就能控制服务器。要开始一个 telnet会话,必须输入用户名和密码来登录服务器。Telnet是常用的远程控制Web服务器的方法。 但是,telnet因为采用明文传送报文,安全性不好,很多Linux服务器都不开放telnet服务,而改用更安全的ssh方式了。但仍然有很多别的系统可能采用了telnet方式来提供远程登录,因此弄清楚telnet客户端的使用方式仍是很有必要的。 telnet命令还可做别的用途,比如确定远程服务的状态,比如确定远程服务器的某个端口是否能访问。 命令格式$ telnet [参数][主机] 命令功能 执行telnet指令开启终端机阶段作业,并登入远端主机。 命令参数 命令 描述 -8 允许使用8位字符资料,包括输入与输出 -a 尝试自动登入远端系统 -b<主机别名> 使用别名指定远端主机名称 -c 不读取用户专属目录里的.telnetrc文件 -d 启动排错模式 -e<脱离字符> 设置脱离字符 -E 滤除脱离字符 -f 此参数的效果和指定”-F”参数相同 -F 使用Kerberos V5认证时,加上此参数可把本地主机的认证数据上传到远端主机 -k<域名> 使用Kerberos认证时,加上此参数让远端主机采用指定的领域名,而非该主机的域名 -K 不自动登入远端主机 -l<用户名称> 指定要登入远端主机的用户名称 -L 允许输出8位字符资料 -n<记录文件> 指定文件记录相关信息 -r 使用类似rlogin指令的用户界面 -S<服务类型> 设置telnet连线所需的IP TOS信息 -x 假设主机有支持数据加密的功能,就使用它 -X<认证形态> 关闭指定的认证形态 使用实例例一:远程服务器无法访问 $ telnet 192.168.120.206 Trying 192.168.120.209... telnet: connect to address 192.168.120.209: No route to host telnet: Unable to connect to remote host: No route to host 说明:处理这种情况方法: (1)确认ip地址是否正确? (2)确认ip地址对应的主机是否已经开机? (3)如果主机已经启动,确认路由设置是否设置正确?(使用route命令查看) (4)如果主机已经启动,确认主机上是否开启了telnet服务?(使用netstat命令查看,TCP的23端口是否有LISTEN状态的行) (5)如果主机已经启动telnet服务,确认防火墙是否放开了23端口的访问?(使用iptables-save查看) 例二:域名无法解析 $ telnet www.baidu.com www.baidu.com/telnet: Temporary failure in name resolution 说明:处理这种情况方法: (1)确认域名是否正确 (2)确认本机的域名解析有关的设置是否正确(/etc/resolv.conf中nameserver的设置是否正确,如果没有,可以使用nameserver 8.8.8.8) (3)确认防火墙是否放开了UDP53端口的访问(DNS使用UDP协议,端口53,使用iptables-save查看) 例三:连接被拒绝 $ telnet 192.168.120.206 Trying 192.168.120.206... telnet: connect to address 192.168.120.206: Connection refused telnet: Unable to connect to remote host: Connection refused 说明:处理这种情况: (1)确认ip地址或者主机名是否正确? (2)确认端口是否正确,是否默认的23端口 例四:正常telnet $ telnet 192.168.120.204 Trying 192.168.120.204... Connected to 192.168.120.204 (192.168.120.204). Escape character is '^]'. localhost (Linux release 2.6.18-274.18.1.el5 #1 SMP Thu Feb 9 12:45:44 EST 2012) (1) login: root Password: Login incorrect 说明: 一般情况下不允许root从远程登录,可以先用普通账号登录,然后再用su -切到root用户。 例五:测试服务器8888端口是否可用 $ telnet 192.168.0.88 8888","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(42): ss","slug":"linux-command-42-ss","date":"2017-01-11T02:34:47.000Z","updated":"2024-07-05T01:50:55.559Z","comments":true,"path":"2017/01/11/linux-command-42-ss/","permalink":"http://yelog.org/2017/01/11/linux-command-42-ss/","excerpt":"ss是Socket Statistics的缩写。顾名思义,ss命令可以用来获取socket统计信息,它可以显示和netstat类似的内容。但ss的优势在于它能够显示更多更详细的有关TCP和连接状态的信息,而且比netstat更快速更高效。","text":"ss是Socket Statistics的缩写。顾名思义,ss命令可以用来获取socket统计信息,它可以显示和netstat类似的内容。但ss的优势在于它能够显示更多更详细的有关TCP和连接状态的信息,而且比netstat更快速更高效。 当服务器的socket连接数量变得非常大时,无论是使用netstat命令还是直接cat /proc/net/tcp,执行速度都会很慢。可能你不会有切身的感受,但请相信我,当服务器维持的连接达到上万个的时候,使用netstat等于浪费 生命,而用ss才是节省时间。 天下武功唯快不破。ss快的秘诀在于,它利用到了TCP协议栈中tcp_diag。tcp_diag是一个用于分析统计的模块,可以获得Linux 内核中第一手的信息,这就确保了ss的快捷高效。当然,如果你的系统中没有tcp_diag,ss也可以正常运行,只是效率会变得稍慢。(但仍然比 netstat要快。) 命令格式$ ss [参数] $ ss[参数] [过滤] 命令功能 ss(Socket Statistics的缩写)命令可以用来获取 socket统计信息,此命令输出的结果类似于 netstat输出的内容,但它能显示更多更详细的 TCP连接状态的信息,且比 netstat 更快速高效。它使用了 TCP协议栈中 tcp_diag(是一个用于分析统计的模块),能直接从获得第一手内核信息,这就使得 ss命令快捷高效。在没有 tcp_diag,ss也可以正常运行。 命令参数 命令 描述 -h, –help 帮助信息 -V, –version 程序版本信息 -n, –numeric 不解析服务名称 -r, –resolve 解析主机名 -a, –all 显示所有套接字(sockets) -l, –listening 显示监听状态的套接字(sockets) -o, –options 显示计时器信息 -e, –extended 显示详细的套接字(sockets)信息 -m, –memory 显示套接字(socket)的内存使用情况 -p, –processes 显示使用套接字(socket)的进程 -i, –info 显示 TCP内部信息 -s, –summary 显示套接字(socket)使用概况 -4, –ipv4 仅显示IPv4的套接字(sockets) -6, –ipv6 仅显示IPv6的套接字(sockets) -0, –packet 显示 PACKET 套接字(socket) -t, –tcp 仅显示 TCP套接字(sockets) -u, –udp 仅显示 UCP套接字(sockets) -d, –dccp 仅显示 DCCP套接字(sockets) -w, –raw 仅显示 RAW套接字(sockets) -x, –unix 仅显示 Unix套接字(sockets) -f, –family=FAMILY 显示 FAMILY类型的套接字(sockets),FAMILY可选,支持 unix, inet, inet6, link, netlink -A, –query=QUERY, –socket=QUERYQUERY := {all inet -D, –diag=FILE 将原始TCP套接字(sockets)信息转储到文件 -F, –filter=FILE 从文件中都去过滤器信息 FILTER := [ state TCP-STATE ] [ EXPRESSION ] 使用实例例一:显示TCP连接 $ ss -t -a 例二:显示 Sockets 摘要 $ ss -s Total: 1385 (kernel 0) TCP: 199 (estab 64, closed 76, orphaned 0, synrecv 0, timewait 1/0), ports 0 Transport Total IP IPv6 * 0 - - RAW 2 1 1 UDP 29 21 8 TCP 123 47 76 INET 154 69 85 FRAG 0 0 0 说明: 列出当前的established, closed, orphaned and waiting TCP sockets 例三:列出所有打开的网络连接端口 $ ss -l 例四:查看进程使用的socket $ ss -pl 例五:找出打开套接字/端口应用程序 $ ss -lp | grep 3306 例六:显示所有UDP Sockets $ ss -u -a 例七:显示所有状态为established的SMTP连接 $ ss -o state established '( dport = :smtp or sport = :smtp )' 例八:显示所有状态为Established的HTTP连接 $ ss -o state established '( dport = :http or sport = :http )' 例九:列举出处于 FIN-WAIT-1状态的源端口为 80或者 443,目标网络为 193.233.7/24所有 tcp套接字命令 $ ss -o state fin-wait-1 '( sport = :http or sport = :https )' dst 193.233.7/24 例十:用TCP 状态过滤Sockets $ ss -4 state FILTER-NAME-HERE $ ss -6 state FILTER-NAME-HERE 说明:FILTER-NAME-HERE 可以代表以下任何一个: established syn-sent syn-recv fin-wait-1 fin-wait-2 time-wait closed close-wait last-ack listen closing all : 所有以上状态 connected : 除了listen and closed的所有状态 synchronized :所有已连接的状态除了syn-sent bucket : 显示状态为maintained as minisockets,如:time-wait和syn-recv. big : 和bucket相反. 例十一:匹配远程地址和端口号 $ ss dst ADDRESS_PATTERN $ ss dst 192.168.1.5 $ ss dst 192.168.119.113:http $ ss dst 192.168.119.113:smtp $ ss dst 192.168.119.113:443 例十二:匹配本地地址和端口号 $ ss src ADDRESS_PATTERN $ ss src 192.168.119.103 $ ss src 192.168.119.103:http $ ss src 192.168.119.103:80 $ ss src 192.168.119.103:smtp $ ss src 192.168.119.103:25 例十三:将本地或者远程端口和一个数比较 $ ss dport OP PORT $ ss sport OP PORT 说明: ss dport OP PORT 远程端口和一个数比较;ss sport OP PORT 本地端口和一个数比较。 OP 可以代表以下任意一个: <= or le : 小于或等于端口号 >= or ge : 大于或等于端口号 == or eq : 等于端口号 != or ne : 不等于端口号 < or gt : 小于端口号 > or lt : 大于端口号 例十四:ss 和 netstat 效率对比 $ time netstat -at $ time ss 说明: 用time 命令分别获取通过netstat和ss命令获取程序和概要占用资源所使用的时间。在服务器连接数比较多的时候,netstat的效率完全没法和ss比。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(41): netstat","slug":"linux-command-41-netstat","date":"2017-01-10T01:54:16.000Z","updated":"2024-07-05T01:50:55.642Z","comments":true,"path":"2017/01/10/linux-command-41-netstat/","permalink":"http://yelog.org/2017/01/10/linux-command-41-netstat/","excerpt":"netstat命令用于显示与IP、TCP、UDP和ICMP协议相关的统计数据,一般用于检验本机各端口的网络连接情况。netstat是在内核中访问网络及相关信息的程序,它能提供TCP连接,TCP和UDP监听,进程内存管理的相关报告。","text":"netstat命令用于显示与IP、TCP、UDP和ICMP协议相关的统计数据,一般用于检验本机各端口的网络连接情况。netstat是在内核中访问网络及相关信息的程序,它能提供TCP连接,TCP和UDP监听,进程内存管理的相关报告。 如果你的计算机有时候接收到的数据报导致出错数据或故障,你不必感到奇怪,TCP/IP可以容许这些类型的错误,并能够自动重发数据报。但如果累计的出错情况数目占到所接收的IP数据报相当大的百分比,或者它的数目正迅速增加,那么你就应该使用netstat查一查为什么会出现这些情况了。 命令格式$ netstat [-acCeFghilMnNoprstuvVwx][-A<网络类型>][--ip] 命令功能 netstat用于显示与IP、TCP、UDP和ICMP协议相关的统计数据,一般用于检验本机各端口的网络连接情况。 命令参数 命令 描述 -a或–all 显示所有连线中的Socket -A<网络类型>或–<网络类型> 列出该网络类型连线中的相关地址 -c或–continuous 持续列出网络状态 -C或–cache 显示路由器配置的快取信息 -e或–extend 显示网络其他相关信息 -F或–fib 显示FIB -g或–groups 显示多重广播功能群组组员名单 -h或–help 在线帮助 -i或–interfaces 显示网络界面信息表单 -l或–listening 显示监控中的服务器的Socket -M或–masquerade 显示伪装的网络连线 -n或–numeric 直接使用IP地址,而不通过域名服务器 -N或–netlink或–symbolic 显示网络硬件外围设备的符号连接名称 -o或–timers 显示计时器 -p或–programs 显示正在使用Socket的程序识别码和程序名称 -r或–route 显示Routing Table -s或–statistice 显示网络工作信息统计表 -t或–tcp 显示TCP传输协议的连线状况 -u或–udp 显示UDP传输协议的连线状况 -v或–verbose 显示指令执行过程 -V或–version 显示版本信息 -w或–raw 显示RAW传输协议的连线状况 -x或–unix 此参数的效果和指定”-A unix”参数相同 –ip或–inet 此参数的效果和指定”-A inet”参数相同 使用实例例一:无参数使用 $ netstat Active Internet connections (w/o servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 268 192.168.120.204:ssh 10.2.0.68:62420 ESTABLISHED udp 0 0 192.168.120.204:4371 10.58.119.119:domain ESTABLISHED Active UNIX domain sockets (w/o servers) Proto RefCnt Flags Type State I-Node Path unix 2 [ ] DGRAM 1491 @/org/kernel/udev/udevd unix 4 [ ] DGRAM 7337 /dev/log unix 2 [ ] DGRAM 708823 unix 2 [ ] DGRAM 7539 unix 3 [ ] STREAM CONNECTED 7287 unix 3 [ ] STREAM CONNECTED 7286 说明: 从整体上看,netstat的输出结果可以分为两个部分: 一个是Active Internet connections,称为有源TCP连接,其中”Recv-Q”和”Send-Q”指的是接收队列和发送队列。这些数字一般都应该是0。如果不是则表示软件包正在队列中堆积。这种情况只能在非常少的情况见到。 另一个是Active UNIX domain sockets,称为有源Unix域套接口(和网络套接字一样,但是只能用于本机通信,性能可以提高一倍)。 Proto显示连接使用的协议,RefCnt表示连接到本套接口上的进程号,Types显示套接口的类型,State显示套接口当前的状态,Path表示连接到套接口的其它进程使用的路径名。套接口类型: -t :TCP -u :UDP -raw :RAW类型 –unix :UNIX域类型 –ax25 :AX25类型 –ipx :ipx类型 –netrom :netrom类型状态说明: LISTEN:侦听来自远方的TCP端口的连接请求 SYN-SENT:再发送连接请求后等待匹配的连接请求(如果有大量这样的状态包,检查是否中招了) SYN-RECEIVED:再收到和发送一个连接请求后等待对方对连接请求的确认(如有大量此状态,估计被flood攻击了) ESTABLISHED:代表一个打开的连接 FIN-WAIT-1:等待远程TCP连接中断请求,或先前的连接中断请求的确认 FIN-WAIT-2:从远程TCP等待连接中断请求 CLOSE-WAIT:等待从本地用户发来的连接中断请求 CLOSING:等待远程TCP对连接中断的确认 LAST-ACK:等待原来的发向远程TCP的连接中断请求的确认(不是什么好东西,此项出现,检查是否被攻击) TIME-WAIT:等待足够的时间以确保远程TCP接收到连接中断请求的确认 CLOSED:没有任何连接状态 例二:列出所有端口 $ netstat -a Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 localhost:smux *:* LISTEN tcp 0 0 *:svn *:* LISTEN tcp 0 0 *:ssh *:* LISTEN tcp 0 284 192.168.120.204:ssh 10.2.0.68:62420 ESTABLISHED udp 0 0 localhost:syslog *:* udp 0 0 *:snmp *:* Active UNIX domain sockets (servers and established) Proto RefCnt Flags Type State I-Node Path unix 2 [ ACC ] STREAM LISTENING 708833 /tmp/ssh-yKnDB15725/agent.15725 unix 2 [ ACC ] STREAM LISTENING 7296 /var/run/audispd_events unix 2 [ ] DGRAM 1491 @/org/kernel/udev/udevd unix 4 [ ] DGRAM 7337 /dev/log unix 2 [ ] DGRAM 708823 unix 2 [ ] DGRAM 7539 unix 3 [ ] STREAM CONNECTED 7287 unix 3 [ ] STREAM CONNECTED 7286 说明: 显示一个所有的有效连接信息列表,包括已建立的连接(ESTABLISHED),也包括监听连接请(LISTENING)的那些连接 例三:显示当前UDP连接状况 $ netstat -nu 例四:显示UDP端口号的使用情况 $ netstat -apu 例五:显示UDP端口号的使用情况 $ netstat -i 例六:显示组播组的关系 $ netstat -g 例七:显示网络统计信息 $ netstat -s 说明: 按照各个协议分别显示其统计数据。如果我们的应用程序(如Web浏览器)运行速度比较慢,或者不能显示Web页之类的数据,那么我们就可以用本选项来查看一下所显示的信息。我们需要仔细查看统计数据的各行,找到出错的关键字,进而确定问题所在。 例八:显示监听的套接口 $ netstat -l 例九:显示所有已建立的有效连接 $ netstat -n 例十:显示关于以太网的统计数据 $ netstat -e 说明: 用于显示关于以太网的统计数据。它列出的项目包括传送的数据报的总字节数、错误数、删除数、数据报的数量和广播的数量。这些统计数据既有发送的数据报数量,也有接收的数据报数量。这个选项可以用来统计一些基本的网络流量) 例十一:显示关于路由表的信息 $ netstat -r 例十二:列出所有 tcp 端口 $ netstat -at 例十三:统计机器中网络连接各个状态个数 $ netstat -a | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' 例十四:把状态全都取出来后使用uniq -c统计后再进行排序 $ netstat -nat |awk '{print $6}'|sort|uniq -c 例十五:查看连接某服务端口最多的的IP地址 $ netstat -nat | grep "192.168.120.20:16067" |awk '{print $5}'|awk -F: '{print $4}'|sort|uniq -c|sort -nr|head -20 例十六:找出程序运行的端口 $ netstat -ap | grep ssh 例十七:在 netstat 输出中显示 PID 和进程名称 $ netstat -pt 说明: netstat -p 可以与其它开关一起使用,就可以添加 “PID/进程名称” 到 netstat 输出中,这样 debugging 的时候可以很方便的发现特定端口运行的程序 例十八:查看所有端口使用情况 $ netstat -tln 例十九:找出运行在指定端口的进程 $ netstat -anpt | grep ':4000' (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 0.0.0.0:4000 0.0.0.0:* LISTEN 5362/hexo tcp 0 0 127.0.0.1:4000 127.0.0.1:45884 ESTABLISHED 5362/hexo tcp 0 0 127.0.0.1:4000 127.0.0.1:45886 ESTABLISHED 5362/hexo 说明: 运行在端口4000的进程id为5362,再通过ps命令就可以找到具体的应用程序了","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(40): traceroute","slug":"linux-command-40-traceroute","date":"2017-01-09T02:56:53.000Z","updated":"2024-07-05T01:50:55.683Z","comments":true,"path":"2017/01/09/linux-command-40-traceroute/","permalink":"http://yelog.org/2017/01/09/linux-command-40-traceroute/","excerpt":"通过traceroute我们可以知道信息从你的计算机到互联网另一端的主机是走的什么路径。当然每次数据包由某一同样的出发点(source)到达某一同样的目的地(destination)走的路径可能会不一样,但基本上来说大部分时候所走的路由是相同的。linux系统中,我们称之为traceroute,在MS Windows中为tracert。 traceroute通过发送小的数据包到目的设备直到其返回,来测量其需要多长时间。一条路径上的每个设备traceroute要测3次。输出结果中包括每次测试的时间(ms)和设备的名称(如有的话)及其IP地址。","text":"通过traceroute我们可以知道信息从你的计算机到互联网另一端的主机是走的什么路径。当然每次数据包由某一同样的出发点(source)到达某一同样的目的地(destination)走的路径可能会不一样,但基本上来说大部分时候所走的路由是相同的。linux系统中,我们称之为traceroute,在MS Windows中为tracert。 traceroute通过发送小的数据包到目的设备直到其返回,来测量其需要多长时间。一条路径上的每个设备traceroute要测3次。输出结果中包括每次测试的时间(ms)和设备的名称(如有的话)及其IP地址。 在大多数情况下,我们会在linux主机系统下,直接执行命令行: traceroute hostname 而在Windows系统下是执行tracert的命令: tracert hostname 命令格式$ traceroute [参数] [主机] 命令功能 traceroute指令让你追踪网络数据包的路由途径,预设数据包大小是40Bytes,用户可另行设置。 具体参数格式:traceroute [-dFlnrvx][-f<存活数值>][-g<网关>...][-i<网络界面>][-m<存活数值>][-p<通信端口>][-s<来源地址>][-t<服务类型>][-w<超时秒数>][主机名称或IP地址][数据包大小] 命令参数 命令 描述 -d 使用Socket层级的排错功能 -f 设置第一个检测数据包的存活数值TTL的大小 -F 设置勿离断位 -g 设置来源路由网关,最多可设置8个 -i 使用指定的网络界面送出数据包 -I 使用ICMP回应取代UDP资料信息 -m 设置检测数据包的最大存活数值TTL的大小 -n 直接使用IP地址而非主机名称 -p 设置UDP传输协议的通信端口 -r 忽略普通的Routing Table,直接将数据包送到远端主机上 -s 设置本地主机送出数据包的IP地址 -t 设置检测数据包的TOS数值 -v 详细显示指令的执行过程 -w 设置等待远端主机回报的时间 -x 开启或关闭数据包的正确性检验 使用实例例一:traceroute 用法简单、最常用的用法 $ traceroute yelog.github.com traceroute to yelog.github.com (151.101.192.133), 30 hops max, 60 byte packets 1 vrouter (192.168.0.1) 0.443 ms 0.565 ms 0.684 ms 2 112.208.32.1.pldt.net (112.208.32.1) 14.518 ms 22.454 ms 23.080 ms 3 119.93.255.197 (119.93.255.197) 24.492 ms 25.380 ms 26.328 ms 4 210.213.131.66.static.pldt.net (210.213.131.66) 29.942 ms 210.213.131.70.static.pldt.net (210.213.131.70) 28.209 ms 28.992 ms 5 122.2.175.30.static.pldt.net (122.2.175.30) 32.429 ms 32.765 ms 210.213.128.29.static.pldt.net (210.213.128.29) 35.165 ms 6 210.213.130.162.static.pldt.net (210.213.130.162) 32.147 ms 31.403 ms 32.107 ms 7 las-b3-link.telia.net (62.115.13.128) 198.546 ms 190.829 ms 191.039 ms 8 las-b21-link.telia.net (213.155.131.82) 194.301 ms las-b21-link.telia.net (62.115.116.179) 191.927 ms las-b21-link.telia.net (213.155.131.84) 194.433 ms 9 * * * 10 * * * 说明: 记录按序列号从1开始,每个纪录就是一跳 ,每跳表示一个网关,我们看到每行有三个时间,单位是 ms,其实就是-q的默认参数。探测数据包向每个网关发送三个数据包后,网关响应后返回的时间;如果您用 traceroute -q 4 www.58.com ,表示向每个网关发送4个数据包。 有时我们traceroute 一台主机时,会看到有一些行是以星号表示的。出现这样的情况,可能是防火墙封掉了ICMP的返回信息,所以我们得不到什么相关的数据包返回数据。 有时我们在某一网关处延时比较长,有可能是某台网关比较阻塞,也可能是物理设备本身的原因。当然如果某台DNS出现问题时,不能解析主机名、域名时,也会 有延时长的现象;您可以加-n 参数来避免DNS解析,以IP格式输出数据。 如果在局域网中的不同网段之间,我们可以通过traceroute 来排查问题所在,是主机的问题还是网关的问题。如果我们通过远程来访问某台服务器遇到问题时,我们用到traceroute 追踪数据包所经过的网关,提交IDC服务商,也有助于解决问题;但目前看来在国内解决这样的问题是比较困难的,就是我们发现问题所在,IDC服务商也不可能帮助我们解决。 例二:跳数设置 $ traceroute -m 10 www.baidu.com 例三:显示IP地址,不查主机名 $ traceroute -n www.baidu.com 例四:探测包使用的基本UDP端口设置6888 $ traceroute -p 6888 www.baidu.com 例五:把探测包的个数设置为值4 $ traceroute -q 4 www.baidu.com 例六:绕过正常的路由表,直接发送到网络相连的主机 $ traceroute -r www.baidu.com 例七:把对外发探测包的等待响应时间设置为3秒 $ traceroute -w 3 www.baidu.com Traceroute的工作原理 Traceroute最简单的基本用法是:traceroute hostname Traceroute程序的设计是利用ICMP及IP header的TTL(Time To Live)栏位(field)。首先,traceroute送出一个TTL是1的IP datagram(其实,每次送出的为3个40字节的包,包括源地址,目的地址和包发出的时间标签)到目的地,当路径上的第一个路由器(router)收到这个datagram时,它将TTL减1。此时,TTL变为0了,所以该路由器会将此datagram丢掉,并送回一个「ICMP time exceeded」消息(包括发IP包的源地址,IP包的所有内容及路由器的IP地址),traceroute 收到这个消息后,便知道这个路由器存在于这个路径上,接着traceroute 再送出另一个TTL是2 的datagram,发现第2 个路由器…… traceroute 每次将送出的datagram的TTL 加1来发现另一个路由器,这个重复的动作一直持续到某个datagram 抵达目的地。当datagram到达目的地后,该主机并不会送回ICMP time exceeded消息,因为它已是目的地了,那么traceroute如何得知目的地到达了呢? Traceroute在送出UDP datagrams到目的地时,它所选择送达的port number 是一个一般应用程序都不会用的号码(30000 以上),所以当此UDP datagram 到达目的地后该主机会送回一个「ICMP port unreachable」的消息,而当traceroute 收到这个消息时,便知道目的地已经到达了。所以traceroute 在Server端也是没有所谓的Daemon 程式。 Traceroute提取发 ICMP TTL到期消息设备的IP地址并作域名解析。每次 ,Traceroute都打印出一系列数据,包括所经过的路由设备的域名及 IP地址,三个包每次来回所花时间。 windows之tracert 格式: $ tracert [-d] [-h maximum_hops] [-j host-list] [-w timeout] target_name 参数说明 tracert [-d] [-h maximum_hops] [-j computer-list] [-w timeout] target_name 该诊断实用程序通过向目的地发送具有不同生存时间 (TL) 的 Internet 控制信息协议 (CMP) 回应报文,以确定至目的地的路由。路径上的每个路由器都要在转发该 ICMP 回应报文之前将其 TTL 值至少减 1,因此 TTL 是有效的跳转计数。当报文的 TTL 值减少到 0 时,路由器向源系统发回 ICMP 超时信息。通过发送 TTL 为 1 的第一个回应报文并且在随后的发送中每次将 TTL 值加 1,直到目标响应或达到最大 TTL 值,Tracert 可以确定路由。通过检查中间路由器发发回的 ICMP 超时 (ime Exceeded) 信息,可以确定路由器。注意,有些路由器“安静”地丢弃生存时间 (TLS) 过期的报文并且对 tracert 无效。参数:-d 指定不对计算机名解析地址。-h maximum_hops 指定查找目标的跳转的最大数目。-jcomputer-list 指定在 computer-list 中松散源路由。-w timeout 等待由 timeout 对每个应答指定的毫秒数。target_name 目标计算机的名称。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(39): ping","slug":"linux-command-39-ping","date":"2017-01-08T02:24:50.000Z","updated":"2024-07-05T01:50:55.481Z","comments":true,"path":"2017/01/08/linux-command-39-ping/","permalink":"http://yelog.org/2017/01/08/linux-command-39-ping/","excerpt":"Linux系统的ping命令是常用的网络命令,它通常用来测试与目标主机的连通性,我们经常会说“ping一下某机器,看是不是开着”、不能打开网页时会说“你先ping网关地址192.168.1.1试试”。它通过发送ICMP ECHO_REQUEST数据包到网络主机(send ICMP ECHO_REQUEST to network hosts),并显示响应情况,这样我们就可以根据它输出的信息来确定目标主机是否可访问(但这不是绝对的)。有些服务器为了防止通过ping探测到,通过防火墙设置了禁止ping或者在内核参数中禁止ping,这样就不能通过ping确定该主机是否还处于开启状态。","text":"Linux系统的ping命令是常用的网络命令,它通常用来测试与目标主机的连通性,我们经常会说“ping一下某机器,看是不是开着”、不能打开网页时会说“你先ping网关地址192.168.1.1试试”。它通过发送ICMP ECHO_REQUEST数据包到网络主机(send ICMP ECHO_REQUEST to network hosts),并显示响应情况,这样我们就可以根据它输出的信息来确定目标主机是否可访问(但这不是绝对的)。有些服务器为了防止通过ping探测到,通过防火墙设置了禁止ping或者在内核参数中禁止ping,这样就不能通过ping确定该主机是否还处于开启状态。 linux下的ping和windows下的ping稍有区别,linux下ping不会自动终止,需要按ctrl+c终止或者用参数-c指定要求完成的回应次数。 命令格式$ ping [参数] [主机名或IP地址] 命令功能 ping命令用于:确定网络和各外部主机的状态;跟踪和隔离硬件和软件问题;测试、评估和管理网络。如果主机正在运行并连在网上,它就对回送信号进行响应。每个回送信号请求包含一个网际协议(IP)和 ICMP 头,后面紧跟一个 tim 结构,以及来填写这个信息包的足够的字节。缺省情况是连续发送回送信号请求直到接收到中断信号(Ctrl-C)。 ping 命令每秒发送一个数据报并且为每个接收到的响应打印一行输出。ping 命令计算信号往返时间和(信息)包丢失情况的统计信息,并且在完成之后显示一个简要总结。ping 命令在程序超时或当接收到 SIGINT 信号时结束。Host 参数或者是一个有效的主机名或者是因特网地址。 命令参数 命令 描述 -d 使用Socket的SO_DEBUG功能 -f 极限检测。大量且快速地送网络封包给一台机器,看它的回应 -n 只输出数值 -q 不显示任何传送封包的信息,只显示最后的结果 -r 忽略普通的Routing Table,直接将数据包送到远端主机上。通常是查看本机的网络接口是否有问题 -R 记录路由过程 -v 详细显示指令的执行过程 -c 数目 在发送指定数目的包后停止 -i 秒数 设定间隔几秒送一个网络封包给一台机器,预设值是一秒送一次 -I 网络界面 使用指定的网络界面送出数据包 -l 前置载入 设置在送出要求信息之前,先行发出的数据包 -p 范本样式 设置填满数据包的范本样式 -s 字节数 指定发送的数据字节数,预设值是56,加上8字节的ICMP头,一共是64ICMP数据字节 -t 存活数值 设置存活数值TTL的大小 使用实例例一:ping通的情况 $ ping 192.168.0.1 PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data. 64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=0.238 ms 64 bytes from 192.168.0.1: icmp_seq=2 ttl=64 time=0.202 ms 64 bytes from 192.168.0.1: icmp_seq=3 ttl=64 time=0.232 ms 64 bytes from 192.168.0.1: icmp_seq=4 ttl=64 time=0.210 ms --- 192.168.0.1 ping statistics --- 6 packets transmitted, 6 received, 0% packet loss, time 4997ms 例二:ping不通的情况 $ ping 192.168.0.222 PING 192.168.0.222 (192.168.0.222) 56(84) bytes of data. From 192.168.0.101 icmp_seq=1 Destination Host Unreachable From 192.168.0.101 icmp_seq=2 Destination Host Unreachable From 192.168.0.101 icmp_seq=3 Destination Host Unreachable --- 192.168.0.222 ping statistics --- 7 packets transmitted, 0 received, +6 errors, 100% packet loss, time 6032ms pipe 3 例二:ping指定次数 $ ping -c 192.168.0.1 例三:时间间隔和次数限制的ping $ ping -c 10 -i 0.5 192.168.120.206 例四:通过域名ping公网上的站点 $ ping -c 5 yelog.github.com 例五:多参数使用 # -i 3 发送周期为 3秒 -s 设置发送包的大小为1024 -t 设置TTL值为 255 $ ping -i 3 -s 1024 -t yelog.github.com PING github.map.fastly.net (151.101.192.133) 1024(1052) bytes of data. 1032 bytes from 151.101.192.133 (151.101.192.133): icmp_seq=1 ttl=56 time=191 ms 1032 bytes from 151.101.192.133 (151.101.192.133): icmp_seq=2 ttl=56 time=190 ms 1032 bytes from 151.101.192.133 (151.101.192.133): icmp_seq=3 ttl=56 time=189 ms 1032 bytes from 151.101.192.133 (151.101.192.133): icmp_seq=4 ttl=56 time=190 ms","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(38): route","slug":"linux-command-38-route","date":"2017-01-07T02:16:34.000Z","updated":"2024-07-05T01:50:55.628Z","comments":true,"path":"2017/01/07/linux-command-38-route/","permalink":"http://yelog.org/2017/01/07/linux-command-38-route/","excerpt":"Linux系统的route命令用于显示和操作IP路由表(show / manipulate the IP routing table)。要实现两个不同的子网之间的通信,需要一台连接两个网络的路由器,或者同时位于两个网络的网关来实现。在Linux系统中,设置路由通常是为了解决以下问题:该Linux系统在一个局域网中,局域网中有一个网关,能够让机器访问Internet,那么就需要将这台机器的IP地址设置为Linux机器的默认路由。要注意的是,直接在命令行下执行route命令来添加路由,不会永久保存,当网卡重启或者机器重启之后,该路由就失效了;可以在/etc/rc.local中添加route命令来保证该路由设置永久有效。","text":"Linux系统的route命令用于显示和操作IP路由表(show / manipulate the IP routing table)。要实现两个不同的子网之间的通信,需要一台连接两个网络的路由器,或者同时位于两个网络的网关来实现。在Linux系统中,设置路由通常是为了解决以下问题:该Linux系统在一个局域网中,局域网中有一个网关,能够让机器访问Internet,那么就需要将这台机器的IP地址设置为Linux机器的默认路由。要注意的是,直接在命令行下执行route命令来添加路由,不会永久保存,当网卡重启或者机器重启之后,该路由就失效了;可以在/etc/rc.local中添加route命令来保证该路由设置永久有效。 命令格式route [-f] [-p] [Command [Destination] [mask Netmask] [Gateway] [metric Metric]] [if Interface]] 命令功能 Route命令是用于操作基于内核ip路由表,它的主要作用是创建一个静态路由让指定一个主机或者一个网络通过一个网络接口,如eth0。当使用”add”或者”del”参数时,路由表被修改,如果没有参数,则显示路由表当前的内容。 命令参数 命令 描述 -c 显示更多信息 -n 不解析名字 -v 显示详细的处理信息 -F 显示发送信息 -C 显示路由缓存 -f 清除所有网关入口的路由表 -p 与 add 命令 -p 与 add 命令一起使用时使路由具有永久性。 add 添加一条新路由 del 删除一条路由 -net 目标地址是一个网络 -host 目标地址是一个主机 netmask 当添加一个网络路由时,需要使用网络掩码 gw 路由数据包通过网关。注意,你指定的网关必须能够达到 metric 设置路由跳数 Command 指定您想运行的命令 (Add/Change/Delete/Print) Destination 指定该路由的网络目标 mask Netmask 指定与网络目标相关的网络掩码(也被称作子网掩码) Gateway 指定网络目标定义的地址集和子网掩码可以到达的前进或下一跃点 IP 地址 metric Metric 为路由指定一个整数成本值标(从 1 至 9999),当在路由表(与转发的数据包目标地址最匹配)的多个路由中进行选择时可以使用 if Interface 为可以访问目标的接口指定接口索引。若要获得一个接口列表和它们相应的接口索引,使用 route print 命令的显示功能。可以使用十进制或十六进制值进行接口索引 使用实例例一:显示当前路由 $ route $ route -n [root@localhost ~]# route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 192.168.120.0 * 255.255.255.0 U 0 0 0 eth0 e192.168.0.0 192.168.120.1 255.255.0.0 UG 0 0 0 eth0 10.0.0.0 192.168.120.1 255.0.0.0 UG 0 0 0 eth0 default 192.168.120.240 0.0.0.0 UG 0 0 0 eth0 [root@localhost ~]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 192.168.120.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 192.168.0.0 192.168.120.1 255.255.0.0 UG 0 0 0 eth0 10.0.0.0 192.168.120.1 255.0.0.0 UG 0 0 0 eth0 0.0.0.0 192.168.120.240 0.0.0.0 UG 0 0 0 eth0 说明: 第一行表示主机所在网络的地址为192.168.120.0,若数据传送目标是在本局域网内通信,则可直接通过eth0转发数据包; 第四行表示数据传送目的是访问Internet,则由接口eth0,将数据包发送到网关192.168.120.240 其中Flags为路由标志,标记当前网络节点的状态。 Flags标志说明: U Up表示此路由当前为启动状态 H Host,表示此网关为一主机 G Gateway,表示此网关为一路由器 R Reinstate Route,使用动态路由重新初始化的路由 D Dynamically,此路由是动态性地写入 M Modified,此路由是由路由守护程序或导向器动态修改 ! 表示此路由当前为关闭状态备注: route -n (-n 表示不解析名字,列出速度会比route 快) 例二:添加网关/设置网关 # 增加一条 到达244.0.0.0的路由 $ route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0 例三:屏蔽一条路由 # 增加一条屏蔽的路由,目的地址为 224.x.x.x 将被拒绝 $ route add -net 224.0.0.0 netmask 240.0.0.0 reject 例四:删除路由记录 $ route del -net 224.0.0.0 netmask 240.0.0.0 $ route del -net 224.0.0.0 netmask 240.0.0.0 reject 例五:删除和添加设置默认网关 $ route del default gw 192.168.120.240 $ route add default gw 192.168.120.240","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(37): ifconfig","slug":"linux-command-37-ifconfig","date":"2017-01-06T01:57:01.000Z","updated":"2024-07-05T01:50:55.579Z","comments":true,"path":"2017/01/06/linux-command-37-ifconfig/","permalink":"http://yelog.org/2017/01/06/linux-command-37-ifconfig/","excerpt":"许多windows非常熟悉ipconfig命令行工具,它被用来获取网络接口配置信息并对此进行修改。Linux系统拥有一个类似的工具,也就是ifconfig(interfaces config)。通常需要以root身份登录或使用sudo以便在Linux机器上使用ifconfig工具。依赖于ifconfig命令中使用一些选项属性,ifconfig工具不仅可以被用来简单地获取网络接口配置信息,还可以修改这些配置。","text":"许多windows非常熟悉ipconfig命令行工具,它被用来获取网络接口配置信息并对此进行修改。Linux系统拥有一个类似的工具,也就是ifconfig(interfaces config)。通常需要以root身份登录或使用sudo以便在Linux机器上使用ifconfig工具。依赖于ifconfig命令中使用一些选项属性,ifconfig工具不仅可以被用来简单地获取网络接口配置信息,还可以修改这些配置。 命令格式$ ifconfig [网络设备] [参数] 命令功能 ifconfig 命令用来查看和配置网络设备。当网络环境发生改变时可通过此命令对网络进行相应的配置。 注意: 用ifconfig命令配置的网卡信息,在网卡重启后机器重启后,配置就不存在。要想将上述的配置信息永远的存的电脑里,那就要修改网卡的配置文件了。 命令参数 命令 描述 up 启动指定网络设备/网卡 down 关闭指定网络设备/网卡。该参数可以有效地阻止通过指定接口的IP信息流,如果想永久地关闭一个接口,我们还需要从核心路由表中将该接口的路由信息全部删除 arp 设置指定网卡是否支持ARP协议 -promisc 设置是否支持网卡的promiscuous模式,如果选择此参数,网卡将接收网络中发给它所有的数据包 -allmulti 设置是否支持多播模式,如果选择此参数,网卡将接收网络中所有的多播数据包 -a 显示全部接口信息 -s 显示摘要信息(类似于 netstat -i) add 给指定网卡配置IPv6地址 del 删除指定网卡的IPv6地址 <硬件地址> 配置网卡最大的传输单元 mtu<字节数> 设置网卡的最大传输单元 (bytes) netmask<子网掩码> 设置网卡的子网掩码。掩码可以是有前缀0x的32位十六进制数,也可以是用点分开的4个十进制数。如果不打算将网络分成子网,可以不管这一选项;如果要使用子网,那么请记住,网络中每一个系统必须有相同子网掩码 tunel 建立隧道 dstaddr 设定一个远端地址,建立点对点通信 -broadcast<地址> 为指定网卡设置广播协议 -pointtopoint<地址> 为网卡设置点对点通讯协议 multicast 为网卡设置组播标志 address 为网卡设置IPv4地址 txqueuelen<长度> 为网卡设置传输列队的长度 使用实例例一:显示网络设备信息(激活状态的) $ ifconfig 说明: eth0 表示第一块网卡, 其中 HWaddr 表示网卡的物理地址,可以看到目前这个网卡的物理地址(MAC地址)是 00:50:56:BF:26:20 inet addr 用来表示网卡的IP地址,此网卡的 IP地址是 192.168.120.204,广播地址, Bcast:192.168.120.255,掩码地址Mask:255.255.255.0 lo 是表示主机的回坏地址,这个一般是用来测试一个网络程序,但又不想让局域网或外网的用户能够查看,只能在此台主机上运行和查看所用的网络接口。比如把 HTTPD服务器的指定到回坏地址,在浏览器输入 127.0.0.1 就能看到你所架WEB网站了。但只是您能看得到,局域网的其它主机或用户无从知道。 第一行:连接类型:Ethernet(以太网)HWaddr(硬件mac地址) 第二行:网卡的IP地址、子网、掩码 第三行:UP(代表网卡开启状态)RUNNING(代表网卡的网线被接上)MULTICAST(支持组播)MTU:1500(最大传输单元):1500字节 第四、五行:接收、发送数据包情况统计 第七行:接收、发送数据字节数统计信息。 例二:启动关闭指定网卡 # 启动eth0网卡 $ ifconfig eth0 up # 关闭eth0网卡 $ ifconfig eth0 down 注意: ssh登陆linux服务器操作要小心,关闭了就不能开启了,除非你有多网卡。 例三:为网卡配置和删除IPv6地址 # 配置IPv6的地址 $ ifconfig eth0 add 33ffe:3240:800:1005::2/64 # 删除IPv6的地址 $ ifconfig eth0 del 33ffe:3240:800:1005::2/64 例四:用ifconfig修改MAC地址 $ ifconfig eth0 hw ether 00:AA:BB:CC:DD:EE 例五:配置IP地址 # 配置ip $ ifconfig eth0 192.168.120.56 # 配置ip和掩码地址 $ ifconfig eth0 192.168.120.56 netmask 255.255.255.0 # 配置ip、掩码地址和广播地址 $ ifconfig eth0 192.168.120.56 netmask 255.255.255.0 broadcast 192.168.120.255 例六:启用和关闭ARP协议 # 启用eth0的ARP协议 $ ifconfig eth0 arp # 关闭eth0的ARP协议 $ ifconfig eth0 -arp 例七:设置最大传输单元 # 设置能通过的最大数据包大小为 1500 bytes $ ifconfig eth0 mtu 1500","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(36): lsof","slug":"linux-command-36-lsof","date":"2017-01-05T02:28:13.000Z","updated":"2024-07-05T01:50:55.467Z","comments":true,"path":"2017/01/05/linux-command-36-lsof/","permalink":"http://yelog.org/2017/01/05/linux-command-36-lsof/","excerpt":"lsof(list open files)是一个列出当前系统打开文件的工具。在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件。所以如传输控制协议 (TCP) 和用户数据报协议 (UDP) 套接字等,系统在后台都为该应用程序分配了一个文件描述符,无论这个文件的本质如何,该文件描述符为应用程序与基础操作系统之间的交互提供了通用接口。因为应用程序打开文件的描述符列表提供了大量关于这个应用程序本身的信息,因此通过lsof工具能够查看这个列表对系统监测以及排错将是很有帮助的。","text":"lsof(list open files)是一个列出当前系统打开文件的工具。在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件。所以如传输控制协议 (TCP) 和用户数据报协议 (UDP) 套接字等,系统在后台都为该应用程序分配了一个文件描述符,无论这个文件的本质如何,该文件描述符为应用程序与基础操作系统之间的交互提供了通用接口。因为应用程序打开文件的描述符列表提供了大量关于这个应用程序本身的信息,因此通过lsof工具能够查看这个列表对系统监测以及排错将是很有帮助的。 命令格式$ lsof [参数][文件] 命令功能 用于查看你进程开打的文件,打开文件的进程,进程打开的端口(TCP、UDP)。找回/恢复删除的文件。是十分方便的系统监视工具,因为 lsof 需要访问核心内存和各种文件,所以需要root用户执行。lsof打开的文件可以是: 普通文件 目录 网络文件系统的文件 字符或设备文件 (函数)共享库 管道,命名管道 符号链接 网络文件(例如:NFS file、网络socket,unix域名socket) 还有其它类型的文件,等等 命令参数 命令 描述 -a 列出打开文件存在的进程 -c<进程名> 列出指定进程所打开的文件 -g 列出GID号进程详情 -d<文件号> 列出占用该文件号的进程 +d<目录> 列出目录下被打开的文件 +D<目录> 递归列出目录下被打开的文件 -n<目录> 列出使用NFS的文件 -i<条件> 列出符合条件的进程。(4、6、协议、:端口、 @ip ) -p<进程号> 列出指定进程号所打开的文件 -u 列出UID号进程详情 -h 显示帮助信息 -v 显示版本信息 使用实例例一:无任何参数 $ lsof COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME init 1 root cwd DIR 8,2 4096 2 / init 1 root rtd DIR 8,2 4096 2 / init 1 root txt REG 8,2 43496 6121706 /sbin/init init 1 root mem REG 8,2 143600 7823908 /lib64/ld-2.5.so init 1 root mem REG 8,2 1722304 7823915 /lib64/libc-2.5.so init 1 root mem REG 8,2 23360 7823919 /lib64/libdl-2.5.so init 1 root mem REG 8,2 95464 7824116 /lib64/libselinux.so.1 init 1 root mem REG 8,2 247496 7823947 /lib64/libsepol.so.1 init 1 root 10u FIFO 0,17 1233 /dev/initctl migration 2 root cwd DIR 8,2 4096 2 / migration 2 root rtd DIR 8,2 4096 2 / migration 2 root txt unknown /proc/2/exe ksoftirqd 3 root cwd DIR 8,2 4096 2 / ksoftirqd 3 root rtd DIR 8,2 4096 2 / ksoftirqd 3 root txt unknown /proc/3/exe migration 4 root cwd DIR 8,2 4096 2 / migration 4 root rtd DIR 8,2 4096 2 / migration 4 root txt unknown /proc/4/exe ksoftirqd 5 root cwd DIR 8,2 4096 2 / ksoftirqd 5 root rtd DIR 8,2 4096 2 / ksoftirqd 5 root txt unknown /proc/5/exe events/0 6 root cwd DIR 8,2 4096 2 / events/0 6 root rtd DIR 8,2 4096 2 / events/0 6 root txt unknown /proc/6/exe events/1 7 root cwd DIR 8,2 4096 2 / 说明:lsof输出各列信息的意义如下:COMMAND:进程的名称PID:进程标识符PPID:父进程标识符(需要指定-R参数)USER:进程所有者PGID:进程所属组FD:文件描述符,应用程序通过文件描述符识别该文件。如cwd、txt等(1)cwd:表示current work dirctory,即:应用程序的当前工作目录,这是该应用程序启动的目录,除非它本身对这个目录进行更改(2)txt :该类型的文件是程序代码,如应用程序二进制文件本身或共享库,如上列表中显示的 /sbin/init 程序(3)lnn:library references (AIX);(4)er:FD information error (see NAME column);(5)jld:jail directory (FreeBSD);(6)ltx:shared library text (code and data);(7)mxx :hex memory-mapped type number xx.(8)m86:DOS Merge mapped file;(9)mem:memory-mapped file;(10)mmap:memory-mapped device;(11)pd:parent directory;(12)rtd:root directory;(13)tr:kernel trace file (OpenBSD);(14)v86 VP/ix mapped file;(15)0:表示标准输出(16)1:表示标准输入(17)2:表示标准错误一般在标准输出、标准错误、标准输入后还跟着文件状态模式:r、w、u等(1)u:表示该文件被打开并处于读取/写入模式(2)r:表示该文件被打开并处于只读模式(3)w:表示该文件被打开并处于(4)空格:表示该文件的状态模式为unknow,且没有锁定(5)-:表示该文件的状态模式为unknow,且被锁定同时在文件状态模式后面,还跟着相关的锁(1)N:for a Solaris NFS lock of unknown type;(2)r:for read lock on part of the file;(3)R:for a read lock on the entire file;(4)w:for a write lock on part of the file;(文件的部分写锁)(5)W:for a write lock on the entire file;(整个文件的写锁)(6)u:for a read and write lock of any length;(7)U:for a lock of unknown type;(8)x:for an SCO OpenServer Xenix lock on part of the file;(9)X:for an SCO OpenServer Xenix lock on the entire file;(10)space:if there is no lock.TYPE:文件类型,如DIR、REG等,常见的文件类型(1)DIR:表示目录(2)CHR:表示字符类型(3)BLK:块设备类型(4)UNIX: UNIX 域套接字(5)FIFO:先进先出 (FIFO) 队列(6)IPv4:网际协议 (IP) 套接字DEVICE:指定磁盘的名称SIZE:文件的大小NODE:索引节点(文件在磁盘上的标识)NAME:打开文件的确切名称 例二:查看谁正在使用某个文件,也就是说查找某个文件相关的进程 $ lsof /bin/bash COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME bash 24159 root txt REG 8,2 801528 5368780 /bin/bash bash 24909 root txt REG 8,2 801528 5368780 /bin/bash bash 24941 root txt REG 8,2 801528 5368780 /bin/bash 例三:递归查看某个目录的文件信息 $ lsof test/test3 COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME bash 24941 root cwd DIR 8,2 4096 2258872 test/test3 vi 24976 root cwd DIR 8,2 4096 2258872 test/test3 说明: 使用了+D,对应目录下的所有子目录和文件都会被列出 例四:不使用+D选项,遍历查看某个目录的所有文件信息的方法 $ lsof |grep 'test/test3' 例五:列出某个用户打开的文件信息 # -u 选项,u其实是user的缩写 $ lsof -u username 例六:列出某个程序进程所打开的文件信息 $ lsof -c mysql 说明:-c 选项将会列出所有以mysql这个进程开头的程序的文件,其实你也可以写成 lsof | grep mysql, 但是第一种方法明显比第二种方法要少打几个字符了 例七:列出多个进程多个打开的文件信息 $ lsof -c mysql -c apache 例八:列出某个用户以及某个进程所打开的文件信息 $ lsof -u test -c mysql 例九:列出除了某个用户外的被打开的文件信息 # ^这个符号在用户名之前,将会把是root用户打开的进程不让显示 $ lsof -u ^root 例十:通过某个进程号显示该进行打开的文件 $ lsof -p 1 例十一:列出多个进程号对应的文件信息 $ lsof -p 1,2,3 例十二:列出除了某个进程号,其他进程号所打开的文件信息 $ lsof -p ^1 例十三:列出所有的网络连接 $ lsof -i 例十四:列出所有tcp 网络连接信息 $ lsof -i tcp 例十五:列出所有udp网络连接信息 $ lsof -i udp 例十六:列出谁在使用某个端口 $ lsof -i :3306 例十七:列出谁在使用某个特定的udp/tcp端口 $ lsof -i udp:55 $ lsof -i tcp:80 例十八:列出某个用户的所有活跃的网络端口 $ lsof -a -u test -i 例十九:列出所有网络文件系统 $ lsof -N 例二十:域名socket文件 $ lsof -u 例二十一:某个用户组所打开的文件信息 $ lsof -g 5555 例二十二:根据文件描述列出对应的文件信息 $ lsof -d description(like 2) 例如:lsof -d txt 例如:lsof -d 1 例如:lsof -d 2 说明:0表示标准输入,1表示标准输出,2表示标准错误,从而可知:所以大多数应用程序所打开的文件的 FD 都是从 3 开始 例二十三:根据文件描述范围列出文件信息 $ lsof -d 2-3 例二十四:列出COMMAND列中包含字符串” sshd”,且文件描符的类型为txt的文件信息 $ lsof -c sshd -a -d txt 例二十五:列出被进程号为1234的进程所打开的所有IPV4 network files $ lsof -i 4 -a -p 1234 例二十六:列出目前连接主机peida.linux上端口为:20,21,22,25,53,80相关的所有文件信息,且每隔3秒不断的执行lsof指令 $ lsof -i @peida.linux:20,21,22,25,53,80 -r 3","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(35): iostat","slug":"linux-command-35-iostat","date":"2017-01-04T02:10:02.000Z","updated":"2024-07-05T01:50:55.669Z","comments":true,"path":"2017/01/04/linux-command-35-iostat/","permalink":"http://yelog.org/2017/01/04/linux-command-35-iostat/","excerpt":"Linux系统中的 iostat是I/O statistics(输入/输出统计)的缩写,iostat工具将对系统的磁盘操作活动进行监视。它的特点是汇报磁盘活动统计情况,同时也会汇报出CPU使用情况。同vmstat一样,iostat也有一个弱点,就是它不能对某个进程进行深入分析,仅对系统的整体情况进行分析。iostat属于sysstat软件包。可以用yum install sysstat 直接安装。","text":"Linux系统中的 iostat是I/O statistics(输入/输出统计)的缩写,iostat工具将对系统的磁盘操作活动进行监视。它的特点是汇报磁盘活动统计情况,同时也会汇报出CPU使用情况。同vmstat一样,iostat也有一个弱点,就是它不能对某个进程进行深入分析,仅对系统的整体情况进行分析。iostat属于sysstat软件包。可以用yum install sysstat 直接安装。 命令格式$ iostat [参数][时间][次数] 命令功能 通过iostat方便查看CPU、网卡、tty设备、磁盘、CD-ROM 等等设备的活动情况, 负载信息。 命令参数 命令 描述 -C 显示CPU使用情况 -d 显示磁盘使用情况 -k 以 KB 为单位显示 -m 以 M 为单位显示 -N 显示磁盘阵列(LVM) 信息 -n 显示 NFS 使用情况 -p[磁盘] 显示磁盘和分区的情况 -t 显示终端和CPU的信息 -x 显示详细信息 -V 显示版本信息 使用实例例一:显示所有设备负载情况 $ iostat Linux 3.10.0-327.el7.x86_64 (s88) 2017年01月22日 _x86_64_ (24 CPU) avg-cpu: %user %nice %system %iowait %steal %idle 0.62 0.00 0.20 1.46 0.00 97.72 Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn sda 64.59 1726.21 255.56 3159941 467823 dm-0 3.55 141.11 4.46 258319 8162 dm-1 0.10 0.83 0.00 1520 0 dm-2 0.10 2.78 1.14 5080 2082 dm-3 60.44 1565.98 248.84 2866640 455511 dm-4 27.54 463.29 105.38 848088 192897 dm-5 1.25 25.57 17.57 46804 32170 dm-6 0.64 12.86 2.07 23535 3786 dm-7 4.14 80.43 36.60 147240 67004 dm-8 1.13 20.52 2.42 37566 4428 dm-9 1.13 21.18 2.40 38766 4396 dm-10 1.15 21.35 2.41 39082 4412 dm-11 0.70 14.40 2.21 26355 4043 dm-12 1.42 22.42 6.85 41035 12541 dm-13 0.46 12.17 1.25 22275 2289 dm-14 1.15 20.47 2.42 37470 4432 dm-15 8.28 101.07 16.51 185018 30220 dm-16 1.10 20.02 2.45 36646 4488 dm-17 1.81 29.08 4.15 53232 7591 dm-18 0.68 18.40 1.43 33689 2611 dm-19 2.33 43.89 4.63 80340 8483 说明:cpu属性值说明:%user:CPU处在用户模式下的时间百分比。%nice:CPU处在带NICE值的用户模式下的时间百分比。%system:CPU处在系统模式下的时间百分比。%iowait:CPU等待输入输出完成时间的百分比。%steal:管理程序维护另一个虚拟处理器时,虚拟CPU的无意识等待时间百分比。%idle:CPU空闲时间百分比。备注:如果%iowait的值过高,表示硬盘存在I/O瓶颈,%idle值高,表示CPU较空闲,如果%idle值高但系统响应慢时,有可能是CPU等待分配内存,此时应加大内存容量。%idle值如果持续低于10,那么系统的CPU处理能力相对较低,表明系统中最需要解决的资源是CPU。disk属性值说明:rrqm/s: 每秒进行 merge 的读操作数目。即 rmerge/swrqm/s: 每秒进行 merge 的写操作数目。即 wmerge/sr/s: 每秒完成的读 I/O 设备次数。即 rio/sw/s: 每秒完成的写 I/O 设备次数。即 wio/srsec/s: 每秒读扇区数。即 rsect/swsec/s: 每秒写扇区数。即 wsect/srkB/s: 每秒读K字节数。是 rsect/s 的一半,因为每扇区大小为512字节。wkB/s: 每秒写K字节数。是 wsect/s 的一半。avgrq-sz: 平均每次设备I/O操作的数据大小 (扇区)。avgqu-sz: 平均I/O队列长度。await: 平均每次设备I/O操作的等待时间 (毫秒)。svctm: 平均每次设备I/O操作的服务时间 (毫秒)。%util: 一秒中有百分之多少的时间用于 I/O 操作,即被io消耗的cpu百分比备注:如果 %util 接近 100%,说明产生的I/O请求太多,I/O系统已经满负荷,该磁盘可能存在瓶颈。如果 svctm 比较接近 await,说明 I/O 几乎没有等待时间;如果 await 远大于 svctm,说明I/O 队列太长,io响应太慢,则需要进行必要优化。如果avgqu-sz比较大,也表示有当量io在等待。 例二:定时显示所有信息 # 每隔 2秒刷新显示,且显示3次 $ iostat 2 3 例三:显示指定磁盘信息 $ iostat -d sda1 例四:显示tty和Cpu信息 $ iostat -t 例五:以M为单位显示所有信息 $ iostat -m 例六:查看TPS和吞吐量信息 $ iostat -d -k 1 1 说明:tps:该设备每秒的传输次数(Indicate the number of transfers per second that were issued to the device.)。“一次传输”意思是“一次I/O请求”。多个逻辑请求可能会被合并为“一次I/O请求”。“一次传输”请求的大小是未知的。kB_read/s:每秒从设备(drive expressed)读取的数据量;kB_wrtn/s:每秒向设备(drive expressed)写入的数据量;kB_read:读取的总数据量;kB_wrtn:写入的总数量数据量;这些单位都为Kilobytes。上面的例子中,我们可以看到磁盘sda以及它的各个分区的统计数据,当时统计的磁盘总TPS是22.73,下面是各个分区的TPS。(因为是瞬间值,所以总TPS并不严格等于各个分区TPS的总和) 例七:查看设备使用率(%util)、响应时间(await) $ iostat -d -x -k 1 1 说明:rrqm/s: 每秒进行 merge 的读操作数目.即 delta(rmerge)/swrqm/s: 每秒进行 merge 的写操作数目.即 delta(wmerge)/sr/s: 每秒完成的读 I/O 设备次数.即 delta(rio)/sw/s: 每秒完成的写 I/O 设备次数.即 delta(wio)/srsec/s: 每秒读扇区数.即 delta(rsect)/swsec/s: 每秒写扇区数.即 delta(wsect)/srkB/s: 每秒读K字节数.是 rsect/s 的一半,因为每扇区大小为512字节.(需要计算)wkB/s: 每秒写K字节数.是 wsect/s 的一半.(需要计算)avgrq-sz:平均每次设备I/O操作的数据大小 (扇区).delta(rsect+wsect)/delta(rio+wio)avgqu-sz:平均I/O队列长度.即 delta(aveq)/s/1000 (因为aveq的单位为毫秒).await: 平均每次设备I/O操作的等待时间 (毫秒).即 delta(ruse+wuse)/delta(rio+wio)svctm: 平均每次设备I/O操作的服务时间 (毫秒).即 delta(use)/delta(rio+wio)%util: 一秒中有百分之多少的时间用于 I/O 操作,或者说一秒中有多少时间 I/O 队列是非空的,即 delta(use)/s/1000 (因为use的单位为毫秒) 如果 %util 接近 100%,说明产生的I/O请求太多,I/O系统已经满负荷,该磁盘可能存在瓶颈。 idle小于70% IO压力就较大了,一般读取速度有较多的wait。同时可以结合vmstat 查看查看b参数(等待资源的进程数)和wa参数(IO等待所占用的CPU时间的百分比,高过30%时IO压力高)。 另外 await 的参数也要多和 svctm 来参考。差的过高就一定有 IO 的问题。 avgqu-sz 也是个做 IO 调优时需要注意的地方,这个就是直接每次操作的数据的大小,如果次数多,但数据拿的小的话,其实 IO 也会很小。如果数据拿的大,才IO 的数据会高。也可以通过 avgqu-sz × ( r/s or w/s ) = rsec/s or wsec/s。也就是讲,读定速度是这个来决定的。 svctm 一般要小于 await (因为同时等待的请求的等待时间被重复计算了),svctm 的大小一般和磁盘性能有关,CPU/内存的负荷也会对其有影响,请求过多也会间接导致 svctm 的增加。await 的大小一般取决于服务时间(svctm) 以及 I/O 队列的长度和 I/O 请求的发出模式。如果 svctm 比较接近 await,说明 I/O 几乎没有等待时间;如果 await 远大于 svctm,说明 I/O 队列太长,应用得到的响应时间变慢,如果响应时间超过了用户可以容许的范围,这时可以考虑更换更快的磁盘,调整内核 elevator 算法,优化应用,或者升级 CPU。 队列长度(avgqu-sz)也可作为衡量系统 I/O 负荷的指标,但由于 avgqu-sz 是按照单位时间的平均值,所以不能反映瞬间的 I/O 洪水。形象的比喻:r/s+w/s 类似于交款人的总数平均队列长度(avgqu-sz)类似于单位时间里平均排队人的个数平均服务时间(svctm)类似于收银员的收款速度平均等待时间(await)类似于平均每人的等待时间平均I/O数据(avgrq-sz)类似于平均每人所买的东西多少I/O 操作率 (%util)类似于收款台前有人排队的时间比例 设备IO操作:总IO(io)/s = r/s(读) +w/s(写) =1.46 + 25.28=26.74 平均每次设备I/O操作只需要0.36毫秒完成,现在却需要10.57毫秒完成,因为发出的 请求太多(每秒26.74个),假如请求时同时发出的,可以这样计算平均等待时间: 平均等待时间=单个I/O服务器时间*(1+2+…+请求总数-1)/请求总数 每秒发出的I/0请求很多,但是平均队列就4,表示这些请求比较均匀,大部分处理还是比较及时。 例八:查看cpu状态 $ iostat -c 1 3","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"人生若只如初见-《围城》","slug":"Fortress-Besieged","date":"2017-01-03T03:27:04.000Z","updated":"2024-07-05T01:50:54.490Z","comments":true,"path":"2017/01/03/Fortress-Besieged/","permalink":"http://yelog.org/2017/01/03/Fortress-Besieged/","excerpt":"婚姻是被围困的城堡,城外的人想冲进去,城里的人想逃出来 –法国谚语","text":"婚姻是被围困的城堡,城外的人想冲进去,城里的人想逃出来 –法国谚语 方鸿渐对于鲍小姐,不堪抵抗;对于苏小姐的垂青,已再纠缠不清(导致方唐感情破裂的导火索);最后与孙柔嘉的婚姻,因属于现实的无奈吧;至于唐晓芙,方对她应该有着最纯粹的爱意。 整部小说给我留下深刻印象的就是唐小姐,她在整部小说的占比是非常少的。 总而言之,唐小姐是摩登文明社会里的那桩罕物――一个真正的女孩子。有许多都市女孩子已经是装模作样的早熟的女人,算不得孩子;有许多女孩子只是浑沌痴顽的无性别的孩子,还说不上女人。 唐晓芙的聪明漂亮、活泼可爱,令方鸿渐一见倾心。 当初,是苏小姐的干预,激起了唐晓芙的逆反心理,不让接近我偏接近,书信往来,见面谈话,时间推移,俩人都投入了真正的感情。 作为苏的姐妹,唐晓芙骨子里也有跟苏小姐的高傲。 方先生的过去太丰富了!我爱的人,我要能够占领他整个生命,他在碰见我以前,没有过去,留着空白等待我—— 方下定决心写信给苏撇清两人关系,苏的狭隘的心理从中挑拨导致方与唐之间的破裂 他象一个受了责骂的孩子那样,泪水在眼睛里打转,却一句话也说不出口。 所以当唐得知方的一些列斑斑劣迹后,愈心痛愈心恨,最后一次见方,连珠炮的发问,又恨方鸿渐为什么不辩护,她的心溶成了苦水。而方鸿渐的悲剧在于,他再次懦弱,该辩解时不辩解,该在屋外多淋雨时而过早走开,接到电话后,不问来人就大声呵斥,俩人都是好面子的人,看不到俩人为这段感情而去采取任何补救措施,而是各自松开了手。两人年轻,都不知退让,任彼此失之交臂。 从此直到最后,唐也没有再次出现。 但唐给我们的留下了一个近乎完美的形象,正是因为她没有和方走在一起,没有真实世俗的一面,才能留下那种只如初见的模样。 生活亦是如此,我们心目中的“女神”、“男神”,完美无缺的人,是那些我们曾经追求不得的人。想想若是得之,经过世俗的一面,ta的完美的形象,还会在你的心中站的稳吗。 唐晓芙这个角色是钱老钟爱的角色,是钱老心中完美的女性形象,简单说就是女神!是围城里任何男人都配不上的,所以不舍得把她许配给任何人。 --杨绛先生 《围城》写出了婚姻的一方面,但不是全部,很多人要冲进这座城,自有其道理,城中有争吵,但更有温情。愿诸位在现实生活中,相互欣赏,相敬如宾,如初见一样,相互爱戴,生活一定更加美好 在新春佳节祝大家幸福美满,阖家欢乐。","categories":[{"name":"读书","slug":"读书","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/"},{"name":"阅读笔记","slug":"读书/阅读笔记","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"reading","slug":"reading","permalink":"http://yelog.org/tags/reading/"}]},{"title":"每天一个linux命令(34): vmstat","slug":"linux-command-34-vmstat","date":"2017-01-03T01:46:57.000Z","updated":"2024-07-05T01:50:55.619Z","comments":true,"path":"2017/01/03/linux-command-34-vmstat/","permalink":"http://yelog.org/2017/01/03/linux-command-34-vmstat/","excerpt":"vmstat是Virtual Meomory Statistics(虚拟内存统计)的缩写,可对操作系统的虚拟内存、进程、CPU活动进行监控。他是对系统的整体情况进行统计,不足之处是无法对某个进程进行深入分析。vmstat 工具提供了一种低开销的系统性能观察方式。因为 vmstat 本身就是低开销工具,在非常高负荷的服务器上,你需要查看并监控系统的健康情况,在控制窗口还是能够使用vmstat 输出结果。在学习vmstat命令前,我们先了解一下Linux系统中关于物理内存和虚拟内存相关信息。","text":"vmstat是Virtual Meomory Statistics(虚拟内存统计)的缩写,可对操作系统的虚拟内存、进程、CPU活动进行监控。他是对系统的整体情况进行统计,不足之处是无法对某个进程进行深入分析。vmstat 工具提供了一种低开销的系统性能观察方式。因为 vmstat 本身就是低开销工具,在非常高负荷的服务器上,你需要查看并监控系统的健康情况,在控制窗口还是能够使用vmstat 输出结果。在学习vmstat命令前,我们先了解一下Linux系统中关于物理内存和虚拟内存相关信息。 物理内存和虚拟内存区别 我们知道,直接从物理内存读写数据要比从硬盘读写数据要快的多,因此,我们希望所有数据的读取和写入都在内存完成,而内存是有限的,这样就引出了物理内存与虚拟内存的概念。 物理内存就是系统硬件提供的内存大小,是真正的内存,相对于物理内存,在linux下还有一个虚拟内存的概念,虚拟内存就是为了满足物理内存的不足而提出的策略,它是利用磁盘空间虚拟出的一块逻辑内存,用作虚拟内存的磁盘空间被称为交换空间(Swap Space)。 作为物理内存的扩展,linux会在物理内存不足时,使用交换分区的虚拟内存,更详细的说,就是内核会将暂时不用的内存块信息写到交换空间,这样以来,物理内存得到了释放,这块内存就可以用于其它目的,当需要用到原始的内容时,这些信息会被重新从交换空间读入物理内存。 linux的内存管理采取的是分页存取机制,为了保证物理内存能得到充分的利用,内核会在适当的时候将物理内存中不经常使用的数据块自动交换到虚拟内存中,而将经常使用的信息保留到物理内存。 要深入了解linux内存运行机制,需要知道下面提到的几个方面: 首先,Linux系统会不时的进行页面交换操作,以保持尽可能多的空闲物理内存,即使并没有什么事情需要内存,Linux也会交换出暂时不用的内存页面。这可以避免等待交换所需的时间。 其次,linux进行页面交换是有条件的,不是所有页面在不用时都交换到虚拟内存,linux内核根据”最近最经常使用“算法,仅仅将一些不经常使用的页面文件交换到虚拟内存,有时我们会看到这么一个现象:linux物理内存还有很多,但是交换空间也使用了很多。其实,这并不奇怪,例如,一个占用很大内存的进程运行时,需要耗费很多内存资源,此时就会有一些不常用页面文件被交换到虚拟内存中,但后来这个占用很多内存资源的进程结束并释放了很多内存时,刚才被交换出去的页面文件并不会自动的交换进物理内存,除非有这个必要,那么此刻系统物理内存就会空闲很多,同时交换空间也在被使用,就出现了刚才所说的现象了。关于这点,不用担心什么,只要知道是怎么一回事就可以了。 最后,交换空间的页面在使用时会首先被交换到物理内存,如果此时没有足够的物理内存来容纳这些页面,它们又会被马上交换出去,如此以来,虚拟内存中可能没有足够空间来存储这些交换页面,最终会导致linux出现假死机、服务异常等问题,linux虽然可以在一段时间内自行恢复,但是恢复后的系统已经基本不可用了。 因此,合理规划和设计linux内存的使用,是非常重要的。 虚拟内存原理: 在系统中运行的每个进程都需要使用到内存,但不是每个进程都需要每时每刻使用系统分配的内存空间。当系统运行所需内存超过实际的物理内存,内核会释放某些进程所占用但未使用的部分或所有物理内存,将这部分资料存储在磁盘上直到进程下一次调用,并将释放出的内存提供给有需要的进程使用。 在Linux内存管理中,主要是通过“调页Paging”和“交换Swapping”来完成上述的内存调度。调页算法是将内存中最近不常使用的页面换到磁盘上,把活动页面保留在内存中供进程使用。交换技术是将整个进程,而不是部分页面,全部交换到磁盘上。 分页(Page)写入磁盘的过程被称作Page-Out,分页(Page)从磁盘重新回到内存的过程被称作Page-In。当内核需要一个分页时,但发现此分页不在物理内存中(因为已经被Page-Out了),此时就发生了分页错误(Page Fault)。 当系统内核发现可运行内存变少时,就会通过Page-Out来释放一部分物理内存。经管Page-Out不是经常发生,但是如果Page-out频繁不断的发生,直到当内核管理分页的时间超过运行程式的时间时,系统效能会急剧下降。这时的系统已经运行非常慢或进入暂停状态,这种状态亦被称作thrashing(颠簸)。 命令格式$ vmstat [-a] [-n] [-S unit] [delay [ count]] $ vmstat [-s] [-n] [-S unit] $ vmstat [-m] [-n] [delay [ count]] $ vmstat [-d] [-n] [delay [ count]] $ vmstat [-p disk partition] [-n] [delay [ count]] $ vmstat [-f] $ vmstat [-V] 命令功能 用来显示虚拟内存的信息 命令参数 命令 描述 -a 显示活跃和非活跃内存 -f 显示从系统启动至今的fork数量 -m 显示slabinfo -n 只在开始时显示一次各字段名称 -s 显示内存相关统计信息及多种系统活动数量 delay 刷新时间间隔。如果不指定,只显示一条结果 count 刷新次数。如果不指定刷新次数,但指定了刷新时间间隔,这时刷新次数为无穷 -d 显示磁盘相关统计信息 -p 显示指定磁盘分区统计信息 -S 使用指定单位显示。参数有 k 、K 、m 、M ,分别代表1000、1024、1000000、1048576字节(byte)。默认单位为K(1024 bytes) -V 显示vmstat版本信息 使用实例例一:显示虚拟内存使用情况 $ vmstat procs -----------memory---------- ---swap-- -----io--- --system--- -----cpu--- r b swpd free buff cache si so bi bo in cs us sy id wa st 0 0 0 7108340 129544 3155916 0 0 184 53 203 995 4 1 95 0 0 说明:Procs(进程):r: 运行队列中进程数量b: 等待IO的进程数量Memory(内存):swpd: 使用虚拟内存大小free: 可用内存大小buff: 用作缓冲的内存大小cache: 用作缓存的内存大小Swap:si: 每秒从交换区写到内存的大小so: 每秒写入交换区的内存大小IO:(现在的Linux版本块的大小为1024bytes)bi: 每秒读取的块数bo: 每秒写入的块数系统:in: 每秒中断数,包括时钟中断。cs: 每秒上下文切换数。CPU(以百分比表示):us: 用户进程执行时间(user time)sy: 系统进程执行时间(system time)id: 空闲时间(包括IO等待时间),中央处理器的空闲时间 。以百分比表示。wa: 等待IO时间备注: 如果 r经常大于 4 ,且id经常少于40,表示cpu的负荷很重。如果pi,po 长期不等于0,表示内存不足。如果disk 经常不等于0, 且在 b中的队列 大于3, 表示 io性能不好。Linux在具有高稳定性、可靠性的同时,具有很好的可伸缩性和扩展性,能够针对不同的应用和硬件环境调整,优化出满足当前应用需要的最佳性能。因此企业在维护Linux系统、进行系统调优时,了解系统性能分析工具是至关重要的。命令:vmstat 5 5表示在5秒时间内进行5次采样。将得到一个数据汇总他能够反映真正的系统情况。 例二:显示活跃和非活跃内存 $ vmstat -a 2 5 说明:使用-a选项显示活跃和非活跃内存时,所显示的内容除增加inact和active外,其他显示内容与例子1相同。Memory(内存):inact: 非活跃内存大小(当使用-a选项时显示)active: 活跃的内存大小(当使用-a选项时显示) 例三:查看系统已经fork了多少次 $ vmstat -f 说明:这个数据是从/proc/stat中的processes字段里取得的 例四:查看内存使用的详细信息 $ vmstat -s 说明:这些信息的分别来自于/proc/meminfo,/proc/stat和/proc/vmstat。 例五:查看磁盘的读/写 $ vmstat -d 说明:这些信息主要来自于/proc/diskstats.merged:表示一次来自于合并的写/读请求,一般系统会把多个连接/邻近的读/写请求合并到一起来操作. 例六:查看/dev/sda1磁盘的读/写 $ vmstat -p /dev/sda1 说明:这些信息主要来自于/proc/diskstats。reads:来自于这个分区的读的次数。read sectors:来自于这个分区的读扇区的次数。writes:来自于这个分区的写的次数。requested writes:来自于这个分区的写请求次数。 例七:查看系统的slab信息 $ vmstat -m 说明:这组信息来自于/proc/slabinfo。slab:由于内核会有许多小对象,这些对象构造销毁十分频繁,比如i-node,dentry,这些对象如果每次构建的时候就向内存要一个页(4kb),而其实只有几个字节,这样就会非常浪费,为了解决这个问题,就引入了一种新的机制来处理在同一个页框中如何分配小存储区,而slab可以对小对象进行分配,这样就不用为每一个对象分配页框,从而节省了空间,内核对一些小对象创建析构很频繁,slab对这些小对象进行缓冲,可以重复利用,减少内存分配次数。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(33): free","slug":"linux-command-33-free","date":"2017-01-02T13:43:26.000Z","updated":"2024-07-05T01:50:55.510Z","comments":true,"path":"2017/01/02/linux-command-33-free/","permalink":"http://yelog.org/2017/01/02/linux-command-33-free/","excerpt":"free命令可以显示Linux系统中空闲的、已用的物理内存及swap内存,及被内核使用的buffer。在Linux系统监控的工具中,free命令是最经常使用的命令之一。","text":"free命令可以显示Linux系统中空闲的、已用的物理内存及swap内存,及被内核使用的buffer。在Linux系统监控的工具中,free命令是最经常使用的命令之一。 命令格式$ free [参数] 命令功能 free 命令显示系统使用和空闲的内存情况,包括物理内存、交互区内存(swap)和内核缓冲区内存。共享内存将被忽略 命令参数 命令 描述 -b 以Byte为单位显示内存使用情况 -k 以KB为单位显示内存使用情况 -m 以MB为单位显示内存使用情况 -g 以GB为单位显示内存使用情况 -o 不显示缓冲区调节列 -s<间隔秒数> 持续观察内存使用状况 -t 显示内存总和列 -V 显示版本信息 使用实例例一:显示内存使用情况 $ free total used free shared buff/cache available Mem: 12095180 8362640 198460 1379116 3534080 2100004 Swap: 8185112 40008 8145104 说明:total:总计物理内存的大小。used:已使用多大。free:可用有多少。Shared:多个进程共享的内存总额。Buffers/cached:磁盘缓存的大小。 例二:显示内存使用情况 $ free -t 例三:周期性的查询内存使用信息 # 每10s 执行一次命令 $ free -s 10","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(32): top","slug":"linux-command-32-top","date":"2017-01-01T11:31:28.000Z","updated":"2024-07-05T01:50:55.486Z","comments":true,"path":"2017/01/01/linux-command-32-top/","permalink":"http://yelog.org/2017/01/01/linux-command-32-top/","excerpt":"top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。下面详细介绍它的使用方法。top是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直到用户终止该程序为止.比较准确的说,top命令提供了实时的对系统处理器的状态监视.它将显示系统中CPU最“敏感”的任务列表.该命令可以按CPU使用.内存使用和执行时间对任务进行排序;而且该命令的很多特性都可以通过交互式命令或者在个人定制文件中进行设定。","text":"top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。下面详细介绍它的使用方法。top是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直到用户终止该程序为止.比较准确的说,top命令提供了实时的对系统处理器的状态监视.它将显示系统中CPU最“敏感”的任务列表.该命令可以按CPU使用.内存使用和执行时间对任务进行排序;而且该命令的很多特性都可以通过交互式命令或者在个人定制文件中进行设定。 命令格式$ top [参数] 命令功能 显示当前系统正在执行的进程的相关信息,包括进程ID、内存占用率、CPU占用率等 命令参数 参数 描述 -b 批处理 -c 显示完整的治命令 -I 忽略失效过程 -s 保密模式 -S 累积模式 -i<时间> 设置间隔时间 -u<用户名> 指定用户名 -p<进程号> 指定进程 -n<次数> 循环显示的次数 top交互命令 在top 命令执行过程中可以使用的一些交互命令。这些命令都是单字母的,如果在命令行中使用了s 选项, 其中一些命令可能会被屏蔽。 参数 描述 h 显示帮助画面,给出一些简短的命令总结说明 k 终止一个进程。 i 忽略闲置和僵死进程。这是一个开关式命令 q 退出程序 r 重新安排一个进程的优先级别 S 切换到累计模式 s 改变两次刷新之间的延迟时间(单位为s),如果有小数,就换算成m s。输入0值则系统将不断刷新,默认值是5 s f或者F 从当前显示中添加或者删除项目 o或者O 改变显示项目的顺序 l 切换显示平均负载和启动时间信息 m 切换显示内存信息 t 切换显示进程和CPU状态信息 c 切换显示命令名称和完整命令行 M 根据驻留内存大小进行排序 P 根据CPU使用百分比大小进行排序 T 根据时间/累计时间进行排序 W 将当前设置写入~/.toprc文件中 使用实例例一:显示进程信息 $ top top讲解其他技巧 数字1,可监控每个逻辑CPU的状况 键盘b(打开/关闭加亮效果),运行状态的进程 键盘x 打开/关闭排序列的加亮效果 shift + >或shift + <改变排序列 例二:显示 完整命令 $ top -c 例三:以批处理模式显示程序信息 $ top -b 例四:以累积模式显示程序信息 $ top -S 例五:设置信息更新次数 # 表示更新两次后终止更新显示 $ top -n 2 例六:设置信息更新时间 # 表示更新周期为3秒 $ top -d 3 例七:显示指定的进程信息 $ top -p 574","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(31): du","slug":"linux-command-31-du","date":"2016-12-31T06:36:01.000Z","updated":"2024-07-05T01:50:55.506Z","comments":true,"path":"2016/12/31/linux-command-31-du/","permalink":"http://yelog.org/2016/12/31/linux-command-31-du/","excerpt":"Linux du命令也是查看使用空间的,但是与df命令不同的是Linux du命令是对文件和目录磁盘使用的空间的查看,还是和df命令有一些区别的.","text":"Linux du命令也是查看使用空间的,但是与df命令不同的是Linux du命令是对文件和目录磁盘使用的空间的查看,还是和df命令有一些区别的. 命令格式$ du [选项][文件] 命令功能 显示每个文件和目录的磁盘使用空间。 命令参数 参数 描述 -a或-all 显示目录中个别文件的大小。 -b或-bytes 显示目录或文件大小时,以byte为单位。 -c或–total 除了显示个别目录或文件的大小外,同时也显示所有目录或文件的总和。 -k或–kilobytes 以KB(1024bytes)为单位输出。 -m或–megabytes 以MB为单位输出。 -s或–summarize 仅显示总计,只列出最后加总的值。 -h或–human-readable 以K,M,G为单位,提高信息的可读性。 -x或–one-file-xystem 以一开始处理时的文件系统为准,若遇上其它不同的文件系统目录则略过。 -L<符号链接>或–dereference<符号链接> 显示选项中所指定符号链接的源文件大小。 -S或–separate-dirs 显示个别目录的大小时,并不含其子目录的大小。 -X<文件>或–exclude-from=<文件> 在<文件>指定目录或文件。 –exclude=<目录或文件> 略过指定的目录或文件。 -D或–dereference-args 显示指定符号链接的源文件大小。 -H或–si 与-h参数相同,但是K,M,G是以1000为换算单位。 -l或–count-links 重复计算硬件链接的文件。 使用实例例一:显示目录或者文件所占空间 $ du 说明: 只显示当前目录下面的子目录的目录大小和当前目录的总的大小,最下面的1288为当前目录的总大小 例二:显示指定文件所占空间 $ du log2012.log 例三:查看指定目录的所占空间 $ du scf 例四:显示多个文件所占空间 $ du log30.tar.gz log31.tar.gz 例五:只显示总和的大小 $ du -s 例六:方便阅读的格式显示 $ du -h test 例七:文件和目录都显示 $ du -ah test 例八:显示几个文件或目录各自占用磁盘空间的大小,还统计它们的总和 $ du -c log30.tar.gz log31.tar.gz 说明: 加上-c选项后,du不仅显示两个目录各自占用磁盘空间的大小,还在最后一行统计它们的总和。 例九:按照空间大小排序 $ du|sort -nr|more 例十:输出当前目录下各个子目录所使用的空间 $ du -h --max-depth=1","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(30): df","slug":"linux-command-30-df","date":"2016-12-30T06:23:31.000Z","updated":"2024-07-05T01:50:55.472Z","comments":true,"path":"2016/12/30/linux-command-30-df/","permalink":"http://yelog.org/2016/12/30/linux-command-30-df/","excerpt":"linux中df命令的功能是用来检查linux服务器的文件系统的磁盘空间占用情况。可以利用该命令来获取硬盘被占用了多少空间,目前还剩下多少空间等信息。","text":"linux中df命令的功能是用来检查linux服务器的文件系统的磁盘空间占用情况。可以利用该命令来获取硬盘被占用了多少空间,目前还剩下多少空间等信息。 命令格式$ df [选项] [文件] 命令功能 显示指定磁盘文件的可用空间。如果没有文件名被指定,则所有当前被挂载的文件系统的可用空间将被显示。默认情况下,磁盘空间将以 1KB 为单位进行显示,除非环境变量 POSIXLY_CORRECT 被指定,那样将以512字节为单位进行显示 命令参数必要参数 参数 描述 -a 全部文件系统列表 -h 方便阅读方式显示 -H 等于“-h”,但是计算式,1K=1000,而不是1K=1024 -i 显示inode信息 -k 区块为1024字节 -l 只显示本地文件系统 -m 区块为1048576字节 –no-sync 忽略 sync 命令 -P 输出格式为POSIX –sync 在取得磁盘信息前,先执行sync命令 -T 文件系统类型 选择参数 参数 描述 –block-size=<区块大小> 指定区块大小 -t<文件系统类型> 只显示选定文件系统的磁盘信息 -x<文件系统类型> 不显示选定文件系统的磁盘信息 –help 显示帮助信息 –version 显示版本信息 使用实例例一:显示磁盘使用情况 $ df 说明: linux中df命令的输出清单的第1列是代表文件系统对应的设备文件的路径名(一般是硬盘上的分区);第2列给出分区包含的数据块(1024字节)的数目;第3,4列分别表示已用的和可用的数据块数目。用户也许会感到奇怪的是,第3,4列块数之和不等于第2列中的块数。这是因为缺省的每个分区都留了少量空间供系统管理员使用。即使遇到普通用户空间已满的情况,管理员仍能登录和留有解决问题所需的工作空间。清单中Use% 列表示普通用户空间使用的百分比,即使这一数字达到100%,分区仍然留有系统管理员使用的空间。最后,Mounted on列表示文件系统的挂载点。 例二:以inode模式来显示磁盘使用情况 $ df -i 例三:显示指定类型磁盘 $ df -t ext3 例四:列出各文件系统的i节点使用情况 $ df -ia 例五:列出文件系统的类型 $ df -T 例六:以更易读的方式显示目前磁盘空间和使用情况 $ df -h 说明:-h更具目前磁盘空间和使用情况 以更易读的方式显示-H根上面的-h参数相同,不过在根式化的时候,采用1000而不是1024进行容量转换-k以单位显示磁盘的使用情况-l显示本地的分区的磁盘空间使用率,如果服务器nfs了远程服务器的磁盘,那么在df上加上-l后系统显示的是过滤nsf驱动器后的结果-i显示inode的使用情况。linux采用了类似指针的方式管理磁盘空间影射.这也是一个比较关键应用","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(29): /etc/group文件详解","slug":"linux-command-29-group","date":"2016-12-29T06:13:05.000Z","updated":"2024-07-05T01:50:55.524Z","comments":true,"path":"2016/12/29/linux-command-29-group/","permalink":"http://yelog.org/2016/12/29/linux-command-29-group/","excerpt":"Linux /etc/group文件与/etc/passwd和/etc/shadow文件都是有关于系统管理员对用户和用户组管理时相关的文件。linux /etc/group文件是有关于系统管理员对用户和用户组管理的文件,linux用户组的所有信息都存放在/etc/group文件中。具有某种共同特征的用户集合起来就是用户组(Group)。用户组(Group)配置文件主要有 /etc/group和/etc/gshadow,其中/etc/gshadow是/etc/group的加密信息文件。","text":"Linux /etc/group文件与/etc/passwd和/etc/shadow文件都是有关于系统管理员对用户和用户组管理时相关的文件。linux /etc/group文件是有关于系统管理员对用户和用户组管理的文件,linux用户组的所有信息都存放在/etc/group文件中。具有某种共同特征的用户集合起来就是用户组(Group)。用户组(Group)配置文件主要有 /etc/group和/etc/gshadow,其中/etc/gshadow是/etc/group的加密信息文件。 将用户分组是Linux系统中对用户进行管理及控制访问权限的一种手段。每个用户都属于某个用户组;一个组中可以有多个用户,一个用户也可以属于不 同的组。当一个用户同时是多个组中的成员时,在/etc/passwd文件中记录的是用户所属的主组,也就是登录时所属的默认组,而其他组称为附加组。 用户组的所有信息都存放在/etc/group文件中。此文件的格式是由冒号(:)隔开若干个字段,这些字段具体如下: 组名:口令:组标识号:组内用户列表 解释组名: 组名是用户组的名称,由字母或数字构成。与/etc/passwd中的登录名一样,组名不应重复。口令: 口令字段存放的是用户组加密后的口令字。一般Linux系统的用户组都没有口令,即这个字段一般为空,或者是*。组标识号: 组标识号与用户标识号类似,也是一个整数,被系统内部用来标识组。别称GID.组内用户列表: 是属于这个组的所有用户的列表,不同用户之间用逗号(,)分隔。这个用户组可能是用户的主组,也可能是附加组。 使用实例$ cat /etc/group 说明: 我们以root:x:0:root,linuxsir 为例: 用户组root,x是密码段,表示没有设置密码,GID是0,root用户组下包括root、linuxsir以及GID为0的其它用户。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(28): chown","slug":"linux-command-28-chown","date":"2016-12-28T01:51:30.000Z","updated":"2024-07-05T01:50:55.697Z","comments":true,"path":"2016/12/28/linux-command-28-chown/","permalink":"http://yelog.org/2016/12/28/linux-command-28-chown/","excerpt":"chown将指定文件的拥有者改为指定的用户或组,用户可以是用户名或者用户ID;组可以是组名或者组ID;文件是以空格分开的要改变权限的文件列表,支持通配符。系统管理员经常使用chown命令,在将文件拷贝到另一个用户的名录下之后,让用户拥有使用该文件的权限。","text":"chown将指定文件的拥有者改为指定的用户或组,用户可以是用户名或者用户ID;组可以是组名或者组ID;文件是以空格分开的要改变权限的文件列表,支持通配符。系统管理员经常使用chown命令,在将文件拷贝到另一个用户的名录下之后,让用户拥有使用该文件的权限。 命令格式$ chown [选项]... [所有者][:[组]] 文件... 命令功能 通过chown改变文件的拥有者和群组。在更改文件的所有者或所属群组时,可以使用用户名称和用户识别码设置。普通用户不能将自己的文件改变成其他的拥有者。其操作权限一般为管理员。 命令参数必要参数 参数 描述 -c 显示更改的部分的信息 -f 忽略错误信息 -h 修复符号链接 -R 处理指定目录以及其子目录下的所有文件 -v 显示详细的处理信息 -deference 作用于符号链接的指向,而不是链接文件本身 选择参数 参数 描述 –reference=<目录或文件> 把指定的目录/文件作为参考,把操作的文件/目录设置成参考文件/目录相同拥有者和群组 –from=<当前用户:当前群组> 只有当前用户和群组跟指定的用户和群组相同时才进行改变 –help 显示帮助信息 –version 显示版本信息 命令实例例一:改变拥有者和群组 $ chown mail:mail log2012.log 例二:改变文件拥有者和群组 # 组可为空,默认为root所在组 $ chown root: log2012.log 例三:改变文件群组 # 只改变所在组 $ chown :mail log2012.log 例四:改变指定目录以及其子目录下的所有文件的拥有者和群组 $ chown -R -v root:mail test6","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(27): chgrp","slug":"linux-command-27-chgrp","date":"2016-12-27T01:40:06.000Z","updated":"2024-07-05T01:50:55.568Z","comments":true,"path":"2016/12/27/linux-command-27-chgrp/","permalink":"http://yelog.org/2016/12/27/linux-command-27-chgrp/","excerpt":"在lunix系统里,文件或目录的权限的掌控以拥有者及所诉群组来管理。可以使用chgrp指令取变更文件与目录所属群组,这种方式采用群组名称或群组识别码都可以。Chgrp命令就是change group的缩写!要被改变的组名必须要在/etc/group文件内存在才行。","text":"在lunix系统里,文件或目录的权限的掌控以拥有者及所诉群组来管理。可以使用chgrp指令取变更文件与目录所属群组,这种方式采用群组名称或群组识别码都可以。Chgrp命令就是change group的缩写!要被改变的组名必须要在/etc/group文件内存在才行。 命令格式$ chgrp [选项] [组] [文件] 命令功能 chgrp命令可采用群组名称或群组识别码的方式改变文件或目录的所属群组。使用权限是超级用户。 命令参数必要参数 参数 描述 -c 当发生改变时,报告处理信息 -f 不显示错误信息 -R 处理指定目录以及其子目录下的所有文件 -v 运行时显示详细的处理信息 –dereference 作用于符号链接的指向,而不是符号链接本身 –no-dereference 作用于符号链接本身 选择参数 参数 描述 –reference=<文件或者目录> 设置为和指定的文件或目录的权限一样 –help 显示帮助信息 –version 显示版本信息 命令实例例一:改变文件的群组属性 # 将log2012.log文件由root群组改为bin群组 $ chgrp -v bin log2012.log “log2012.log” 的所属组已更改为 bin 例二:根据指定文件改变文件的群组属性 # 改变文件log2013.log 的群组属性,使得文件log2013.log的群组属性和参考文件log2012.log的群组属性相同 $ chgrp --reference=log2012.log log2013.log 例三:改变指定目录以及其子目录下的所有文件的群组属性 # 改变指定目录以及其子目录下的所有文件的群组属性 $ chgrp -R bin test6 例四:通过群组识别码改变文件群组属性 # 通过群组识别码改变文件群组属性,100为users群组的识别码,具体群组和群组识别码可以去/etc/group文件中查看 $ chgrp -R 100 test6","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(26): chmod","slug":"linux-command-26-chmod","date":"2016-12-26T12:22:43.000Z","updated":"2024-07-05T01:50:55.591Z","comments":true,"path":"2016/12/26/linux-command-26-chmod/","permalink":"http://yelog.org/2016/12/26/linux-command-26-chmod/","excerpt":"chmod命令用于改变linux系统文件或目录的访问权限。用它控制文件或目录的访问权限。该命令有两种用法。一种是包含字母和操作符表达式的文字设定法;另一种是包含数字的数字设定法。","text":"chmod命令用于改变linux系统文件或目录的访问权限。用它控制文件或目录的访问权限。该命令有两种用法。一种是包含字母和操作符表达式的文字设定法;另一种是包含数字的数字设定法。 Linux系统中的每个文件和目录都有访问许可权限,用它来确定谁可以通过何种方式对文件和目录进行访问和操作。 文件或目录的访问权限分为只读,只写和可执行三种。以文件为例,只读权限表示只允许读其内容,而禁止对其做任何的更改操作。可执行权限表示允许将该文件作为一个程序执行。文件被创建时,文件所有者自动拥有对该文件的读、写和可执行权限,以便于对文件的阅读和修改。用户也可根据需要把访问权限设置为需要的任何组合。 有三种不同类型的用户可对文件或目录进行访问:文件所有者,同组用户、其他用户。所有者一般是文件的创建者。所有者可以允许同组用户有权访问文件,还可以将文件的访问权限赋予系统中的其他用户。在这种情况下,系统中每一位用户都能访问该用户拥有的文件或目录。 每一文件或目录的访问权限都有三组,每组用三位表示,分别为文件属主的读、写和执行权限;与属主同组的用户的读、写和执行权限;系统中其他用户的读、写和执行权限。当用ls -l命令显示文件或目录的详细信息时,最左边的一列为文件的访问权限。 例如: $ ls -al -rw-r--r-- 1 root root 296K 11-13 06:03 log2012.log 第一列共有10个位置,第一个字符指定了文件类型。在通常意义上,一个目录也是一个文件。如果第一个字符是横线,表示是一个非目录的文件。如果是d,表示是一个目录。从第二个字符开始到第十个共9个字符,3个字符一组,分别表示了3组用户对文件或者目录的权限。权限字符用横线代表空许可,r代表只读,w代表写,x代表可执行。 - rw- r-- r-- 表示log2012.log是一个普通文件;log2012.log的属主有读写权限;与log2012.log属主同组的用户只有读权限;其他用户也只有读权限。 确定了一个文件的访问权限后,用户可以利用Linux系统提供的chmod命令来重新设定不同的访问权限。也可以利用chown命令来更改某个文件或目录的所有者。利用chgrp命令来更改某个文件或目录的用户组。 chmod命令是非常重要的,用于改变文件或目录的访问权限。用户用它控制文件或目录的访问权限。chmod命令详细情况如下。 命令格式$ chmod [-cfvR] [--help] [--version] mode file 命令功能用于改变文件或目录的访问权限,用它控制文件或目录的访问权限。 命令参数必要参数 参数 描述 -c 当发生改变时,报告处理信息 -f 错误信息不输出 -R 处理指定目录以及其子目录下的所有文件 -v 运行时显示详细处理信息 选择参数 参数 描述 –reference=<目录或者文件> 设置成具有指定目录或者文件具有相同的权限 –version 显示版本信息 <权限范围>+<权限设置> 使权限范围内的目录或者文件具有指定的权限 <权限范围>-<权限设置> 删除权限范围的目录或者文件的指定权限 <权限范围>=<权限设置> 设置权限范围内的目录或者文件的权限为指定的值 权限范围 参数 描述 u 目录或者文件的当前的用户 g 目录或者文件的当前的群组 o 除了目录或者文件的当前用户或群组之外的用户或者群组 a 所有的用户及群组 权限代号 参数 描述 r 读权限,用数字4表示 w 写权限,用数字2表示 x 执行权限,用数字1表示 - 删除权限,用数字0表示 s 特殊权限》该命令有两种用法。一种是包含字母和操作符表达式的文字设定法;另一种是包含数字的数字设定法。 文字设定法 $ chmod [who] [+ | - | =] [mode] 文件名 数字设定法 我们必须首先了解用数字表示的属性的含义:0表示没有权限,1表示可执行权限,2表示可写权限,4表示可读权限,然后将其相加。所以数字属性的格式应为3个从0到7的八进制数,其顺序是(u)(g)(o)。 例如,如果想让某个文件的属主有“读/写”二种权限,需要把4(可读)+2(可写)=6(读/写)。 数字设定法的一般形式为: chmod [mode] 文件名数字与字符对应关系如下:r=4,w=2,x=1若要rwx属性则4+2+1=7若要rw-属性则4+2=6;若要r-x属性则4+1=7。 命令实例例一:增加文件所有用户组可执行权限 $ chmod a+x log2012.log 例二:同时修改不同用户权限 $ chmod ug+w,o-x log2012.log 例三:删除文件权限 $ chmod a-x log2012.log 例四:使用“=”设置权限 $ chmod u=x log2012.log 例五:其他 # 给file的属主分配读、写、执行(7)的权限,给file的所在组分配读、执行(5)的权限,给其他用户分配执行(1)的权限 $ chmod 751 file # 上例的另一种形式 $ chmod u=rwx,g=rx,o=x file # 为所有用户分配读权限 $ chmod =r file # 同上例 $ chmod 444 file # 同上例 $ chmod a-wx,a+r file","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(25): gzip","slug":"linux-command-25-gzip","date":"2016-12-25T08:55:15.000Z","updated":"2024-07-05T01:50:55.491Z","comments":true,"path":"2016/12/25/linux-command-25-gzip/","permalink":"http://yelog.org/2016/12/25/linux-command-25-gzip/","excerpt":"减少文件大小有两个明显的好处,一是可以减少存储空间,二是通过网络传输文件时,可以减少传输的时间。gzip是在Linux系统中经常使用的一个对文件进行压缩和解压缩的命令,既方便又好用。gzip不仅可以用来压缩大的、较少使用的文件以节省磁盘空间,还可以和tar命令一起构成Linux操作系统中比较流行的压缩文件格式。据统计,gzip命令对文本文件有60%~70%的压缩率。","text":"减少文件大小有两个明显的好处,一是可以减少存储空间,二是通过网络传输文件时,可以减少传输的时间。gzip是在Linux系统中经常使用的一个对文件进行压缩和解压缩的命令,既方便又好用。gzip不仅可以用来压缩大的、较少使用的文件以节省磁盘空间,还可以和tar命令一起构成Linux操作系统中比较流行的压缩文件格式。据统计,gzip命令对文本文件有60%~70%的压缩率。 命令格式$ gzip [参数] [文件或者目录] 命令功能 gzip是个使用广泛的压缩程序,文件经它压缩过后,其名称后面会多出”.gz”的扩展名。 命令参数 参数 描述 -a或–ascii 使用ASCII文字模式。 -c或–stdout或–to-stdout 把压缩后的文件输出到标准输出设备,不去更动原始文件。 -d或–decompress或—-uncompress 解开压缩文件。 -f或–force 强行压缩文件。不理会文件名称或硬连接是否存在以及该文件是否为符号连接。 -h或–help 在线帮助。 -l或–list 列出压缩文件的相关信息。 -L或–license 显示版本与版权信息。 -n或–no-name 压缩文件时,不保存原来的文件名称及时间戳记。 -N或–name 压缩文件时,保存原来的文件名称及时间戳记。 -q或–quiet 不显示警告信息 -r或–recursive 递归处理,将指定目录下的所有文件及子目录一并处理。 -S<压缩字尾字符串>或—-suffix<压缩字尾字符串> 更改压缩字尾字符串。 -t或–test 测试压缩文件是否正确无误。 -v或–verbose 显示指令执行过程。 -V或–version 显示版本信息。 -num 用指定的数字num调整压缩的速度,-1或–fast表示最快压缩方法(低压缩比),-9或–best表示最慢压缩方法(高压缩比)。系统缺省值为6。 命令实例例一:把test目录下的每个文件压缩成.gz文件 # 忽略目录,只打包其中文件 $ gzip * 例二:把例1中每个压缩的文件解压,并列出详细的信息 $ gzip -dv * 例三:详细显示例1中每个压缩的文件的信息,并不解压 $ gzip -l * 例四:压缩一个tar备份文件,此时压缩文件的扩展名为.tar.gz $ gzip -r log.tar 例五:递归的压缩目录 $ gzip -rv test6 例六:递归地解压目录 $ gzip -dr test6","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"Git之reset揭秘","slug":"git-reset","date":"2016-12-24T03:19:24.000Z","updated":"2024-07-05T01:50:55.324Z","comments":true,"path":"2016/12/24/git-reset/","permalink":"http://yelog.org/2016/12/24/git-reset/","excerpt":"本文主要选自于《Pro Git》这本书,加上自己平时使用时的理解整理于此,以此给大家借鉴。本文主要讨论 reset 与 checkout。它们能做很多事情,所以我们要真正理解他们到底在底层做了哪些工作,以便能够恰当的运用它们。","text":"本文主要选自于《Pro Git》这本书,加上自己平时使用时的理解整理于此,以此给大家借鉴。本文主要讨论 reset 与 checkout。它们能做很多事情,所以我们要真正理解他们到底在底层做了哪些工作,以便能够恰当的运用它们。 三棵树理解 reset 和 checkout 的最简方法,就是以 Git 的思维框架(将其作为内容管理器)来管理三棵不同的树。 “树” 在我们这里的实际意思是 “文件的集合”,而不是指特定的数据结构。 (在某些情况下索引看起来并不像一棵树,不过我们现在的目的是用简单的方式思考它。) 树 描述 HEAD 上一次提交的快照,下一次提交的父结点 Index 预期的下一次提交的快照 Working Directory 沙盒 HEADHEAD 是当前分支引用的指针,它总是指向该分支上的最后一次提交。 这表示 HEAD 将是下一次提交的父结点。 通常,理解 HEAD 的最简方式,就是将它看做 你的上一次提交 的快照。 其实,查看快照的样子很容易。 下例就显示了 HEAD 快照实际的目录列表,以及其中每个文件的 SHA-1 校验和: $ git cat-file -p HEAD tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf author Scott Chacon 1301511835 -0700 committer Scott Chacon 1301511835 -0700 initial commit $ git ls-tree -r HEAD 100644 blob a906cb2a4a904a152... README 100644 blob 8f94139338f9404f2... Rakefile 040000 tree 99f1a6d12cb4b6f19... lib cat-file 与 ls-tree 是底层命令,它们一般用于底层工作,在日常工作中并不使用。不过它们能帮助我们了解到底发生了什么。 索引(Index)索引是你的 预期的下一次提交。 我们也会将这个概念引用为 Git 的 “暂存区域”,这就是当你运行 git commit 时 Git 看起来的样子。 Git 将上一次检出到工作目录中的所有文件填充到索引区,它们看起来就像最初被检出时的样子。 之后你会将其中一些文件替换为新版本,接着通过 git commit 将它们转换为树来用作新的提交。 $ git ls-files -s 100644 a906cb2a4a904a152e80877d4088654daad0c859 0 README 100644 8f94139338f9404f26296befa88755fc2598c289 0 Rakefile 100644 47c6340d6459e05787f644c2447d2595f5d3a54b 0 lib/simplegit.rb 再说一次,我们在这里又用到了 ls-files 这个幕后的命令,它会显示出索引当前的样子。 确切来说,索引并非技术上的树结构,它其实是以扁平的清单实现的。不过对我们而言,把它当做树就够了。 工作目录(Working Directory)最后,你就有了自己的工作目录。 另外两棵树以一种高效但并不直观的方式,将它们的内容存储在 .git 文件夹中。 工作目录会将它们解包为实际的文件以便编辑。 你可以把工作目录当做 沙盒。在你将修改提交到暂存区并记录到历史之前,可以随意更改。 $ tree . ├── README ├── Rakefile └── lib └── simplegit.rb 1 directory, 3 files 工作流程Git 主要的目的是通过操纵这三棵树来以更加连续的状态记录项目的快照。让我们来可视化这个过程:假设我们进入到一个新目录,其中有一个文件。 我们称其为该文件的 v1 版本,将它标记为蓝色。 现在运行 git init,这会创建一个 Git 仓库,其中的 HEAD 引用指向未创建的分支(master 还不存在)。此时,只有工作目录有内容。 现在我们想要提交这个文件,所以用 git add 来获取工作目录中的内容,并将其复制到索引中。接着运行 git commit,它首先会移除索引中的内容并将它保存为一个永久的快照,然后创建一个指向该快照的提交对象,最后更新 master 来指向本次提交。此时如果我们运行 git status,会发现没有任何改动,因为现在三棵树完全相同。 现在我们想要对文件进行修改然后提交它。 我们将会经历同样的过程;首先在工作目录中修改文件。 我们称其为该文件的 v2 版本,并将它标记为红色。如果现在运行 git status,我们会看到文件显示在 “Changes not staged for commit,” 下面并被标记为红色,因为该条目在索引与工作目录之间存在不同。 接着我们运行 git add 来将它暂存到索引中。此时,由于索引和 HEAD 不同,若运行 git status 的话就会看到 “Changes to be committed” 下的该文件变为绿色 ——也就是说,现在预期的下一次提交与上一次提交不同。 最后,我们运行 git commit 来完成提交。现在运行 git status 会没有输出,因为三棵树又变得相同了。 切换分支或克隆的过程也类似。 当检出一个分支时,它会修改 HEAD 指向新的分支引用,将 索引 填充为该次提交的快照,然后将 索引 的内容复制到 工作目录 中。 重置的作用在以下情景中观察 reset 命令会更有意义。 为了演示这些例子,假设我们再次修改了 file.txt 文件并第三次提交它。 现在的历史看起来是这样的:让我们跟着 reset 看看它都做了什么。 它以一种简单可预见的方式直接操纵这三棵树。 它做了三个基本操作。 1.移动 HEADreset 做的第一件事是移动 HEAD 的指向。 这与改变 HEAD 自身不同(checkout 所做的);reset 移动 HEAD 指向的分支。 这意味着如果 HEAD 设置为 master 分支(例如,你正在 master 分支上),运行 git reset 9e5e64a 将会使 master 指向 9e5e64a。无论你调用了何种形式的带有一个提交的 reset,它首先都会尝试这样做。 使用 reset --soft,它将仅仅停在那儿。 现在看一眼上图,理解一下发生的事情:它本质上是撤销了上一次 git commit 命令。 当你在运行 git commit 时,Git 会创建一个新的提交,并移动 HEAD 所指向的分支来使其指向该提交。 当你将它 reset 回 HEAD~(HEAD 的父结点)时,其实就是把该分支移动回原来的位置,而不会改变索引和工作目录。 现在你可以更新索引并再次运行 git commit 来完成 git commit --amend 所要做的事情了。 2.更新索引(–mixed)注意,如果你现在运行 git status 的话,就会看到新的 HEAD 和以绿色标出的它和索引之间的区别。 接下来,reset 会用 HEAD 指向的当前快照的内容来更新索引。如果指定 --mixed 选项,reset 将会在这时停止。 这也是默认行为,所以如果没有指定任何选项(在本例中只是 git reset HEAD~),这就是命令将会停止的地方。 现在再看一眼上图,理解一下发生的事情:它依然会撤销一上次 提交,但还会 取消暂存 所有的东西。 于是,我们回滚到了所有 git add 和 git commit 的命令执行之前。 3.更新工作目录reset 要做的的第三件事情就是让工作目录看起来像索引。 如果使用 –hard 选项,它将会继续这一步。现在让我们回想一下刚才发生的事情。 你撤销了最后的提交、git add 和 git commit 命令以及工作目录中的所有工作。 必须注意,--hard 标记是 reset 命令唯一的危险用法,它也是 Git 会真正地销毁数据的仅有的几个操作之一。 其他任何形式的 reset 调用都可以轻松撤消,但是 --hard 选项不能,因为它强制覆盖了工作目录中的文件。 在这种特殊情况下,我们的 Git 数据库中的一个提交内还留有该文件的 v3 版本,我们可以通过 reflog 来找回它。但是若该文件还未提交,Git 仍会覆盖它从而导致无法恢复。 回顾reset 命令会以特定的顺序重写这三棵树,在你指定以下选项时停止: 移动 HEAD 分支的指向 (若指定了 --soft,则到此停止) 使索引看起来像 HEAD (不带参数或 --mixed,则到此停止) 使工作目录看起来像索引 (指定了 --hard) 通过路径来重置前面讲述了 reset 基本形式的行为,不过你还可以给它提供一个作用路径。 若指定了一个路径,reset 将会跳过第 1 步,并且将它的作用范围限定为指定的文件或文件集合。 这样做自然有它的道理,因为 HEAD 只是一个指针,你无法让它同时指向两个提交中各自的一部分。 不过索引和工作目录 可以部分更新,所以重置会继续进行第 2、3 步。 现在,假如我们运行 git reset file.txt (这其实是 git reset --mixed HEAD file.txt 的简写形式,因为你既没有指定一个提交的 SHA-1 或分支,也没有指定 --soft 或 --hard),它会: 移动 HEAD 分支的指向 (已跳过) 让索引看起来像 HEAD (到此处停止) 所以它本质上只是将 file.txt 从 HEAD 复制到索引中。它还有 取消暂存文件 的实际效果。 如果我们查看该命令的示意图,然后再想想 git add 所做的事,就会发现它们正好相反。 这就是为什么 git status 命令的输出会建议运行此命令来取消暂存一个文件。 (查看 取消暂存的文件 来了解更多。) 我们可以不让 Git 从 HEAD 拉取数据,而是通过具体指定一个提交来拉取该文件的对应版本。 我们只需运行类似于 git reset eb43bf file.txt 的命令即可。它其实做了同样的事情,也就是把工作目录中的文件恢复到 v1 版本,运行 git add 添加它,然后再将它恢复到 v3 版本(只是不用真的过一遍这些步骤)。 如果我们现在运行 git commit,它就会记录一条“将该文件恢复到 v1 版本”的更改,尽管我们并未在工作目录中真正地再次拥有它。 还有一点同 git add 一样,就是 reset 命令也可以接受一个 --patch 选项来一块一块地取消暂存的内容。 这样你就可以根据选择来取消暂存或恢复内容了。 压缩我们来看看如何利用这种新的功能来做一些有趣的事情 - 压缩提交。 假设你的一系列提交信息中有 “oops.”、“WIP” 和 “forgot this file”, 聪明的你就能使用 reset 来轻松快速地将它们压缩成单个提交,也显出你的聪明。 (压缩提交 展示了另一种方式,不过在本例中用 reset 更简单。) 假设你有一个项目,第一次提交中有一个文件,第二次提交增加了一个新的文件并修改了第一个文件,第三次提交再次修改了第一个文件。 由于第二次提交是一个未完成的工作,因此你想要压缩它。那么可以运行 git reset --soft HEAD~2 来将 HEAD 分支移动到一个旧一点的提交上(即你想要保留的第一个提交):然后只需再次运行 git commit:现在你可以查看可到达的历史,即将会推送的历史,现在看起来有个 v1 版 file-a.txt 的提交,接着第二个提交将 file-a.txt 修改成了 v3 版并增加了 file-b.txt。 包含 v2 版本的文件已经不在历史中了。 checkout最后,你大概还想知道 checkout 和 reset 之间的区别。 和 reset 一样,checkout 也操纵三棵树,不过它有一点不同,这取决于你是否传给该命令一个文件路径。 不带路径运行 git checkout [branch] 与运行 git reset --hard [branch] 非常相似,它会更新所有三棵树使其看起来像 [branch],不过有两点重要的区别。 首先不同于 reset --hard,checkout 对工作目录是安全的,它会通过检查来确保不会将已更改的文件吹走。 其实它还更聪明一些。它会在工作目录中先试着简单合并一下,这样所有_还未修改过的_文件都会被更新。 而 reset --hard 则会不做检查就全面地替换所有东西。 第二个重要的区别是如何更新 HEAD。 reset 会移动 HEAD 分支的指向,而 checkout 只会移动 HEAD 自身来指向另一个分支。 例如,假设我们有 master 和 develop 分支,它们分别指向不同的提交;我们现在在 develop 上(所以 HEAD 指向它)。 如果我们运行 git reset master,那么 develop 自身现在会和 master 指向同一个提交。 而如果我们运行 git checkout master 的话,develop 不会移动,HEAD 自身会移动。 现在 HEAD 将会指向 master。 所以,虽然在这两种情况下我们都移动 HEAD 使其指向了提交 A,但_做法_是非常不同的。 reset 会移动 HEAD 分支的指向,而 checkout 则移动 HEAD 自身。 带路径运行 checkout 的另一种方式就是指定一个文件路径,这会像 reset 一样不会移动 HEAD。 它就像 git reset [branch] file 那样用该次提交中的那个文件来更新索引,但是它也会覆盖工作目录中对应的文件。 它就像是 git reset --hard [branch] file(如果 reset 允许你这样运行的话)- 这样对工作目录并不安全,它也不会移动 HEAD。 此外,同 git reset 和 git add 一样,checkout 也接受一个 –patch 选项,允许你根据选择一块一块地恢复文件内容。 总结希望你现在熟悉并理解了 reset 命令,不过关于它和 checkout 之间的区别,你可能还是会有点困惑,毕竟不太可能记住不同调用的所有规则。 下面的速查表列出了命令对树的影响。 “HEAD” 一列中的 “REF” 表示该命令移动了 HEAD 指向的分支引用,而‘HEAD’ 则表示只移动了 HEAD 自身。 特别注意 WD Safe? 一列 - 如果它标记为 NO,那么运行该命令之前请考虑一下。 **head****index****workdir****wd safe****commit level**`reset --soft [commit]`refnonoyes`reset [commit]`refyesnoyes`reset --hard [commit]`refyesyesno`checkout [commit]`headyesyesyes**file level**`reset (commit) [file]`noyesnoyes`checkout (commit) [file]`noyesyesno","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"}]},{"title":"每天一个linux命令(24): tar","slug":"linux-command-24-tar","date":"2016-12-24T02:24:17.000Z","updated":"2024-07-05T01:50:55.533Z","comments":true,"path":"2016/12/24/linux-command-24-tar/","permalink":"http://yelog.org/2016/12/24/linux-command-24-tar/","excerpt":"通过SSH访问服务器,难免会要用到压缩,解压缩,打包,解包等,这时候tar命令就是是必不可少的一个功能强大的工具。linux中最流行的tar是麻雀虽小,五脏俱全,功能强大。","text":"通过SSH访问服务器,难免会要用到压缩,解压缩,打包,解包等,这时候tar命令就是是必不可少的一个功能强大的工具。linux中最流行的tar是麻雀虽小,五脏俱全,功能强大。 tar命令可以为linux的文件和目录创建档案。利用tar,可以为某一特定文件创建档案(备份文件),也可以在档案中改变文件,或者向档案中加入新的文件。tar最初被用来在磁带上创建档案,现在,用户可以在任何设备上创建档案。利用tar命令,可以把一大堆的文件和目录全部打包成一个文件,这对于备份文件或将几个文件组合成为一个文件以便于网络传输是非常有用的。 首先要弄清两个概念:打包和压缩。打包是指将一大堆文件或目录变成一个总的文件;压缩则是将一个大的文件通过一些压缩算法变成一个小文件。 为什么要区分这两个概念呢?这源于Linux中很多压缩程序只能针对一个文件进行压缩,这样当你想要压缩一大堆文件时,你得先将这一大堆文件先打成一个包(tar命令),然后再用压缩程序进行压缩(gzip bzip2命令)。 linux下最常用的打包程序就是tar了,使用tar程序打出来的包我们常称为tar包,tar包文件的命令通常都是以.tar结尾的。生成tar包后,就可以用其它的程序来进行压缩。 命令格式$ tar [必要参数] [选择参数] [文件] 命令功能 用来压缩和解压文件。tar本身不具有压缩功能。他是调用压缩功能实现的 命令参数必要参数 参数 描述 -A 新增压缩文件到已存在的压缩 -B 设置区块大小 -c 建立新的压缩文件 -d 记录文件的差别 -r 添加文件到已经压缩的文件 -u 添加改变了和现有的文件到已经存在的压缩文件 -x 从压缩的文件中提取文件 -t 显示压缩文件的内容 -z 支持gzip解压文件 -j 支持bzip2解压文件 -Z 支持compress解压文件 -v 显示操作过程 -l 文件系统边界设置 -k 保留原有文件不覆盖 -m 保留文件不被覆盖 -W 确认压缩文件的正确性 可选参数 参数 描述 -b 设置区块数目 -C 切换到指定目录 -f 指定压缩文件 –help 显示帮助信息 –version 显示版本信息 使常见解压/压缩命令例一:.tar文件 $ tar xvf FileName.tar # 解包 $ tar cvf FileName.tar DirName # 打包 # 注:tar是打包,不是压缩! 例二:.gz文件 # 解压 $ gunzip FileName.gz $ gzip -d FileName.gz # 压缩 gzip FileName 例三:.tar.gz和.tgz文件 $ tar xvf FileName.tar.gz # 解包 $ tar cvf FileName.tar.gz DirName # 打包 # 注:tar是打包,不是压缩! 例四:.bz2文件 # 解压 $ bzip2 -d FileName.bz2 $ bunzip2 FileName.bz2 # 压缩 $ bzip2 -z FileName 例五:.tar.bz2文件 $ tar jxvf FileName.tar.bz2 # 解压 $ tar jcvf FileName.tar.bz2 DirName # 压缩 例六:.bz文件 # 解压 $ bzip2 -d FileName.bz $ bunzip2 FileName.bz 例七:.tar.bz文件 $ tar jxvf FileName.tar.bz # 解压 例八:.Z文件 $ uncompress FileName.Z # 解压 $ compress FileName # 压缩 例九:.tar.Z文件 $ tar Zxvf FileName.tar.Z # 解压 $ tar Zcvf FileName.tar.Z DirName # 压缩 例十:.zip文件 $ unzip FileName.zip # 解压 $ zip FileName.zip DirName # 压缩 例十一:.rar文件 $ rar x FileName.rar # 解压 $ rar a FileName.rar DirName # 压缩 使用实例例一:将文件全部打包成tar包 $ tar -cvf log.tar log2012.log # 仅打包,不压缩! $ tar -zcvf log.tar.gz log2012.log # 打包后,以 gzip 压缩 $ tar -jcvf log.tar.bz2 log2012.log # 打包后,以 bzip2 压缩 在参数 f 之后的文件档名是自己取的,我们习惯上都用 .tar 来作为辨识。 如果加 z 参数,则以 .tar.gz 或 .tgz 来代表 gzip 压缩过的 tar包; 如果加 j 参数,则以 .tar.bz2 来作为tar包名。 例二:查阅上述 tar包内有哪些文件 $ tar -ztvf log.tar.gz # 由于我们使用 gzip 压缩的log.tar.gz,所以要查阅log.tar.gz包内的文件时,就得要加上 z 这个参数了 例三:将tar 包解压缩 $ tar -zxvf /opt/soft/test/log.tar.gz # 在预设的情况下,我们可以将压缩档在任何地方解开的 例四:只将 /tar 内的 部分文件解压出来 $ tar -zxvf /opt/soft/test/log30.tar.gz log2013.log # 我可以透过 tar -ztvf 来查阅 tar 包内的文件名称,如果单只要一个文件,就可以透过这个方式来解压部分文件! 例五:文件备份下来,并且保存其权限 $ tar -zcvpf log31.tar.gz log2014.log log2015.log log2016.log # 这个 -p 的属性是很重要的,尤其是当您要保留原本文件的属性时 例六:在 文件夹当中,比某个日期新的文件才备份 $ tar -N "2012/11/13" -zcvf log17.tar.gz test 例七:备份文件夹内容是排除部分文件 $ tar --exclude scf/service -zcvf scf.tar.gz scf/*","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"Git操作之高手过招","slug":"git-master","date":"2016-12-23T02:59:01.000Z","updated":"2024-07-05T01:50:55.329Z","comments":true,"path":"2016/12/23/git-master/","permalink":"http://yelog.org/2016/12/23/git-master/","excerpt":"在使用git的过程中,总有一天你会遇到下面的问题:)这些也是在开发过程中很常见的问题,以下也是作者的经验之谈,有不对的地方还请指出。","text":"在使用git的过程中,总有一天你会遇到下面的问题:)这些也是在开发过程中很常见的问题,以下也是作者的经验之谈,有不对的地方还请指出。 最后一次commit信息写错了如果只是提交信息写错了信息,可以通过以下命令单独修改提交信息 $ git commit --amend 注意: 通过这样的过程修改提交信息后,相当于删除原来的提交,重新提交了一次。所有如果你在修改前已经将错误的那次提交push到服务端,那在修改后就需要通过 git pull 来合并代码(类似于两个分支了)。通过 git log --graph --oneline 查看就会发现两个分支合并的痕迹 最后一次commit少添加一个文件$ git add file1 $ git commit --amend 最后一次commit多添加一个文件$ git rm --cached file1 $ git commit --amend 移除add过的文件#方法一 $ git rm --cache [文件名] #方法二 $ git reset head [文件/文件夹] 回退本地commit(还未push)这种情况发生在你的本地仓库,可能你add,commit以后发现代码有点问题,打算取消提交,用到下面命令 #只会保留源码(工作区),回退commit(本地仓库)与index(暂存区)到某个版本 $ git reset <commit_id> #默认为 --mixed模式 $ git reset --mixed <commit_id> #保留源码(工作区)和index(暂存区),只回退commit(本地仓库)到某个版本 $ git reset --soft <commit_id> #源码(工作区)、commit(本地仓库)与index(暂存区)都回退到某个版本 $ git reset --hard <commit_id> 当然有人在push代码以后,也是用reset –hard回退代码到某个版本之前,但是这样会有一个问题,你线上的代码没有变化。 !!!可以通过 git push –force 将本地的回退推送到服务端,但是除非你很清楚在这么做, 不推荐. 所以,这种情况你要使用下面的方式了。 回退本地commit(已经push)对于已经把代码push到线上仓库,你回退本地代码其实也想同时回退线上代码,回滚到某个指定的版本,线上,线下代码保持一致.你要用到下面的命令 $ git revert <commit_id> 注意: git revert 用于反转提交,执行命令时要求工作树必须是干净的。 git revert 用一个新的提交来消除一个历时提交所做出的修改 回退单个文件的历史版本#查看历史版本 git log 1.txt #回退该文件到指定版本 git reset [commit_id] 1.txt git checkout 1.txt #提交 git commit -m "回退1.txt的历史版本" 修改提交历史中的author和email旧的:author:Old-Author email:old@mail.com新的:author:New-Author email:new@mail.com1.在git仓库内创建下面的脚本,如change.sh # !/bin/sh git filter-branch --env-filter ' an="$GIT_AUTHOR_NAME" am="$GIT_AUTHOR_EMAIL" cn="$GIT_COMMITTER_NAME" cm="$GIT_COMMITTER_EMAIL" if [ "$GIT_COMMITTER_EMAIL" = "old@mail.com" ] then cn="New-Author" cm="new@mail.com" fi if [ "$GIT_AUTHOR_EMAIL" = "old@mail.com" ] then an="New-Author" am="new@mail.com" fi export GIT_AUTHOR_NAME="$an" export GIT_AUTHOR_EMAIL="$am" export GIT_COMMITTER_NAME="$cn" export GIT_COMMITTER_EMAIL="$cm" ' 2.运行脚本 $ sh change.sh 忽略已提交的文件(.iml) 删除已提交的文件 # 删除项目中所有的.iml后缀的文件 $ find . -name "*.iml" | xargs rm -f 添加.gitignore文件 *.iml /**/*.iml 持续更新中~~~","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"}]},{"title":"每天一个linux命令(23): 用SecureCRT来上传和下载文件","slug":"linux-command-23-secureCRT","date":"2016-12-23T01:48:15.000Z","updated":"2024-07-05T01:50:55.660Z","comments":true,"path":"2016/12/23/linux-command-23-用SecureCRT来上传和下载文件/","permalink":"http://yelog.org/2016/12/23/linux-command-23-%E7%94%A8SecureCRT%E6%9D%A5%E4%B8%8A%E4%BC%A0%E5%92%8C%E4%B8%8B%E8%BD%BD%E6%96%87%E4%BB%B6/","excerpt":"用SSH管理linux服务器时经常需要远程与本地之间交互文件.而直接用SecureCRT自带的上传下载功能无疑是最方便的,SecureCRT下的文件传输协议有ASCII、Xmodem、Zmodem。","text":"用SSH管理linux服务器时经常需要远程与本地之间交互文件.而直接用SecureCRT自带的上传下载功能无疑是最方便的,SecureCRT下的文件传输协议有ASCII、Xmodem、Zmodem。 文件传输协议 文件传输是数据交换的主要形式。在进行文件传输时,为使文件能被正确识别和传送,我们需要在两台计算机之间建立统一的传输协议。这个协议包括了文件的识别、传送的起止时间、错误的判断与纠正等内容。常见的传输协议有以下几种: ASCII:这是最快的传输协议,单只能传输文本文件。 Xmodem:这种古老的传输协议速度较慢,但由于使用了CRC错误侦测方法,传输的准确率可高达99.6%。 Ymodem:这是Xmodem的改良版,使用了1024位区段传送,速度比Xmodem要快 Zmodem:Zmodem采用了串流式(streaming)传输方式,传输速度较快,而且还具有自动改变区段大小和断点续传、快速错误侦测等功能。这是目前最流行的文件传输协议。 除以上几种外,还有Imodem、Jmodem、Bimodem、Kermit、Lynx等协议,由于没有多数厂商支持,这里就略去不讲。 SecureCRT可以使用linux下的zmodem协议来快速的传送文件,使用非常方便.具体步骤: 在使用SecureCRT上传下载之前需要给服务器安装lrzsz 从下面的地址下载 lrzsz-0.12.20.tar.gz 我是下载地址 查看里面的INSTALL文档了解安装参数说明和细节 解压文件 $ tar zxvf lrzsz-0.12.20.tar.gz 进入目录,配置编译 $ cd lrzsz-0.12.20 $ ./configure --prefix=/usr/local/lrzsz $ make $ make install 建立软链接 $ cd /usr/bin $ ln -s /usr/local/lrzsz/bin/lrz rz $ ln -s /usr/local/lrzsz/bin/lsz sz 测试 运行 rz 弹出 SecureCrt上传窗口,用SecureCRT来上传和下载文件。 设置SecureCRT上传和下载的默认目录 options->session options ->Terminal->Xmodem/Zmodem 右栏directory设置上传和下载的目录 使用Zmodem从客户端上传文件到linux服务器 用SecureCRT登陆linux终端 选中你要放置上传文件的路径,在目录下然后输入rz命令,SecureCRT会弹出文件选择对话框,在查找范围中找到你要上传的文件,按Add按钮。然后OK就可以把文件上传到linux上了。 或者在Transfer->Zmodem Upoad list弹出文件选择对话框,选好文件后按Add按钮。然后OK窗口自动关闭。然后在linux下选中存放文件的目录,输入rz命令。liunx就把那个文件上传到这个目录下了。 使用Zmodem下载文件到客户端$ sz filename zmodem 接收可以自行启动.下载的文件存放在你设定的默认下载目录下 rz,sz 是 Linux/Unix 同 Windows 进行 ZModem 文件传输的命令行工具 , windows 端需要支持ZModem的telnet/ssh客户端,SecureCRT 就可以用 SecureCRT 登陆到 Unix/Linux 主机(telnet或ssh均可)O 运行命令rz,即是接收文件,SecureCRT就会弹出文件选择对话框,选好文件之后关闭对话框,文件就会上传到当前目录 O 运行命令sz file1 file2就是发文件到windows上(保存的目录是可以配置) 比ftp命令方便多了,而且服务器不用再开FTP服务了","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(22): find命令的参数详解","slug":"linux-command-22-find-args","date":"2016-12-22T03:44:14.000Z","updated":"2024-07-05T01:50:55.601Z","comments":true,"path":"2016/12/22/linux-command-22-find命令的参数详解/","permalink":"http://yelog.org/2016/12/22/linux-command-22-find%E5%91%BD%E4%BB%A4%E7%9A%84%E5%8F%82%E6%95%B0%E8%AF%A6%E8%A7%A3/","excerpt":"find一些常用参数的一些常用实例和一些具体用法和注意事项。","text":"find一些常用参数的一些常用实例和一些具体用法和注意事项。 使用name选项 文件名选项是find命令最常用的选项,要么单独使用该选项,要么和其他选项一起使用。可以使用某种文件名模式来匹配文件,记住要用引号将文件名模式引起来。 # 在自己的根目录$HOME中查找文件名符合*.log的文件,使用~作为 'pathname'参数,波浪号~代表了你的$HOME目录。 $ find ~ -name "*.log" -print # 在当前目录及子目录中查找所有的‘ *.log‘文件 $ find . -name "*.log" -print # 当前目录及子目录中查找文件名以一个大写字母开头的文件 $ find . -name "[A-Z]*" -print # 在/etc目录中查找文件名以host开头的文件 $ find /etc -name "host*" -print # 想要查找$HOME目录中的文件 $ find ~ -name "*" -print $ find . -print # 让系统高负荷运行,就从根目录开始查找所有的文件 $ find / -name "*" -print # 在当前目录查找文件名以一个个小写字母开头,最后是4到9加上.log结束的文件 $ find . -name "[a-z]*[4-9].log" -print 用perm选项 按照文件权限模式用-perm选项,按文件权限模式来查找文件的话。最好使用八进制的权限表示法 # 在当前目录下查找文件权限位为755的文件 $ find . -perm 755 -print 还有一种表达方法:在八进制数字前面要加一个横杠-,表示都匹配,如-007就相当于777,-005相当于555, $ find . -perm -005 忽略某个目录 如果在查找文件时希望忽略某个目录,因为你知道那个目录中没有你所要查找的文件,那么可以使用-prune选项来指出需要忽略的目录。在使用-prune选项时要当心,因为如果你同时使用了-depth选项,那么-prune选项就会被find命令忽略。如果希望在test目录下查找文件,但不希望在test/test3目录下查找,可以用: $ find test -path "test/test3" -prune -o -print 使用find查找文件的时候怎么避开某个文件目录例一:在test 目录下查找不在test4子目录之内的所有文件 $ find test -path "test/test4" -prune -o -print 说明:find [-path ..] [expression]在路径列表的后面的是表达式-path “test” -prune -o -print 是 -path “test” -a -prune -o -print 的简写表达式按顺序求值, -a 和 -o 都是短路求值,与 shell 的 && 和 || 类似如果-path “test” 为真,则求值 -prune , -prune 返回真,与逻辑表达式为真;否则不求值 -prune,与逻辑表达式为假。如果 -path “test” -a -prune 为假,则求值 -print ,-print返回真,或逻辑表达式为真;否则不求值 -print,或逻辑表达式为真。这个表达式组合特例可以用伪码写为:if -path “test” then-pruneelse-print 例二:避开多个文件夹 # 圆括号表示表达式的结合。\\ 表示引用,即指示 shell 不对后面的字符作特殊解释,而留给 find 命令去解释其意义 $ find test \\( -path test/test4 -o -path test/test3 \\) -prune -o -print 例三:查找某一确定文件,-name等选项加在-o 之后 $ find test \\(-path test/test4 -o -path test/test3 \\) -prune -o -name "*.log" -print 使用user和nouser选项# 在$HOME目录中查找文件属主为peida的文件 $ find ~ -user peida -print # 在/etc目录下查找文件属主为peida的文件 $ find /etc -user peida -print # 为了查找属主帐户已经被删除的文件,可以使用-nouser选项。在/home目录下查找所有的这类文件 $ find /home -nouser -print 使用group和nogroup选项# 在/apps目录下查找属于gem用户组的文件 $ find /apps -group gem -print # 查找没有有效所属用户组的所有文件 $ find / -nogroup-print 按照更改时间或访问时间等查找文件 如果希望按照更改时间来查找文件,可以使用mtime,atime或ctime选项。如果系统突然没有可用空间了,很有可能某一个文件的长度在此期间增长迅速,这时就可以用mtime选项来查找这样的文件。用减号-来限定更改时间在距今n日以内的文件,而用加号+来限定更改时间在距今n日以前的文件 # 在系统根目录下查找更改时间在5日以内的文件 $ find / -mtime -5 -print # 在/var/adm目录下查找更改时间在3日以前的文件 $ find /var/adm -mtime +3 -print 查找比某个文件新或旧的文件 如果希望查找更改时间比某个文件新但比另一个文件旧的所有文件,可以使用-newer选项。 # 查找更改时间比文件log2012.log新但比文件log2017.log旧的文件 $ find -newer log2012.log ! -newer log2017.log # 查找更改时间在比log2012.log文件新的文件 $ find . -newer log2012.log -print 使用type选项# 在/etc目录下查找所有的目录 $ find /etc -type d -print # 在当前目录下查找除目录以外的所有类型的文件 $ find . ! -type d -print # 在/etc目录下查找所有的符号链接文件 $ find /etc -type l -print 使用size选项 可以按照文件长度来查找文件,这里所指的文件长度既可以用块(block)来计量,也可以用字节来计量。以字节计量文件长度的表达形式为N c;以块计量文件长度只用数字表示即可。在按照文件长度查找文件时,一般使用这种以字节表示的文件长度,在查看文件系统的大小,因为这时使用块来计量更容易转换。 # 在当前目录下查找文件长度大于1 M字节的文件 $ find . -size +1000000c -print # 在/home/apache目录下查找文件长度恰好为100字节的文件 $ find /home/apache -size 100c -print # 在当前目录下查找长度超过10块的文件(一块等于512字节) $ find . -size +10 -print 使用depth选项 在使用find命令时,可能希望先匹配所有的文件,再在子目录中查找。使用depth选项就可以使find命令这样做。这样做的一个原因就是,当在使用find命令向磁带上备份文件系统时,希望首先备份所有的文件,其次再备份子目录中的文件。 # find命令从文件系统的根目录开始,查找一个名为CON.FILE的文件 # 它将首先匹配所有的文件然后再进入子目录中查找 $ find / -name "CON.FILE" -depth -print 使用mount选项 在当前的文件系统中查找文件(不进入其他文件系统),可以使用find命令的mount选项 # 从当前目录开始查找位于本文件系统中文件名以XC结尾的文件 $ find . -name "*.XC" -mount -print","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"Git之SSH与HTTPS免密码配置","slug":"git-ssh-https-verify-configuration","date":"2016-12-21T07:31:55.000Z","updated":"2024-07-05T01:50:55.342Z","comments":true,"path":"2016/12/21/git-ssh-https-verify-configuration/","permalink":"http://yelog.org/2016/12/21/git-ssh-https-verify-configuration/","excerpt":"Git作为当前最受欢迎的版本控制软件,使用是很频繁的。但每次使用git push等操作时都要输入密码,实在是挺麻烦的。本文对使用ssh与https两种通讯协议讨论一下免密码配置。注:这个过程在所有操作系统上都是相似的:)","text":"Git作为当前最受欢迎的版本控制软件,使用是很频繁的。但每次使用git push等操作时都要输入密码,实在是挺麻烦的。本文对使用ssh与https两种通讯协议讨论一下免密码配置。注:这个过程在所有操作系统上都是相似的:) SSH通信协议GitHub版许多Git服务器都使用SSH公钥进行认证,当然也包括github。首先你需要确认一下自己是否已经拥有密钥了,默认情况下,用户的 SSH 密钥存储在其 ~/.ssh 目录下。进入该目录并列出其中内容,你变可以下快速确认自己是否已经拥有密钥: $ cd ~/.ssh $ ls authorized_keys2 id_rsa known_hosts config id_rsa.pub 我们需要寻找一对 id_rsa 或 id_dsa 命名的文件,其中一个带 .pub 扩展名。 ‘.pub’文件是你的公钥,另一个则是私钥。如果没有找不到这样的文件(或者根本就没有.ssh目录),我们可以通过 ssh-keygen 程序来创建它们。 #邮箱可以随便填 $ ssh-keygen -t rsa -C "xx@xx.com" 首先 ssh-keygen 会确认密钥的存储位置和文件名(默认是 .ssh/id_rsa),然后他会要求你输入两次密钥口令,留空即可。所以一般选用默认,全部回车即可。 接下来我们登陆到GitHub上,右上角小头像->Setting->SSH and GPG keys中,点击new SSH key。Title:可以随便填写,但最好起的名字能让自己知道这个公钥是哪个设备的。Key:将上面生成的.pub文件中的所有内容复制到这里。点击下面的Add SSH key即可。然后你就会发现可以免密码访问了 Git服务器如果服务端是自己搭建的git服务器,生成密钥公钥对的步骤是一样的。然后将生成的 .pub 文件内容,导入到git服务器 /home/git/.ssh/authorized_keys 文件内,一行一个。然后你就会发现git push 不再需要密码了搭建git服务器和相关免登陆的详细步骤可参考我的另一篇 搭建Git服务器 HTTPS通信协议上面讲了SSH方式的免密码,接下来讲一下越来越常用的HTTPS方式的免密码新建文件并保存密码 $ touch ~/.git-credentials $ vim ~/.git-credentials 添加内容 https://{username}:{passwd}@github.com 添加git配置 $ git config --global credential.helper store 查看~/.gitconfig文件变化 [credential] helper = store 然后再尝试一下git push不再在需要密码了","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"}]},{"title":"每天一个linux命令(21): find命令之xargs","slug":"linux-command-21-find-xargs","date":"2016-12-21T03:08:01.000Z","updated":"2024-07-05T01:50:55.519Z","comments":true,"path":"2016/12/21/linux-command-21-find命令之xargs/","permalink":"http://yelog.org/2016/12/21/linux-command-21-find%E5%91%BD%E4%BB%A4%E4%B9%8Bxargs/","excerpt":"在使用 find命令的-exec选项处理匹配到的文件时, find命令将所有匹配到的文件一起传递给exec执行。但有些系统对能够传递给exec的命令长度有限制,这样在find命令运行几分钟之后,就会出现溢出错误。错误信息通常是“参数列太长”或“参数列溢出”。这就是xargs命令的用处所在,特别是与find命令一起使用。","text":"在使用 find命令的-exec选项处理匹配到的文件时, find命令将所有匹配到的文件一起传递给exec执行。但有些系统对能够传递给exec的命令长度有限制,这样在find命令运行几分钟之后,就会出现溢出错误。错误信息通常是“参数列太长”或“参数列溢出”。这就是xargs命令的用处所在,特别是与find命令一起使用。 find命令把匹配到的文件传递给xargs命令,而xargs命令每次只获取一部分文件而不是全部,不像-exec选项那样。这样它可以先处理最先获取的一部分文件,然后是下一批,并如此继续下去。 在有些系统中,使用-exec选项会为处理每一个匹配到的文件而发起一个相应的进程,并非将匹配到的文件全部作为参数一次执行;这样在有些情况下就会出现进程过多,系统性能下降的问题,因而效率不高; 而使用xargs命令则只有一个进程。另外,在使用xargs命令时,究竟是一次获取所有的参数,还是分批取得参数,以及每一次获取参数的数目都会根据该命令的选项及系统内核中相应的可调参数来确定。 使用实例例一:查找系统中的每一个普通文件,然后使用xargs命令来测试它们分别属于哪类文件 $ find . -type f -print | xargs file 例二:在整个系统中查找内存信息转储文件(core dump) ,然后把结果保存到/tmp/core.log 文件中 $ find / -name "core" -print | xargs echo "" >/tmp/core.log 例三:在当前目录下查找所有用户具有读、写和执行权限的文件,并收回相应的写权限 $ find . -perm -7 -print | xargs chmod o-w 例四:用grep命令在所有的普通文件中搜索hostname这个词 $ find . -type f -print | xargs grep "hostname" 例五:用grep命令在当前目录下的所有普通文件中搜索hostnames这个词 # \\用来取消find命令中的*在shell中的特殊含义 $ find . -name \\* -type f -print | xargs grep "hostnames" 例六:使用xargs执行mv $ find . -name "*.log" | xargs -i mv {} test4 例七:find后执行xargs提示xargs: argument line too long解决方法 # -l1是一次处理一个;-t是处理之前打印出命令 $ find . -type f -atime +0 -print0 | xargs -0 -l1 -t rm -f 例八:使用-i参数默认的前面输出用{}代替,-I参数可以指定其他代替字符,如例子中的[] $ find . -name "file" | xargs -I [] cp [] .. 例九:xargs的-p参数的使用 # -p参数会提示让你确认是否执行后面的命令,y执行,n不执行 $ find . -name "*.log" | xargs -p -i mv {} ..","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(20): find命令之exec","slug":"linux-command-20-find-exec","date":"2016-12-20T02:47:32.000Z","updated":"2024-07-05T01:50:55.573Z","comments":true,"path":"2016/12/20/linux-command-20-find命令之exec/","permalink":"http://yelog.org/2016/12/20/linux-command-20-find%E5%91%BD%E4%BB%A4%E4%B9%8Bexec/","excerpt":"find是我们很常用的一个Linux命令,但是我们一般查找出来的并不仅仅是看看而已,还会有进一步的操作,这个时候exec的作用就显现出来了","text":"find是我们很常用的一个Linux命令,但是我们一般查找出来的并不仅仅是看看而已,还会有进一步的操作,这个时候exec的作用就显现出来了 命令介绍 -exec 参数后面跟的是command命令,它的终止是以;为结束标志的,所以这句命令后面的分号是不可缺少的,考虑到各个系统中分号会有不同的意义,所以前面加反斜杠。 {} 花括号代表前面find查找出来的文件名。 使用find时,只要把想要的操作写在一个文件里,就可以用exec来配合find查找,很方便的。在有些操作系统中只允许-exec选项执行诸如ls或ls -l这样的命令。大多数用户使用这一选项是为了查找旧文件并删除它们。建议在真正执行rm命令删除文件之前,最好先用ls命令看一下,确认它们是所要删除的文件。 exec选项后面跟随着所要执行的命令或脚本,然后是一对儿{ },一个空格和一个\\,最后是一个分号。为了使用exec选项,必须要同时使用print选项。如果验证一下find命令,会发现该命令只输出从当前路径起的相对路径及文件名。 使用实例例一:ls -l命令放在find命令的-exec选项中 # find命令匹配到了当前目录下的所有普通文件,并在-exec选项中使用ls -l命令将它们列出 $ find . -type f -exec ls -l {} \\; 例二:在目录中查找更改时间在n日以前的文件并删除它们 $ find . -type f -mtime +14 -exec rm {} \\; 例三:在目录中查找更改时间在n日以前的文件并删除它们,在删除之前先给出提示 $ find . -name "*.log" -mtime +5 -ok rm {} \\; 例四:-exec中使用grep命令 $ find /etc -name "passwd*" -exec grep "root" {} \\; 例五:查找文件移动到指定目录 $ find . -name "*.log" -exec mv {} .. \\; 例六:用exec选项执行cp命令 $ find . -name "*.log" -exec cp {} test3 \\;","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(19): find命令概览","slug":"linux-command-19-find","date":"2016-12-19T07:19:10.000Z","updated":"2024-07-05T01:50:55.712Z","comments":true,"path":"2016/12/19/linux-command-19-find命令概览/","permalink":"http://yelog.org/2016/12/19/linux-command-19-find%E5%91%BD%E4%BB%A4%E6%A6%82%E8%A7%88/","excerpt":"Linux下find命令在目录结构中搜索文件,并执行指定的操作。Linux下find命令提供了相当多的查找条件,功能很强大。由于find具有强大的功能,所以它的选项也很多,其中大部分选项都值得我们花时间来了解一下。即使系统中含有网络文件系统( NFS),find命令在该文件系统中同样有效,只你具有相应的权限。 在运行一个非常消耗资源的find命令时,很多人都倾向于把它放在后台执行,因为遍历一个大的文件系统可能会花费很长的时间(这里是指30G字节以上的文件系统)。","text":"Linux下find命令在目录结构中搜索文件,并执行指定的操作。Linux下find命令提供了相当多的查找条件,功能很强大。由于find具有强大的功能,所以它的选项也很多,其中大部分选项都值得我们花时间来了解一下。即使系统中含有网络文件系统( NFS),find命令在该文件系统中同样有效,只你具有相应的权限。 在运行一个非常消耗资源的find命令时,很多人都倾向于把它放在后台执行,因为遍历一个大的文件系统可能会花费很长的时间(这里是指30G字节以上的文件系统)。 命令格式$ find pathname -options [-print -exec -ok ...] 命令功能 用于在文件树种查找文件,并作出相应的处理 命令参数 参数 描述 pathname find命令所查找的目录路径。例如用.来表示当前目录,用/来表示系统根目录 -print find命令将匹配的文件输出到标准输出 -exec find命令对匹配的文件执行该参数所给出的shell命令。相应命令的形式为’command’ { } ;,注意{ }和\\;之间的空格 -ok 和-exec的作用相同,只不过以一种更为安全的模式来执行该参数所给出的shell命令,在执行每一个命令之前,都会给出提示,让用户来确定是否执行 命令选项 选项 描述 -name 按照文件名查找文件 -perm 按照文件权限来查找文件 -prune 使用这一选项可以使find命令不在当前指定的目录中查找,如果同时使用-depth选项,那么-prune将被find命令忽略 -user 按照文件属主来查找文件 -group 按照文件所属的组来查找文件 -mtime -n +n 按照文件的更改时间来查找文件, - n表示文件更改时间距现在n天以内,+ n表示文件更改时间距现在n天以前。find命令还有-atime和-ctime 选项,但它们都和-m time选项 -nogroup 查找无有效所属组的文件,即该文件所属的组在/etc/groups中不存在 -nouser 查找无有效属主的文件,即该文件的属主在/etc/passwd中不存在 -newer file1 ! file2 查找更改时间比文件file1新但比文件file2旧的文件 -type 查找某一类型的文件,诸如:b - 块设备文件d - 目录c - 字符设备文件p - 管道文件l - 符号链接文件f - 普通文件 -size n:[c] 查找文件长度为n块的文件,带有c时表示文件长度以字节计。-depth:在查找文件时,首先查找当前目录中的文件,然后再在其子目录中查找 -fstype 查找位于某一类型文件系统中的文件,这些文件系统类型通常可以在配置文件/etc/fstab中找到,该配置文件中包含了本系统中有关文件系统的信息 -mount 在查找文件时不跨越文件系统mount点 -follow 如果find命令遇到符号链接文件,就跟踪至链接所指向的文件 -cpio 对匹配的文件使用cpio命令,将这些文件备份到磁带设备中 -amin n 查找系统中最后N分钟访问的文件 -atime n 查找系统中最后n*24小时访问的文件 -cmin n 查找系统中最后N分钟被改变文件状态的文件 -ctime n 查找系统中最后n*24小时被改变文件状态的文件 -mmin n 查找系统中最后N分钟被改变文件数据的文件 -mtime n 查找系统中最后n*24小时被改变文件数据的文件 使用实例例一:查找指定时间内修改过的文件 # 查找48小时内修改过的文件 $ find -atime -2 例二:根据关键字查找 # 在当前目录查找一.log结尾的文件。 ". "代表当前目录 $ find . -name "*.log" 例三:按照目录或文件的权限来查找文件 # 查找/opt/soft/test/目录下 权限为 777的文件 $ find /opt/soft/test/ -perm 777 例四:按类型查找 # 查找当目录,以.log结尾的普通文件 $ find . -type f -name "*.log" 例五:查找当前所有目录并排序 $ find . -type d | sort 例六:按大小查找文件 # 查找当前目录大于1K的文件 $ find . -size +1000c -print","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(18): locate","slug":"linux-command-18-locate","date":"2016-12-18T07:09:28.000Z","updated":"2024-07-05T01:50:55.515Z","comments":true,"path":"2016/12/18/linux-command-18-locate/","permalink":"http://yelog.org/2016/12/18/linux-command-18-locate/","excerpt":"locate 让使用者可以很快速的搜寻档案系统内是否有指定的档案。其方法是先建立一个包括系统内所有档案名称及路径的数据库,之后当寻找时就只需查询这个数据库,而不必实际深入档案系统之中了。在一般的 distribution 之中,数据库的建立都被放在 crontab 中自动执行。","text":"locate 让使用者可以很快速的搜寻档案系统内是否有指定的档案。其方法是先建立一个包括系统内所有档案名称及路径的数据库,之后当寻找时就只需查询这个数据库,而不必实际深入档案系统之中了。在一般的 distribution 之中,数据库的建立都被放在 crontab 中自动执行。 命令格式$ locate [选择参数] [样式] 命令功能 locate命令可以在搜寻数据库时快速找到档案,数据库由updatedb程序来更新,updatedb是由cron daemon周期性建立的,locate命令在搜寻数据库时比由整个由硬盘资料来搜寻资料来得快,但较差劲的是locate所找到的档案若是最近才建立或 刚更名的,可能会找不到,在内定值中,updatedb每天会跑一次,可以由修改crontab来更新设定值。(etc/crontab) locate指定用在搜寻符合条件的档案,它会去储存档案与目录名称的数据库内,寻找合乎范本样式条件的档案或目录录,可以使用特殊字元(如* 或 ?等)来指定范本样式,如指定范本为kcpa*ner, locate会找出所有起始字串为kcpa且结尾为ner的档案或目录,如名称为kcpartner若目录录名称为kcpa_ner则会列出该目录下包括 子目录在内的所有档案。 locate指令和find找寻档案的功能类似,但locate是透过update程序将硬盘中的所有档案和目录资料先建立一个索引数据库,在 执行loacte时直接找该索引,查询速度会较快,索引数据库一般是由操作系统管理,但也可以直接下达update强迫系统立即修改索引数据库。 命令参数 参数 描述 -e 将排除在寻找的范围之外 -1 如果 是 1.则启动安全模式。在安全模式下,使用者不会看到权限无法看到的档案。这会始速度减慢,因为 locate 必须至实际的档案系统中取得档案的权限资料 -f 将特定的档案系统排除在外,例如我们没有到理要把 proc 档案系统中的档案放在资料库中 -q 安静模式,不会显示任何错误讯息 -n 至多显示 n个输出 -r 使用正规运算式 做寻找的条件 -o 指定资料库存的名称 -d 指定资料库的路径 -h 显示辅助讯息 -V 显示程式的版本讯息 使用实例例一:查找和pwd相关的所有文件 $ locate pwd 例二:搜索etc目录下所有以sh开头的文件 $ locate /etc/sh","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(17): whereis","slug":"linux-command-17-whereis","date":"2016-12-17T02:42:58.000Z","updated":"2024-07-05T01:50:55.674Z","comments":true,"path":"2016/12/17/linux-command-17-whereis/","permalink":"http://yelog.org/2016/12/17/linux-command-17-whereis/","excerpt":"whereis命令只能用于程序名的搜索,而且只搜索二进制文件(参数-b)、man说明文件(参数-m)和源代码文件(参数-s)。如果省略参数,则返回所有信息。","text":"whereis命令只能用于程序名的搜索,而且只搜索二进制文件(参数-b)、man说明文件(参数-m)和源代码文件(参数-s)。如果省略参数,则返回所有信息。 和find相比,whereis查找的速度非常快,这是因为linux系统会将 系统内的所有文件都记录在一个数据库文件中,当使用whereis和下面即将介绍的locate时,会从数据库中查找数据,而不是像find命令那样,通 过遍历硬盘来查找,效率自然会很高。 但是该数据库文件并不是实时更新,默认情况下时一星期更新一次,因此,我们在用whereis和locate 查找文件时,有时会找到已经被删除的数据,或者刚刚建立文件,却无法查找到,原因就是因为数据库文件没有被更新。 命令格式$ whereis [-bmsu] [BMS 目录名 -f ] 文件名 命令功能 whereis命令是定位可执行文件、源代码文件、帮助文件在文件系统中的位置。这些文件的属性应属于原始代码,二进制文件,或是帮助文件。whereis 程序还具有搜索源代码、指定备用搜索路径和搜索不寻常项的能力。 命令参数 参数 描述 -b 定位可执行文件 -m 定位帮助文件 -s 定位源代码文件 -u 搜索默认路径下除可执行文件、源代码文件、帮助文件以外的其它文件 -B 指定搜索可执行文件的路径 -M 指定搜索帮助文件的路径 -S 指定搜索源代码文件的路径 使用实例例一:将和git文件相关的文件都查找出来 $ whereis git 例二:只将二进制文件 查找出来 $ whereis -b svn whereis -m svn 查出说明文档路径,whereis -s svn 找source源文件","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(16): which","slug":"linux-command-16-which","date":"2016-12-16T03:25:49.000Z","updated":"2024-07-05T01:50:55.550Z","comments":true,"path":"2016/12/16/linux-command-16-which/","permalink":"http://yelog.org/2016/12/16/linux-command-16-which/","excerpt":"我们经常在linux要查找某个文件,但不知道放在哪里了,可以使用下面的一些命令来搜索:which 查看可执行文件的位置。whereis 查看文件的位置。locate 配合数据库查看文件位置。find 实际搜寻硬盘查询文件名称。 which命令的作用是,在PATH变量指定的路径中,搜索某个系统命令的位置,并且返回第一个搜索结果。也就是说,使用which命令,就可以看到某个系统命令是否存在,以及执行的到底是哪一个位置的命令。","text":"我们经常在linux要查找某个文件,但不知道放在哪里了,可以使用下面的一些命令来搜索:which 查看可执行文件的位置。whereis 查看文件的位置。locate 配合数据库查看文件位置。find 实际搜寻硬盘查询文件名称。 which命令的作用是,在PATH变量指定的路径中,搜索某个系统命令的位置,并且返回第一个搜索结果。也就是说,使用which命令,就可以看到某个系统命令是否存在,以及执行的到底是哪一个位置的命令。 命令格式$ which 可执行文件名称 命令功能 which指令会在PATH变量指定的路径中,搜索某个系统命令的位置,并且返回第一个搜索结果。 命令参数 参数 描述 -n 指定文件名长度,指定的长度必须大于或等于所有文件中最长的文件名 -p 与-n参数相同,但此处的包括了文件的路径 -w 指定输出时栏位的宽度 -V 显示版本信息 使用实例例一:查找文件、显示命令路径 # which 是根据使用者所配置的 PATH 变量内的目录去搜寻可运行档的! # 所以,不同的 PATH 配置内容所找到的命令当然不一样的! $ which pwd 例二:用 which 去找出 which # 竟然会有两个 which ,其中一个是 alias 这就是所谓的『命令别名』,意思是输入 which 会等於后面接的那串命令! $ which which 例三:找出 cd 这个命令 # cd 这个常用的命令竟然找不到啊!为什么呢?这是因为 cd 是bash 内建的命令! # 但是 which 默认是找 PATH 内所规范的目录,所以当然一定找不到的! $ which cd","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(15): tail","slug":"linux-command-15-tail","date":"2016-12-15T07:00:26.000Z","updated":"2024-07-05T01:50:55.633Z","comments":true,"path":"2016/12/15/linux-command-15-tail/","permalink":"http://yelog.org/2016/12/15/linux-command-15-tail/","excerpt":"tail 命令从指定点开始将文件写到标准输出.使用tail命令的-f选项可以方便的查阅正在改变的日志文件,tail -f filename会把filename里最尾部的内容显示在屏幕上,并且不但刷新,使你看到最新的文件内容.","text":"tail 命令从指定点开始将文件写到标准输出.使用tail命令的-f选项可以方便的查阅正在改变的日志文件,tail -f filename会把filename里最尾部的内容显示在屏幕上,并且不但刷新,使你看到最新的文件内容. 命令格式tail[必要参数][选择参数][文件] 命令功能 用于显示指定文件末尾内容,不指定文件时,作为输入信息进行处理。常用查看日志文件。 命令参数 参数 描述 -f 循环读取 -q 不显示处理信息 -v 显示详细的处理信息 -c<数目> 显示的字节数 -n<行数> 显示行数 –pid=PID 与-f合用,表示在进程ID,PID死掉之后结束 -q, –quiet, –silent 从不输出给出文件名的首部 -s, –sleep-interval=S 与-f合用,表示在每次反复的间隔休眠S秒 使用实例例一:显示文件末尾内容 # 显示文件最后5行内容 $ tail -n 5 log2014.log 例二:循环查看文件内容 $ tail -f test.log 例三:从第5行开始显示文件 $ tail -n +5 log2014.log","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(14): head","slug":"linux-command-14-head","date":"2016-12-14T06:35:49.000Z","updated":"2024-07-05T01:50:55.501Z","comments":true,"path":"2016/12/14/linux-command-14-head/","permalink":"http://yelog.org/2016/12/14/linux-command-14-head/","excerpt":"head 与 tail 就像它的名字一样的浅显易懂,它是用来显示开头或结尾某个数量的文字区块,head 用来显示档案的开头至标准输出中,而 tail 想当然尔就是看档案的结尾。","text":"head 与 tail 就像它的名字一样的浅显易懂,它是用来显示开头或结尾某个数量的文字区块,head 用来显示档案的开头至标准输出中,而 tail 想当然尔就是看档案的结尾。 命令格式head [参数]... [文件]... 命令功能 head 用来显示档案的开头至标准输出中,默认head命令打印其相应文件的开头10行。 命令参数 参数 描述 -q 隐藏文件名 -v 显示文件名 -c<字节> 显示字节数 -n<行数> 显示的行数 使用实例例一:显示文件的前n行 $ head -n 5 log2014.log 例二:显示文件前n个字节 $ head -c 20 log2014.log 例三:文件的除了最后n个字节以外的内容 $ head -c -32 log2014.log 例四:输出文件除了最后n行的全部内容 $ head -n -6 log2014.log","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(13): less","slug":"linux-command-13-less","date":"2016-12-13T13:46:14.000Z","updated":"2024-07-05T01:50:55.692Z","comments":true,"path":"2016/12/13/linux-command-13-less/","permalink":"http://yelog.org/2016/12/13/linux-command-13-less/","excerpt":"less 工具也是对文件或其它输出进行分页显示的工具,应该说是linux正统查看文件内容的工具,功能极其强大。less 的用法比起 more 更加的有弹性。在 more 的时候,我们并没有办法向前面翻, 只能往后面看,但若使用了 less 时,就可以使用 [pageup] [pagedown] 等按键的功能来往前往后翻看文件,更容易用来查看一个文件的内容!除此之外,在 less 里头可以拥有更多的搜索功能,不止可以向下搜,也可以向上搜。","text":"less 工具也是对文件或其它输出进行分页显示的工具,应该说是linux正统查看文件内容的工具,功能极其强大。less 的用法比起 more 更加的有弹性。在 more 的时候,我们并没有办法向前面翻, 只能往后面看,但若使用了 less 时,就可以使用 [pageup] [pagedown] 等按键的功能来往前往后翻看文件,更容易用来查看一个文件的内容!除此之外,在 less 里头可以拥有更多的搜索功能,不止可以向下搜,也可以向上搜。 命令格式$ less [参数] 文件 命令功能 less 与 more 类似,但使用 less 可以随意浏览文件,而 more 仅能向前移动,却不能向后移动,而且 less 在查看之前不会加载整个文件。 命令参数 参数 描述 -b <缓冲区大小> 设置缓冲区的大小 -e 当文件显示结束后,自动离开 -f 强迫打开特殊文件,例如外围设备代号、目录和二进制文件 -g 只标志最后搜索的关键词 -i 忽略搜索时的大小写 -m 显示类似more命令的百分比 -N 显示每行的行号 -o <文件名> 将less 输出的内容在指定文件中保存起来 -Q 不使用警告音 -s 显示连续空行为一行 -S 行过长时间将超出部分舍弃 -x <数字> 将“tab”键显示为规定的数字空格 /字符串 向下搜索“字符串”的功能 ?字符串 向上搜索“字符串”的功能 n 重复前一个搜索(与 / 或 ? 有关) N 反向重复前一个搜索(与 / 或 ? 有关) b 向后翻一页 d 向后翻半页 h 显示帮助界面 Q 退出less 命令 u 向前滚动半页 y 向前滚动一行 空格键 滚动一行 回车键 滚动一页 [pagedown] 向下翻动一页 [pageup] 向上翻动一页 常用操作全屏导航 操作 描述 ctrl + F 向前移动一屏 ctrl + B 向后移动一屏 ctrl + D 向前移动半屏 ctrl + U 向后移动半屏 单行导航 操作 描述 j 向前移动一行 k 向后移动一行 其他导航 操作 描述 G 移动到最后一行 g 移动到第一行 q / ZZ 退出 less 命令 其它有用的命令 操作 描述 v 使用配置的编辑器编辑当前文件 h 显示 less 的帮助文档 &pattern 仅显示匹配模式的行,而不是整个文件 标记导航 当使用 less 查看大文件时,可以在任何一个位置作标记,可以通过命令导航到标有特定标记的文本位置 操作 描述 ma 使用 a 标记文本的当前位置 ‘a 导航到标记 a 处 使用实例例一:查看文件 $ less log2013.log 例二:ps查看进程信息并通过less分页显示 $ ps -ef |less 例三:查看命令历史使用记录并通过less分页显示 $ history | less 例三:浏览多个文件 $ Less log2013.log log2014.log 输入 :n后,切换到 log2014.log输入 :p 后,切换到log2013.log","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(12): more","slug":"linux-command-12-more","date":"2016-12-12T13:29:39.000Z","updated":"2024-07-05T01:50:55.646Z","comments":true,"path":"2016/12/12/linux-command-12-more/","permalink":"http://yelog.org/2016/12/12/linux-command-12-more/","excerpt":"more命令,功能类似 cat ,cat命令是整个文件的内容从上到下显示在屏幕上。 more会以一页一页的显示方便使用者逐页阅读,而最基本的指令就是按空白键(space)就往下一页显示,按 b 键就会往回(back)一页显示,而且还有搜寻字串的功能 。more命令从前向后读取文件,因此在启动时就加载整个文件。","text":"more命令,功能类似 cat ,cat命令是整个文件的内容从上到下显示在屏幕上。 more会以一页一页的显示方便使用者逐页阅读,而最基本的指令就是按空白键(space)就往下一页显示,按 b 键就会往回(back)一页显示,而且还有搜寻字串的功能 。more命令从前向后读取文件,因此在启动时就加载整个文件。 命令格式$ more [-dlfpcsu ] [-num ] [+/ pattern] [+ linenum] [file ... ] 命令功能 more命令和cat的功能一样都是查看文件里的内容,但有所不同的是more可以按页来查看文件的内容,还支持直接跳转行等功能。 命令参数 参数 描述 +n 从笫n行开始显示 -n 定义屏幕大小为n行 +/pattern 每个档案显示前搜寻该字串(pattern),然后从该字串前两行之后开始显示 -c 从顶部清屏,然后显示 -d 提示“Press space to continue,’q’ to quit(按空格键继续,按q键退出)”,禁用响铃功能 -l 忽略Ctrl+l(换页)字符 -p 通过清除窗口而不是滚屏来对文件进行换页,与-c选项相似 -s 把连续的多个空行显示为一行 -u 把文件内容中的下画线去掉 常用操作 操作 描述 Enter 向下n行,需要定义。默认为1行 Ctrl+F 向下滚动一屏 空格键 向下滚动一屏 Ctrl+B 返回上一屏 = 输出当前行的行号 :f 输出文件名和当前行的行号 V 调用vi编辑器 !命令 调用Shell,并执行命令 q 退出more 命令实例例一:显示文件中从第3行起的内容 $ more +3 log2012.log 例二:从文件中查找第一个出现”day3”字符串的行,并从该处前两行开始显示输出 $ more +/day3 log2012.log 例三:设定每屏显示行数 $ more -5 log2012.log 例四:列一个目录下的文件,由于内容太多,我们应该学会用more来分页显示。这得和管道 | 结合起来 $ ls -l | more -5 说明:每页显示5个文件信息,按 Ctrl+F 或者 空格键 将会显示下5条文件信息。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(11): nl","slug":"linux-command-11-nl","date":"2016-12-11T13:11:48.000Z","updated":"2024-07-05T01:50:55.729Z","comments":true,"path":"2016/12/11/linux-command-11-nl/","permalink":"http://yelog.org/2016/12/11/linux-command-11-nl/","excerpt":"nl命令在linux系统中用来计算文件中行号。nl 可以将输出的文件内容自动的加上行号!其默认的结果与 cat -n 有点不太一样, nl 可以将行号做比较多的显示设计,包括位数与是否自动补齐 0 等等的功能。","text":"nl命令在linux系统中用来计算文件中行号。nl 可以将输出的文件内容自动的加上行号!其默认的结果与 cat -n 有点不太一样, nl 可以将行号做比较多的显示设计,包括位数与是否自动补齐 0 等等的功能。 命令格式$ nl [选项]... [文件]... 命令功能 nl 命令读取 File 参数(缺省情况下标准输入),计算输入中的行号,将计算过的行号写入标准输出。 在输出中,nl 命令根据您在命令行中指定的标志来计算左边的行。 输入文本必须写在逻辑页中。每个逻辑页有头、主体和页脚节(可以有空节)。 除非使用 -p 标志,nl 命令在每个逻辑页开始的地方重新设置行号。 可以单独为头、主体和页脚节设置行计算标志(例如,头和页脚行可以被计算然而文本行不能)。 命令参数 种类 参数 描述 -b -b a 表示不论是否为空行,也同样列出行号(类似 cat -n) -b t 如果有空行,空的那一行不要列出行号(默认值) -n -n ln 行号在萤幕的最左方显示 -n rn 行号在自己栏位的最右方显示,且不加 0 -n rz 行号在自己栏位的最右方显示,且加 0 -w -w 行号栏位的占用的位数 -p -p 在逻辑定界符处不重新开始计算 命令实例例一:用 nl 列出 log2012.log 的内容 # 文件中的空白行,nl 不会加上行号 $ nl log2012.log 例二:用 nl 列出 log2012.log 的内容,空本行也加上行号 $ nl -b a log2012.log 例三:让行号前面自动补上0,统一输出格式 $ nl -b a -n rz log2014.log nl -b a -n rz 命令行号默认为六位,要调整位数可以加上参数 -w 3 调整为3位。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(10): cat","slug":"linux-command-10-cat","date":"2016-12-10T02:52:50.000Z","updated":"2024-07-05T01:50:55.606Z","comments":true,"path":"2016/12/10/linux-command-10-cat/","permalink":"http://yelog.org/2016/12/10/linux-command-10-cat/","excerpt":"cat命令的用途是连接文件或标准输入并打印。这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用。","text":"cat命令的用途是连接文件或标准输入并打印。这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用。 命令格式$ cat [选项] [文件]... 命令功能cat主要有三大功能 一次显示整个文件:cat filename 从键盘创建一个文件:cat > filename 只能创建新文件,不能编辑已有文件. 将几个文件合并为一个文件:cat file1 file2 > file 命令参数 参数 描述 -A, –show-all 等价于 -vET -b, –number-nonblank 对非空输出行编号 -e 等价于 -vE -E, –show-ends 在每行结束处显示 $ -n, –number 对输出的所有行编号,由1开始对所有输出的行数编号 -s, –squeeze-blank 有连续两行以上的空白行,就代换为一行的空白行 -t 与 -vT 等价 -T, –show-tabs 将跳格字符显示为 ^I -u (被忽略) -v, –show-nonprinting 使用 ^ 和 M- 引用,除了 LFD 和 TAB 之外 命令实例例一:把 log2012.log 的文件内容加上行号后输入 log2013.log 这个文件里 $ cat -n log2012.log log2013.log 例二:把 log2012.log 和 log2013.log 的文件内容加上行号(空白行不加)之后将内容附加到 log.log 里 $ cat -b log2012.log log2013.log log.log 例三:把 log2012.log 的文件内容加上行号后输入 log.log 这个文件里 $ cat -n log2012.log > log.log 例四:使用here doc来生成文件 $ cat >log.txt <<EOF > Hello > World > Linux > PWD=$(pwd) > EOF 例五:tac (反向列示) $ tac log.txt PWD=/opt/soft/test Linux World Hello tac 是将 cat 反写过来,所以他的功能就跟 cat 相反, cat 是由第一行到最后一行连续显示在萤幕上,而 tac 则是由最后一行到第一行反向在萤幕上显示出来!","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(9): touch","slug":"linux-command-9-touch","date":"2016-12-08T23:15:12.000Z","updated":"2024-07-05T01:50:55.716Z","comments":true,"path":"2016/12/09/linux-command-9-touch/","permalink":"http://yelog.org/2016/12/09/linux-command-9-touch/","excerpt":"linux的touch命令不常用,一般在使用make的时候可能会用到,用来修改文件时间戳,或者新建一个不存在的文件。","text":"linux的touch命令不常用,一般在使用make的时候可能会用到,用来修改文件时间戳,或者新建一个不存在的文件。 命令格式$ touch [选项]... 文件... 命令功能 touch命令参数可更改文档或目录的日期时间,包括存取时间和更改时间。 命令参数 参数 描述 -a 或–time=atime或–time=access或–time=use 只更改存取时间 -c 或–no-create 不建立任何文档 -d 使用指定的日期时间,而非现在的时间 -f 此参数将忽略不予处理,仅负责解决BSD版本touch指令的兼容性问题 -m 或–time=mtime或–time=modify 只更改变动时间 -r 把指定文档或目录的日期时间,统统设成和参考文档或目录的日期时间相同 -t 使用指定的日期时间,而非现在的时间 命令实例例一:创建不存在的文件 $ touch 1.txt 例二:更新1.txt的时间和2.txt时间戳相同 $ touch -r 1.txt 2.txt 例三:设定文件的时间戳 $ touch -t 201211142234.50 1.txt 例四:创建不存在的文件 $ touch 1.txt 说明: -t time 使用指定的时间值 time 作为指定文件相应时间戳记的新值.此处的 time规定为如下形式的十进制数: [[CC]YY]MMDDhhmm[.SS] 这里,CC为年数中的前两位,即”世纪数”;YY为年数的后两位,即某世纪中的年数.如果不给出CC的值,则touch 将把年数CCYY限定在1969–2068之内.MM为月数,DD为天将把年数CCYY限定在1969–2068之内.MM为月数,DD为天数,hh 为小时数(几点),mm为分钟数,SS为秒数.此处秒的设定范围是0–61,这样可以处理闰秒.这些数字组成的时间是环境变量TZ指定的时区中的一个时 间.由于系统的限制,早于1970年1月1日的时间是错误的。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(8): cp","slug":"linux-command-8-cp","date":"2016-12-08T08:31:43.000Z","updated":"2024-07-05T01:50:55.725Z","comments":true,"path":"2016/12/08/linux-command-8-cp/","permalink":"http://yelog.org/2016/12/08/linux-command-8-cp/","excerpt":"cp命令用来复制文件或者目录,是Linux系统中最常用的命令之一。一般情况下,shell会设置一个别名,在命令行下复制文件时,如果目标文件已经存在,就会询问是否覆盖,不管你是否使用-i参数。但是如果是在shell脚本中执行cp时,没有-i参数时不会询问是否覆盖。这说明命令行和shell脚本的执行方式有些不同。","text":"cp命令用来复制文件或者目录,是Linux系统中最常用的命令之一。一般情况下,shell会设置一个别名,在命令行下复制文件时,如果目标文件已经存在,就会询问是否覆盖,不管你是否使用-i参数。但是如果是在shell脚本中执行cp时,没有-i参数时不会询问是否覆盖。这说明命令行和shell脚本的执行方式有些不同。 命令格式$ cp [选项]... [-T] 源 目的 $ cp [选项]... -t 目录 源... 命令功能 将源文件复制至目标文件,或将多个源文件复制至目标目录。 命令参数 参数 描述 -a,–archive 为每个已存在的目标文件创建备份 -f, –force 如果目标文件无法打开则将其移除并重试(当 -n 选项存在时则不需再选此项) -i, –interactive 覆盖前询问(使前面的 -n 选项失效) -H 跟随源文件中的命令行符号链接 -l, –link 链接文件而不复制 -L, –dereference 总是跟随符号链接 -n, –no-clobber 不要覆盖已存在的文件(使前面的 -i 选项失效) -P, –no-dereference 不跟随源文件中的符号链接 -p 等于–preserve=模式,所有权,时间戳 -R, -r, –recursive 复制目录及目录内的所有项目 命令实例例一:复制单个文件到目标目录,文件在目标文件中存在,会询问覆盖 # 在没有带-a参数时,两个文件的时间是不一样的。在带了-a参数时,两个文件的时间是一致的。 $ cp 1.txt test5 例二:复制整个目录 # 注意目标目录存在与否结果是不一样的。目标目录存在时,整个源目录被复制到目标目录里面。 $ cp -a test3 test5 例三:复制的 log.log 建立一个连结档 log_link.log $ cp -s log.log log_link.log","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(7): mv","slug":"linux-command-7-mv","date":"2016-12-07T07:55:20.000Z","updated":"2024-07-05T01:50:55.554Z","comments":true,"path":"2016/12/07/linux-command-7-mv/","permalink":"http://yelog.org/2016/12/07/linux-command-7-mv/","excerpt":"mv命令是move的缩写,可以用来移动文件或者将文件改名(move (rename) files),是Linux系统下常用的命令,经常用来备份文件或者目录","text":"mv命令是move的缩写,可以用来移动文件或者将文件改名(move (rename) files),是Linux系统下常用的命令,经常用来备份文件或者目录 命令格式$ mv [选项] 源文件或目录 目标文件或目录 命令功能 视mv命令中第二个参数类型的不同(是目标文件还是目标目录),mv命令将文件重命名或将其移至一个新的目录中。当第二个参数类型是文件时,mv命令完成文件重命名,此时,源文件只能有一个(也可以是源目录名),它将所给的源文件或目录重命名为给定的目标文件名。当第二个参数是已存在的目录名称时,源文件或目录参数可以有多个,mv命令将各参数指定的源文件均移至目标目录中。在跨文件系统移动文件时,mv先拷贝,再将原有文件删除,而链至该文件的链接也将丢失。 命令参数 参数 描述 -b 若需覆盖文件,则覆盖前先行备份。 -f force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖 -i 若目标文件 (destination) 已经存在时,就会询问是否覆盖! -u 若目标文件已经存在,且 source 比较新,才会更新(update) -t –target-directory=DIRECTORY move all SOURCE arguments into DIRECTORY,即指定mv的目标目录,该选项适用于移动多个源文件到一个目录的情况,此时目标目录在前,源文件在后。 命令实例例一:文件改名 $ mv test.txt test1.txt 例二:移动文件 #将文件test.txt 移动到/usr/doc目录下 $ mv test.txt /usr/doc 例三:将文件log1.txt,log2.txt,log3.txt移动到目录/usr/doc中 $ mv log1.txt log2.txt log3.txt /usr/doc $ mv -t /usr/doc log1.txt log2.txt log3.txt 例四:将文件file1改名为file2,如果file2已经存在,则询问是否覆盖 $ mv -i log1.txt log2.txt 例五:将文件file1改名为file2,即使file2存在,也是直接覆盖掉 $ mv -f log3.txt log2.txt 例六:目录的移动 #将doc下的product目录移动到/usr/doc目录下 $ mv doc/product /usr/doc 例七:移动当前文件夹下的所有文件到上一级目录 $ mv * ../ 例八:文件被覆盖前做简单备份,前面加参数-b $ mv log1.txt -b log2.txt -b不接受参数,mv会去读取环境变量VERSION_CONTROL来作为备份策略。–backup该选项指定如果目标文件存在时的动作,共有四种备份策略: CONTROL=none或off : 不备份。 CONTROL=numbered或t:数字编号的备份 CONTROL=existing或nil:如果存在以数字编号的备份,则继续编号备份m+1…n:执行mv操作前已存在以数字编号的文件log2.txt.1,那么再次执行将产生log2.txt2,以次类推。如果之前没有以数字编号的文件,则使用下面讲到的简单备份。 CONTROL=simple或never:使用简单备份:在被覆盖前进行了简单备份,简单备份只能有一份,再次被覆盖时,简单备份也会被覆盖。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(6): rmdir","slug":"linux-command-6-rmdir","date":"2016-12-06T07:24:32.000Z","updated":"2024-07-05T01:50:55.707Z","comments":true,"path":"2016/12/06/linux-command-6-rmdir/","permalink":"http://yelog.org/2016/12/06/linux-command-6-rmdir/","excerpt":"今天学习一下linux中命令: rmdir命令。rmdir是常用的命令,该命令的功能是删除空目录,一个目录被删除之前必须是空的。(注意,rm - r dir命令可代替rmdir,但是有很大危险性。)删除某目录时也必须具有对父目录的写权限。","text":"今天学习一下linux中命令: rmdir命令。rmdir是常用的命令,该命令的功能是删除空目录,一个目录被删除之前必须是空的。(注意,rm - r dir命令可代替rmdir,但是有很大危险性。)删除某目录时也必须具有对父目录的写权限。 命令格式$ rmdir [选项]... 目录... 命令功能 该命令从一个目录中删除一个或多个子目录项,删除某目录时也必须具有对父目录的写权限。 命令参数 参数 描述 - p 递归删除目录dirname,当子目录删除后其父目录为空时,也一同被删除。如果整个路径被删除或者由于某种原因保留部分路径,则系统在标准输出上显示相应的信息 -v, –verbose 显示指令执行过程 命令实例例一:rmdir 不能删除非空目录 $ rmdir doc rmdir: doc: 目录非空 例二:rmdir -p 当子目录被删除后使它也成为空目录的话,则顺便一并删除 $ rmdir -p log/product","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(5): rm","slug":"linux-command-5-rm","date":"2016-12-05T05:40:38.000Z","updated":"2024-07-05T01:50:55.546Z","comments":true,"path":"2016/12/05/linux-command-5-rm/","permalink":"http://yelog.org/2016/12/05/linux-command-5-rm/","excerpt":"rm是常用的命令,该命令的功能为删除一个目录中的一个或多个文件或目录,它也可以将某个目录及其下的所有文件及子目录均删除。对于链接文件,只是删除了链接,原有文件均保持不变。rm是一个危险的命令,使用的时候要特别当心,尤其对于新手,否则整个系统就会毁在这个命令(比如在/(根目录)下执行rm * -rf)。所以,我们在执行rm之前最好先确认一下在哪个目录,到底要删除什么东西,操作时保持高度清醒的头脑。","text":"rm是常用的命令,该命令的功能为删除一个目录中的一个或多个文件或目录,它也可以将某个目录及其下的所有文件及子目录均删除。对于链接文件,只是删除了链接,原有文件均保持不变。rm是一个危险的命令,使用的时候要特别当心,尤其对于新手,否则整个系统就会毁在这个命令(比如在/(根目录)下执行rm * -rf)。所以,我们在执行rm之前最好先确认一下在哪个目录,到底要删除什么东西,操作时保持高度清醒的头脑。 命令格式$ rm [选项] 文件... 命令功能 参数 描述 -f, –force 忽略不存在的文件,从不给出提示 -i, –interactive 进行交互式删除 -r, -R, –recursive 指示rm将参数中列出的全部目录和子目录均递归地删除 -v, –verbose 详细显示进行的步骤 –help 显示此帮助信息并退出 –version 输出版本信息并退出 命令实例例一:删除文件file,系统会先询问是否删除 $ rm file 例二:强行删除file,系统不再提示 $ rm -f file **例三:删除任何.log文件;删除前逐一询问确认 ** $ rm -i *.log 例四:对test文件夹进行递归删除 $ rm -r test 例五:递归删除,系统不用一一确认 $ rm -rf test 例六:删除以 -f 开头的文件 $ rm -- -f 例七:自定义回收站功能 #下面的操作过程模拟了回收站的效果,即删除文件的时候只是把文件放到一个临时目录中,这样在需要的时候还可以恢复过来。 $ myrm(){ D=/tmp/$(date +%Y%m%d%H%M%S); mkdir -p $D; mv "$@" $D && echo "moved to $D ok"; }","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(4): mkdir","slug":"linux-command-4-mkdir","date":"2016-12-04T01:27:32.000Z","updated":"2024-07-05T01:50:55.651Z","comments":true,"path":"2016/12/04/linux-command-4-mkdir/","permalink":"http://yelog.org/2016/12/04/linux-command-4-mkdir/","excerpt":"linux mkdir 命令用来创建指定的名称的目录,要求创建目录的用户在当前目录中具有写权限,并且指定的目录名不能是当前目录中已有的目录。","text":"linux mkdir 命令用来创建指定的名称的目录,要求创建目录的用户在当前目录中具有写权限,并且指定的目录名不能是当前目录中已有的目录。 命令格式$ mkdir [选项] 目录... 命令功能 通过 mkdir 命令可以实现在指定位置创建以 DirName(指定的文件名)命名的文件夹或目录。要创建文件夹或目录的用户必须对所创建的文件夹的父文件夹具有写权限。并且,所创建的文件夹(目录)不能与其父目录(即父文件夹)中的文件名重名,即同一个目录下不能有同名的(区分大小写)。 命令参数 参数 说明 -m, –mode=模式 设定权限<模式> (类似 chmod),而不是 rwxrwxrwx 减 umask -p, –parents 可以是一个路径名称。此时若路径中的某些目录尚不存在,加上此选项后,系统将自动建立好那些尚不存在的目录,即一次可以建立多个目录 -v, –verbose 每次创建新目录都显示信息 –help 显示此帮助信息并退出 –version 输出版本信息并退出 命令是实例例一:创建一个空目录 $ mkdir test 例二:递归创建多个目录 #在当前目录创建一个嵌套文件夹test1/test11 $ mkdir -p test1/test11 例三:创建权限为777的目录 $ mkdir -m 777 test 例四:创建新目录都显示信息 $ mkdir -v test 例五:一个命令创建项目的目录结构 $ mkdir -vp scf/{lib/,bin/,doc/{info,product},logs/{info,product},service/deploy/{info,product}} mkdir: 已创建目录 “scf” mkdir: 已创建目录 “scf/lib” mkdir: 已创建目录 “scf/bin” mkdir: 已创建目录 “scf/doc” mkdir: 已创建目录 “scf/doc/info” mkdir: 已创建目录 “scf/doc/product” mkdir: 已创建目录 “scf/logs” mkdir: 已创建目录 “scf/logs/info” mkdir: 已创建目录 “scf/logs/product” mkdir: 已创建目录 “scf/service” mkdir: 已创建目录 “scf/service/deploy” mkdir: 已创建目录 “scf/service/deploy/info” mkdir: 已创建目录 “scf/service/deploy/product” [root@localhost test]# tree scf/ scf/ |-- bin |-- doc | |-- info | `-- product |-- lib |-- logs | |-- info | `-- product `-- service `-- deploy |-- info `-- product 12 directories, 0 files","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(3): pwd","slug":"linux-command-3-pwd","date":"2016-12-03T01:15:52.000Z","updated":"2024-07-05T01:50:55.624Z","comments":true,"path":"2016/12/03/linux-command-3-pwd/","permalink":"http://yelog.org/2016/12/03/linux-command-3-pwd/","excerpt":"Linux中用 pwd 命令来查看”当前工作目录“的完整路径。 简单得说,每当你在终端进行操作时,你都会有一个当前工作目录。 在不太确定当前位置时,就会使用pwd来判定当前目录在文件系统内的确切位置。","text":"Linux中用 pwd 命令来查看”当前工作目录“的完整路径。 简单得说,每当你在终端进行操作时,你都会有一个当前工作目录。 在不太确定当前位置时,就会使用pwd来判定当前目录在文件系统内的确切位置。 命令格式$ pwd [选项] 命令功能 查看”当前工作目录“的完整路径 常用参数一般情况下不带任何参数如果目录是链接时:格式:pwd -P 显示出实际路径,而非使用链接(link) 的路径 实用实例例一:用 pwd 命令查看当前工作目录的完整路径 $ pwd /home/faker 例二:目录连接链接时,pwd -P 显示出实际路径,而非使用连接(link)路径;pwd显示的是连接路径 #目录为链接时,输出链接路径 $ pwd -L #目录为链接时,输出物理路径 $ pwd -P /home/faker 例三:当前目录被删除了,而pwd命令仍然显示那个目录 $ cd /opt/soft $ rm ../soft -rf $ pwd /opt/soft $ /bin/pwd /bin/pwd: couldnt find directory entry in “..” with matching i-node /home/faker","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(2): cd","slug":"linux-command-2-cd","date":"2016-12-02T03:04:55.000Z","updated":"2024-07-05T01:50:55.637Z","comments":true,"path":"2016/12/02/linux-command-2-cd/","permalink":"http://yelog.org/2016/12/02/linux-command-2-cd/","excerpt":"Linux cd 命令可以说是Linux中最基本的命令语句,其他的命令语句要进行操作,都是建立在使用 cd 命令上的。所以,学习Linux 常用命令,首先就要学好 cd 命令的使用方法技巧。","text":"Linux cd 命令可以说是Linux中最基本的命令语句,其他的命令语句要进行操作,都是建立在使用 cd 命令上的。所以,学习Linux 常用命令,首先就要学好 cd 命令的使用方法技巧。 命令格式$ cd [目录名] 命令功能 切换当前目录至目标目录 常用范例例一:进入系统根目录 $ cd / 例二:进入父级目录 $ cd .. $ cd ..// 例三:使用 cd 命令进入当前用户主目录 $ cd $ cd ~ 例四:跳转到指定目录 $ cd /usr/bin 例五:返回进入此目录之前所在的目录 $ cd - 例六:把上个命令的参数作为cd参数使用 $ cd !$","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(1): ls","slug":"linux-command-1-ls","date":"2016-12-01T01:38:59.000Z","updated":"2024-07-05T01:50:55.563Z","comments":true,"path":"2016/12/01/linux-command-1-ls/","permalink":"http://yelog.org/2016/12/01/linux-command-1-ls/","excerpt":"ls命令是linux下最常用的命令。ls命令就是list的缩写,缺省下ls用来打印出当前目录的清单,如果ls指定其他目录,那么就会显示指定目录里的文件及文件夹清单。 通过ls命令不仅可以查看linux文件夹包含的文件,而且可以查看文件权限(包括目录、文件夹、文件权限)、查看目录信息等等。ls命令在日常的linux操作中用的很多!","text":"ls命令是linux下最常用的命令。ls命令就是list的缩写,缺省下ls用来打印出当前目录的清单,如果ls指定其他目录,那么就会显示指定目录里的文件及文件夹清单。 通过ls命令不仅可以查看linux文件夹包含的文件,而且可以查看文件权限(包括目录、文件夹、文件权限)、查看目录信息等等。ls命令在日常的linux操作中用的很多! 命令格式$ ls [选项] [目录名] 命令功能列出目标目录中所有的子目录和文件。 常用参数 参数 说明 -a,–all 列出目录下的所有文件,包括以 . 开头的隐含文件 -A 同-a,但不列出“.”(表示当前目录)和“..”(表示当前目录的父目录)。 -c 配合 -lt 根据 ctime 排序及显示 ctime (文件状态最后更改的时间)配合 -lt:显示 ctime 但根据名称排序否则:根据 ctime 排序 -C 每栏由上至下列出项目 -color[=WHEN] 控制是否使用色彩分辨文件。WHEN 可以是’never’、’always’或’auto’其中之一 -d,–directory 将目录象文件一样显示,而不是显示其下的文件。 -D,–dired 产生适合 Emacs 的 dired 模式使用的结果 -f 对输出的文件不进行排序,-aU 选项生效,-lst 选项失效 -g 类似 -l,但不列出所有者 -G, –no-group 不列出任何有关组的信息 -h,–human-readable 以容易理解的格式列出文件大小 (例如 1K 234M 2G) –si 类似 -h,但文件大小取 1000 的次方而不是 1024 -H, –dereference-command-line 使用命令列中的符号链接指示的真正目的地 –indicator-style=<方式> 指定在每个项目名称后加上指示符号<方式>:none (默认),classify (-F),file-type (-p) -i, –inode 印出每个文件的 inode 号 -I,–ignore=样式 不印出任何符合 shell 万用字符<样式>的项目 -k 即 –block-size=1K,以 k 字节的形式表示文件的大小 -l 除了文件名之外,还将文件的权限、所有者、文件大小等信息详细列出来。 -L, –dereference -m 所有项目以逗号分隔,并填满整行行宽 -o 类似 -l,显示文件的除组信息外的详细信息。 -r, –reverse 依相反次序排列 -R, –recursive 同时列出所有子目录层 -s,–size 以块大小为单位列出所有文件的大小 -S 根据文件大小排序 –sort=WORD 可选用的 WORD 和它们代表的相应选项: extension -X status -cnone -U time -tsize -S atime -utime -t access -uversion -v use -u -t 以文件修改时间排序 -u 配合 -lt:显示访问时间而且依访问时间排序配合 -l:显示访问时间但根据名称排序否则:根据访问时间排序 -U 不进行排序;依文件系统原有的次序列出项目 -v 根据版本进行排序 -w, –width=COLS 自行指定屏幕宽度而不使用目前的数值 -x 逐行列出项目而不是逐栏列出 -X 根据扩展名排序 -1 每行只列出一个文件 –help 显示此帮助信息并离开 –version 显示版本信息并离开 常用范例例一:列出/home/faker/ 文件夹下的所有文件和目录的详细资料 $ ls -l -R /home/faker $ ls -lR /home/faker 例二:列出当前目录中所有以“t”开头的目录的详细内容,可以使用如下命令 $ ls -l t* 例三:只列出文件下的子目录 $ ls -F /opt/soft |grep /$ 例四:列出文件下的子目录详细情况 $ ls -l /opt/soft | grep "^d" 例五:列出目前工作目录下所有名称是s 开头的文件,愈新的排愈后面,可以使用如下命令 $ ls -ltr s* 例六:列出目前工作目录下所有档案及目录;目录于名称后加”/“, 可执行档于名称后加* $ ls -AF 例七:计算当前目录下的文件数和目录数 $ ls -l * |grep "^-"|wc -l ---文件个数 $ ls -l * |grep "^d"|wc -l ---目录个数 例八:在ls中列出文件的绝对路径 $ ls | sed "s:^:`pwd`/:" 例九:列出当前目录下的所有文件(包括隐藏文件)的绝对路径, 对目录不做递归 $ find $PWD -maxdepth 1 | xargs ls -ld 例十:列出当前目录下的所有文件(包括隐藏文件)的绝对路径, 对目录不做递归 $ find $PWD -maxdepth 1 | xargs ls -ld 例十:递归列出当前目录下的所有文件(包括隐藏文件)的绝对路径 $ find $PWD | xargs ls -ld 例十:指定文件时间输出格式 $ ls -tl --time-style=full-iso $ ls -ctl --time-style=long-iso 2016-08-05 22:17:06.020535551 +0800 2016-10-29 12:03","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"jQuery之checkbox|radio|select操作","slug":"jQuery之checkbox-radio-select操作","date":"2016-11-23T08:19:34.000Z","updated":"2024-07-05T01:50:55.119Z","comments":true,"path":"2016/11/23/jQuery-checkbox-radio-select/","permalink":"http://yelog.org/2016/11/23/jQuery-checkbox-radio-select/","excerpt":"jQuery1.6中添加了prop方法,看起来和用起来都和attr方法一样,但是在一些特殊情况下,attribute和properties的区别非常大,在jQuery1.6之前,.attr()方法在获取一些attributes的时候使用了property值,这样会导致一些不一致的行为。在jQuery1.6中,.prop()方法提供了一中明确的获取property值得方式,这样.attr()方法仅返回attributes。 –摘自jQuery API文档","text":"jQuery1.6中添加了prop方法,看起来和用起来都和attr方法一样,但是在一些特殊情况下,attribute和properties的区别非常大,在jQuery1.6之前,.attr()方法在获取一些attributes的时候使用了property值,这样会导致一些不一致的行为。在jQuery1.6中,.prop()方法提供了一中明确的获取property值得方式,这样.attr()方法仅返回attributes。 –摘自jQuery API文档 checkbox<input type='checkbox' value='1'/> <input type='checkbox' value='2'/> <input type='checkbox' value='3'/> $("input[type=checkbox]") //获取所有的checkbox $("input[type=checkbox]:checked") //获取所有的被选中的checkbox $("input[type=checkbox]:not(:checked)") //获取所有未被选中的checkbox $("input[type=checkbox]").not(":checked") //获取所有未被选中的checkbox $("input[type=checkbox]:first") //获取第一个checkbox的value值 $("input[type=checkbox]:checked").length //获取被选中checkbox的数量 $("input[type=checkbox]:first").prop("checked") //判断第一个checkbox是否被选中 $("input[type=checkbox]:first").prop("checkbox",true) //选中第一个checkbox $("input[type=checkbox]:not(:checked)").prop("checked",true) //全选 $("input[type=checkbox]:checkbox").prop("checked",false) //都不选中 //反选 $("input[type=checkbox]").each(function(){ if($(this).prop("checked")){ $(this).prop("checked",false); }else{ $(this).prop("checked",true); } }) radio<input type='radio' name='rank' value='1' /> <input type='radio' name='rank' value='2' /> <input type='radio' name='rank' value='3' /> $("input[type=radio]") //获取所有的radio $("input[type=radio]:checked") //获取被选中的radio $("input[type=radio]:not(:checkbox)") //获取所有没有被选中的radio $("input[type=radio]:checked").val() //获取被选中的radio的value值 $("input[type=radio]:first").prop("checked") //判断第一个radio是否被选中 $("input[type=radio]:first").prop("checked",true) //选中第一个radio select<select> <option value='1'>1</option> <option value='2'>2</option> <option value='4'>3</option> </select> $("select option:selected") //获取被选中的option $("select").val() //获取选中option的value值 $("select option:selected").text() //获取被选中的option的text值 $("select option:first").prop("selected") //判断第一个option是否被选中 $("select option:first").prop("selected",true) //选中第一个option $("select option:selected").prop("selected",false) //取消当前选中,然后默认选中第一个","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"jQuery","slug":"jQuery","permalink":"http://yelog.org/tags/jQuery/"}]},{"title":"jQuery选择器与节点操作","slug":"jQuery选择器与节点操作","date":"2016-11-22T09:11:48.000Z","updated":"2024-07-05T01:50:55.069Z","comments":true,"path":"2016/11/22/jQuery-selector/","permalink":"http://yelog.org/2016/11/22/jQuery-selector/","excerpt":"jQuery 是一个 JavaScript 函数库。jQuery的语法设计使得许多操作变得容易,如操作文档对象(document)、选择文档对象模型(DOM)元素、创建动画效果、处理事件、以及开发Ajax程序。jQuery也提供了给开发人员在其上创建插件的能力。这使开发人员可以对底层交互与动画、高级效果和高级主题化的组件进行抽象化。","text":"jQuery 是一个 JavaScript 函数库。jQuery的语法设计使得许多操作变得容易,如操作文档对象(document)、选择文档对象模型(DOM)元素、创建动画效果、处理事件、以及开发Ajax程序。jQuery也提供了给开发人员在其上创建插件的能力。这使开发人员可以对底层交互与动画、高级效果和高级主题化的组件进行抽象化。 jQuery获取元素元素选择器//元素选择器 <div > $("div") id选择器//id选择器 <div id='id'> $("#id") $("div#id") class选择器//class选择器 <div class='class'> $(".class") $("div.class") 属性过滤选择器<li class='check' type='li_01'></li> <li type='li_02'></li> <li type='li_03'></li> //通过属性获取 如果属性值为有特殊字符,一定要加引号 $("[type]") //获取有type属性的元素 $("[type='li_01']") //获取type值等于'li_01'的元素 $("[type!='li_01']") //获取type值不等于'li_01'的元素 $("[type*='li']") //模糊匹配 获取type值包含'li'的元素 $("[type^='li']") //模糊匹配 获取type值以'li'开始的元素 $("[type$='01']") //模糊匹配 获取type值以'01'结尾的元素 $("li[class='check'][type]") //获取多个条件同时满足的元素 * 选择器//遍历form下的所有元素,将其margin设置0 $('form *').css('margin','0px') 并列选择器$('p, div').css('color','red'); //将p元素和div元素的字体颜色设置为red 层叠选择器<div class='a'> <!-- 父级div --> <div class='a1'> <!-- 子级div1 --> <div class='a11'></div> <!-- 孙级div1 --> </div> <div class='a2'></div> <!-- 子级div2 --> <div class='a3'></div> <!-- 子级div3 --> <span></span> <!-- 子级span1 --> </div> $(".a div") //选择class=a的元素下所有的div 即选择到子级div1,2,3 孙级div1 $(".a > div") //选择class=a的元素的所有子div元素, 即选择到子级div1,2,3; $("div + span") //选择所有的div元素的下一个input元素节点,即选择到:子级div3 $(".a1 ~ div") //同胞选择器,返回class为a2的标签元素的所有属于同一个父元素的div标签,即div1,2,3 基本过滤选择器<ul> <li></li> <li></li> <li></li> <li></li> <li></li> </ul> $("li:first") //选择所有li元素的第一个 $("li:last") //选择所有li元素的最后一个 $("li:even") //选择所有li元素的第0,2,4... ...个元素(序号从0开始) $("li:odd") //选择所有li元素的第1,3,5... ...个元素 $("li:eq(2)") //选择所有li元素中的第三个(即序号为2) $("li:gt(3)") //选择所有li元素中序号大于3的li元素 $("li:ll(2)") //选择所有li元素中序号小于2的li元素 <input type="checkbox" /> <input type="checkbox" /> $("input[type='checkbox']:checked") //获取所有已被选中的type等于checkbox的input元素 $("input[type='checkbox']:not(:checked)") //获取所有未被选中的type等于checkbox的input元素 内容过滤器$("div:contains('Faker')") //选择所有div中含有Faker文本的元素 $("div:empty") //选择所有div中为空(不包含任何元素/文本)的元素 $("div:has('p')") //选择所有div中包含p元素的元素 $("div:parent") //选择所有的含有子元素或文本的div 可视化过滤器$("div:hidden") //选择所有被hidden的div元素 $("div:not(:hidden)") //选择所有没有被hidden的div元素 $("div:visible") //所有可视化的div元素 $("div:not(:visible)") //所有非可视化的div元素 子元素过滤器<body> <div class='d1'> <div class='d11'> <div class='d111'> </div> </div> </div> </body> $("body div:first-child") //返回所有的body元素下 所有div 为父元素的第一个元素 的元素. //:first 与 :first-child 的区别用法 //$("body div:first")只匹配到第一个合适的元素 即只匹配到 d1 //$("body div:first-child") 匹配所有合适的元素:d1是body的第一个元素,d11是d1的第一个元素.. //所以匹配到d1,d11,d111 $("div span:last-child") //返回所有的body元素下 所有div 为父元素的最后一个元素 的元素. //:last 与 :last-child 的区别参考first $("div button:only-child") //如果button是它父级元素的唯一子元素,此button将会被匹配 表单元素选择器$(":input") //选择所有的表单输入元素,包括input, textarea, select 和 button $(":text") //选择所有的text input元素 $(":password") //选择所有的password input元素 $(":radio") //选择所有的radio input元素 $(":checkbox") //选择所有的checkbox input元素 $(":submit") //选择所有的submit input元素 $(":image") //选择所有的image input元素 $(":reset") //选择所有的reset input元素 $(":button") //选择所有的button input元素 $(":file") //选择所有的file input元素 $(":hidden") //选择所有类型为hidden的input元素或表单的隐藏域 表单元素过滤器$(":enabled") //选择所有的可操作的表单元素 $(":disabled") //选择所有的不可操作的表单元素 $(":checked") //选择所有的被checked的表单元素 $("select option:selected") //选择所有的select 的子元素中被selected的元素 节点操作获取和操作节点属性<a href='index.html' data-type='a' style="color:red;">index</a> <input value='user' /> $("a").attr("href"); //获取href属性值 $("a").attr("href","about.html"); //设置href属性值 $("a").data("type"); //获取data-type属性值 $("a").css("color"); //通过key(color/display/....)获取css值 $("a").css("color","black"); //通过key/value 设置css属性 $("a").text(); //获取a的文本节点值 $("a").text("Index.html"); //设置a的文本节点值 $("input").val(); //获取input的value值 $("input").val("username"); //设置input的value值 插入节点的方法<div class="head"> <span>Faker<span> </div> $(".head").append("<span>hello</span>") //在.head中的最后插入一段html //结果: <div class="head"><span>Faker</span><span>hello</span></div> $("<span>hello</span>").appendTo(".head") //在.head中的最后插入一段html, //结果: <div class="head"><span>Faker</span><span>hello</span></div> $(".head").prepend("<span>hello</span>") //在.head中的开始插入一段html, //结果: <div class="head"><span>hello</span><span>Faker</span></div> $("<span>hello</span>").prependTo(".head") //在.head中的开始插入一段html, //结果: <div class="head"><span>hello</span><span>Faker</span></div> $(".head *:first").after("<span>hello</span>") //在.head中的第一个元素后插入一段html, //结果: <div class="head"><span>Faker</span><span>hello</span></div> $("<span>hello</span>").insertAfter(".head *:first") //在.head中的第一个元素后插入一段html, //结果: <div class="head"><span>Faker</span><span>hello</span></div> $(".head *:first").before("<span>hello</span>") //在.head中的第一个元素前插入一段html, //结果: <div class="head"><span>hello</span><span>Faker</span></div> $("<span>hello</span>").insertBefore(".head *:first") //在.head中的第一个元素后插入一段html, //结果: <div class="head"><span>hello</span><span>Faker</span></div> $.load()方法 在指定位置加载请求回来的html页面 <div class="head"> </div> $(".head").load(url[,data][,callback]) url: 请求HTML页面的URL地址 data(可选): 请求的key/value参数 callback(可选) 请求完成的回调函数","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"jQuery","slug":"jQuery","permalink":"http://yelog.org/tags/jQuery/"}]},{"title":"idea常用快捷键","slug":"idea-shortcuts","date":"2016-11-05T07:22:48.000Z","updated":"2024-07-05T01:50:55.207Z","comments":true,"path":"2016/11/05/idea-shortcuts/","permalink":"http://yelog.org/2016/11/05/idea-shortcuts/","excerpt":"工欲善其事 , 必先利其器","text":"工欲善其事 , 必先利其器 Idea作为IDE是相当niubility,但是要运用自如还得掌握一些常用快捷键,才能在开发过程中运用自如以下是idea的默认快捷键,如果英语能力没有问题,可以在Help->Keymap Reference 查看官方文档当然作为一款优秀的IDE,怎么会少了自定义快捷键(File->Setting->Keymap,可通过动作名/快捷键组合双向查找)好了,下面是博主在Java相关开发过程中常用到的一些快捷键 常用快捷键组合编辑 序号 快捷键组合 作用 Ctrl+D 重复光标所在行/或选中部分 Ctrl+C 复制光标所在行/或选中部分 Ctrl+V 粘贴 Ctrl+Shift+V 选择粘贴最近5次复制的内容 Ctrl+X 删除光标所在行/或选中部分 Ctrl+Y 删除光标所在行/或选中行 Shift+Enter 向下插入新行 Alt+Shift+↑/↓ 移动当前行到上/下一行 Ctrl+Alt+←/→ 定位到上/下一次光标位置 Ctrl+I 实现接口方法 Ctrl+Shift+o 删除没用的import Ctrl+O 重写父类方法 Ctrl+W 选中当前单词 Ctrl+P 提示参数 Ctrl+Q 查看方法/类的注释文档 Ctrl+Alt+L 格式化当前模板 Ctrl+/ 注释当前行,或选中行 Ctrl+Shift+/ 注释选中部分 /**+回车(类/方法/属性前) 添加注释 搜索/替换 序号 快捷键组合 作用 Ctrl+N 通过类名(文件名)的关键字快速打开文件(仅限.java文件) Ctrl+Shift+N 通过文件名关键字快速打开文件 Ctrl+Shift+N(两次) 通过文件名关键字快速打开文件(包括非本项目内文件,如Maven引入的) Ctrl+Shift+Alt+N 通过关键字(包括类名/方法名/url映射)快速打开文件,定位到类名/方法名/url映射的方法 Ctrl+F 搜索关键字(支持正则)在当前文件 F3 找下一个 Shift+F3 找上一个 Ctrl+R 替换关键字(支持正则)在当前文件 Ctrl+Shift+F 在所有文件(可以指定过滤文件)中查找关键字(支持正则) Ctrl+Shift+R 在所有文件(可以指定过滤文件)中替换关键字(支持正则) debugging 序号 快捷键组合 作用 F8 Step over(跳过下一行) F7 Step into(跳入当前行调用的方法体内) Shift+F7 Smart Step into(跳过) 未完待续","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"软件记录","slug":"工具/软件记录","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95/"}],"tags":[{"name":"IntellijIDEA","slug":"IntellijIDEA","permalink":"http://yelog.org/tags/IntellijIDEA/"}]},{"title":"web.xml详解","slug":"web-xml详解","date":"2016-10-24T02:10:45.000Z","updated":"2024-07-05T01:50:55.371Z","comments":true,"path":"2016/10/24/web-xml/","permalink":"http://yelog.org/2016/10/24/web-xml/","excerpt":"web.xml文件是用来配置:欢迎页、servlet、filter、listener等的. 当你的web项目工程没用到这些时,你可以不用web.xml文件来配置你的web工程。如果项目中有多项标签,其加载顺序依次是:context-param >> listener >> filter >> servlet(同类多个节点出现顺序依次加载)","text":"web.xml文件是用来配置:欢迎页、servlet、filter、listener等的. 当你的web项目工程没用到这些时,你可以不用web.xml文件来配置你的web工程。如果项目中有多项标签,其加载顺序依次是:context-param >> listener >> filter >> servlet(同类多个节点出现顺序依次加载) web.xml先读取context-param和listener这两种节点; 然后容器创建一个ServletContext(上下文),应用于整个项目; 容器会将读取到的context-param转化为键值对并存入servletContext; 根据listener创建监听; 容器会读取,根据指定的类路径来实例化过滤器; 此时项目初始化完成; 在发起第一次请求是,servlet节点才会被加载实例化。 参数设置context-paramcontext-param节点是web.xml中用于配置应用于整个web项目的​上下文。包括两个子节点,其中param-name 设定上下文的参数名称。必须是唯一名称;param-value 设定的参数名称的值。 读取节点的方法如下: ${initParam.参数名} Servlet中String paramValue=getServletContext().getInitParameter(“参数名”)​ web.xml中配置spring必须使用listener节点,但context-param节点可有可无,如果缺省则默认contextConfigLocation路径为“/WEB-INF/applicationContext.xml”;如果有多个xml文件,使用”,“分隔 listener<listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener> 为web应用程序定义监听器,监听器用来监听各种事件,比如:application和session事件,所有的监听器按照相同的方式定义,功能取决去它们各自实现的接口,常用的Web事件接口有如下几个:ServletContextListener:用于监听Web应用的启动和关闭;ServletContextAttributeListener:用于监听ServletContext范围(application)内属性的改变;ServletRequestListener:用于监听用户的请求;ServletRequestAttributeListener:用于监听ServletRequest范围(request)内属性的改变;HttpSessionListener:用于监听用户session的开始和结束;HttpSessionAttributeListener:用于监听HttpSession范围(session)内属性的改变。 配置Listener只要向Web应用注册Listener实现类即可,无序配置参数之类的东西,因为Listener获取的是Web应用ServletContext(application)的配置参数。为Web应用配置Listener的两种方式: 使用@WebListener修饰Listener实现类即可。 在web.xml文档中使用进行配置。 servletservlet即配置所需用的servlet,用于处理及响应客户的请求。容器的Context对象对请求路径(URL)做出处理,去掉请求URL的上下文路径后,按路径映射规则和Servlet映射路径()做匹配,如果匹配成功,则调用这个Servlet处理请求。 为Servlet命名:<servlet> <servlet-name>servlet</servlet-name> <servlet-class>org.whatisjava.TestServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> 为Servlet定制URL<servlet-mapping> <servlet-name>servlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> Load-on-startupLoad-on-startup 元素在web应用启动的时候指定了servlet被加载的顺序,它的值必须是一个整数。如果它的值是一个负整数或是这个元素不存在,那么容器会在该servlet被调用的时候,加载这个servlet 。如果值是正整数或零,容器在配置的时候就加载并初始化这个servlet,容器必须保证值小的先被加载。如果值相等,容器可以自动选择先加载谁。当值为0或者大于0时,表示容器在应用启动时就加载这个servlet;当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。正数的值越小,启动该servlet的优先级越高。 filter设置过滤器:如编码过滤器,过滤所有资源 <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> session设置会话(Session)过期时间,其中时间以分钟为单位,加入设置60分超时: <session-config> <session-timeout>60</session-timeout> </session-config> welcom-file-list<welcome-file-list> <welcome-file>index.jsp</welcome-file> <welcome-file>index.html</welcome-file> </welcome-file-list> PS:指定了两个欢迎页面,显示时按顺序从第一个找起,如果第一个存在,就显示第一个,后面的不起作用。如果第一个不存在,就找第二个,以此类推。如果都没有就404. 关于欢迎页面:访问一个网站时,默认看到的第一个页面就叫欢迎页,一般情况下是由首页来充当欢迎页的。一般情况下,我们会在web.xml中指定欢迎页。但 web.xml并不是一个Web的必要文件,没有web.xml,网站仍然是可以正常工作的。只不过网站的功能复杂起来后,web.xml的确有非常大用处,所以,默认创建的动态web工程在WEB-INF文件夹下面都有一个web.xml文件。 error-page<!-- 错误码 --> <error-page> <error-code>404</error-code> <location>/error404.jsp</location> </error-page> <!-- 错误类型 --> <error-page> <exception-type>java.lang.Exception<exception-type> <location>/exception.jsp<location> </error-page>","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"javaee","slug":"javaee","permalink":"http://yelog.org/tags/javaee/"}]},{"title":"Hexo+Git服务器搭建blog","slug":"hexo-git-server-blog","date":"2016-10-23T01:24:03.000Z","updated":"2024-07-05T01:50:55.218Z","comments":true,"path":"2016/10/23/hexo-git-server-blog/","permalink":"http://yelog.org/2016/10/23/hexo-git-server-blog/","excerpt":"博主最近在服务器上搭建Hexo发布平台,感觉整个搭建过程和搭建思想蛮有意思,在此记录一下,供猿友参考Hexo 是一个快速,简单,功能强大,主题社区特别庞大的开源blog框架-》官网本次搭建是通过在服务器上搭建Git服务器来实现一键发布blog","text":"博主最近在服务器上搭建Hexo发布平台,感觉整个搭建过程和搭建思想蛮有意思,在此记录一下,供猿友参考Hexo 是一个快速,简单,功能强大,主题社区特别庞大的开源blog框架-》官网本次搭建是通过在服务器上搭建Git服务器来实现一键发布blog 搭建思路 客户端就是自己的电脑,可以把hexo的静态资源目录当成一个git仓库. 首先配置好远程git仓库,通过 hexo d 将静态网站资源push到远程git仓库 git仓库接收到push处理完成后,自动触发post-receive这个钩子. 执行钩子内容,进入到 /var/www/blog 目录(也是一个git仓库),拉取刚才hexo推送到git服务端的静态网站资源. 配置nginx,将80端口映射到 /var/www/blog 目录. 就可以直接通过ip访问到静态blog了 搭建过程环境准备在服务器上安装git并创建git远程仓库 如 blog.git搭建过程移步 搭建Git服务器 在 _config.yml 中配置git服务器deploy: type: git repo: git@server:/home/git/blog.git branch: master 如果ssh端口不是默认的22的话,如下配置,8080改为自己服务器上ssh端口 deploy: type: git repo: ssh://git@server:8080/home/git/blog.git branch: master 配置nginx现在已经可以使用 hexo d 将hexo中的生成的静态资源发送到远程服务器中,接下来我们要配置nginx来配置静态web。安装过程可以自行Google,在此只说明nginx如何配置静态web首先创建一个目录作为存放web资源(hexo生成的)的目录,如: /var/www/blog cd /var/www #创建blog目录,并克隆blog.git仓库的内容 git clone /home/git/blog.git blog 找到 nginx.conf 添加以下信息 server { listen 80; charset utf-8; root /var/www/blog; index index.htm index.html index.jsp; } #重启并加载配置文件 $ nginx -s reload 配置git服务器hooks这个钩子的作用是,当git服务器接受客户端push完成更新,执行此文件内容 #创建并编辑post-receive $ vim blog.git/hooks/post-receive 内容如下 #!/bin/sh unset GIT_DIR #还原环境变量,否则会拉不到代码 cd /var/www/blog git pull origin master #拉取最新代码 测试效果在本地的hexo下执行 hexo d查看 /var/www/blog文件夹内的内容也发生变化","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"},{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"}]},{"title":"Hexo+GitHub Pages搭建属于自己的blog","slug":"hexo-gitHub-pages-create-own-blog","date":"2016-10-22T13:24:05.000Z","updated":"2024-07-05T01:50:55.298Z","comments":true,"path":"2016/10/22/hexo-gitHub-pages-create-own-blog/","permalink":"http://yelog.org/2016/10/22/hexo-gitHub-pages-create-own-blog/","excerpt":"Hexo是一个快速,简单,功能强大的开源博客框架-》官网GitHub Pages 是一个不受限的网站空间。两者相得益彰。给那些喜欢自己折腾的人提供一些借鉴。","text":"Hexo是一个快速,简单,功能强大的开源博客框架-》官网GitHub Pages 是一个不受限的网站空间。两者相得益彰。给那些喜欢自己折腾的人提供一些借鉴。 搭建过程环境介绍博主使用系统:Deepin Linux 15.3桌面版安装 node与npm 安装Hexonpm install hexo-cli -g 初始化bloghexo init blog 至此,本地blog已经创建完成,是不是很简单,简单到没朋友 选择主题可以在hexo官网查看自己喜欢的主题通过git clone [url] themes/xxx 将主题克隆到本地,修改 _config.yml 中的theme:xxx 常用命令#创建一个新的文章 $ hexo new "文章名" #生成静态文件 $ hexo generate #将一个草稿发布出去 $ hexo publish [layout] <filename> #启动一个本地服务器 $ hexo server 更多命令移步官方文档 搭建github pages本地blog已经搭建完成,现在可以发布到github pages上 注册github账户到github官网注册一个github账户 配置登录免密码移步 Git之SSH与HTTPS免密码配置 创建github远程仓库在github上创建一个仓库 xxx.github.io xxx为自己的github用户名 安装插件$ npm install hexo-deployer-git --save 配置Hexo修改 _comfig.yml,xxx为你的用户名 deploy: type: git repo: git@github.com:xxx/xxx.github.io.git branch: master 推送服务器$ hexo deploy 若出现ERROR Deployer not found: git报错,请执行上面安装插件步骤 测试打开 xxx.github.io ,就能看到你的blog了","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"},{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"},{"name":"GitHub","slug":"GitHub","permalink":"http://yelog.org/tags/GitHub/"}]},{"title":"搭建Git服务器","slug":"set-up-git-server-on-vps","date":"2016-10-22T12:08:04.000Z","updated":"2024-07-05T01:50:55.333Z","comments":true,"path":"2016/10/22/set-up-git-server-on-vps/","permalink":"http://yelog.org/2016/10/22/set-up-git-server-on-vps/","excerpt":"最近由于准备在公司的服务器上面搭建静态博客(Hexo),然后需要先搭建一个git服务器作为转接,整个过程看似顺利,十几分钟就搭建完成,不过最后在验证这块卡了两个小时,在此记录下来,供准备搭建git服务器的新手小伙伴们借鉴。","text":"最近由于准备在公司的服务器上面搭建静态博客(Hexo),然后需要先搭建一个git服务器作为转接,整个过程看似顺利,十几分钟就搭建完成,不过最后在验证这块卡了两个小时,在此记录下来,供准备搭建git服务器的新手小伙伴们借鉴。 搭建git服务器通过ssh链接到服务器,开始进行操作 第一步在服务器上安装 git $ sudo apt-get install git 第二步创建 git 用户,用来运行git服务 $ sudo adduser git 第三步创建证书,免密码登录:收集所有需要登录的用户的公钥(id_rsa.pub)文件,把所有公钥导入到 /home/git/.ssh/authorized_keys 文件内,一行一个。如果个人的git中的公钥已经连接了其他服务器如:github,可以参考 一个客户端设置多个github账号 注意:一定要通过下面的命令将该文件其他用户的所有权限移除,否则会出现文章尾部问题 $ chmod 600 authorized_keys 第四步初始化git仓库 $ git init --bare test.git git创建一个裸仓库,裸仓库没有工作区,因为服务器上的git仓库纯粹为了共享,所有不能让用户直接登录到服务器上去改工作区,并且服务器的git仓库通常以 .git 结尾。然后,修改owner改为git: $ sudo chown -R git:git test.git 第五步禁用shell登录:处于安全的考虑,第二步创建的git用户不允许登录shell,这可以通过编辑 /etc/passwd 文件完成。 git:x:1003:1003::/home/git:/bin/bash 改为 git:x:1003:1003::/home/git:/usr/bin/git-shell 这样,git用户可以正常通过ssh使用git,但无法登录shell,因为我们为git用户指定的git-shell每次一登录就自动退出。 第六步克隆远程仓库:现在,可以通过git clone命令克隆远程仓库了,在各自的电脑上运行: $ git clone git@server:/home/git/test.git 如果服务器的ssh端口不是默认的22的话,比如说6789,可以这样写: $ git clone ssh://git@server:6789/home/git/test.git 问题来了本来根据文档,根据广大猿友的经验,我的搭建之路已经完成了,然后最后一步出现了问题。每次跟服务器进行交互(clone,pull,push),都让我输入git的密码,也就是说,我配置的ssh没有生效。然后就开始到处找原因,重新生成rsa,提升authorized_keys权限,重新创建服务器git账户,重新。。。。。 翻遍了 Stack Overflow 和 segmentfault ,两个小时过去了,问题仍然没有进展,这么简单的东西,问题到底出在哪里。 就在心灰意冷,准备放弃的时候,不知道是哪里来的灵感,准备把 authorized_keys 文件的其他用户的权限删掉,然后就能用了,后就能用了,就能用了,能用了,用了,了~~~~,命令如下,不想多说话,我想静静。 $ chmod 600 authorized_keys","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"}]},{"title":"如何给GitHub上的项目贡献代码","slug":"contributing-to-open-source-on-github","date":"2016-10-13T07:44:07.000Z","updated":"2024-07-05T01:50:55.346Z","comments":true,"path":"2016/10/13/contributing-to-open-source-on-github/","permalink":"http://yelog.org/2016/10/13/contributing-to-open-source-on-github/","excerpt":"最近一直在使用 hexo 的一款主题 yelee ,但是发现它的代码块由于空行不占位导致的显示错位,所以就去GitHub上翻issue,果然有好多人都在反映这个问题,并且作者已经打上bug标签,事情应该就马上结束了,就去忙别的了。这两天又去逛了一下issue,发现这个bug仍然屹立在那里,强迫症又犯了,趁着今天工作不怎么忙,就把这个bug解决了。然后问题来了,怎么才能给作者贡献代码呢。","text":"最近一直在使用 hexo 的一款主题 yelee ,但是发现它的代码块由于空行不占位导致的显示错位,所以就去GitHub上翻issue,果然有好多人都在反映这个问题,并且作者已经打上bug标签,事情应该就马上结束了,就去忙别的了。这两天又去逛了一下issue,发现这个bug仍然屹立在那里,强迫症又犯了,趁着今天工作不怎么忙,就把这个bug解决了。然后问题来了,怎么才能给作者贡献代码呢。 准备工作 首先通过 git clone 将项目克隆到本地(我早已拉下来,跳过此步骤) git pull 拉取最新代码(将所有的change都同步到本地) 将 原项目 fork 到 自己的github上,并复制代码url 在本地添加第二个仓库地址:git remote add [nickname] [your url] 修改 修改bug 或 新增功能 git commit [file1] [file2] ... -m [message] 本地提交代码 同步到github中并发到原项目 git push [nickname] 将代码 push 到自己的项目里,nickname就是添加的第二个仓库的名字 自己项目内,点击 pull requests -》 new pull request 将本次修改提交到原项目进行同步。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"},{"name":"GitHub","slug":"GitHub","permalink":"http://yelog.org/tags/GitHub/"}]},{"title":"PostgreSQL常用操作","slug":"PostgreSQL常用操作","date":"2016-10-12T12:27:07.000Z","updated":"2024-07-05T01:50:55.007Z","comments":true,"path":"2016/10/12/PostgreSQL-2/","permalink":"http://yelog.org/2016/10/12/PostgreSQL-2/","excerpt":"控制台命令\\h: #查看SQL命令的解释,比如\\h select。 \\?: #查看psql命令列表。 \\l: #列出所有数据库。 \\c [database_name]: #连接其他数据库。 \\d: #列出当前数据库的所有表格。 \\d [table_name]: #列出某一张表格的结构。 \\du: #列出所有用户。 \\e: #打开文本编辑器。 \\conninfo: #列出当前数据库和连接的信息。","text":"控制台命令\\h: #查看SQL命令的解释,比如\\h select。 \\?: #查看psql命令列表。 \\l: #列出所有数据库。 \\c [database_name]: #连接其他数据库。 \\d: #列出当前数据库的所有表格。 \\d [table_name]: #列出某一张表格的结构。 \\du: #列出所有用户。 \\e: #打开文本编辑器。 \\conninfo: #列出当前数据库和连接的信息。 数据库操作基本的数据库操作,就是使用一般的SQL语言 # 创建新表 CREATE TABLE user_tbl(name VARCHAR(20), signup_date DATE); # 插入数据 INSERT INTO user_tbl(name, signup_date) VALUES('张三', '2013-12-22'); # 选择记录 SELECT * FROM user_tbl; # 更新数据 UPDATE user_tbl set name = '李四' WHERE name = '张三'; # 删除记录 DELETE FROM user_tbl WHERE name = '李四' ; # 添加栏位 ALTER TABLE user_tbl ADD email VARCHAR(40); # 更新结构 ALTER TABLE user_tbl ALTER COLUMN signup_date SET NOT NULL; # 更名栏位 ALTER TABLE user_tbl RENAME COLUMN signup_date TO signup; # 删除栏位 ALTER TABLE user_tbl DROP COLUMN email; # 表格更名 ALTER TABLE user_tbl RENAME TO backup_tbl; # 删除表格 DROP TABLE IF EXISTS backup_tbl;","categories":[{"name":"数据库","slug":"数据库","permalink":"http://yelog.org/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"}],"tags":[{"name":"PostgreSQL","slug":"PostgreSQL","permalink":"http://yelog.org/tags/PostgreSQL/"}]},{"title":"PostgreSQL初体验","slug":"PostgreSQL初体验","date":"2016-10-12T11:49:40.000Z","updated":"2024-07-05T01:50:55.029Z","comments":true,"path":"2016/10/12/PostgreSQL-1/","permalink":"http://yelog.org/2016/10/12/PostgreSQL-1/","excerpt":"创建操作系统用户创建一个新的Linux用户:dbuser $sudo adduser dbuser #创建一个新的Linux用户:dbuser","text":"创建操作系统用户创建一个新的Linux用户:dbuser $sudo adduser dbuser #创建一个新的Linux用户:dbuser 登录PostgreSQL控制台切换到postgres用户 $sudo su - postgres #切换到postgres用户 系统用户postgres以同名数据库用户的身份,登录数据库 $psql #系统用户postgres以同名数据库用户的身份,登录数据库 成功登录到控制台后,显示 postgres=# 注意:后面分号不能省略 \\password postgres #给postgres用户设置密码 创建数据库用户dbuser CREATE USER dbuser WITH PASSWORD 'dbuser'; #创建数据库用户dbuser 创建用户数据库,这里为exampledb,并指定所有者为dbuser。 CREATE DATABASE exampledb OWNER dbuser; #创建用户数据库,这里为exampledb,并指定所有者为dbuser。 将exampledb数据库的所有权限都赋予dbuser GRANT ALL PRIVILEGES ON DATABASE exampledb to dbuser; #将exampledb数据库的所有权限都赋予dbuser 推出控制台(也可以直接按ctrl+D) \\q #退出控制台","categories":[{"name":"数据库","slug":"数据库","permalink":"http://yelog.org/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"}],"tags":[{"name":"PostgreSQL","slug":"PostgreSQL","permalink":"http://yelog.org/tags/PostgreSQL/"}]},{"title":"PostgreSQL的介绍与安装","slug":"PostgreSQL的安装","date":"2016-10-12T07:44:16.000Z","updated":"2024-07-05T01:50:55.012Z","comments":true,"path":"2016/10/12/PostgreSQL-3/","permalink":"http://yelog.org/2016/10/12/PostgreSQL-3/","excerpt":"由于工作认识了PostgreSQL,在此系统学习一下这个数据库,本文除博主实践所得以外,大量译于 官方文档 PostgreSQL是什么 PostgreSQL 是一个基于 POSTGRES, Version 4.2 的对象关系数据库系统(ORDBMS),由加州大学伯克利分校计算机科学系开发。PostgreSQL 是一个开源的数据库,因为自由许可,任何人都可以免费的使用、修改、分发 PostgreSQL 数据库用于任何目的。","text":"由于工作认识了PostgreSQL,在此系统学习一下这个数据库,本文除博主实践所得以外,大量译于 官方文档 PostgreSQL是什么 PostgreSQL 是一个基于 POSTGRES, Version 4.2 的对象关系数据库系统(ORDBMS),由加州大学伯克利分校计算机科学系开发。PostgreSQL 是一个开源的数据库,因为自由许可,任何人都可以免费的使用、修改、分发 PostgreSQL 数据库用于任何目的。 它支持大部分的SQL标准并提供了许多流行的功能: 复杂查询(complex queries) 外键(foreign keys) 触发器(triggers) 可更新的视图(updatable views) 事务完整性(transactional integrity) 多版本并发控制(multiversion concurrency control) 用户也可以给PostgreSQL扩展很多东西,比如: 数据类型(data types) 函数(functions) 运算符(operators) 聚合函数(aggregate functions) 索引方法(index methods) 安装博主开发环境: 系统 :深度Linux 15.3 桌面版 PostgreSQL :9.4 通过apt-get安装$ apt-get install postgresql-9.4 仓库有许多不同的包(包括第三方插件),最常见、最重要的包(根据需要替换版本号): postgresql-client-9.4 - 客户端库和二进制文件 postgresql-9.4 - 核心数据库服务器 postgresql-contrib-9.4 - 提供额外的模块 libpq-dev - C语言前端开发库和头文件 postgresql-server-dev-9.4 - C语言后端开发库和头文件 pgadmin3 - pgAdmin III 图形化管理工具","categories":[{"name":"数据库","slug":"数据库","permalink":"http://yelog.org/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"}],"tags":[{"name":"PostgreSQL","slug":"PostgreSQL","permalink":"http://yelog.org/tags/PostgreSQL/"}]},{"title":"FreeMarker语法详解","slug":"freemarker语法详解","date":"2016-10-05T03:58:50.000Z","updated":"2024-07-05T01:50:55.137Z","comments":true,"path":"2016/10/05/FreeMarker/","permalink":"http://yelog.org/2016/10/05/FreeMarker/","excerpt":"FreeMarker是一款 模板引擎 :即一种基于模板和要改变的数据,并用来生成输出文本(HTML网页、电子邮件、配置文件、源代码等)的通用工具。FreeMarker模板文件主要有4部分组成 文本,直接输出的部分 注释,即<#–…–>格式不会输出 插值(Interpolation):即${..}或者#{..}格式的部分,将使用数据模型中的部分替代输出 FTL指令:FreeMarker指令,和HTML标记类似,名字前加#予以区分,不会输出。","text":"FreeMarker是一款 模板引擎 :即一种基于模板和要改变的数据,并用来生成输出文本(HTML网页、电子邮件、配置文件、源代码等)的通用工具。FreeMarker模板文件主要有4部分组成 文本,直接输出的部分 注释,即<#–…–>格式不会输出 插值(Interpolation):即${..}或者#{..}格式的部分,将使用数据模型中的部分替代输出 FTL指令:FreeMarker指令,和HTML标记类似,名字前加#予以区分,不会输出。 一些规则FTL指令规则FreeMarker有三种FTL标签,这和HTML的标签是完全类似的 开始标签:<#directivename parameters> 结束标签:</#directivename> 空标签: <#directivename parameters /> 实际上,使用标签时前面的#符号也可能变成@,如果该指令是一个用户指令而不是系统内建指令时,应将#符号改为@符号 插值规则FreeMarker的插值有如下两种类型 1、通用插值:${expr} 2、数字格式化插值:#{expr}或者#{expr;format}通用插值,有可以分为四种情况 a、插值结果为字符串值:直接输出表达式结果 b、插值结果为数字值:根据默认格式(#setting 指令设置)将表达式结果转换成文本输出。可以使用内建的字符串函数格式单个插值,例如 <#setting number_format = "currency" /> <#assign str = 42 /> ${str} ${str?string} ${str?string.number} ${str?string.currency} ${str?string.percent} ${str?string.computer} 日期处理 ${openingTime?string.short} ${openingTime?string.medium} ${openingTime?string.long} ${openingTime?string.full} ${nextDiscountDay?string.short} ${nextDiscountDay?string.medium} ${nextDiscountDay?string.long} ${nextDiscountDay?string.full} ${lastUpdated?string.short} ${lastUpdated?string.medium} ${lastUpdated?string.long} ${lastUpdated?string.full} ${lastUpdated?string("yyyy-MM-dd HH:mm:ss zzzz")} ${lastUpdated?string("EEE, MMM d, ''yy")} ${lastUpdated?string("EEEE, MMMM dd, yyyy, hh:mm:ss a '('zzz')'")} if,elseif,elseif<#if condition> …… <#elseif condition2> …… <#else> …… </#if> switch,case<#switch value> <#case refValue1> ... <#break> <#case refValue2> ... <#break> ... <#case refValueN> ... <#break> <#default> ... </#switch> <#t> 去掉左右空白和回车换行 <#lt>去掉左边空白和回车换行 <#rt>去掉右边空白和回车换行 <#nt>取消上面的效果 list<#list sequence as item> ... <#if item = "spring"> <#break> </#if> ... </#list> iterm_index:当前值得下标,从0开始item_has_next:判断list是否还有值 include<#include filename [options]> options 包含两个属性encoding=”GBK”parse=”true” 是否作为ftl语法解析,默认是true示例:<#include “/common/copyright.ftl” encoding=”GBK” parse=”true”> import<#import path as hash> 类似于java里的import,它导入文件,然后就可以在当前文件里使用被导入文件里的宏组件 compress<#compress> ... </#compress> escape, noescape<#escape identifier as expression> ... <#noescape>...</#noescape> ... </#escape> 主要使用在相似的字符串变量输出,比如某一个模块的所有字符串输出都必须是html安全的,这个时候就可以使用该表达式示例: <#escape x as x?html> First name: ${firstName} <#noescape>Last name: ${lastName}</#noescape> Maiden name: ${maidenName} </#escape> 相同表达式 First name: ${firstName?html} Last name: ${lastName } Maiden name: ${maidenName?html} assign<#assign name=value> <#-- 或则 --> <#assign name1=value1 name2=value2 ... nameN=valueN> <#-- 或则 --> <#assign same as above... in namespacehash> <#-- 或则 --> <#assign name> capture this </#assign> <#-- 或则 --> <#assign name in namespacehash> capture this </#assign> 生成变量,并且给变量赋值 global<#global name=value> <#--或则--> <#global name1=value1 name2=value2 ... nameN=valueN> <#--或则--> <#global name> capture this </#global> 全局赋值语法,利用这个语法给变量赋值,那么这个变量在所有的namespace [A1] 中是可见的, 如果这个变量被当前的assign 语法覆盖 如<#global x=2> <#assign x=1> 在当前页面里x=2 将被隐藏,或者通过${.global.x} 来访问 setting<#setting name=value> 用来设置整个系统的一个环境localenumber_formatboolean_formatdate_format , time_format , datetime_formattime_zoneclassic_compatible macro, nested, return<#macro name param1 param2 ... paramN> ... <#nested loopvar1, loopvar2, ..., loopvarN> ... <#return> ... </#macro> t, lt, rt<#t> 去掉左右空白和回车换行 <#lt>去掉左边空白和回车换行 <#rt>去掉右边空白和回车换行 <#nt>取消上面的效果","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"freemarker","slug":"freemarker","permalink":"http://yelog.org/tags/freemarker/"}]},{"title":"正则表达式详解","slug":"regular-expression","date":"2016-10-04T01:41:40.000Z","updated":"2024-07-05T01:50:55.174Z","comments":true,"path":"2016/10/04/regular-expression/","permalink":"http://yelog.org/2016/10/04/regular-expression/","excerpt":"本文目标 本文旨在更加简洁清晰的展现正则表达式, 第一部分 是对正则表达式语法的简洁介绍, 第二部分 则是常用正则表达式的示例。","text":"本文目标 本文旨在更加简洁清晰的展现正则表达式, 第一部分 是对正则表达式语法的简洁介绍, 第二部分 则是常用正则表达式的示例。 简介及语法正则表达式是什么 在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。 正则表达式语言由两种基本字符类型组成:原义(正常)文本字符和元字符。元字符使正则表达式具有处理能力。 常用元字符 元字符 做什么用 . 匹配除换行符以外的任意字符 \\w 匹配字母或数字或下划线或汉字 \\s 匹配任意的空白符,包括空格,制表符(Tab),换行符,中文全角空格等) \\d 匹配数字 \\b 匹配单词的开始或结束 ^ 匹配字符串的开始 $ 匹配字符串的结束 字符转义 如果你想查找元字符本身的话,比如你查找 . ,或者 * ,就出现了问题:你没办法指定它们,因为它们会被解释成别的意思。这时你就得使用 \\ 来取消这些字符的特殊意义。因此,你应该使用 \\. 和 \\* 。当然,要查找 \\ 本身,你也得用 \\\\ .例如: deerchao\\.net 匹配 deerchao.net,C:\\\\Windows 匹配 C:\\Windows 。 重复 你已经看过了前面的 * , + , {2} , {5,12} 这几个匹配重复的方式了。下面是正则表达式中所有的限定符(指定数量的代码,例如*,{5,12}等) 元字符 做什么用 * 重复零次或更多次 + 重复一次或更多次 ? 重复零次或一次 {n} 重复n次 {n,} 重复n次或更多次 {n,m} 重复n到m次 下面是一些重复的示例:Windows\\d+ 匹配Windows后面跟1个或更多数字^\\w+ 匹配一行的第一个单词(或整个字符串的第一个单词,具体匹配哪个意思得看选项设置) 字符类 要想查找数字,字母或数字,空白是很简单的,因为已经有了对应这些字符集合的元字符,但是如果你想匹配没有预定义元字符的字符集合(比如元音字母a,e,i,o,u),应该怎么办?很简单,你只需要在方括号里列出它们就行了,像 [aeiou] 就匹配任何一个英文元音字母, [.?!] 匹配标点符号(.或?或!)。我们也可以轻松地指定一个字符范围,像 [0-9] 代表的含意与 \\d 就是完全一致的:一位数字;同理[a-z0-9A-Z_]也完全等同于 \\w (如果只考虑英文的话)。下面是一个更复杂的表达式: \\(?0\\d{2}[) -]?\\d{8} 。这个表达式可以匹配几种格式的电话号码,像(010)88886666,或022-22334455,或02912345678等。我们对它进行一些分析吧:首先是一个转义字符(,它能出现0次或1次(?),然后是一个0,后面跟着2个数字(\\d{2}),然后是)或-或空格中的一个,它出现1次或不出现(?),最后是8个数字(\\d{8})。 分支条件 正则表达式里的分枝条件指的是有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用|把不同的规则分隔开。示例: 0\\d{2}-\\d{8}|0\\d{3}-\\d{7}这个表达式匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔。你可以试试用分枝条件把这个表达式扩展成也支持4位区号的。示例: \\d{5}-\\d{4}|\\d{5}这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。之所以要给出这个示例是因为它能说明一个问题:使用分枝条件时,要注意各个条件的顺序。如果你把它改成 \\d{5}|\\d{5}-\\d{4} 的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。原因是匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。 分组 重复单个字符,直接在字符后面加上限定符就行了。但如果想要重复多个字符,我们可以用小括号来指定 子表达式(也叫作分组)。(\\d{1,3}\\.){3}\\d{1,3} 是一个简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它: \\d{1,3} 匹配1到3位的数字, (\\d{1,3}\\.){3} 匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,最后再加上一个一到三位的数字 (\\d{1,3}) 。不幸的是,它也将匹配 256.300.888.999 这种不可能存在的IP地址。我们只能使用冗长的分组,选择,字符串来描述一个 正确的IP地址: ((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?) 反义 有时需要查找不属于某个能简单定义的字符类的字符。比如想查找除了数字以外,其它任意字符都行的情况,这时需要用到反义: 元字符 做什么用 \\W 匹配任意不是字母,数字,下划线,汉字的字符 \\S 匹配任意不是空白符的字符 \\D 匹配任意非数字的字符 \\B 匹配不是单词开头或结束的位置 [^x] 匹配除了x以外的任意字符 [^aeiou] 匹配除了aeiou这几个字母以外的任意字符 示例: \\S+ 匹配不包含空白符的字符串。 <a[^>]+> 匹配用尖括号括起来的以a开头的字符串。 后向引用 使用小括号指定一个子表达式后,匹配这个子表达式 的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个 组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。 后向引用 用于重复搜索前面某个分组匹配的文本。示例: \\b(\\w+)\\b\\s+\\1\\b 可以用来匹配重复的单词,像go go, 或者kitty kitty。这个表达式首先是一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字 \\b(\\w+)\\b ,这个单词会被捕获到编号为1的分组中,然后是1个或几个空白符\\s+,最后是分组1中捕获的内容(也就是前面匹配的那个单词) \\1 。你也可以自己指定子表达式的 组名.要指定一个子表达式的组名,请使用这样的语法: (?<Word>\\w+) (或者把尖括号换成 ' 也行: (?'Word'\\w+)),这样就把\\w+的组名指定为 Word 了。要反向引用这个分组捕获的内容,你可以使用 \\k<Word> ,所以上一个示例也可以写成这样: \\b(?<Word>\\w+)\\b\\s+\\k<Word>\\b 。 零宽断言 接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\\b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为 零宽断言。(?=exp) 也叫 零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp。比如\\b\\w+(?=ing\\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找 I’m singing while you’re dancing. 时,它会匹配 sing 和 danc 。(?<=exp) 也叫 零宽度正回顾后发断言 ,它断言自身出现的位置的前面能匹配表达式exp。比如 (?<=\\bre)\\w+\\b 会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找 reading a book 时,它匹配ading。假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分: ((?<=\\d)\\d{3})+\\b ,用它对1234567890进行查找时结果是234567890。下面这个示例同时使用了这两种断言: (?<=\\s)\\d+(?=\\s) 匹配以空白符间隔的数字( 再次强调,不包括这些空白符 )。 负向零宽断言 前面我们提到过怎么查找不是某个字符或不在某个字符类里的字符的方法(反义)。但是如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?例如,如果我们想查找这样的单词–它里面出现了字母q,但是q后面跟的不是字母u,我们可以尝试这样:\\b\\w*q[^u]\\w*\\b 匹配 包含后面不是字母u的字母q的单词 。但是如果多做测试(或者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像Iraq,Benq,这个表达式就会出错。这是因为[^u]总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的[^u]将会匹配q后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的 \\w*\\b 将会匹配下一个单词,于是 \\b\\w*q[^u]\\w*\\b 就能匹配整个Iraq fighting。负向零宽断言 能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,我们可以这样来解决这个问题: \\b\\w*q(?!u)\\w*\\b 。零宽度负预测先行断言(?!exp),断言此位置的后面不能匹配表达式exp。例如: \\d{3}(?!\\d) 匹配三位数字,而且这三位数字的后面不能是数字; \\b((?!abc)\\w)+\\b 匹配不包含连续字符串abc的单词。同理,我们可以用(?<!exp),*零宽度负回顾后发断言来断言** 此位置的前面不能匹配表达式exp:(?<![a-z])\\d{7}匹配前面不是小写字母的七位数字。一个更复杂的示例:`(?<=<(\\w+)>).(?=</\\1>) 匹配不包含属性的简单HTML标签内里的内容。(?<=<(\\w+)>)指定了这样的 **前缀**:被尖括号括起来的单词(比如可能是<b>),然后是.*(任意的字符串),最后是一个 **后缀**(?=</\\1>)。注意后缀里的 \\/ ,它用到了前面提过的字符转义;\\1则是一个反向引用,引用的正是捕获的第一组,前面的 (\\w+)` 匹配的内容,这样如果前缀实际上是的话,后缀就是了。整个表达式匹配的是和之间的内容(再次提醒,不包括前缀和后缀本身)。 注释 小括号的另一种用途是通过语法(?#comment)来包含注释。例如:2[0-4]\\d(?#200-249)|25[0-5](?#250-255)|[01]?\\d\\d?(?#0-199)。要包含注释的话,最好是启用“忽略模式里的空白符”选项,这样在编写表达式时能任意的添加空格,Tab,换行,而实际使用时这些都将被忽略。启用这个选项后,在#后面到这一行结束的所有文本都将被当成注释忽略掉。例如,我们可以前面的一个表达式写成这样: (?<= # 断言要匹配的文本的前缀 <(\\w+)> # 查找尖括号括起来的字母或数字(即HTML/XML标签) ) # 前缀结束 .* # 匹配任意文本 (?= # 断言要匹配的文本的后缀 <\\/\\1> # 查找尖括号括起来的内容:前面是一个”/“,后面是先前捕获的标签 ) # 后缀结束 贪婪与懒惰 当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配 尽可能多 的字符。以这个表达式为例: a.*b ,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为 贪婪匹配。有时,我们更需要 懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号 ? 。这样 .*? 就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。示例: a.*?b 匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)和ab(第四到第五个字符)。 语法 做什么用 *? 重复任意次,但尽可能少重复 +? 重复1次或更多次,但尽可能少重复 ?? 重复0次或1次,但尽可能少重复 {n,m}? 重复n到m次,但尽可能少重复 {n,}? 重复n次以上,但尽可能少重复 其他元字符 元字符 做什么用 \\a 报警字符(打印它的效果是电脑嘀一声) \\b 通常是单词分界位置,但如果在字符类里使用代表退格 \\t 制表符,Tab \\r 回车 \\v 竖向制表符 \\f 换页符 \\n 换行符 \\e Escape \\0nn ASCII代码中八进制代码为nn的字符 \\xnn ASCII代码中十六进制代码为nn的字符 \\unnnn Unicode代码中十六进制代码为nnnn的字符 \\cN ASCII控制字符。比如\\cC代表Ctrl+C \\A 字符串开头(类似^,但不受处理多行选项的影响) \\Z 字符串结尾或行尾(不受处理多行选项的影响) \\z 字符串结尾(类似$,但不受处理多行选项的影响) \\G 当前搜索的开头 \\p{name} Unicode中命名为name的字符类,例如\\p{IsGreek} (?>exp) 贪婪子表达式 (?<x>-<y>exp) 平衡组 (?im-nsx:exp) 在子表达式exp中改变处理选项 (?im-nsx) 为表达式后面的部分改变处理选项 1 把exp当作零宽正向先行断言,如果在这个位置能匹配,使用yes作为此组的表达式;否则使用no (?(exp)yes) 同上,只是使用空表达式作为no 2 如果命名为name的组捕获到了内容,使用yes作为表达式;否则使用no (?(name)yes) 同上,只是使用空表达式作为no 正则表达式常用实例账号/密码帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线) = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$" 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线) = "^[a-zA-Z]\\w{5,17}$" 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间) = "^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$" 字符串校验汉字 = "^[\\u4e00-\\u9fa5]{0,}$"; 英文和数字 = "^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$"; 长度为3-20的所有字符 = "^.{3,20}$"; 由26个英文字母组成的字符串 = "^[A-Za-z]+$"; 由26个大写英文字母组成的字符串 = "^[A-Z]+$"; 由26个小写英文字母组成的字符串 = "^[a-z]+$"; 由数字和26个英文字母组成的字符串 = "^[A-Za-z0-9]+$"; 由数字、26个英文字母或者下划线组成的字符串 = "^\\w+$ 或 ^\\w{3,20}$"; 中文、英文、数字包括下划线 = "^[\\u4E00-\\u9FA5A-Za-z0-9_]+$"; 中文、英文、数字但不包括下划线等符号 = "^[\\u4E00-\\u9FA5A-Za-z0-9]+$ 或 ^[\\u4E00-\\u9FA5A-Za-z0-9]{2,20}$"; 禁止输入含有~的字符 = "[^~\\x22]+"; 手机号/** * 手机号码 * 移动:134,135,136,137,138,139,147,150,151,152,157,158,159,170,178,182,183,184,187,188 * 联通:130,131,132,145,152,155,156,1709,171,176,185,186 * 电信:133,134,153,1700,177,180,181,189 */ String MOBILE = "^1(3[0-9]|4[57]|5[0-35-9]|7[01678]|8[0-9])\\\\d{8}$"; /** * 中国移动:China Mobile * 134,135,136,137,138,139,147,150,151,152,157,158,159,170,178,182,183,184,187,188 */ String CM = "^1(3[4-9]|4[7]|5[0-27-9]|7[0]|7[8]|8[2-478])\\\\d{8}$"; /** * 中国联通:China Unicom * 130,131,132,145,152,155,156,1709,171,176,185,186 */ String CU = "^1(3[0-2]|4[5]|5[56]|709|7[1]|7[6]|8[56])\\\\d{8}$"; /** * 中国电信:China Telecom * 133,134,153,1700,177,180,181,189 */ String CT = "^1(3[34]|53|77|700|8[019])\\\\d{8}$"; IP地址String IPREGEXP = "((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)"; EMAIL地址Email地址 = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"; 域名域名 = "[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?"; InternetURL = "[a-zA-z]+://[^\\s]* 或 ^http://([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?$" 身份证身份证号(15位、18位数字) = "^\\d{15}|\\d{18}$" 短身份证号码(数字、字母x结尾) = "^([0-9]){7,18}(x|X)?$ 或 ^\\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$" 数字类校验数字 = "^[0-9]*$"; n位的数字 = "^\\d{n}$"; 至少n位的数字 = "^\\d{n,}$"; m-n位的数字 = "^\\d{m,n}$"; 零和非零开头的数字 = "^(0|[1-9][0-9]*)$"; 非零开头的最多带两位小数的数字 = "^([1-9][0-9]*)+(.[0-9]{1,2})?$"; 带1-2位小数的正数或负数 = "^(\\-)?\\d+(\\.\\d{1,2})?$"; 正数、负数、和小数 = "^(\\-|\\+)?\\d+(\\.\\d+)?$"; 有两位小数的正实数 = "^[0-9]+(.[0-9]{2})?$"; 有1~3位小数的正实数 = "^[0-9]+(.[0-9]{1,3})?$"; 非零的正整数 = "^[1-9]\\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\\+?[1-9][0-9]*$"; 非零的负整数 = "^\\-[1-9][]0-9"*$" 或 "^-[1-9]\\d*$";","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"软件记录","slug":"工具/软件记录","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95/"}],"tags":[{"name":"regex","slug":"regex","permalink":"http://yelog.org/tags/regex/"}]},{"title":"Git常用命令","slug":"git-command","date":"2016-10-02T09:15:39.000Z","updated":"2024-07-05T01:50:55.319Z","comments":true,"path":"2016/10/02/git-command/","permalink":"http://yelog.org/2016/10/02/git-command/","excerpt":"经常用到Git,但是很多命令记不住,将其整理于此。(大量摘自网络) 一般来说,日常使用只要记住下图6个命令,就可以了。但是熟练使用,恐怕要要记住60~100个命令。","text":"经常用到Git,但是很多命令记不住,将其整理于此。(大量摘自网络) 一般来说,日常使用只要记住下图6个命令,就可以了。但是熟练使用,恐怕要要记住60~100个命令。 下面整理的 Git 命令清单。几个专业名词的译名如下。 Workspace:工作区 Index / Stage:暂存区 Repository:仓库区(本地仓库) Remote:远程仓库 新建版本仓库# 在当前目录新建一个Git代码库 $ git init # 新建一个目录,将其初始化为Git代码库 $ git init [project-name] # 下载一个项目和它的整个代码历史, -o 给远程仓库起名:faker,默认origin $ git clone [-o faker] [url] 配置Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)。 # 显示当前的Git配置 $ git config --list # 编辑Git配置文件 $ git config -e [--global] # 设置提交代码时的用户信息 $ git config [--global] user.name "[name]" $ git config [--global] user.email "[email address]" # 设置大小写敏感(windows不区分大小写的解决办法) $ git config core.ignorecase false 增加/删除文件# 添加指定文件到暂存区 $ git add [file1] [file2] ... # 添加指定目录到暂存区,包括子目录 $ git add [dir] # 添加当前目录的所有文件到暂存区 $ git add . # 添加每个变化前,都会要求确认 # 对于同一个文件的多处变化,可以实现分次提交 $ git add -p # 删除工作区文件,并且将这次删除放入暂存区 $ git rm [file1] [file2] ... # 停止追踪指定文件,但该文件会保留在工作区 $ git rm --cached [file] # 改名文件,并且将这个改名放入暂存区 $ git mv [file-original] [file-renamed] 代码提交# 提交暂存区到仓库区 $ git commit -m [message] # 提交暂存区的指定文件到仓库区 $ git commit [file1] [file2] ... -m [message] # 提交工作区自上次commit之后的变化,直接到仓库区 $ git commit -a # 提交时显示所有diff信息 $ git commit -v # 使用一次新的commit,替代上一次提交 # 如果代码没有任何新变化,则用来改写上一次commit的提交信息 $ git commit --amend -m [message] # 重做上一次commit,并包括指定文件的新变化 $ git commit --amend [file1] [file2] ... 分支# 列出所有本地分支 $ git branch # 列出所有远程分支 $ git branch -r # 列出所有本地分支和远程分支 $ git branch -a # 列出所有本地分支,并展示没有分支最后一次提交的信息 $ git branch -v # 列出所有本地分支,并展示没有分支最后一次提交的信息和远程分支的追踪情况 $ git branch -vv # 列出所有已经合并到当前分支的分支 $ git branch --merged # 列出所有还没有合并到当前分支的分支 $ git branch --no-merged # 新建一个分支,但依然停留在当前分支 $ git branch [branch-name] # 新建一个分支,并切换到该分支 $ git checkout -b [branch] # 新建一个与远程分支同名的分支,并切换到该分支 $ git checkout --track [branch-name] # 新建一个分支,指向指定commit $ git branch [branch] [commit] # 新建一个分支,与指定的远程分支建立追踪关系 $ git branch --track [branch] [remote-branch] # 切换到指定分支,并更新工作区 $ git checkout [branch-name] # 切换到上一个分支 $ git checkout - # 建立追踪关系,在现有分支与指定的远程分支之间 $ git branch --set-upstream-to=[remote-branch] $ git branch --set-upstream [branch] [remote-branch] # 已被弃用 # 合并指定分支到当前分支 $ git merge [branch] # 中断此次合并(你可能不想处理冲突) $ git merge --abort # 选择一个commit,合并进当前分支 $ git cherry-pick [commit] # 删除分支 $ git branch -d [branch-name] #新增远程分支 远程分支需先在本地创建,再进行推送 $ git push origin [branch-name] # 删除远程分支 $ git push origin --delete [branch-name] $ git branch -dr [remote/branch] 标签# 列出所有tag $ git tag # 新建一个tag在当前commit $ git tag [tag] # 新建一个tag在指定commit $ git tag [tag] [commit] # 删除本地tag $ git tag -d [tag] # 删除远程tag $ git push origin :refs/tags/[tagName] # 查看tag信息 $ git show [tag] # 提交指定tag $ git push [remote] [tag] # 提交所有tag $ git push [remote] --tags # 新建一个分支,指向某个tag $ git checkout -b [branch] [tag] 查看信息/搜索# 显示有变更的文件 $ git status [-sb] #s:short,给一个短格式的展示,b:展示当前分支 # 显示当前分支的版本历史 $ git log # 显示commit历史,以及每次commit发生变更的文件 $ git log --stat # 搜索提交历史,根据关键词 $ git log -S [keyword] # 显示某个commit之后的所有变动,每个commit占据一行 $ git log [tag] HEAD --pretty=format:%s # 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件 $ git log [tag] HEAD --grep feature # 显示某个文件的版本历史,包括文件改名 $ git log --follow [file] $ git whatchanged [file] # 显示指定文件相关的每一次diff $ git log -p [file] # 显示过去5次提交 $ git log -5 --pretty --oneline # 图形化显示所有分支 $ git log --oneline --graph --all # 显示在分支2而不在分支1中的提交 $ git log [分支1]..[分支2] $ git log ^[分支1] [分支2] $ git log [分支2] --not [分支1] # 显示两个分支不同时包含的提交 $ git log [分支1]...[分支2] # 显示所有提交过的用户,按提交次数排序 $ git shortlog -sn # 显示指定文件是什么人在什么时间修改过 $ git blame [file] # 显示暂存区和工作区的差异 $ git diff # 显示暂存区和上一个commit的差异 $ git diff --cached [file] # 显示工作区与当前分支最新commit之间的差异 $ git diff HEAD # 显示两次提交之间的差异 $ git diff [first-branch]...[second-branch] # 显示今天你写了多少行代码 $ git diff --shortstat "@{0 day ago}" # 显示某次提交的元数据和内容变化 $ git show [commit] # 显示某次提交发生变化的文件 $ git show --name-only [commit] # 显示某次提交时,某个文件的内容 $ git show [commit]:[filename] # 显示当前分支的最近几次提交 $ git reflog # 搜索你工作目录的文件,输出匹配行号 $ git grep -n [关键字] # 搜索你工作目录的文件,输出每个文件包含多少个匹配 $ git grep --count [关键字] # 优化阅读 $ git grep --break --heading [关键字] # 查询iCheck这个字符串那次提交的 $ git log -SiCheck --oneline # 查询git_deflate_bound函数每一次的变更 $ git log -L :git_deflate_bound:zlib.c 远程同步# 下载远程仓库的所有变动 [shortname] 为远程仓库的shortname, 如origin,为空时:默认origin $ git fetch [shortname] # 显示所有远程仓库 $ git remote -v #显式地获得远程引用的完整列表 [shortname] 为远程仓库的shortname, 如origin,为空时:默认origin $ git ls-remote [shortname] # 显示某个远程仓库的信息 [remote] 为远程仓库的shortname, 如origin $ git remote show [shortname] # 增加一个新的远程仓库,并命名 $ git remote add [shortname] [url] # 重命名一个远程仓库(shortname) $ git remote rename [旧仓库名] [新仓库名] # 删除一个远程链接 $ git remote rm [shortname] [url] $ git remote remove [shortname] [url] # 修改远程仓库地址 $ git remote set-url [shortname] [url] # 取回远程仓库的变化,并与本地分支合并 $ git pull [remote] [branch] # 上传本地当前分支到远程仓库 git push [remote] # 上传本地指定分支到远程仓库 $ git push [remote] [branch] # 推送所有分支到远程仓库 $ git push [remote] --all # 强行推送当前分支到远程仓库,即使有冲突 $ git push [remote] --force 撤销# 恢复暂存区的指定文件到工作区 $ git checkout [file] # 恢复某个commit的指定文件到暂存区和工作区 $ git checkout [commit] [file] # 恢复暂存区的所有文件到工作区 $ git checkout . #只会保留源码(工作区),回退commit(本地仓库)与index(暂存区)到某个版本 $ git reset <commit_id> #默认为 --mixed模式 $ git reset --mixed <commit_id> #保留源码(工作区)和index(暂存区),只回退commit(本地仓库)到某个版本 $ git reset --soft <commit_id> #源码(工作区)、commit(本地仓库)与index(暂存区)都回退到某个版本 $ git reset --hard <commit_id> # 恢复到最后一次提交的状态 $ git reset --hard HEAD # 新建一个commit,用来撤销指定commit # 后者的所有变化都将被前者抵消,并且应用到当前分支 $ git revert [commit] # 将工作区和暂存区的代码全都存储起来了 $ git stash [save] # 只保存工作区,不存储暂存区 $ git stash --keep-index # 存储工作区、暂存区和未跟踪文件 $ git stash -u $ git stash --include-untracked # 不存储所有改动的东西,但会交互式的提示那些改动想要被储藏、哪些改动需要保存在工作目录中 $ git stash --patch # 不指定名字,Git认为指定最近的储藏,将存储的代码(工作区和暂存区)都应用到工作区 $ git stash apply [stash@{2}] # 存储的工作区和暂存区的代码应用到工作区和暂存区 $ git stash apply [stash@{2}] --index # 将存储的代码(工作区和暂存区)都应用到工作区,并从栈上扔掉他 $ git stash pop # 删除stash@{2}的存储 $ git stash drop [stash@{2}] # 获取储藏的列表 $ git stash list # 移除工作目录中所有未跟踪的文件及口口那个的子目录,不会移除.gitiignore忽略的文件 $ git clean -f -d 其他# 生成一个可供发布的压缩包 $ git archive","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"}]},{"title":"一个客户端设置多个github账号","slug":"computer-mutiple-github-account","date":"2016-09-30T08:42:48.000Z","updated":"2024-07-05T01:50:55.351Z","comments":true,"path":"2016/09/30/computer-mutiple-github-account/","permalink":"http://yelog.org/2016/09/30/computer-mutiple-github-account/","excerpt":"最近想要使用自己的GitHub搭建Hexo博客,同时还要使用工作的GitHub开发项目,所以在网上找寻了一些文章,在此将自己的搭建过程记录一下。","text":"最近想要使用自己的GitHub搭建Hexo博客,同时还要使用工作的GitHub开发项目,所以在网上找寻了一些文章,在此将自己的搭建过程记录一下。 前期工作两个GitHub账号(假设两个账号为one,two)取消Git全局设置 $ git config --global --unset user.name $ git config --global --unset user.email SSH配置生成id_rsa私钥,id_rsa.pub公钥。one可以直接回车,默认生成 id_rsa 和 id_rsa.pub 。 $ ssh-keygen -t rsa -C "one@xx.com" 添加two会出现提示输入文件名,输入与默认配置不一样的文件名,如:id_rsa_two。 $ cd ~/.ssh $ ssh-keygen -t rsa -C "two@126.com" # 之后会提示输入文件名 GitHub添加公钥 id_rsa.pub 、 id_rsa_two.pub,分别登陆one,two的账号,在 Account Settings 的 SSH Keys 里,点 Add SSH Keys ,将公钥(.pub文件)中的内容粘贴到 Key 中,并输入 Title。添加 ssh Key $ ssh-add ~/.ssh/id_rsa $ ssh-add ~/.ssh/id_rsa_two 可以在添加前使用下面命令删除所有的 key $ ssh-add -D 最后可以通过下面命令,查看 key 的设置 $ ssh-add -l 修改ssh config文件$ cd ~/.ssh/ $ touch config 打开 .ssh 文件夹下的 config 文件,进行配置 # default Host github.com HostName github.com User git IdentityFile ~/.ssh/id_rsa # two Host two.github.com # 前缀名可以任意设置 HostName github.com User git IdentityFile ~/.ssh/id_rsa_two 这里必须采用这样的方式设置,否则 push 时会出现以下错误: ERROR: Permission to two/two.github.com.git denied to one. 简单分析下原因,我们可以发现 ssh 客户端是通过类似: git@github.com:one/one.github.com.git 这样的 Git 地址中的 User 和 Host 来识别使用哪个本地私钥的。很明显,如果 User 和 Host 始终为 git 和 github.com,那么就只能使用一个私钥。所以需要上面的方式配置,每个账号使用了自己的 Host,每个 Host 的域名做 CNAME 解析到 github.com,这样 ssh 在连接时就可以区别不同的账号了。 $ ssh -T git@github.com # 测试one ssh连接 # Hi ***! You've successfully authenticated, but GitHub does not provide shell access. $ ssh -T git@two.github.com # 测试two ssh连接 # Hi ***! You've successfully authenticated, but GitHub does not provide shell access. 但是这样还没有完,下面还有关联的设置。 在Git项目中配置账号关联可以用 git init 或者 git clone 创建本地项目分别在one和two的git项目目录下,使用下面的命令设置名字和邮箱 $ git config user.name "__name__" # __name__ 例如 one $ git config user.email "__email__" # __email__ 例如 one@126.com 注意:由于我不知道Hexo怎样配置 局部的config,所以,我将two的config使用全局,而工作目录配置局部。 $ git config --global user.name "__name__" # __name__ 例如 two $ git config --global user.email "__email__" # __email__ 例如 two@126.com 查看git项目的配置 $ git config --list 查看 one 的 remote.origin.url=git@github.com:one/one.github.com.git查看 two 的 remote.origin.url=git@github.com:two/two.github.com.git由于 one 使用的是默认的 Host ,所以不需要修改,但是 two 使用的是 two.github.com ,则需要进行修改 $ git remote rm origin $ git remote add origin git@two.github.com:two/two.github.com.git 我在Hexo中的配置(使用two账号) deploy: type: git repo: git@two.github.com:two/two.github.io.git branch: master 上传更改上面所有的设置无误后,可以修改代码,然后上传了。 $ git add -A $ git commit -m "your comments" $ git push 如果遇到warning warning: push.default is unset; its implicit value is changing in Git 2.0 from ‘matching’ to ‘simple’. To squelch this messageand maintain the current behavior after the default changes, use… 推荐使用 $ git config --global push.default simple","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"},{"name":"GitHub","slug":"GitHub","permalink":"http://yelog.org/tags/GitHub/"}]}],"categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"软件记录","slug":"工具/软件记录","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95/"},{"name":"IOT","slug":"工具/IOT","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/IOT/"},{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"},{"name":"开发","slug":"开发","permalink":"http://yelog.org/categories/%E5%BC%80%E5%8F%91/"},{"name":"swift","slug":"开发/swift","permalink":"http://yelog.org/categories/%E5%BC%80%E5%8F%91/swift/"},{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"},{"name":"IDEA","slug":"工具/IDEA","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/IDEA/"},{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"},{"name":"读书","slug":"读书","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/"},{"name":"阅读笔记","slug":"读书/阅读笔记","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/"},{"name":"数据库","slug":"数据库","permalink":"http://yelog.org/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"},{"name":"书单","slug":"读书/书单","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/%E4%B9%A6%E5%8D%95/"}],"tags":[{"name":"mac","slug":"mac","permalink":"http://yelog.org/tags/mac/"},{"name":"efficiency","slug":"efficiency","permalink":"http://yelog.org/tags/efficiency/"},{"name":"terminal","slug":"terminal","permalink":"http://yelog.org/tags/terminal/"},{"name":"home-assistant","slug":"home-assistant","permalink":"http://yelog.org/tags/home-assistant/"},{"name":"iot","slug":"iot","permalink":"http://yelog.org/tags/iot/"},{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"},{"name":"k8s","slug":"k8s","permalink":"http://yelog.org/tags/k8s/"},{"name":"swift","slug":"swift","permalink":"http://yelog.org/tags/swift/"},{"name":"macos","slug":"macos","permalink":"http://yelog.org/tags/macos/"},{"name":"ocr","slug":"ocr","permalink":"http://yelog.org/tags/ocr/"},{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"spring-boot","slug":"spring-boot","permalink":"http://yelog.org/tags/spring-boot/"},{"name":"vim","slug":"vim","permalink":"http://yelog.org/tags/vim/"},{"name":"IntellijIDEA","slug":"IntellijIDEA","permalink":"http://yelog.org/tags/IntellijIDEA/"},{"name":"concurrent","slug":"concurrent","permalink":"http://yelog.org/tags/concurrent/"},{"name":"nacos","slug":"nacos","permalink":"http://yelog.org/tags/nacos/"},{"name":"springcloud","slug":"springcloud","permalink":"http://yelog.org/tags/springcloud/"},{"name":"gray-release","slug":"gray-release","permalink":"http://yelog.org/tags/gray-release/"},{"name":"ElementUI","slug":"ElementUI","permalink":"http://yelog.org/tags/ElementUI/"},{"name":"Vue","slug":"Vue","permalink":"http://yelog.org/tags/Vue/"},{"name":"SpringCloud","slug":"SpringCloud","permalink":"http://yelog.org/tags/SpringCloud/"},{"name":"SkyWalking","slug":"SkyWalking","permalink":"http://yelog.org/tags/SkyWalking/"},{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"},{"name":"javascript","slug":"javascript","permalink":"http://yelog.org/tags/javascript/"},{"name":"linux","slug":"linux","permalink":"http://yelog.org/tags/linux/"},{"name":"reading","slug":"reading","permalink":"http://yelog.org/tags/reading/"},{"name":"活着","slug":"活着","permalink":"http://yelog.org/tags/%E6%B4%BB%E7%9D%80/"},{"name":"shell","slug":"shell","permalink":"http://yelog.org/tags/shell/"},{"name":"nginx","slug":"nginx","permalink":"http://yelog.org/tags/nginx/"},{"name":"PostgreSQL","slug":"PostgreSQL","permalink":"http://yelog.org/tags/PostgreSQL/"},{"name":"keybord","slug":"keybord","permalink":"http://yelog.org/tags/keybord/"},{"name":"emacs","slug":"emacs","permalink":"http://yelog.org/tags/emacs/"},{"name":"encoding","slug":"encoding","permalink":"http://yelog.org/tags/encoding/"},{"name":"dubbo","slug":"dubbo","permalink":"http://yelog.org/tags/dubbo/"},{"name":"zookeeper","slug":"zookeeper","permalink":"http://yelog.org/tags/zookeeper/"},{"name":"fragment_cache","slug":"fragment-cache","permalink":"http://yelog.org/tags/fragment-cache/"},{"name":"浏览器","slug":"浏览器","permalink":"http://yelog.org/tags/%E6%B5%8F%E8%A7%88%E5%99%A8/"},{"name":"js","slug":"js","permalink":"http://yelog.org/tags/js/"},{"name":"firewall","slug":"firewall","permalink":"http://yelog.org/tags/firewall/"},{"name":"maven","slug":"maven","permalink":"http://yelog.org/tags/maven/"},{"name":"nexus","slug":"nexus","permalink":"http://yelog.org/tags/nexus/"},{"name":"mybatis","slug":"mybatis","permalink":"http://yelog.org/tags/mybatis/"},{"name":"mathjax","slug":"mathjax","permalink":"http://yelog.org/tags/mathjax/"},{"name":"pjax","slug":"pjax","permalink":"http://yelog.org/tags/pjax/"},{"name":"samba","slug":"samba","permalink":"http://yelog.org/tags/samba/"},{"name":"tale","slug":"tale","permalink":"http://yelog.org/tags/tale/"},{"name":"docker仓库","slug":"docker仓库","permalink":"http://yelog.org/tags/docker%E4%BB%93%E5%BA%93/"},{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"},{"name":"运维","slug":"运维","permalink":"http://yelog.org/tags/%E8%BF%90%E7%BB%B4/"},{"name":"centos","slug":"centos","permalink":"http://yelog.org/tags/centos/"},{"name":"磁盘分区","slug":"磁盘分区","permalink":"http://yelog.org/tags/%E7%A3%81%E7%9B%98%E5%88%86%E5%8C%BA/"},{"name":"git","slug":"git","permalink":"http://yelog.org/tags/git/"},{"name":"encryption","slug":"encryption","permalink":"http://yelog.org/tags/encryption/"},{"name":"jsp","slug":"jsp","permalink":"http://yelog.org/tags/jsp/"},{"name":"jstl","slug":"jstl","permalink":"http://yelog.org/tags/jstl/"},{"name":"spring","slug":"spring","permalink":"http://yelog.org/tags/spring/"},{"name":"springmvc","slug":"springmvc","permalink":"http://yelog.org/tags/springmvc/"},{"name":"postgres","slug":"postgres","permalink":"http://yelog.org/tags/postgres/"},{"name":"sql","slug":"sql","permalink":"http://yelog.org/tags/sql/"},{"name":"deepin","slug":"deepin","permalink":"http://yelog.org/tags/deepin/"},{"name":"node","slug":"node","permalink":"http://yelog.org/tags/node/"},{"name":"jQuery","slug":"jQuery","permalink":"http://yelog.org/tags/jQuery/"},{"name":"translation","slug":"translation","permalink":"http://yelog.org/tags/translation/"},{"name":"ftp","slug":"ftp","permalink":"http://yelog.org/tags/ftp/"},{"name":"AngularJs","slug":"AngularJs","permalink":"http://yelog.org/tags/AngularJs/"},{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"},{"name":"javaee","slug":"javaee","permalink":"http://yelog.org/tags/javaee/"},{"name":"GitHub","slug":"GitHub","permalink":"http://yelog.org/tags/GitHub/"},{"name":"freemarker","slug":"freemarker","permalink":"http://yelog.org/tags/freemarker/"},{"name":"regex","slug":"regex","permalink":"http://yelog.org/tags/regex/"}]} \ No newline at end of file +{"meta":{"title":"叶落阁","subtitle":"","description":null,"author":"叶落阁","url":"http://yelog.org","root":"/"},"pages":[{"title":"","date":"2023-07-10T00:28:41.033Z","updated":"2023-07-10T00:28:41.033Z","comments":true,"path":"README.html","permalink":"http://yelog.org/README.html","excerpt":"","text":"为 layui 扩展的 下拉多选select在线demo: http://yelog.org/layui-select-multiple/ 这个在线 demo就是本项目的 index.html。 可将项目 clone 到本地查看效果。 效果图 参数 属性名 属性值 备注 multiple 无 开启多选 lay-search 无 开启搜索 lay-case 无 大小写敏感 lay-omit 无 开启多选简写,显示勾选条数 使用 将项目中的 form.js 覆盖自己项目中的 form.js。 引入下面css select[multiple]+.layui-form-select>.layui-select-title>input.layui-input{ border-bottom: 0} select[multiple]+.layui-form-select dd{ padding:0;} select[multiple]+.layui-form-select .layui-form-checkbox[lay-skin=primary]{ margin:0 !important; display:block; line-height:36px !important; position:relative; padding-left:26px;} select[multiple]+.layui-form-select .layui-form-checkbox[lay-skin=primary] span{line-height:36px !important; float:none;} select[multiple]+.layui-form-select .layui-form-checkbox[lay-skin=primary] i{ position:absolute; left:10px; top:0; margin-top:9px;} .multiSelect{ line-height:normal; height:auto; padding:4px 10px; overflow:hidden;min-height:38px; margin-top:-38px; left:0; z-index:99;position:relative;background:none;} .multiSelect a{ padding:2px 5px; background:#908e8e; border-radius:2px; color:#fff; display:block; line-height:20px; height:20px; margin:2px 5px 2px 0; float:left;} .multiSelect a span{ float:left;} .multiSelect a i {float:left;display:block;margin:2px 0 0 2px;border-radius:2px;width:8px;height:8px;padding:4px;position:relative;-webkit-transition:all .3s;transition:all .3s} .multiSelect a i:before, .multiSelect a i:after {position:absolute;left:8px;top:2px;content:'';height:12px;width:1px;background-color:#fff} .multiSelect a i:before {-webkit-transform:rotate(45deg);transform:rotate(45deg)} .multiSelect a i:after {-webkit-transform:rotate(-45deg);transform:rotate(-45deg)} .multiSelect a i:hover{ background-color:#545556;} 使用实例下面实例 开启了下拉多选(multiple), 并开启了检索功能(lay-search)。效果可以参考 在线实例 的 多选+搜索+大小写不敏感 模块 <select name="多选+搜索+大小写不敏感" lay-verify="required" multiple lay-search> <option value="">请选择您的兴趣爱好</option> <option>sing1</option> <option selected>sing2</option> <option>SING1-大写</option> <option>movie1</option> <option selected>movie2</option> <option>movie3</option> <option>MOVIE4</option> <option>swim</option> <option>moon</option> </select> 更多实例参考 在线实例、或 index.html。 声明此项目基于 https://gitee.com/layuicms/XiaLaDuoXuan 项目修改得来,修复了一些bug,扩展了 简化多选、多选搜索、大小写敏感控制等功能。"},{"title":"","date":"2023-07-10T00:28:41.056Z","updated":"2023-07-10T00:28:41.056Z","comments":true,"path":"index.html","permalink":"http://yelog.org/index.html","excerpt":"","text":"layui-select-multiple /* 下拉多选样式 需要引用*/ select[multiple]+.layui-form-select>.layui-select-title>input.layui-input{ border-bottom: 0} select[multiple]+.layui-form-select dd{ padding:0;} select[multiple]+.layui-form-select .layui-form-checkbox[lay-skin=primary]{ margin:0 !important; display:block; line-height:36px !important; position:relative; padding-left:26px;} select[multiple]+.layui-form-select .layui-form-checkbox[lay-skin=primary] span{line-height:36px !important; float:none;} select[multiple]+.layui-form-select .layui-form-checkbox[lay-skin=primary] i{ position:absolute; left:10px; top:0; margin-top:9px;} .multiSelect{ line-height:normal; height:auto; padding:4px 10px; overflow:hidden;min-height:38px; margin-top:-38px; left:0; z-index:99;position:relative;background:none;} .multiSelect a{ padding:2px 5px; background:#908e8e; border-radius:2px; color:#fff; display:block; line-height:20px; height:20px; margin:2px 5px 2px 0; float:left;} .multiSelect a span{ float:left;} .multiSelect a i {float:left;display:block;margin:2px 0 0 2px;border-radius:2px;width:8px;height:8px;padding:4px;position:relative;-webkit-transition:all .3s;transition:all .3s} .multiSelect a i:before, .multiSelect a i:after {position:absolute;left:8px;top:2px;content:'';height:12px;width:1px;background-color:#fff} .multiSelect a i:before {-webkit-transform:rotate(45deg);transform:rotate(45deg)} .multiSelect a i:after {-webkit-transform:rotate(-45deg);transform:rotate(-45deg)} .multiSelect a i:hover{ background-color:#545556;} /* 下面是页面内样式,无需引用 */ .layui-block { margin-bottom: 10px; } .layui-form-label { width: 180px; } .code { color: gray; margin-left: 10px; } .unshow>#result { display: none; } pre { padding: 5px; margin: 5px; } .string { color: green; } .number { color: darkorange; } .boolean { color: blue; } .null { color: magenta; } .key { color: red; } 基础属性 属性名 属性值 备注 multiple 无 开启多选 lay-search 无 开启搜索 lay-case 无 大小写敏感 lay-omit 无 开启多选简写,显示勾选条数 单选select 单选 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon <select> 单选+搜索+大小写不敏感 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon <select lay-search> 单选+搜索+大小写敏感 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon <select lay-search lay-case> 查看表单信息 多选select 多选 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon <select multiple> 简化多选 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon <select multiple lay-omit> 多选+搜索+大小写不敏感 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon <select multiple lay-search> 简化多选+搜索+大小写敏感 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon <select multiple lay-search lay-case lay-omit> 查看表单信息 赋值 // 有两种赋值方式: 1. 直接在option中写selected。 2. 通过 js 赋值。 // 1. 直接在option中写selected 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon // 2. 通过 js 赋值。 请选择您的兴趣爱好 sing1 sing2 SING1-大写 movie1 movie2 movie3 MOVIE4 swim moon <script> // 手动赋值 $('select[name=\"简化多选+搜索+大小写敏感\"]').val(['sing1', 'movie2']); form.render(); </script> layui.use(['form','code'], function () { var form = layui.form, $ = layui.$; // 代码块 layui.code({ title: 'html', encode: true, about: false }); // 手动赋值 $('select[name=\"简化多选+搜索+大小写敏感\"]').val(['sing1', 'movie2']); form.render(); // 提交事件 form.on(\"submit(*)\", function (data) { $('#result').html(syntaxHighlight(data.field)); layer.open({ type: 1, title: '提交信息', shadeClose: true, content:$('#result') }); return false; }); // json 格式化+高亮 function syntaxHighlight(json) { if (typeof json != 'string') { json = JSON.stringify(json, undefined, 2); } json = json.replace(/&/g, '&').replace(//g, '>'); return json.replace(/(\"(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\\"])*\"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)/g, function(match) { var cls = 'number'; if (/^\"/.test(match)) { if (/:$/.test(match)) { cls = 'key'; } else { cls = 'string'; } } else if (/true|false/.test(match)) { cls = 'boolean'; } else if (/null/.test(match)) { cls = 'null'; } return '' + match + ''; }); } })"},{"title":"404 Not Found:该页无法显示","date":"2016-09-27T03:31:01.000Z","updated":"2023-07-10T00:28:41.032Z","comments":true,"path":"/404.html","permalink":"http://yelog.org/404.html","excerpt":"","text":"很抱歉,您所访问的地址并不存在"},{"title":"关于&留言板","date":"2016-09-27T02:27:42.000Z","updated":"2023-07-10T00:28:41.049Z","comments":true,"path":"about/index.html","permalink":"http://yelog.org/about/index.html","excerpt":"","text":"个人简介杨玉杰, 毕业于厦小理,目前从事 Java 及相关工作。 喜欢研究新兴技术和未来发展方向。 最近在彭小六的六扇门内,研习阅读方法、知识管理、时间管理等方法。 联系方式 QQ : 872336115 邮箱 : jaytp@qq.com"}],"posts":[{"title":"Jackson 时间序列化/反序列化详解","slug":"jackson-time-serializer","date":"2024-07-05T08:00:00.000Z","updated":"2024-07-05T11:10:22.659Z","comments":true,"path":"2024/07/05/Jackson Time Serializer/Deserializer/","permalink":"http://yelog.org/2024/07/05/Jackson%20Time%20Serializer/Deserializer/","excerpt":"","text":"前言最近在项目中遇到了时间序列化的问题,所以研究了一下 Jackson 的时间序列化/反序列化,这里做一个详细的总结。 0. 准备工作准备实体类 User.java package com.example.testjava.entity; import lombok.Builder; import lombok.Data; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.Date; @Builder @Data public class User { private String name; private Date date; private LocalDate localDate; private LocalDateTime localDateTime; private LocalTime localTime; private java.sql.Date sqlDate; private java.sql.Time sqlTime; private java.sql.Timestamp timestamp; } 简单查询 package com.example.testjava.controller; import com.example.testjava.entity.User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Date; @RestController @RequestMapping("/jackson") public class JacksonTestController { @GetMapping("/query") public User testJavaDate() { return User.builder() .name("test") .date(new Date()) .localDate(java.time.LocalDate.now()) .localDateTime(java.time.LocalDateTime.now()) .localTime(java.time.LocalTime.now()) .sqlDate(new java.sql.Date(System.currentTimeMillis())) .sqlTime(new java.sql.Time(System.currentTimeMillis())) .timestamp(new java.sql.Timestamp(System.currentTimeMillis())) .build(); } } 1. 序列化1.1. 默认返回{ "name": "test", "date": "2024-07-05T08:09:47.100+00:00", "localDate": "2024-07-05", "localDateTime": "2024-07-05T16:09:47.100462", "localTime": "16:09:47.100514", "sqlDate": "2024-07-05", "sqlTime": "16:09:47", "timestamp": "2024-07-05T08:09:47.100+00:00" } 1.2. 添加配置配置如下 spring: jackson: date-format: yyyy-MM-dd HH:mm:ss 返回效果 { "name": "test", "date": "2024-07-05 08:16:07", "localDate": "2024-07-05", "localDateTime": "2024-07-05T16:16:07.097035", "localTime": "16:16:07.09705", "sqlDate": "2024-07-05", "sqlTime": "16:16:07", "timestamp": "2024-07-05 08:16:07" } 可以发现, 日期时间类型中, 只有 java.time.LocalDateTime 没有按照配置序列化, java.util.Date 和 java.sql.Timestamp 按照配置序列化了。 1.3. 添加注解package com.example.testjava.entity; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Builder; import lombok.Data; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.Date; @Builder @Data public class User { private String name; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date date; private LocalDate localDate; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime localDateTime; @JsonFormat(pattern = "HH:mm:ss") private LocalTime localTime; private java.sql.Date sqlDate; private java.sql.Time sqlTime; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp timestamp; } 返回效果 { "name": "test", "date": "2024-07-05 08:24:36", "localDate": "2024-07-05", "localDateTime": "2024-07-05 16:24:36", "localTime": "16:24:36", "sqlDate": "2024-07-05", "sqlTime": "16:24:36", "timestamp": "2024-07-05 08:24:36" } 注解是可以都有效的 2. 反序列化准备请求 @PostMapping("/save") public User save(@RequestBody User user) { return user; } 请求参数 { "name": "test", "date": "2024-07-05 08:24:36", "localDate": "2024-07-05", "localDateTime": "2024-07-05 16:24:36", "localTime": "16:24:36", "sqlDate": "2024-07-05", "sqlTime": "16:24:36", "timestamp": "2024-07-05 08:24:36" } 2.1 默认效果默认报错 JSON parse error: Cannot deserialize value of type `java.util.Date` from String \\"2024-07-05 08:24:36\\" 2.2 添加配置有两种方法可以解决, 一个是自定义时间序列化, 一个是自定义 objectMapper 2.2.1 自定义时间序列化/** * 此转换方法试用于 json 请求 * LocalDateTime 时间格式转换 支持 */ @JsonComponent @Configuration public class LocalDateTimeFormatConfiguration extends JsonDeserializer<LocalDateTime> { @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}") private String pattern; /** * LocalDate 类型全局时间格式化 * @return */ @Bean public LocalDateTimeSerializer localDateTimeDeserializer() { return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern)); } @Bean public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { return builder -> builder.serializerByType(LocalDateTime.class, localDateTimeDeserializer()); } @Override public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { return StrUtil.isEmpty(jsonParser.getText()) ? null : LocalDateTimeUtil.of(new DateTime(jsonParser.getText())); } } @JsonComponent @Configuration public class DateFormatConfiguration extends JsonDeserializer<Date> { @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}") private String pattern; /** * date 类型全局时间格式化 * * @return */ @Bean public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilder() { return builder -> { TimeZone tz = TimeZone.getTimeZone("UTC"); DateFormat df = new SimpleDateFormat(pattern); df.setTimeZone(tz); builder.failOnEmptyBeans(false) .failOnUnknownProperties(false) .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .dateFormat(df); }; } @Override public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { return StrUtil.isEmpty(jsonParser.getText()) ? null : new DateTime(jsonParser.getText()); } } 2.2.2 自定义 objectMapperpackage com.example.testjava.config; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.util.StrUtil; import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.io.IOException; import java.sql.Time; import java.sql.Timestamp; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.Date; @Configuration @AutoConfigureBefore(JacksonAutoConfiguration.class) public class JacksonConfig { @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}") private String pattern; @Bean public ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); // 在反序列化时, 如果对象没有对应的字段, 不抛出异常 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.registerModule(javaTimeModule()); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); return objectMapper; } private Module javaTimeModule() { JavaTimeModule module = new JavaTimeModule(); // 序列化 module.addSerializer(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern))); module.addSerializer(new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN))); module.addSerializer(new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN))); module.addSerializer(Date.class, new JsonSerializer<>() { @Override public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { SimpleDateFormat sdf = new SimpleDateFormat(pattern); jsonGenerator.writeString(sdf.format(date)); } }); module.addSerializer(java.sql.Date.class, new JsonSerializer<>() { @Override public void serialize(java.sql.Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { SimpleDateFormat sdf = new SimpleDateFormat(pattern); jsonGenerator.writeString(sdf.format(date)); } }); module.addSerializer(Timestamp.class, new JsonSerializer<>() { @Override public void serialize(Timestamp timestamp, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { SimpleDateFormat sdf = new SimpleDateFormat(pattern); jsonGenerator.writeString(sdf.format(timestamp)); } }); module.addSerializer(Time.class, new JsonSerializer<>() { @Override public void serialize(Time time, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.NORM_TIME_PATTERN); jsonGenerator.writeString(sdf.format(time)); } }); // 反序列化 module.addDeserializer(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() { @Override public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException { return StrUtil.isEmpty(jsonParser.getText()) ? null : LocalDateTimeUtil.of(new DateTime(jsonParser.getText())); } }); module.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN))); module.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN))); module.addDeserializer(Date.class, new JsonDeserializer<Date>() { @Override public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { return StrUtil.isEmpty(jsonParser.getText()) ? null : new DateTime(jsonParser.getText()); } }); module.addDeserializer(java.sql.Date.class, new JsonDeserializer<java.sql.Date>() { @Override public java.sql.Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { return StrUtil.isEmpty(jsonParser.getText()) ? null : new java.sql.Date(new DateTime(jsonParser.getText()).getTime()); } }); module.addDeserializer(Timestamp.class, new JsonDeserializer<Timestamp>() { @Override public Timestamp deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException { return StrUtil.isEmpty(jsonParser.getText()) ? null : new Timestamp(new DateTime(jsonParser.getText()).getTime()); } }); module.addDeserializer(Time.class, new JsonDeserializer<Time>() { @Override public Time deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException { return StrUtil.isEmpty(jsonParser.getText()) ? null : Time.valueOf(jsonParser.getText()); } }); // 添加默认处理 return module; } } 效果可以返回正确的数据 { "name": "test", "date": "2024-07-05 08:24:36", "localDate": "2024-07-05", "localDateTime": "2024-07-05 16:24:36", "localTime": "16:24:36", "sqlDate": "2024-07-05 00:00:00", "sqlTime": "16:24:36", "timestamp": "2024-07-05 08:24:36" }","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"translation","slug":"translation","permalink":"http://yelog.org/tags/translation/"}]},{"title":"2024年MacOS终端大比拼","slug":"mac-terminal","date":"2024-06-23T07:54:00.000Z","updated":"2024-07-05T11:10:22.346Z","comments":true,"path":"2024/06/23/macos-terminal/","permalink":"http://yelog.org/2024/06/23/macos-terminal/","excerpt":"","text":"最流行的终端横评| capability | Kitty | Alacritty | WezTerm | iTerm2 | Native || —- | —- | —- | —- | —- | || key-bind | | | | | | log2024-06-27 放弃 Kitty -> 转为 WezTerm Kitty 绑定 cmd-shift-f 在 tmux 下无法使用, 且没有 vim-mode 2024-06-26 放弃使用 Alacritty -> 转为 Kitty 因为在 vim 的 normal 模式下, 如果是中文输入法, 输入的内容会出现在输入法的候选框内, 然后按 <CAPS> 按键切换输入法, 候选框中的输入的字母, 会以 insert 的方式输出到光标所在的位置, 这个问题在 WezTerm 和 Kitty 中没有出现. 注意: Kitty 的 map cmd+1 send_key cmd+1 能够正常映射到 NeoVim 中进行 maps.n["<D-1>"] = { "<cmd>Neotree left toggle<cr>", desc = "Toggle Explorer" } 绑定, 但是开启 tmux 后, cmd+1 映射到 NeoVim 中就不行了 2024-06-20 放弃 WezTerm -> 转为 Alacritty","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"软件记录","slug":"工具/软件记录","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95/"}],"tags":[{"name":"mac","slug":"mac","permalink":"http://yelog.org/tags/mac/"},{"name":"efficiency","slug":"efficiency","permalink":"http://yelog.org/tags/efficiency/"},{"name":"terminal","slug":"terminal","permalink":"http://yelog.org/tags/terminal/"}]},{"title":"离线安装Home Assistant","slug":"home-assistant","date":"2024-03-22T01:05:44.000Z","updated":"2024-07-05T11:10:22.735Z","comments":true,"path":"2024/03/22/home-assistant/","permalink":"http://yelog.org/2024/03/22/home-assistant/","excerpt":"","text":"前言最近搬到了新家,家里的智能设备也越来越多, 引入很多米家设备, 但博主使用的是苹果生态, 需要将这些不支持 homekit 的米家设备接入到 homekit 中, 经过调研发现 Home Assistant 是一个不错的选择, 本文会介绍联网安装的过程, 并且如果有需要联网的步骤, 也会提供离线安装的方法. 本篇主要介绍通过 docker 部署的方式 安装 Docker如已有 docker 环境, 可以跳过这一步 sudo apt-get update sudo","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"IOT","slug":"工具/IOT","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/IOT/"}],"tags":[{"name":"home-assistant","slug":"home-assistant","permalink":"http://yelog.org/tags/home-assistant/"},{"name":"iot","slug":"iot","permalink":"http://yelog.org/tags/iot/"}]},{"title":"K8s-内部培训","slug":"K8s-内部培训","date":"2024-02-05T01:17:27.000Z","updated":"2024-07-05T11:10:21.843Z","comments":true,"path":"2024/02/05/k8s-inner-training/","permalink":"http://yelog.org/2024/02/05/k8s-inner-training/","excerpt":"","text":"K8s 内部培训介绍K8s 为 Kubernetes 的简称, 是一个开源的容器编排平台, 最初是由 Google 工程师开发和设计的, 后于 2015 年捐赠给了云原生计算机基金会-CNCF 应用服务管理发展史早期服务应用大多以单包的形式运行在服务器上, 当我们增加一些服务时, 比如添加 JOB 应用时, 我们会另开一个新的应用, 但基本用一台服务器上就能完成 所以我们早期应用的发展就如下面图表所示, 由于用户数量较少, 所以更新应用时短暂的暂停服务也是可以接受的 ┌──────────────**石器时代**───────────────┐ ┌──────────────**石器时代**───────────────┐ ┌──────────────**石器时代**───────────────┐ │ ┌─────────────`Server1`───────────────┐ │ │ ┌─────────────`Server1`───────────────┐ │ │ ┌─────────────`Server1`───────────────┐ │ │ │ ┌──☕──┐ │ │ │ │ ┌──☕──┐ ┌──☕──┐ │ │ │ │ ┌──☕──┐ ┌──☕──┐ ┌──☕──┐ │ │ │ │ │ APP1 │ │ │ │ │ │ APP1 │ │ APP2 │ │ │ │ │ │ APP1 │ │ APP2 │ │ APP3 │ │ │ │ │ └──────┘ │ │ ==> │ │ └──────┘ └──────┘ │ │ ==> │ │ └──────┘ └──────┘ └──────┘ │ │ │ ├─────────────────────────────────────┤ │ │ ├─────────────────────────────────────┤ │ │ ├─────────────────────────────────────┤ │ │ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ └─────────────────────────────────────┘ │ │ └─────────────────────────────────────┘ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────┘ └─────────────────────────────────────────┘ └─────────────────────────────────────────┘ 随着用户数量的上升, 应用的并发也随之提高, 单台服务器的压力也随之增大, 有了如下情况: 高峰期经常出现卡顿 更新应用时的暂停服务已经不可接受 在服务器出现故障时的高可用有了更高的要求 为了解决上面的问题, 我们就进入了下个时代(下图一), 采购多台服务器, 对应用进行支持集群的改造, 这时我们的应用分别在三台服务器上, 并发能力提高了3倍, 并且冗灾能力大幅提升 尽管我们解决了上面的问题, 但是带来了新的问题, 因为服务器数量过多, 在安装应用需要的工具如 JDK、Tomcat、Node、Nginx、Redis 等等, 可能会因为安装版本和服务器系统版本不一致导致应用运行失败 所以会在环境安装中浪费太多时间, 所以很多企业开始引入如 Docker 的虚拟化技术(下图二), 用来解决环境不一致的问题, 并且一并解决了守护进程, 开机启动等问题 这时我们通过 docker-compose 技术, 升级应用、调整配置相比以前大大简化, 但是随着应用规模的扩大, 对应用高可用有了更高的要求, 纷纷开始进行微服务拆分, 应用数量和服务器数量越来越多, 服务的运维管理越来越复杂、 大家开始开发各种集群管理, 让大家可以在一个地方并且可视化的管理集群中的所有 Docker, 以 Google 开源的 Kubernetes 做的最功能完善且灵活可配置, 从而开始爆火. 于是众多企业开始上K8s(下图三), 不仅解决运维复杂的问题, 而且带来了更多更好的特性: 服务发现和负载均衡 存储编排 自动部署和回滚 自我修复 密钥和配置管理 ┌──────────────**农耕文明**───────────────┐ ┌─────────────────**工业文明**───────────────┐ ┌─────────────────**信息文明**───────────────┐ │ ┌─────────────`Server1`───────────────┐ │ │ ┌────────────────`Server1`───────────────┐ │ │ ┌────────────────`K8s 集群`──────────────┐ │ │ │ ┌──☕──┐ ┌──☕──┐ ┌──☕──┐ │ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ │ │ │ │ APP1 │ │ APP2 │ │ APP3 │ │ │ │ │ │ APP1 ││ APP2 ││ APP3 ││ │ │ │ │ POD1 ││ POD2 ││ POD3 ││ │ │ │ └──────┘ └──────┘ └──────┘ │ │ │ │ ├───────────┤├───────────┤├───────────┤│ │ │ │ ├───────────┤├───────────┤├───────────┤│ │ │ ├─────────────────────────────────────┤ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ │ │ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ │ └───────────┘└───────────┘└───────────┘│ │ │ │ └───────────┘└───────────┘└───────────┘│ │ │ └─────────────────────────────────────┘ │ │ └────────────────────────────────────────┘ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ │ │ ┌─────────────`Server2`───────────────┐ │ │ ┌────────────────`Server1`───────────────┐ │ │ │ │ POD4 ││ POD5 ││ POD6 ││ │ │ │ ┌──☕──┐ ┌──☕──┐ ┌──☕──┐ │ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ │ │ │ ├───────────┤├───────────┤├───────────┤│ │ │ │ │ APP1 │ │ APP2 │ │ APP3 │ │ │ │ │ │ APP1 ││ APP2 ││ APP3 ││ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ │ │ │ └──────┘ └──────┘ └──────┘ │ │ ==> │ │ ├───────────┤├───────────┤├───────────┤│ │ ==> │ │ └───────────┘└───────────┘└───────────┘│ │ │ ├─────────────────────────────────────┤ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ │ │ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ │ └───────────┘└───────────┘└───────────┘│ │ │ │ │ POD7 ││ POD8 ││ POD9 ││ │ │ └─────────────────────────────────────┘ │ │ └────────────────────────────────────────┘ │ │ │ ├───────────┤├───────────┤├───────────┤│ │ │ ┌─────────────`Server3`───────────────┐ │ │ ┌────────────────`Server1`───────────────┐ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ │ │ │ ┌──☕──┐ ┌──☕──┐ ┌──☕──┐ │ │ │ │ ┌─────🐳────┐┌─────🐳────┐┌─────🐳────┐│ │ │ │ └───────────┘└───────────┘└───────────┘│ │ │ │ │ APP1 │ │ APP2 │ │ APP3 │ │ │ │ │ │ APP1 ││ APP2 ││ APP3 ││ │ │ ├────────────────────────────────────────┤ │ │ │ └──────┘ └──────┘ └──────┘ │ │ │ │ ├───────────┤├───────────┤├───────────┤│ │ │ │ ┌───────┐ ┌───────┐ ┌───────┐ │ │ │ ├─────────────────────────────────────┤ │ │ │ │JDK8/Tomcat││JDK8/Tomcat││JDK8/Tomcat││ │ │ │ │Server1│ │Server2│ │Server3│ ••• │ │ │ │ 安装: JDK8, Tomcat, 需要手动启动服务│ │ │ │ └───────────┘└───────────┘└───────────┘│ │ │ │ └───────┘ └───────┘ └───────┘ │ │ │ └─────────────────────────────────────┘ │ │ └────────────────────────────────────────┘ │ │ └────────────────────────────────────────┘ │ ├─────────────────────────────────────────┤ ├────────────────────────────────────────────┤ ├────────────────────────────────────────────┤ │ 优点: 节省资源, 需要掌握的知识较少 │ │ 优点: 环境搭建容易, 安装Docker和配置文件 │ │ 优点: 自动故障恢复, 监控完善, 操作方便 │ │ 缺点: 运维操作繁杂, JDK版本难以统一 │ │ 缺点: 随着规模扩大, 日常运维也变得繁杂 │ │ 缺点: k8s 功能较多, 需要掌握的知识也多 │ └─────────────────────────────────────────┘ └────────────────────────────────────────────┘ └────────────────────────────────────────────┘ 图一 图二 图三 Kubernetes 组件 Control Plane Components: 控制平面组件 kube-apiserver: 负责公开 Kubernetes API, 处理请求, 类似 cloud 中的网关 etcd: key-value 存储, 用于保存集群数据 kube-scheduler: 任务调度, 监听有新创建但未运行的pods, 选择节点来让 pod 在上面运行 kube-controller-manager: 负责运行控制器进程, 有如下不同类型的控制器 Node Controller: 节点控制器, 负责节点出现故障时进行通知和响应 Job Controller: 任务控制器, 检测代表一次性任务的 Job 对象, 然后创建 Pod 来运行这些任务直至完成 EndpointSlice Controller: 端点分片控制器, 提供 Service 和 Pod 之间的链接 ServiceAccount Controller: 为新的命名空间创建默认的服务账号 cloud-controller-manager: 云控制管理器, 集成云提供商的API, 我们内网部署的用不到 Node Components: 节点组件, 运行在各个节点, 负责维护运行的 Pod, 提供 Kubernetes 的运行环境 kubelet: 在每个节点中运行, 保证容器都运行在 Pod 中, kubelet 接受一组 PodSpec, 确保 PodSpec 中描述的容器处于运行状态且健康 kube-proxy: 网络代理, 是实现 Service 的一部分 Container Runtime: 容器运行时, Kubernetes 支持需要容器运行环境, 例如: docker, containerd, CRI-O Addons: 插件, 提供集群级别的功能, 插件提供的资源属于 kube-system 命名空间 DNS: 提供集群内的域名系统 Web UI/Dashboard: 通用的基于 Web 的用户界面, 它使用户可以集中管理集群中的应用已经集群本身 Container Resource Monitoring: 将容器的一些常见的时间序列度量值保存到一个集中的数据库中, 并提供浏览这些数据的界面 Cluster-level Logging: 集群级日志, 将容器日志保存到一个集中的日志存储中, 这种集中日志存储提供搜索和浏览接口 Network Plugins: 网络插件, 实现容器网络接口(CNI)规范的软件组件, 负责为 Pod 分配 IP 地址, 并使这些 Pod 能在集群内部互相通信 Kubernetes 架构 Node 节点Kubernetes 通过将容器放入在节点(Node) 上运行的 Pod 来执行你的负载. 节点可以是一个虚拟机或物理机, 每个节点包含 Pod 所需的服务器, 这些节点由 Control Plane 负责管理 一个集群的节点数量可以是1个, 也可以是多个. 且节点名称是唯一的. 可以通过 kubectl 来创建和修改 Node 对象 # 查一下集群中的所有节点信息 kubectl get node # 查看某个节点详细信息 kubectl descibe $NODENAME # 标记一个 Node 为不可调度 kubectl cordon $NODENAME Controllers 控制器在机器人和自动化领域, 又一个类似的概念叫控制回路 (Control Loop), 用于调节系统状态, 如: 房间里的温度自动调节器 当你设置了温度, 温度自动调节器让其当前状态接近期望温度; 在 Kubernetes 中, 控制器通过监控集群的公共状态, 并致力于将当前的状态转为期望状态 控制器是通过通知 apiserver 来管理状态的, 就像温度自动调节器是通过控制空调来调节气温的 Container Runtime Interface/CRI 容器运行时接口CRI 是一个插件接口, 它使 kubelet 能够使用各种容器运行时, 定义了主要 gRPC 协议, 用于节点组件 kubelet 和容器运行时之间的通信 Containers 容器容器将应用从底层主机设备中解耦, 这使得在不同的云或 OS 环境中部署更加容易 Kubernetes 集群中的每个节点都会运行容器, 这些容器构成分配给该节点的 Pod, 单个 Pod 中的容器会在共同调度下, 运行在相同的节点 容器镜像是一个随时可以运行的软件包, 它包含了运行容器程序所需要的一切, 代码和它需要的运行时、应用程序和系统库, 以及一些基本设置 容器运行时这个基础组件使 Kubernetes 能够有效运行容器, 他负责管理 Kubernetes 环境中的容器的执行和生命周期 PodPod 是可以在 Kubernetes 中创建和管理的、最小的可部署计算单元 Pod 是有一个或多个容器组成, 这些容器共享存储、网络、已经怎么样运行这些容器的声明, 统一调度. 此外还可以包含 init container, 用于做一些启动主应用前的准备工作, 比如通过 init container 注入 tingyun 等 agent 包 如下示例, 它由一个运行镜像 nginx:1.14.2 的容器组成 apiVersion: v1 kind: Pod metadata: name: nginx labels: app: my-nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 要创建上面显示的 Pod, 保存上面内容到 my-nginx.yaml, 可以通过如下命令 kubectl apply -f my-nginx.yaml Workloads 工作负载工作负载是在 Kubernetes 上运行的应用程序, 无论是又一个还是多个组件构成, 你都可以通过一组 Pod 来运行它, Pod 代表的是集群上处于运行状态的一组容器的集合, 但通常一个 Pod 内只运行一个容器 Kubernetes 提供若干种内置的工作负载资源: Deployment 和 ReplicaSet Deployment 适合无状态应用, Deployment 中的所有 Pod 都是互相等价的 StatefulSet 有状态应用, 比如可以独立持久化文件, 互不影响 DaemonSet 提供节点本地支撑设施的 Pod, 保证每个节点上一个 Job 和 CronJob 定义只需要执行一次并且执行后视为完完成的任务 Network 网络ServiceService 是将一个或者一组 Pod 公开代理给集群内部, 使之能够各个应用之间通信, 甚至用于公开到集群外(NodePort 或者 代理给 Ingress) 它提供了类似域名的访问方式, 使用者无需关心后面有多少个 Pod 在提供服务, 他们是否健康, 他们 IP 是否发生变化. 定义 Service apiVersion: v1 kind: Service metadata: name: nginx-service spec: selector: app: my-nginx ports: - name: name-of-service-port protocol: TCP port: 80 targetPort: http-web-svc 服务类型(type): ClusterIp: 默认值, 智能在集群内访问 NodePort: 直接向集群外暴露, 通过节点端口访问 LoadBalancer: 使用云平台的负载均衡, Kubernetes 不直接提供 Externalname: 将服务映射到 externalName 字段的内容, 例如api.foo.bar.example","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"},{"name":"k8s","slug":"k8s","permalink":"http://yelog.org/tags/k8s/"}]},{"title":"swift 离线图片识别文字(ocr)","slug":"macos-ocr-swift","date":"2024-01-02T02:09:19.000Z","updated":"2024-07-05T11:10:22.750Z","comments":true,"path":"2024/01/02/macos-ocr-swift/","permalink":"http://yelog.org/2024/01/02/macos-ocr-swift/","excerpt":"","text":"背景最近打算写一个 macos 翻译软件, 需要用到 ocr 图像识别, 并且因为速度问题, 一开始就考虑使用系统的自带能力来实现. 经过翻阅文档和 chatgpt 拉扯了一下午, 最终成功实现. 代码代码逻辑为, 接受参数: 图片路径, 然后获取图片, 通过 VNImageRequestHandler 对图片进行文字识别 如下代码可以直接放进一个 ocr.swift, 然后执行 swiftc -o ocr ocr.swift , 在执行 ./ocr /Users/yelog/Desktop/3.png 后面为你实际的有文字的图片路经 // // ocr.swift // Fast Translation // // Created by 杨玉杰 on 2023/12/31. // import SwiftUI import Vision func handleDetectedText(request: VNRequest?, error: Error?) { if let error = error { print("ERROR: \\(error)") return } guard let results = request?.results, results.count > 0 else { print("No text found") return } for result in results { if let observation = result as? VNRecognizedTextObservation { for text in observation.topCandidates(1) { let string = text.string print("识别: \\(string)") } } } } func ocrImage(path: String) { let cgImage = NSImage(byReferencingFile: path)?.ciImage()?.cgImage let requestHandler = VNImageRequestHandler(cgImage: cgImage!) let request = VNRecognizeTextRequest(completionHandler: handleDetectedText) // 设置文本识别的语言为英文 request.recognitionLanguages = ["en"] request.recognitionLevel = .accurate do { try requestHandler.perform([request]) } catch { print("Unable to perform the requests: \\(error).") } } extension NSImage { func ciImage() -> CIImage? { guard let data = self.tiffRepresentation, let bitmap = NSBitmapImageRep(data: data) else { return nil } let ci = CIImage(bitmapImageRep: bitmap) return ci } } // 执行函数,从命令行参数中获取图片的地址 ocrImage(path: CommandLine.arguments[1]) 然后准备待识别的有文字的图片 # 编译 swift 文件 swiftc -o ocr ocr.swift # 执行并且传递图片路径参数 ./ocr /Users/yelog/Desktop/3.png 最后最近打算着手写一些 macos 的小工具, 如果对 swift 或者 macos 感兴趣的可以关注或评论.","categories":[{"name":"开发","slug":"开发","permalink":"http://yelog.org/categories/%E5%BC%80%E5%8F%91/"},{"name":"swift","slug":"开发/swift","permalink":"http://yelog.org/categories/%E5%BC%80%E5%8F%91/swift/"}],"tags":[{"name":"swift","slug":"swift","permalink":"http://yelog.org/tags/swift/"},{"name":"macos","slug":"macos","permalink":"http://yelog.org/tags/macos/"},{"name":"ocr","slug":"ocr","permalink":"http://yelog.org/tags/ocr/"}]},{"title":"Caused by: java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher","slug":"Springboot-JarLauncher","date":"2023-12-11T08:58:00.000Z","updated":"2024-07-05T11:10:22.663Z","comments":true,"path":"2023/12/11/springboot-jarlauncher/","permalink":"http://yelog.org/2023/12/11/springboot-jarlauncher/","excerpt":"","text":"问题现象今天同事再升级框架后(spring-cloud 2022.0.4 -> 2023.0.0)(spring-boot 3.1.6 -> 3.2.0) 同时因为 spring-boot 的版本问题, 需要将 maven 升级到 3.6.3+ 升级后构建 jar 包和构建镜像都是正常的, 但是发布到测试环境就报错 Caused by: java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher 问题分析报错为 JarLauncher 找不到, 检查了 Jenkins 中的打包任务, 发现并没有编译报错, 同事直接使用打包任务中产生的 xx.jar, 可以正常运行. 说明在打包 Docker 镜像前都没有问题, 这时就想起来我们在打包镜像时, 先解压 xx.jar, 然后直接执行 org.springframework.boot.loader.JarLauncher, 所以很可能是升级后, 启动文件 JarLauncher 的路径变了. 为了验证我们的猜想, 我们得看一下实际容器内的文件结构, 但是这时容器一直报错导致无法启动, 不能直接通过 Rancher 查看文件结构, 我们可以通过文件拷贝的方式来解决, 如下: # 下载有问题的镜像, 并且创建容器(不启动) docker create -it --name dumy 10.188.132.123:5000/lemes-cloud/lemes-gateway:develop-202312111536 bash # 直接拷贝容器内的我们想要看的目录 docker cp dumy:/data . 到本地后, 就可以通过合适的工具查找 JarLauncher 文件, 我这里通过 vim 来寻找, 如下图: 发现比原来多了一层目录 launch, 所以问题就发生在这里了. 解决方案我们在打包脚本 JenkinsCommon.groovy 中根据当前打包的 JDK 版本来判断使用的启动类路径, 如下: 再次打包, 应用正常启动. ReferenceJarLauncher","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"},{"name":"spring-boot","slug":"spring-boot","permalink":"http://yelog.org/tags/spring-boot/"}]},{"title":"ideavim 使用百分号%支持xml的对应标签跳转","slug":"idea-tips-percent-match-xml","date":"2023-06-17T08:09:00.000Z","updated":"2024-07-05T11:10:22.475Z","comments":true,"path":"2023/06/17/idea-tips-percent-mach-xml/","permalink":"http://yelog.org/2023/06/17/idea-tips-percent-mach-xml/","excerpt":"","text":"前言由于最近几年使用 vim 的频率越来越高, 所以在 idea 中也大量开始使用 vim 技巧, 在一年多前碰到个问题, 终于在最近解决了。 问题描述在 idea 中, 在 normal 模式下, 使用 % 不能在匹配标签(xml/html等) 之间跳转 解决方案在 ~/.ideavimrc 中添加如下设置, 重启 idea 即可 set matchit 效果 最后最近会把一些加的 tips 分享出来,大家有什么建议和问题都可以在评论区讨论.","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"IDEA","slug":"工具/IDEA","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/IDEA/"}],"tags":[{"name":"efficiency","slug":"efficiency","permalink":"http://yelog.org/tags/efficiency/"},{"name":"vim","slug":"vim","permalink":"http://yelog.org/tags/vim/"},{"name":"IntellijIDEA","slug":"IntellijIDEA","permalink":"http://yelog.org/tags/IntellijIDEA/"}]},{"title":"Raycast 最强效率软件推荐","slug":"mac-raycast","date":"2023-05-17T09:45:00.000Z","updated":"2024-07-05T11:10:22.335Z","comments":true,"path":"2023/05/17/mac-raycast/","permalink":"http://yelog.org/2023/05/17/mac-raycast/","excerpt":"","text":"软件介绍 最近在很多平台上看到 Raycast 的推荐文章, 今天就尝试了一下, 发现确实不错, 完全可以替代我目前对 Alfred 的使用, 甚至很多地方做得更好, 所以本文就是介绍我使用 Raycast 的一些效果(多图预警), 方便那些还没有接触这个软件的人对它有个了解, 如果有插件推荐, 欢迎在评论区进行讨论。 Raycast 是 MacMac 平台独占的效率工具, 主要包含如下功能: 应用启动 文件查找 窗口管理 剪贴板历史 Snippets 应用菜单查询 插件扩展功能 翻译 斗图 结束进程 查询端口占用 查询ip 除此之外, Raycast提供的在线插件商店, 可以很方便的进行功能扩展 Raycast官网 核心功能应用启动 并且支持卸载应用, 找到应用, cmd+k 打开操作菜单, 下拉到最后 文件查找 窗口管理 剪贴板历史推荐使用 Clipboard History 这个扩展,和 Alfred 的一样, 并且有分类,效果如下 设置快捷键 cmd+shift+v Snippets通过 Snippets 可以保存自定义片段, 通过关键字快速查询并输出到光标处, 如常用语、 邮箱、手机号、税号、代码片段等等 创建 Snippets 可以通过搜索 Create Snippet, 搜索 Snippets 可以通过搜索Search Snippet 应用菜单查询查询当前激活应用的所有菜单, 不限层级. 可以通过搜索 Search Menu Items 来查询。 推荐插件Easydict(翻译软件)超强的翻译软件, 完美替代我在 Alfred 的 workflow 中配置的有道翻译, 我配置了如下功能 输入中文, 自动翻译成英文 输入英文, 自动翻译成中文 支持一键发音 Open AI 翻译长文本 Doutu表情包查询,选中回车就复制到剪贴板了, 就可以粘贴到 Discord/QQ/Wechat 斗图了, 非常方便。 Kill Process关键字查询, 并一键结束进程 Kill Port查询端口占用的进程, 并支持一键结束进程 MyIp查询当前ip","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"软件记录","slug":"工具/软件记录","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95/"}],"tags":[{"name":"mac","slug":"mac","permalink":"http://yelog.org/tags/mac/"},{"name":"efficiency","slug":"efficiency","permalink":"http://yelog.org/tags/efficiency/"}]},{"title":"[Java]通过 CompletableFuture 实现异步多线程优化请求处理速度","slug":"CompletableFuture","date":"2022-08-01T12:06:14.000Z","updated":"2024-07-05T11:10:22.719Z","comments":true,"path":"2022/08/01/[Java]optimize-request-processing-speed-by-completablefuture/","permalink":"http://yelog.org/2022/08/01/[Java]optimize-request-processing-speed-by-completablefuture/","excerpt":"","text":"零、背景我们在写后端请求的时候, 可能涉及多次 SQL 执行(或其他操作), 当这些请求相互不关联, 在顺序执行时就浪费了时间, 这些不需要先后顺序的操作可以通过多线程进行同时执行, 来加速整个逻辑的执行速度. 既然有了目标和大致思路, 如果有做过前端的小伙伴应该能想起来 Js 里面有个 Promise.all 来解决这个问题, 在 Java 里也有类似功能的类 CompletableFuture , 它可以实现多线程和线程阻塞, 这样能够保证等待多个线程执行完成后再继续操作. 一、CompletableFuture 是什么首先我们先了解一下 CompletableFuture 是干什么, 接下来我们通过简单的示例来介绍他的作用. long startTime = System.currentTimeMillis(); //生成几个任务 List<CompletableFuture<String>> futureList = new ArrayList<>(); futureList.add(CompletableFuture.supplyAsync(()->{ sleep(4000); System.out.println("任务1 完成"); return "任务1的数据"; })); futureList.add(CompletableFuture.supplyAsync(()->{ sleep(2000); System.out.println("任务2 完成"); return "任务2的数据"; })); futureList.add(CompletableFuture.supplyAsync(()->{ sleep(3000); System.out.println("任务3 完成"); return "任务3的数据"; })); //完成任务 CompletableFuture<Void> allTask = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])) .whenComplete((t, e) -> { System.out.println("所有任务都完成了, 返回结果集: " + futureList.stream().map(CompletableFuture::join).collect(Collectors.joining(","))); }); // 阻塞主线程 allTask.join(); System.out.println("main end, cost: " + (System.currentTimeMillis() - startTime)); 执行结果 任务2 完成 任务3 完成 任务1 完成 所有任务都完成了, 返回结果集: 任务1的数据,任务2的数据,任务3的数据 main end, cost: 4032 结果分析: 我们需要执行3个任务, 3个任务同时执行, 互不影响 其中任务2耗时最短,提前打印完成 其次是任务3 最后是执行1完成 当所有任务完成后, 触发 whenComplete 方法, 打印任务的返回结果 最后打印总耗时为 4.032s 结论: 多线程执行后, 耗时取决于最耗时的操作, 而单线程是所有操作耗时之和 二、封装工具类经过上面的测试, 通过 CompletableFuture 已经能够实现我们的预想, 为了操作方便, 我们将封装起来, 便于统一管理 package org.yelog.java.usage.concurrent; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; /** * 执行并发任务 * * @author yangyj13 * @date 11/7/22 9:49 PM */ public class MultiTask<T> { private List<CompletableFuture<T>> futureList; /** * 添加待执行的任务 * * @param completableFuture 任务 * @return 当前对象 */ public MultiTask<T> addTask(CompletableFuture<T> completableFuture) { if (futureList == null) { futureList = new ArrayList<>(); } futureList.add(completableFuture); return this; } /** * 添加待执行的任务(无返回) * * @param task 任务 * @return 当前对象 */ public MultiTask<T> addTask(Consumer<T> task) { addTask(CompletableFuture.supplyAsync(() -> { task.accept(null); return null; })); return this; } /** * 添加待执行的任务(有返回) * * @param task 任务 * @return 当前对象 */ public MultiTask<T> addTask(Function<Object, T> task) { addTask(CompletableFuture.supplyAsync(() -> task.apply(null))); return this; } /** * 开始执行任务 * * @param callback 当所有任务都完成后触发的回调方法 * @param waitTaskExecuteComplete 是否阻塞主线程 */ private void execute(Consumer<List<T>> callback, Boolean waitTaskExecuteComplete) { CompletableFuture<Void> allFuture = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])) .whenComplete((t, e) -> { if (callback != null) { List<T> objectList = new ArrayList<>(); futureList.forEach((future) -> { objectList.add(future.join()); }); callback.accept(objectList); } }); if (callback != null || waitTaskExecuteComplete == null || waitTaskExecuteComplete) { allFuture.join(); } } /** * 开始执行任务 * 等待所有任务完成(阻塞主线程) */ public void execute() { execute(null, true); } /** * 开始执行任务 * * @param waitTaskExecuteComplete 是否阻塞主线程 */ public void execute(Boolean waitTaskExecuteComplete) { execute(null, waitTaskExecuteComplete); } /** * 开始执行任务 * * @param callback 当所有任务都完成后触发的回调方法 */ public void execute(Consumer<List<T>> callback) { execute(callback, true); } } 那么上一步我们测试的流程转换成工具类后如下 long startTime = System.currentTimeMillis(); MultiTask<String> multiTask = new MultiTask<>(); multiTask.addTask(t -> { sleep(1000); System.out.println("任务1 完成"); }).addTask(t -> { sleep(3000); System.out.println("任务2 完成"); }).addTask(CompletableFuture.supplyAsync(()->{ sleep(2000); System.out.println("任务3 完成"); return "任务3的数据"; })).execute(resultList->{ System.out.println("all complete: " + resultList); }); System.out.println("main end, cost: " + (System.currentTimeMillis() - startTime)); 三、应用到实际的效果执行两次数据库的操作如下 public interface TestMapper { @Select("select count(*) from test_user where score < 1000 and user_id = #{userId}") int countScoreLess1000(Integer userId); @Select("select count(1) from test_log where success = true and user_id = #{userId}") int countSuccess(Integer userId); } 调用方法: long start = System.currentTimeMillis(); testMapper.countScoreLess1000(userId); long countScoreLess1000End = System.currentTimeMillis(); log.info("countScoreLess1000 cost: " + (countScoreLess1000End - start)); testMapper.countSuccess(userId); long countSuccessEnd = System.currentTimeMillis(); log.info("countSuccess cost: " + (countSuccessEnd - countScoreLess1000End)); log.info("all cost: " + (countSuccessEnd - start)); 顺序执行的平均时间如下 countScoreLess1000 cost: 368 countSuccess cost: 404 all cost: 772 当我们应用的上面的工具类后的调用方法 MultiTask multiTask = new MultiTask<>(); multiTask.addTask(t -> { testMapper.countScoreLess1000(userId); log.info("countScoreLess1000 cost: " + (System.currentTimeMillis() - start)); }).addTask(t -> { testMapper.countSuccess(userId); log.info("countSuccess cost: " + (System.currentTimeMillis() - start)); }).execute(); log.info("all cost: " + (System.currentTimeMillis() - start)); 效果如下 countScoreLess1000 cost: 433 countSuccess cost: 463 all cost: 464 可以看到各子任务执行时长是差不多的, 但是总耗时使用多线程后有了明显下降 四、总结通过使用 CompletableFuture 实现多线程阻塞执行后, 大幅降低这类请求, 并且当可以异步执行的子任务越多, 效果越明显.","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"concurrent","slug":"concurrent","permalink":"http://yelog.org/tags/concurrent/"}]},{"title":"基于 nacos/灰度发布 实现减少本地启动微服务数量的实践","slug":"reducing-local-springcloud-base-on-nacos-and-gray-release","date":"2022-08-01T12:06:14.000Z","updated":"2024-07-05T11:10:22.654Z","comments":true,"path":"2022/08/01/reducing-local-springcloud-base-on-nacos-and-gray-release/","permalink":"http://yelog.org/2022/08/01/reducing-local-springcloud-base-on-nacos-and-gray-release/","excerpt":"","text":"一、背景后台框架是基于 spring cloud 的微服务体系, 当开发同学在自己电脑上进行开发工作时, 比如开发订单模块, 除了需要启动订单模块外, 还需要启动网关模块、权限校验模块、公共服务模块等依赖模块, 非常消耗开发同学的本地电脑的资源, 也及其浪费时间. 二、解决方案2.1 目标和关键问题能不能开发同学本地只需要启动需要开发的模块:订单模块, 其他模块均适用测试环境中正在运行的服务. 既然要实现的目标有了, 我们就开始研究可行性和关键问题 开发环境和测试环境要在同一个 nacos 的 namespace 中, 这样才有可能让开发环境调用到测试环境的服务. 测试环境只能调用测试环境的微服务, 实现和开发环境的服务隔离 开发同学之间的微服务也要实现服务隔离 2.2 思路既要在同一个 namespace 下, 又要能够实现不同人访问不同的副本, 很容易想到可以利用灰度发布来实现: 测试环境设置 metadata lemes-env=product 来标识测试环境副本, 用于区分开发环境的微服务测测试环境的微服务 开发同学本地启动注册开发环境副本, 都会携带唯一IP, 则我们可以通过IP来区分不同开发同学的副本 假设我们需要开发的 API 的后台服务调用链条如下: 我们需要开发的 API 为 /addMo, 打算写在 Order 这个微服务里面, 并且他会调用 common 这个微服务的 /getDict 获取一个字典数据, /getDict 是现成的, 不需要开发, 如果是之前的情况, 开发本地至少需要启动5个微服务才能进行调试. 三、具体实现3.1 测试环境设置 metadata由于测试环境都是通过容器部署的, 那么启动方式就是下面容器中的 CMD, 我们在其中加入 -Dspring.cloud.nacos.discovery.metadata.lemes-env=product, 用于区分开发环境的微服务测测试环境的微服务 # 说明:Dockerfile 过程分为两部分。第一次用来解压 jar 包,并不会在目标镜像内产生 history/layer。第二部分将解压内容分 layer 拷贝到目标镜像内 # 目的:更新镜像时,只需要传输代码部分,依赖没有变动则不更新,节省发包时的网络传输量 # 原理:在第二部分中,每次 copy 就会在目标镜像内产生一层 layer,将依赖和代码分开, # 绝大部分更新都不会动到依赖,所以只需更新代码几十k左右的代码层即可 FROM 10.176.66.20:5000/library/amazoncorretto:11.0.11 as builder WORKDIR /build ARG ARTIFACT_ID COPY target/${ARTIFACT_ID}.jar app.jar RUN java -Djarmode=layertools -jar app.jar extract && rm app.jar FROM 10.176.66.20:5000/library/amazoncorretto:11.0.11 LABEL maintainer="yangyj13@lenovo.com" WORKDIR /data ARG ARTIFACT_ID ENV ARTIFACT_ID ${ARTIFACT_ID} # 依赖 COPY --from=builder /build/dependencies/ ./ COPY --from=builder /build/snapshot-dependencies/ ./ COPY --from=builder /build/spring-boot-loader/ ./ # 应用代码 COPY --from=builder /build/application/ ./ # 容器运行时启动命令 CMD echo "NACOS_ADDR: ${NACOS_ADDR}"; \\ echo "JAVA_OPTS: ${JAVA_OPTS}"; \\ echo "TZ: ${TZ}"; \\ echo "ARTIFACT_ID: ${ARTIFACT_ID}"; \\ # 去除了 server 的应用名 REAL_APP_NAME=${ARTIFACT_ID//-server/}; \\ echo "REAL_APP_NAME: ${REAL_APP_NAME}"; \\ # 获取当前时间 now=`date +%F+%T+%Z`; \\ # java 启动命令 java $JAVA_OPTS \\ -Dtingyun.app_name=${REAL_APP_NAME}-${TINGYUN_SUFFIX} \\ -Dspring.cloud.nacos.discovery.metadata.lemes-env=product \\ -Dspring.cloud.nacos.discovery.metadata.startup-time=${now} \\ -Dspring.cloud.nacos.discovery.server-addr=${NACOS_ADDR} \\ -Dspring.cloud.nacos.discovery.group=${NACOS_GROUP} \\ -Dspring.cloud.nacos.config.namespace=${NACOS_NAMESPACE} \\ -Dspring.cloud.nacos.discovery.namespace=${NACOS_NAMESPACE} \\ -Dspring.cloud.nacos.discovery.ip=${HOST_IP} \\ org.springframework.boot.loader.JarLauncher 3.2 开发前端传递开启智能连接const devIp = getLocalIP('10.') module.exports = { devServer: { proxy: { '/lemes-api': { target: 'http://10.176.66.58/lemes-api', ws: true, pathRewrite: { '^/lemes-api': '/' }, headers: { 'dev-ip': devIp, 'dev-sc': 'true' } } } }, } // 获取本机 IP function getLocalIP(prefix) { const excludeNets = ['docker', 'cni', 'flannel', 'vi', 've'] const os = require('os') const osType = os.type() // 系统类型 const netInfo = os.networkInterfaces() // 网络信息 const ipList = [] if (prefix) { for (const netInfoKey in netInfo) { if (excludeNets.filter(item => netInfoKey.startsWith(item)).length === 0) { for (let i = 0; i < netInfo[netInfoKey].length; i++) { const net = netInfo[netInfoKey][i] if (net.family === 'IPv4' && net.address.startsWith(prefix)) { ipList.push(net.address) } } } } } if (ipList.length === 0) { if (osType === 'Windows_NT') { for (const dev in netInfo) { // win7的网络信息中显示为本地连接,win10显示为以太网 if (dev === '本地连接' || dev === '以太网') { for (let j = 0; j < netInfo[dev].length; j++) { if (netInfo[dev][j].family === 'IPv4') { ipList.push(netInfo[dev][j].address) } } } } } else if (osType === 'Linux') { ipList.push(netInfo.eth0[0].address) } else if (osType === 'Darwin') { ipList.push(netInfo.en0[0].address) } } console.log('识别到的网卡信息', JSON.stringify(ipList)) return ipList.length > 0 ? ipList[0] : '' } 3.3 后端灰度处理不论是 gateway 还是 openfeign 都是通过 spring 的 loadbalancer 进行应用选择的, 那我们通过实现或者继承 ReactorServiceInstanceLoadBalancer 来重写选择的过程. @Log4j2 public class LemesLoadBalancer implements ReactorServiceInstanceLoadBalancer{ @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; final AtomicInteger position; // loadbalancer 提供的访问当前服务的名称 final String serviceId; // loadbalancer 提供的访问的服务列表 ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider; public LemesLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) { this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000)); } public LemesLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId, int seedPosition) { this.serviceId = serviceId; this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider; this.position = new AtomicInteger(seedPosition); } @Override public Mono<Response<ServiceInstance>> choose(Request request) { ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider .getIfAvailable(NoopServiceInstanceListSupplier::new); RequestDataContext context = (RequestDataContext) request.getContext(); RequestData clientRequest = context.getClientRequest(); return supplier.get(request).next() .map(serviceInstances -> processInstanceResponse(clientRequest,supplier, serviceInstances)); } private Response<ServiceInstance> processInstanceResponse(RequestData clientRequest,ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) { Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(clientRequest,serviceInstances); if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) { ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer()); } return serviceInstanceResponse; } private Response<ServiceInstance> getInstanceResponse(RequestData clientRequest, List<ServiceInstance> instances) { if (instances.isEmpty()) { if (log.isWarnEnabled()) { log.warn("No servers available for service: " + serviceId); } return new EmptyResponse(); } int pos = Math.abs(this.position.incrementAndGet()); // 筛选后的服务列表 List<ServiceInstance> filteredInstances; String devSmartConnect = clientRequest.getHeaders().getFirst(CommonConstants.DEV_SMART_CONNECT); if (StrUtil.equals(devSmartConnect, "true")) { String devIp = clientRequest.getHeaders().getFirst(CommonConstants.DEV_IP); // devIp 为空,为异常情况不处理,返回空实例集合 if (StrUtil.isBlank(devIp)) { log.warn("devIp is NULL,No servers available for service: " + serviceId); return new EmptyResponse(); } // 智能连接: 如果本地启动了服务,则优先访问本地服务,如果本地没有启动,则访问测试环境服务 // 优先调用本地自有服务 filteredInstances = instances.stream().filter(item -> StrUtil.equals(devIp, item.getHost())).collect(Collectors.toList()); // 如果本地服务没有开启,则调用生产/测试服务 if (CollUtil.isEmpty(filteredInstances)) { filteredInstances = instances.stream() .filter(item -> StrUtil.equals(CommonConstants.LEMES_ENV_PRODUCT, item.getMetadata().get("lemes-env"))) .collect(Collectors.toList()); // 解决开发环境无法访问 k8s 集群内 ip 的问题 String oneNacosIp = nacosDiscoveryProperties.getServerAddr().split(",")[0].replaceAll(":[\\\\s\\\\S]*", ""); filteredInstances.forEach(item -> { NacosServiceInstance instance = (NacosServiceInstance) item; // cloud 以 80 端口启动,认为是 k8s 内的应用 if (instance.getPort() == 80) { instance.setHost(oneNacosIp); instance.setPort(Integer.parseInt(item.getMetadata().get("port"))); } }); } } else { // 不是智能访问,则只访问一个环境 // 当前服务 ip String currentIp = nacosDiscoveryProperties.getIp(); String lemesEnv = nacosDiscoveryProperties.getMetadata().get("lemes-env"); filteredInstances = instances.stream() .filter(item -> StrUtil.equals(lemesEnv, CommonConstants.LEMES_ENV_PRODUCT) // 访问测试环境 ? StrUtil.equals(CommonConstants.LEMES_ENV_PRODUCT, item.getMetadata().get("lemes-env")) // 访问开发环境 : StrUtil.equals(currentIp, item.getHost())) .collect(Collectors.toList()); } if (filteredInstances.isEmpty()) { log.warn("No oneself servers and beta servers available for service: " + serviceId + ", use other instances"); // 找不到自己注册IP对应的服务和测试服务,则用nacos中其它的服务 filteredInstances = instances; } //最终的返回的 serviceInstance ServiceInstance instance = filteredInstances.get(pos % filteredInstances.size()); return new DefaultResponse(instance); } }","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"k8s","slug":"k8s","permalink":"http://yelog.org/tags/k8s/"},{"name":"nacos","slug":"nacos","permalink":"http://yelog.org/tags/nacos/"},{"name":"springcloud","slug":"springcloud","permalink":"http://yelog.org/tags/springcloud/"},{"name":"gray-release","slug":"gray-release","permalink":"http://yelog.org/tags/gray-release/"}]},{"title":"基于 nacos/springcloud/k8s 的不停机服务更新[graceful shutdown]","slug":"k8s 的不停机服务更新","date":"2022-07-27T07:35:39.000Z","updated":"2024-07-05T11:10:22.434Z","comments":true,"path":"2022/07/27/springboot-graceful-shutdown-based-on-nacos2-and-k8s/","permalink":"http://yelog.org/2022/07/27/springboot-graceful-shutdown-based-on-nacos2-and-k8s/","excerpt":"","text":"背景我们的 SpringCloud 是部署在 k8s 上的, 当通过 k8s 进行滚动升级时, 会有请求 500 的情况, 不利于用户体验, 严重的可能造成数据错误的问题 k8s 滚动更新策略介绍假设我们要升级的微服务在环境上为3个副本的集群, 升级应用时, 会先启动1个新版本的副本, 然后下线一个旧版本的副本, 之后再启动1个新版本的副本, 一次类推,直到所有旧副本都替换新副本. 通过链路追踪分析, 报错的原因分别由以下两种情况 SpringCloud 中的微服务在升级过程中, 当旧的微服务中还有没有处理完成的请求时, 就开始关闭动作, 造成请求中断 当旧应用执行关闭动作时, 已经开始拒绝请求, 但是 nacos 中的路由并没有及时更新, 造成 gateway/openfeign 在路由时仍会命中正在关闭的应用, 造成请求报错 为了解决这个问题, 我们将利用 springboot 的 graceful shutdown 功能和 nacos 的主动下线功能来解决这个问题. 具体思路如下: 比如当我们执行订单微服务(3个副本)滚动更新时 先启动一个新版本副本4 然后准备关闭副本1, 在关闭之前先通知 nacos 订单服务的副本1下线, 然后由 nacos 通知给其他应用(nacos2.x 是grpc, 所以通知速度比较快), 这样, 订单服务的副本1就不会再接收到请求, 然后执行 graceful shutdown(springboot 原生支持, 启用方法可以看后面代码), 所有请求处理完成后关闭应用. 这样就完成了 副本1 的关闭 启动新版本副本5 再优雅关闭副本2(参考第2点副本1的流程) 然后启动新版本副本6 再优雅关闭副本3 完成了服务不中断的应用升级 实现关键点为了实现上面背景中提到的思路, 主要从如下几个方面入手 创建从 nacos 中下线副本的API我们通过创建自定义名为 deregister 的 endpoint 来通知 nacos 下线副 import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.registry.NacosRegistration; import com.alibaba.cloud.nacos.registry.NacosServiceRegistry; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.stereotype.Component; @Component @Endpoint(id = "deregister") @Log4j2 public class LemesNacosServiceDeregisterEndpoint { @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; @Autowired private NacosRegistration nacosRegistration; @Autowired private NacosServiceRegistry nacosServiceRegistry; /** * 从 nacos 中主动下线,用于 k8s 滚动更新时,提前下线分流流量 * * @param * @return com.lenovo.lemes.framework.core.util.ResultData<java.lang.String> * @author Yujie Yang * @date 4/6/22 2:57 PM */ @ReadOperation public String endpoint() { String serviceName = nacosDiscoveryProperties.getService(); String groupName = nacosDiscoveryProperties.getGroup(); String clusterName = nacosDiscoveryProperties.getClusterName(); String ip = nacosDiscoveryProperties.getIp(); int port = nacosDiscoveryProperties.getPort(); log.info("deregister from nacos, serviceName:{}, groupName:{}, clusterName:{}, ip:{}, port:{}", serviceName, groupName, clusterName, ip, port); // 设置服务下线 nacosServiceRegistry.setStatus(nacosRegistration, "DOWN"); return "success"; } } 支持 Graceful Shutdown由于 springboot 原生支持, 我们只需要在 bootstrap.yaml 中添加如下配置即可 server: # 开启优雅下线 shutdown: graceful spring: lifecycle: # 优雅下线超时时间 timeout-per-shutdown-phase: 5m # 暴露 shutdown 接口 management: endpoint: shutdown: enabled: true endpoints: web: exposure: include: shutdown K8s 配置有了上面两个 API, 接下来就配置到 k8s 上 terminationGracePeriodSeconds 如果关闭应用的时间超过 10 分钟, 则向容器发送 TERM 信号, 防止应用长时间下线不了 preStop 先执行下线操作, 等待30s, 留够通知到其他应用的时间, 然后执行 graceful shutdown 关闭应用 --- apiVersion: apps/v1 kind: Deployment metadata: name: lemes-service-common labels: app: lemes-service-common spec: replicas: 2 selector: matchLabels: app: lemes-service-common # strategy: # type: RollingUpdate # rollingUpdate: ## replicas - maxUnavailable < running num < replicas + maxSurge # maxUnavailable: 1 # maxSurge: 1 template: metadata: labels: app: lemes-service-common spec: # 容器重启策略 Never Always OnFailure # restartPolicy: Never # 如果关闭时间超过10分钟, 则向容器发送 TERM 信号 terminationGracePeriodSeconds: 600 affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - podAffinityTerm: topologyKey: "kubernetes.io/hostname" labelSelector: matchExpressions: - key: app operator: In values: - lemes-service-common weight: 100 # requiredDuringSchedulingIgnoredDuringExecution: # - labelSelector: # matchExpressions: # - key: app # operator: In # values: # - lemes-service-common # topologyKey: "kubernetes.io/hostname" volumes: - name: lemes-host-path hostPath: path: /data/logs type: DirectoryOrCreate - name: sidecar emptyDir: { } containers: - name: lemes-service-common image: 10.176.66.20:5000/lemes-cloud/lemes-service-common-server:v0.1 imagePullPolicy: Always volumeMounts: - name: lemes-host-path mountPath: /data/logs - name: sidecar mountPath: /sidecar ports: - containerPort: 80 resources: # 资源通常情况下的占用 requests: memory: '2048Mi' # 资源占用上限 limits: memory: '4096Mi' livenessProbe: httpGet: path: /actuator/health/liveness port: 80 initialDelaySeconds: 5 # 探针可以连续失败的次数 failureThreshold: 10 # 探针超时时间 timeoutSeconds: 10 # 多久执行一次探针查询 periodSeconds: 10 startupProbe: httpGet: path: /actuator/health/liveness port: 80 failureThreshold: 30 timeoutSeconds: 10 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health/readiness port: 80 initialDelaySeconds: 5 timeoutSeconds: 10 periodSeconds: 10 lifecycle: preStop: exec: # 应用关闭操作:1. 从 nacos 下线,2. 等待30s, 保证 nacos 通知到其他应用 2.触发 springboot 的 graceful shutdown command: - sh - -c - curl http://127.0.0.1/actuator/deregister;sleep 30;curl -X POST http://127.0.0.1/actuator/shutdown;","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"k8s","slug":"k8s","permalink":"http://yelog.org/tags/k8s/"},{"name":"nacos","slug":"nacos","permalink":"http://yelog.org/tags/nacos/"},{"name":"springcloud","slug":"springcloud","permalink":"http://yelog.org/tags/springcloud/"}]},{"title":"el-drawer 实现鼠标拖拽宽度[ElementUI]","slug":"el-drawer-drag-width","date":"2022-06-24T11:38:00.000Z","updated":"2024-07-05T11:10:22.394Z","comments":true,"path":"2022/06/24/el-drawer-drag-width/","permalink":"http://yelog.org/2022/06/24/el-drawer-drag-width/","excerpt":"","text":"实现效果 实现思路通过指令的方式, 在 drawer 的左侧边缘, 添加一个触发拖拽的长条形区域, 监听鼠标左键按下时启动 document.onmousemove 的监听, 监听鼠标距离浏览器右边的距离, 设置为 drawer 的宽度, 并添加约束: 不能小于浏览器宽度的 20%, 不能大于浏览器宽度的 80%. 指令代码创建文件 src/directive/elment-ui/drawer-drag-width.js, 内容如下 import Vue from 'vue' /** * el-drawer 拖拽指令 */ Vue.directive('el-drawer-drag-width', { bind(el, binding, vnode, oldVnode) { const drawerEle = el.querySelector('.el-drawer') console.log(drawerEle) // 创建触发拖拽的元素 const dragItem = document.createElement('div') // 将元素放置到抽屉的左边边缘 dragItem.style.cssText = 'height: 100%;width: 5px;cursor: w-resize;position: absolute;left: 0;' drawerEle.append(dragItem) dragItem.onmousedown = (downEvent) => { // 拖拽时禁用文本选中 document.body.style.userSelect = 'none' document.onmousemove = function(moveEvent) { // 获取鼠标距离浏览器右边缘的距离 let realWidth = document.body.clientWidth - moveEvent.pageX const width30 = document.body.clientWidth * 0.2 const width80 = document.body.clientWidth * 0.8 // 宽度不能大于浏览器宽度 80%,不能小于宽度的 20% realWidth = realWidth > width80 ? width80 : realWidth < width30 ? width30 : realWidth drawerEle.style.width = realWidth + 'px' } document.onmouseup = function(e) { // 拖拽时结束时,取消禁用文本选中 document.body.style.userSelect = 'initial' document.onmousemove = null document.onmouseup = null } } } }) 然后在 main.js 中将其导入 import './directive/element-ui/drawer-drag-width' 指令使用在 el-drawer 上添加指令 v-el-drawer-drag-width 即可, 如下 <el-drawer v-el-drawer-drag-width :visible.sync="helpDrawer.show" direction="rtl" class="my-drawer" > <template #title> <div class="draw-title">{{ helpDrawer.title }}</div> </template> <Editor v-model="helpDrawer.html" v-loading="helpDrawer.loading" class="my-wang-editor" style="overflow-y: auto;" :default-config="helpDrawer.editorConfig" :mode="helpDrawer.mode" @onCreated="onCreatedHelp" /> </el-drawer>","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"ElementUI","slug":"ElementUI","permalink":"http://yelog.org/tags/ElementUI/"},{"name":"Vue","slug":"Vue","permalink":"http://yelog.org/tags/Vue/"}]},{"title":"SpringCloud系列之接入SkyWalking进行链路追踪和日志收集","slug":"springcloud-skywalking","date":"2021-09-26T10:08:00.000Z","updated":"2024-07-05T11:10:22.696Z","comments":true,"path":"2021/09/26/spring-cloud-skywalking/","permalink":"http://yelog.org/2021/09/26/spring-cloud-skywalking/","excerpt":"","text":"前言前一段时间一直在研究升级公司项目的架构,在不断学习和试错后,最终确定了一套基于 k8s 的高可用架构体系,未来几期会将这套架构体系的架设过程和注意事项以系列文章的形式分享出来,敬请期待! 由于集群和分布式规模的扩大,对微服务链路的监控和日志收集,越来越有必要性,所以在筛选了了一些方案后,发现 SkyWalking 完美符合我们的预期,对链路追踪和日志收集都有不错的实现。 SkyWalking 简介SkyWalking 是一款 APM(应用程序监控)系统,转为微服务、云原生、基于容器的架构而设计。主要包含了一下核心功能 对服务、运行实例、API进行指标分析 链路检测,检查缓慢的服务和API 对基础设施(VM、网络、磁盘、数据库)进行监控 对超出阈值的情况进行警报 等等 开源地址:apache/skywalking 官网:Apache SkyWalking SpringCloud 整合 SkyWalking1. 搭建 SkyWalking 服务在使用 SkyWalking 进行链路追踪和日志收集之前,需要先搭建起一套 SkyWalking 的服务,然后才能通过 agent 将 SpringCloud 的运行状态和日志发送给 SkyWalking 进行解析和展示。 SkyWalking 的搭建方式有很多中,我这里介绍两种 docker-compose(非高可用,快速启动,方便测试、学习) 和 k8s(高可用、生产级别) docker-compose 的方式docker 和 docker-compose 的安装不是本文的重点,所以有需要可以自行查询。 以下操作会启动三个容器 elasticsearch 作为 skywalking 的存储,保存链路和日志数据等 oap 数据接收和分析 Observability Analysis Platform ui web端的数据展示 # 创建配置文件保存的目录 mkdir -p /data/docker/admin/skywalking # 切换到刚创建的目录 cd /data/docker/admin/skywalking # 将下面的 docker-compose.yml 文件保存到这个目录 vi docker-compose.yml # 拉去镜像并启动 docker-compose up -d # 查看日志 docker-compose logs -f docker-compose.yml version: '3.8' services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:7.14.1 container_name: elasticsearch restart: always ports: - 9200:9200 healthcheck: test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 40s environment: - discovery.type=single-node - bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - TZ=Asia/Shanghai ulimits: memlock: soft: -1 hard: -1 oap: image: apache/skywalking-oap-server:8.7.0-es7 container_name: oap depends_on: - elasticsearch links: - elasticsearch restart: always ports: - 11800:11800 - 12800:12800 healthcheck: test: ["CMD-SHELL", "/skywalking/bin/swctl"] interval: 30s timeout: 10s retries: 3 start_period: 40s environment: TZ: Asia/Shanghai SW_STORAGE: elasticsearch7 SW_STORAGE_ES_CLUSTER_NODES: elasticsearch:9200 ui: image: apache/skywalking-ui:8.7.0 container_name: ui depends_on: - oap links: - oap restart: always ports: - 8088:8080 environment: TZ: Asia/Shanghai SW_OAP_ADDRESS: http://oap:12800 启动之后浏览器访问 服务ip:8080 即可 k8s等待更新。。 2. 下载 agent 代理包点击链接进行下载,skywalking-apm-8.7 其他版本可以看 apache 归档站,找到对应版本的 .tar.gz 后缀的包,进行下载 通过命令或者软件进行解压 tar -zxvf apache-skywalking-apm-8.7.0.tar.gz 3. java 命令使用代码启动 jar 包springcloud/springboot 一般是通过 java -jar xxx.jar 进行启动。我们只需要在其中加上 -javaagent 参数即可,如下 其中 自定义服务名 可以改为应用名 如 lemes-auth,服务ip 为第一步搭建的 SkyWalking 服务的ip,端口11800 为启动的 oap 这个容器的端口 java -javaagent:上一步解压目录/agent/skywalking-agent.jar=agent.service_name=自定义服务名,collector.backend_service=服务ip:11800 -jar xx.jar 执行命令启动后,访问以下接口,就可以在第一步 服务ip:8080 中看到访问的链接和调用链路。 4. 开启日志收集本文主要以 log4j2 来介绍,其他的大同小异,可以网上找教程。SpringCloud 集成 log4j2 不是本文重点,所以请自行 Google。 引入依赖要开启日志收集,必须要添加依赖,如下 <dependency> <groupId>org.apache.skywalking</groupId> <artifactId>apm-toolkit-log4j-2.x</artifactId> <version>8.7.0</version> </dependency> 修改 log4j2.xml需要修改 log4j2.xml 主要添加下面两个关键点 添加 %traceId 来打印 traceid 声明 GRPCLogClientAppender 完整内容如下 <?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!-- Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时, 你会看到log4j2内部各种详细输出。可以设置成OFF(关闭) 或 Error(只输出错误信息)。 --> <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数--> <configuration status="WARN" monitorInterval="30"> <Properties> <Property name="log.path">logs/lemes-auth</Property> <Property name="logging.lemes.pattern"> %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%traceId] [%logger{50}.%M:%L] - %msg%n </Property> </Properties> <Appenders> <!-- 输出控制台日志的配置 --> <Console name="Console" target="SYSTEM_OUT"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/> <!-- 输出日志的格式 --> <PatternLayout pattern="${logging.lemes.pattern}"/> </Console> <RollingRandomAccessFile name="debugRollingFile" fileName="${log.path}/debug.log" filePattern="${log.path}/debug/$${date:yyyy-MM}/debug.%d{yyyy-MM-dd}-%i.log.gz"> <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout charset="UTF-8" pattern="${logging.lemes.pattern}"/> <Policies> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="100 MB"/> </Policies> <DefaultRolloverStrategy max="30"/> </RollingRandomAccessFile> <GRPCLogClientAppender name="grpc-log"> <PatternLayout pattern="${logging.lemes.pattern}"/> </GRPCLogClientAppender> </Appenders> <Loggers> <!-- ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF --> <Logger name="com.lenovo.lemes" level="debug"/> <Logger name="org.apache.kafka" level="warn"/> <Root level="info"> <AppenderRef ref="Console"/> <AppenderRef ref="debugRollingFile"/> <AppenderRef ref="grpc-log"/> </Root> </Loggers> </configuration> 启动命令中声明上报日志在上一步的 agent 中添加上报日志的参数 plugin.toolkit.log.grpc.reporter.server_host=服务ip,plugin.toolkit.log.grpc.reporter.server_port=11800 完整如下 java -javaagent:上一步解压目录/agent/skywalking-agent.jar=agent.service_name=自定义服务名,collector.backend_service=服务ip:11800,plugin.toolkit.log.grpc.reporter.server_host=服务ip,plugin.toolkit.log.grpc.reporter.server_port=11800 -jar xx.jar 日志收集效果这样启动日志中就会打印 traceid , N/A 代表的是非请求的日志,有 traceid 的为 api 请求日志 在 skywalking 中就能看到我们上报的日志 重点:SkyWalking 可以在链路追踪中查看当前请求的所有日志(不同实例/模块) 5. 兼容 spring-cloud-gateway经过上面的步骤之后,链路已经搭建完成,查看发现了一个问题,gateway 模块的 traceId 和 业务模块的 traceId 不统一。 这是由于 SkyWalking 对于 spring-cloud-gateway 的支持不是默认的,所以需要将 agent/optional-plugins/apm-spring-cloud-gateway-2.1.x-plugin-8.7.0.jar 复制到 agent/plugins 下,然后重启即可。 最后SkyWalking 上面这两个功能就已经非常强大,能够有效帮助我们优化我们的程序,监控系统的问题,并及时报警。日志收集也解决的在大规模分布式集群下日志查询难的问题。 SkyWalking 还支持 VM、浏览器、k8s等监控,后续如果有实践,将会逐步更新。","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"SpringCloud","slug":"SpringCloud","permalink":"http://yelog.org/tags/SpringCloud/"},{"name":"SkyWalking","slug":"SkyWalking","permalink":"http://yelog.org/tags/SkyWalking/"}]},{"title":"3-hexo添加自定义图标","slug":"3-hexo-add-icon","date":"2020-12-28T14:00:00.000Z","updated":"2024-07-05T11:10:22.494Z","comments":true,"path":"2020/12/28/3-hexo-add-icon/","permalink":"http://yelog.org/2020/12/28/3-hexo-add-icon/","excerpt":"","text":"一、前言鉴于许多人问过如何添加自定义图标,这里就详细说明一下,以备后人乘凉。 这篇文章主要讲解是从 iconfont 添加图标。 二、添加彩色图标2.1 登录并添加图标访问 iconfont,点击如下图位置登录,可以使用 Github 账号登录。 登录成功后,搜索合适的图标,然后点击添加到购物车,如下图所示。 添加了多个后,可以点击右上角的“购物车”,添加到项目,点击加号创建项目,如下图所示。 添加完成后回到项目页面,找到自己刚刚创建的项目。 如果没有到项目页面,可以点击上面菜单进入:资源管理 -> 我的项目 2.2 引入 3-hexo 中点击下载到本地,解压并复制其中的 iconfont.js 到项目 3-hexo/source/js/ 下,并改名 custom-iconfont.js。 在文件 3-hexo/layout/_partial/meta.ejs 最后追加下面一行。 <script src="<%=theme.blog_path?theme.blog_path.lastIndexOf("/") === theme.blog_path.length-1?theme.blog_path.slice(0, theme.blog_path.length-1):theme.blog_path:'' %>/js/custom-iconfont.js?v=<%=theme.version%>" ></script> 2.3 在配置文件中添加生效修改 3-hexo/_config.yml 如下图所示 完成! 图标名如上面的 gitee 可以在 网站上修改,如下图所示 三、添加黑白图标link.theme=white 3.1 同 2.13.2 引入 3-hexo 中点击生成代码,如下图所示。 复制生成的代码,修改 font-family 的值为 custom-iconfont,添加到 3-hexo/source/css/_partial/font.styl 最后,并写入图标信息,content 可以移到图标上进行复制,注意前面斜杠转译和去掉后面的分号。 @font-face { font-family: 'custom-iconfont'; /* project id 2298064 */ src: url('//at.alicdn.com/t/font_2298064_34vkk4c9945.eot'); src: url('//at.alicdn.com/t/font_2298064_34vkk4c9945.eot?#iefix') format('embedded-opentype'), url('//at.alicdn.com/t/font_2298064_34vkk4c9945.woff2') format('woff2'), url('//at.alicdn.com/t/font_2298064_34vkk4c9945.woff') format('woff'), url('//at.alicdn.com/t/font_2298064_34vkk4c9945.ttf') format('truetype'), url('//at.alicdn.com/t/font_2298064_34vkk4c9945.svg#iconfont') format('svg'); } .icon-gitee:before { content: "\\e602"; } .icon-youtubeautored:before { content: "\\e649"; } 3.3 在配置文件中添加生效 同2.2结束!","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"一文看懂JavaScript中的Promise","slug":"一文看懂JavaScript中的Promise","date":"2020-10-20T11:43:44.000Z","updated":"2024-07-05T11:10:22.378Z","comments":true,"path":"2020/10/20/know-javascript-promise/","permalink":"http://yelog.org/2020/10/20/know-javascript-promise/","excerpt":"","text":"一、Promise 是什么Promise 是 ES6 提供的原生对象,用来处理异步操作 它有三种状态 pending: 初始状态,不是成功或失败状态。 fulfilled: 意味着操作成功完成。 rejected: 意味着操作失败。 二、使用2.1 创建 Promise通过 new Promise 来实例化,支持链式调用 new Promise((resolve, reject)=>{ // 逻辑 }).then(()=>{ //当上面"逻辑"中调用 resolve() 时触发此方法 }).catch(()=>{ //当上面"逻辑"中调用 reject() 时触发此方法 }) 2.2 执行顺序Promise一旦创建就立即执行,并且无法中途取消,执行逻辑和顺序可以从下面的示例中获得 如下,可修改 if 条件来改变异步结果,下面打印开始的数字是执行顺序 在线调试此示例 - jsbin console.log('1.开始创建并执行 Promise') new Promise(function(resolve, reject) { console.log('2.由于创建会立即执行,所以会立即执行到本行') setTimeout(()=>{ // 模拟异步请求 console.log('4. 1s之期已到,开始执行异步操作') if (true) { // 一般我们符合预期的结果时调用 resolve(),会在 .then 中继续执行 resolve('成功') } else { // 不符合预期时调用 reject(),会在 .catch 中继续执行 reject('不符合预期') } }, 1000) }).then((res)=>{ console.log('5.调用了then,接收数据:' + res) }).catch((error)=>{ console.log('5.调用了catch,错误信息:' + error) }) console.log('3.本行为同步操作,所以先于 Promise 内的异步操作(setTimeout)') 执行结果如下 "1.开始创建并执行 Promise" "2.由于创建会立即执行,所以会立即执行到本行" "3.本行为同步操作,所以先于 Promise 内的异步操作(setTimeout)" "4. 1s之期已到,开始执行异步操作" "5.调用了then,接收数据:成功" 2.3 用函数封装 Promise这是比较常用的方法,如下用 setTimeout 模拟异步请求,封装通用请求函数 在线调试此示例 - jsbin // 这是一个异步方法 function ajax(url){ return new Promise(resolve=>{ console.log('异步方法开始执行') setTimeout(()=>{ console.log('异步方法执行完成') resolve(url+'的结果集') }, 1000) }) } // 调用请求函数,并接受处理返回结果 ajax('/user/list').then((res)=>{ console.log(res) }) 执行结果 "异步方法开始执行" "异步方法执行完成" "/user/list的结果集" 三、高级用法3.1 同时支持Callback与Promise在线调试此示例 - jsbin function ajax(url, success, fail) { if (typeof success === 'function') { setTimeout(() => { if (true) { success({user: '羊'}) } else if (typeof fail === 'function') { console.log(typeof fail) fail('用户不存在') } }, 1000) } else { return new Promise((resolve, reject) => { this.ajax(url, resolve, reject) }) } } // callback 调用方式 ajax('/user/get', (res)=>{ console.log('Callback请求成功!返回结果:', res) }, (error)=>{ console.log('Callback请求失败!错误信息:', error) }) // Promise 调用方式 ajax('/user/get').then((res)=>{ console.log('Pormise请求成功!返回结果:', res) }).catch((error)=>{ console.log('Promise请求失败!返回结果:', error) }) 执行结果 Callback请求成功!返回结果: {user: "羊"} Pormise请求成功!返回结果: {user: "羊"} 3.2 链式调用.then 支持返回 Promise 对象进行链式调用 ajax('/user/info').then((res)=>{ // 用户信息查询成功后,可以根据返回结果查询后续信息 console.log('用户信息:', res) return ajax('/user/score') }).then((res)=>{ console.log('用户成绩:', res) return ajax('/user/friends') }).then((res)=>{ console.log('用户朋友:', res) }) 3.3 Promise.allPromise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。在线调试此示例 - jsbin // 生成一个Promise对象的数组 var promises = [2, 3, 5, 7, 11, 13].map(function(id){ return new Promise((resolve, reject)=>{ if (id % 3 === 0) { resolve(id) } else { reject(id) } }); }); Promise.all(promises).then(function(post) { console.log('全部通过') }).catch(function(reason){ console.log('未全部通过,有问题id:'+reason) }); 执行结果 未全部通过,有问题id:2 Referencemozilla web docs","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"javascript","slug":"javascript","permalink":"http://yelog.org/tags/javascript/"}]},{"title":"Docker 技术整理","slug":"Docker-技术整理","date":"2020-09-01T14:11:00.000Z","updated":"2024-07-05T11:10:22.245Z","comments":true,"path":"2020/09/01/Docker-summary/","permalink":"http://yelog.org/2020/09/01/Docker-summary/","excerpt":"","text":"一、概述1.1 什么是dockerDocker 诞生于 2013 年初,由 dotCloud 公司(后改名为 Docker Inc)基于 Go 语言实现并开源的项目。此项目后来加入 Linux基金会,遵从了 Apache 2.0 协议 Docker 项目的目标是实现轻量级的操作系统虚拟化解决方案。Docker 是在 Linux 容器技术(LXC)的基础上进行了封装,让用户可以快速并可靠的将应用程序从一台运行到另一台上。 使用容器部署应用被称为容器化,容器化技术的几大优势: 灵活:甚至复杂的应用也可以被容器化 轻量:容器利用和共享宿主机内核,从而在利用系统资源比虚拟机更加的有效 可移植:你可以在本地构建,在云端部署并在任何地方运行 松耦合:容器是高度封装和自给自足的,允许你在不破环其他容器的情况下替换或升级任何一个 可扩展:你可以通过数据中心来新增和自动分发容器 安全:容器依赖强约束和独立的进程 1.2 和传统虚拟机的区别容器在Linux上本地运行,并与其他容器共享主机的内核。它运行一个离散进程,不占用任何其他可执行文件更多的内存,从而使其轻巧。 1.3 相关链接官网:https://www.docker.com/ 文档:https://docs.docker.com/ 二、Image镜像2.1 介绍Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。 父镜像:每个镜像都可能依赖于有一个或多个下层组成的另一个镜像。下层那个镜像就是上层镜像的父镜像 基础镜像:一个没有任何父镜像的镜像,被称为基础镜像 镜像ID:所有镜像都是通过一个 64 位十六进制字符串(256 bit 的值)来标识的。为了简化使用,前 12 个自负可以组成一个短ID,可以在命令行中使用。短ID还是有一定的碰撞几率,所以服务器总是返回长ID 2.2 从仓库下载镜像可以通过 docker pull 命令从仓库获取所需要的镜像 docker pull [选项] [Docker Registry 地址]<镜像名>:<标签> 选项: –all-tags,-a : 拉去所有 tagged 镜像 –disable-content-trust:忽略镜像的校验,默认 –platform:如果服务器是开启多平台支持的,则需要设置平台 –quiet,-q:静默执行,不打印详细信息 标签: 下载指定标签的镜像,默认 latest 示例 # 从 Docker Hub 下载最新的 debian 镜像 docker pull debian # 从 Docker Hub 下载 jessie 版 debian 镜像 docker pull debian:jessie # 下载指定摘要(sha256)的镜像 docker pull ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2 2.3 列出本地镜像# 列出已下载的镜像 image_name: 指定列出某个镜像 docker images [选项] [image_name] 选项 参数 描述 –all, -a 展示所有镜像(包括 intermediate 镜像) –digests 展示摘要 –filter, -f 添加过滤条件 –format 使用 Go 模版更好的展示 –no-trunc 不删减输出 –quiet, -q 静默输出,仅仅展示 IDs 示例 # 展示本地所有下载的镜像 docker images # 在本地查找镜像名是 "java" 标签是 "8" 的 奖项 docker images: java:8 # 查找悬挂镜像 docker images --filter "dangling=true" # 过滤 lable 为 "com.example.version" 的值为 0.1 的镜像 docker images --filter "label=com.example.version=0.1" 2.4 Dockerfile创建镜像为了方便分享和快速部署,我们可以使用 docker build 来创建一个新的镜像,首先创建一个文件 Dockerfile,如下 # This is a comment FROM ubuntu:14.04 MAINTAINER Chris <jaytp@qq.com> RUN apt-get -qq update RUN apt-get -qqy install ruby ruby-dev RUN gem install sinatra 然后在此 Dockerfile 所在目录执行 docker build -t yelog/ubuntu:v1 . 来生成镜像,所属组织/镜像名:标签 2.5 上传镜像用户可以通过 docker push 命令,把自己创建的镜像上传到仓库中来共享。例如,用户在 Docker Hub 上完成注册后,可以推送自己的镜像到仓库中。 docker push yelog/ubuntu 2.6 导出和载入镜像docker 支持将镜像导出为文件,然后可以再从文件导入到本地镜像仓库 # 导出 docker load --input yelog_ubuntu_v1.tar # 载入 docker load < yelog_ubuntu_v1.tar 2.7 移除本地镜像# -f 强制删除 docker rmi [-f] yelog/ubuntu:v1 # 删除悬挂镜像 docker rmi $(docker images -f "dangling=true" -q) # 删除所有未被容器使用的镜像 docker image prune -a 三、容器3.1 介绍容器和镜像,就像面向对象中的 类 和 示例 一样,镜像是静态的定义,容器是镜像运行的实体,容器可以被创建、启动、停止、删除和暂停等 容器的实质是进城,耽于直接的宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的 root 文件系统、网络配置和进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。 3.2 创建容器我们可以通过命令 docker run 命令创建容器 如下,启动一个容器,执行命令输出 “Hello word”,之后终止容器 docker run ubuntu:14.04 /bin/echo 'Hello world' 下面的命令则是启动一个 bash 终端,允许用户进行交互 docker run -t -i ubuntu:14.04 /bin/bash -t 让 Dcoker 分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上 -i 责让容器的标准输入保持打开 更多参数可选 -a stdin 指定标准输入输出内容类型 -d 后台运行容器,并返回容器ID -i 以交互模式运行容器,通常与 -t 同时使用 -P 随机端口映射,容器端口内部随即映射到宿主机的端口上 -p 指定端口映射, -p 宿主机端口:容器端口 -t \b为容器重新分配一个伪输入终,通常与 -i 同时使用 –name=”gate” 为容器指定一个名称 –dns 8.8.8.8 指定容器的 DNS 服务器,默认与宿主机一致 –dns-search example.com 指定容器 DNS 搜索域名,默认与宿主机一致 -h “gate” 指定容器的 hostname -e username=’gate’ 设置环境变量 –env-file=[] 从指定文件读入环境变量 –cpuset=”0-2” or –cpuset=”0,1,2” 绑定容器到指定 CPU 运行 -m 设置容器使用内存最大值 –net=”bridge” 指定容器的网络连接类型支持 bridge/host/none/container –link=[] 添加链接到另一个容器 –expose=[] 开放一个端口或一组端口 –volume,-v 绑定一个卷 当利用 docker run 来创建容器时,Dcoker 在后台运行的标准操作包括: 检查本地是否存在指定的镜像,不存在就从公有仓库下载 利用镜像创建并启动一个容器 分配一个文件系统,并在只读的镜像外面挂在一层可读写层 从宿主主机配置的网桥接口中桥接一个虚拟借口到容器中去 从地址池配置一个 ip 地址给容器 执行用户指定的应用程序 执行用户指定的应用程序 执行完毕后容器被终止 3.3 启动容器# 创建一个名为 test 的容器,容器任务是:打印一行 Hello word docker run --name='test' ubuntu:14.04 /bin/echo 'Hello world' # 查看所有可用容器 [-a]包括终止在内的所有容器 docker ps -a # 启动指定 name 的容器 docker start test # 重启指定 name 的容器 docker restart test # 查看日志运行日志(每次启动的日志均被查询出来) $ docker logs test Hello world Hello world 3.4 守护态运行前面创建的容器都是执行任务(打印Hello world)后,容器就终止了。更多的时候,我们需要让 Docker 容器在后台以守护态(Daemonized)形式运行。此时,可以通过添加 -d 参数来实现 注意:docker是否会长久运行,和 docker run 指定的命令有关 # 创建 docker 后台守护进程的容器 docker run --name='test2' -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done" # 查看容器 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 237e555d4457 ubuntu:14.04 "/bin/sh -c 'while t…" 52 seconds ago Up 51 seconds test2 # 获取容器的输出信息 $ docker logs test2 hello world hello world hello world 3.5 进入容器上一步我们已经实现了容器守护态长久运行,某些时候需要进入容器进行操作,可以使用 attach 、exec 进入容器。 # 不安全的,ctrl+d 退出时容器也会终止 docker attach [容器Name] # 以交互式命令行进入,安全的,推荐使用 docker exec -it [容器Name] /bin/bash 命令优化 使用 docker exec 命令时,好用,但是命令过长,我们可以通过自定义命令来简化使用 创建文件 /user/bin/ctn 命令文件,内容如下 docker exec -it $1 /bin/bash 检查环境变量有没有配置目录 /usr/bin (一般是有配置在环境变量里面的,不过最好再确认一下) $PATH bash: /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games: No such file or directory 完成上面步骤后,就可以直接通过命令 ctn 来进入容器 注意:如果是使用非 root 账号创建的命令,而 docker 命令是 root 权限,可能存在权限问题,可以通过设置 chmod 777 /usr/bin/ctn 设置权限,使用 sudo ctn [容器Name] 即可进入容器 $ ctn [容器Name] 使用上面命令时,容器Name 需要手动输入,容器出错。我们可以借助 complete 命令来补全 容器Name,在 ~/.bashrc (作用于当前用户,如果想要所要用户上校,可以修改 /etc/bashrc)文件中添加一行,内容如下。保存后执行 source ~/.bashrc 使之生效,之后我们输入 ctn 后,按 tab 就会提示或自动补全容器名了了 # ctn auto complete complete -W "$(docker ps --format"{{.Names}}")" ctn 注意: 由于提示的 容器Name 是 ~/.bashrc 生效时的列表,所有如果之后 docker 容器列表有变动,需要重新执行 source ~/.bashrc 使之更新提示列表 3.6 终止容器通过 docker stop [容器Name] 来终止一个运行中的容器 # 终止容器名为 test2 的容器 docker stop test2 # 查看正在运行中的容器 docker ps # 查看所有容器(包括终止的) docker ps -a 3.7 将容器保存为镜像我们修改一个容器后,可以经当前容器状态打包成镜像,方便下次直接通过镜像仓库生成当前状态的容器。 # 创建容器 docker run -t -i training/sinatra /bin/bash # 添加两个应用 gem install json # 将修改后的容器打包成新的镜像 docker commit -m "Added json gem" -a "Docker Newbee" 0b2616b0e5a8 ouruser/sinatra:v2 3.8 导出/导入容器容器 ->导出> 容器快照文件 ->导入> 本地镜像仓库 ->新建> 容器 $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2a8bffa405c8 ubuntu:14.04 "/bin/sh -c 'while t…" About an hour ago Up 3 seconds test2 # 导出 $ docker export 2a8bffa405c8 > ubuntu.tar # 导入为镜像 $ docker ubuntu.tar | docker import - test/ubuntu:v1.0 # 从指定 URL 或者某个目录导入 $ docker import http://example.com/exampleimage.tgz example/imagerepo 注意:用户既可以通过 docker load 来导入镜像存储文件到本地镜像仓库,也可以使用 docker import 来导入一个容器快找到本地镜像仓库,两者的区别在于容器快照将丢失所有的历史记录和元数据信息,仅保存容器当时的状态,而镜像存储文件将保存完成的记录,体积要更大。所有容器快照文件导入时需要重新指定标签等元数据信息。 3.9 删除容器可以使用 docker rm [容器Name] 来删除一个终止状态的容器,如果容器还未终止,可以先使用 docker stop [容器Name] 来终止容器,再进行删除操作 docker rm test2 # 删除容器 -f: 强制删除,无视是否运行 $ docker [-f] rm myubuntu # 删除所有已关闭的容器 $ docker rm $(docker ps -a -q) 3.10 查看容器状态docker stats $(docker ps --format={{.Names}}) 四、数据卷4.1 介绍数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多特性: 数据卷可以在容器之间共享和重用 对数据卷的修改会立马生效 对数据卷的更新,不会影响镜像 卷会一直存在,直到没有容器使用 数据卷类似于 Linux 下对目录或文件进行 mount 4.2 创建数据卷在用 docker run 命令的时候,使用 -v 标记来创建一个数据卷并挂在在容器里,可同时挂在多个。 # 创建一个 web 容器,并加载一个数据卷到容器的 /webapp 目录 docker run -d -P --name web -v /webapp training/webapp python app.py # 挂载一个宿主机目录 /data/webapp 到容器中的 /opt/webapp docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py # 默认是读写权限,也可以指定为只读 docker run -d -P --name web -v /src/webapp:/opt/webapp:ro # 挂载单个文件 docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash 4.3 数据卷容器如果需要多个容器共享数据,最好创建数据卷容器,就是一个正常的容器,撰文用来提供数据卷供其他容器挂载的 # 创建一个数据卷容器 dbdata docker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres # 其他容器挂载 dbdata 容器的数据卷 docker run -d --volumes-from dbdata --name db1 training/postgres docker run -d --volumes-from dbdata --name db2 training/postgres 五、网络5.1 外部访问容器在容器内运行一些服务,需要外部可以访问到这些服务,可以通过 -P 或 -p 参数来指定端口映射。 当使用 -P 标记时,Docker 会随即映射一个 49000~49900 的端口到内部容器开放的网络端口。 使用 docker ps 可以查看端口映射情况 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7f43807dc042 training/webapp "python app.py" 3 seconds ago Up 2 seconds 0.0.0.0:32770->5000/tcp amazing_liskov -p 指定端口映射,支持格式 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort # 不限制ip访问 docker run -d -p 5000:5000 training/webapp python app.py # 只允许宿主机回环地址访问 docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py # 宿主机自动分配绑定端口 docker run -d -p 127.0.0.1::5000 training/webapp python app.py # 指定 udp 端口 docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py # 指定多个端口映射 docker run -d -p 5000:5000 -p 3000:80 training/webapp python app.py # 查看映射端口配置 $ docker port amazing_liskov 5000/tcp -> 0.0.0.0:32770 5.2 容器互联容器除了跟宿主机端口映射外,还有一种容器间交互的方式,可以在源/目标容器之间建立一个隧道,目标容器可以看到源容器指定的信息。 可以通过 --link name:alias 来连接容器,下面就是 “web容器连接db容器” 的例子 # 创建 容器db docker run -d --name db training/postgres # 创建 容器web 并连接到 容器db docker run -d -P --name web --link db:db training/webapp python app.py # 进入 容器web,测试连通性 $ ctn web $ ping db PING db (172.17.0.3) 56(84) bytes of data. 64 bytes from db (172.17.0.3): icmp_seq=1 ttl=64 time=0.254 ms 64 bytes from db (172.17.0.3): icmp_seq=2 ttl=64 time=0.190 ms 64 bytes from db (172.17.0.3): icmp_seq=3 ttl=64 time=0.389 ms 5.3 访问控制容器想要访问外部网络,需要宿主机的转发支持。在 Linux 系统中,通过以下命令检查是否打开 $ sysctl net.ipv4.ip_forward net.ipv4.ip_forward = 1 如果是 0,说明没有开启转发,则需要手动打开。 $ sysctl -w net.ipv4.ip_forward=1 5.4 配置 docker0 桥接Docker 服务默认会创建一个 docker0 网桥,他在内核层连通了其他物理或虚拟网卡,这就将容器和主机都放在同一个物理网络。 Docker 默认制定了 docker0 接口的IP地址和子网掩码,让主机和容器间可以通过网桥相互通信,他还给了 MTU(接口允许接收的最大单元),通常是 1500 Bytes,或宿主机网络路由上支持的默认值。这些都可以在服务启动的时候进行配置。 --bip=CIDR ip地址加子网掩码格式,如 192.168.1.5/24 --mtu=BYTES 覆盖默认的 Docker MTU 配置 可以通过 brctl show 来查看网桥和端口连接信息 5.5 网络配置文件Docker 1.2.0 开始支持在运行中的容器里编辑 /etc/hosts 、/etc/hostsname 和 /etc/resolve.conf 文件,修改都是临时的,重新容器将会丢失修改,通过 docker commit 也不会被提交。 六、Dockerfile6.1 介绍Dockerfile 是由一行行命令组成的命令集合,分为四个部分: 基础镜像信息 维护着信息 镜像操作指令 容器启动时执行指令 如下: # 最前面一般放这个 Dockerfile 的介绍、版本、作者及使用说明等 # This dockerfile uses the ubuntu image # VERSION 2 - EDITION 1 # Author: docker_user # Command format: Instruction [arguments / command] .. # 使用的基础镜像,必须放在非注释第一行 FROM ubuntu # 维护着信息信息: 名字 联系方式 MAINTAINER docker_user docker_user@email.com # 构建镜像的命令:对镜像做的调整都在这里 RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list RUN apt-get update && apt-get install -y nginx RUN echo "\\ndaemon off;" >> /etc/nginx/nginx.conf # 创建/运行 容器时的操作指令 # 可以理解为 docker run 后跟的运行指令 CMD /usr/sbin/nginx 6.2 指令指令一般格式为 INSTRUCTION args,包括 FORM 、 MAINTAINER 、RUN 等 FORM 第一条指令必须是 FORM 指令,并且如果在同一个Dockerfile 中创建多个镜像,可以使用多个 FROM 指令(每个镜像一次) FORM ubuntuFORM ubuntu:14.04 MAINTAINER 维护者信息 MAINTAINER Chris xx@gmail.com RUN 每条 RUN 指令在当前镜像基础上执行命令,并提交为新的镜像。当命令过长时可以使用 \\ 来换行 在 shell 终端中运行命令RUN apt-get update && apt-get install -y nginx在 exec 中执行:RUN ["/bin/bash", "-c", "echo hello"] CMD 指定启动容器时执行的命令,每个 Dockerfile 只能有一条 CMD 命令。如果指定了多条命令,只有最后一条会被执行。 CMD ["executable","param1","param2"] 使用 exec 执行,推荐方式;CMD command param1 param2 在 /bin/sh 中执行,提供给需要交互的应用;CMD ["param1","param2"] 提供给 ENTRYPOINT 的默认参数; EXPOSE 告诉服务端容器暴露的端口号, EXPOSE ENV 指定环境变量 ENV PG_MAJOR 9.3ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH ADD ADD 该命令将复制指定的 到容器中的 。其中 可以是 Dockerfile 所在目录的一个相对路径,也可以是一个URL;还可以是一个 tar文件(自动解压为目录) COPY 格式为 COPY 复制本地主机的 (为 Dockerfile 所在目录的相对路径)到容器中的 。当使用本地目录为源目录时,推荐使用 COPY ENTRYPOINT 配置容器启动执行的命令,并且不可被 docker run 提供的参数覆盖每个Docekrfile 中只能有一个 ENTRYPOINT ,当指定多个时,只有最后一个起效 两种格式ENTRYPOINT ["executable", "param1", "param2"]``ENTRYPOINT command param1 param2(shell中执行) VOLUME 创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。 VOLUME [“/data”] USER 指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户 USER daemon WORKDIR 为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录。可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。 格式为 WORKDIR /path/to/workdir。 WORKDIR /aWORKDIR bWORKDIR cRUN pwd最后的路径为 /a/b/c ONBUILD 配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。 格式为 ONBUILD [INSTRUCTION]。 6.3 创建镜像编写完成 Dockerfile 之后,可以通过 docker build 命令来创建镜像 docker build [选项] 路径 该命令江都区指定路径下(包括子目录)的Dockerfile,并将该路径下所有内容发送给 Docker 服务端,有服务端来创建镜像。可以通过 .dockerignore 文件来让 Docker 忽略路径下的目录与文件 # 使用 -t 指定镜像的标签信息 docker build -t myrepo/myimage . 七、Docker Compose7.1 介绍Docker Compose 是 Docker 官方编排项目之一,负责快速在集群中部署分布式应用。维护地址:https://github.com/docker/compose,由 Python 编写,实际调用 Docker提供的API实现。 Dockerfile 可以让用户管理一个单独的应用容器,而 Compose 则允许用户在一个模版(YAML格式)中定义一组相关联的应用容器(被称为一个project/项目),例如一个 web容器再加上数据库、redis等。 7.2 安装# 使用 pip 进行安装 pip install -U docker-compose # 查看用法 docker-ompose -h # 添加 bash 补全命令 curl -L https://raw.githubusercontent.com/docker/compose/1.2.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose 7.3 使用术语 服务/service: 一个应用容器,实际上可以运行多个相同镜像的实例 项目/project: 有一组关联的应用容器组成的完成业务单元 示例:创建一个 Haproxy 挂载三个 Web 容器 创建一个 compose-haproxy-web 目录,作为项目工作目录,并在其中分别创建两个子目录: haproxy 和 web 。 compose-haproxy-webcompose-haproxy-web git clone https://github.com/yelog/compose-haproxy-web.git 目录长这样: compose-haproxy-web ├── docker-compose.yml ├── haproxy │ └── haproxy.cfg └── web ├── Dockerfile ├── index.html └── index.py 在该目录执行 docker-compose up 命令,会整合输出所有容器的输出 $ docker-compose up Starting compose-haproxy-web_webb_1 ... done Starting compose-haproxy-web_webc_1 ... done Starting compose-haproxy-web_weba_1 ... done Recreating compose-haproxy-web_haproxy_1 ... done Attaching to compose-haproxy-web_webb_1, compose-haproxy-web_weba_1, compose-haproxy-web_webc_1, compose-haproxy-web_haproxy_1 haproxy_1 | [NOTICE] 244/131022 (1) : haproxy version is 2.2.2 haproxy_1 | [NOTICE] 244/131022 (1) : path to executable is /usr/local/sbin/haproxy haproxy_1 | [ALERT] 244/131022 (1) : parsing [/usr/local/etc/haproxy/haproxy.cfg:14] : 'listen' cannot handle unexpected argument ':70'. haproxy_1 | [ALERT] 244/131022 (1) : parsing [/usr/local/etc/haproxy/haproxy.cfg:14] : please use the 'bind' keyword for listening addresses. haproxy_1 | [ALERT] 244/131022 (1) : Error(s) found in configuration file : /usr/local/etc/haproxy/haproxy.cfg haproxy_1 | [ALERT] 244/131022 (1) : Fatal errors found in configuration. compose-haproxy-web_haproxy_1 exited with code 1 此时访问本地的 80 端口,会经过 haproxy 自动转发到后端的某个 web 容器上,刷新页面,可以观察到访问的容器地址的变化。 7.4 命令说明大部分命令都可以运行在一个或多个服务上。如果没有特别的说明,命令则应用在项目所有的服务上。 执行 docker-compose [COMMAND] --help 查看具体某个命令的使用说明 使用格式 docker-compose [options] [COMMAND] [ARGS...] build 构建/重建服务服务一旦构建后,将会带上一个标记名,例如 web_db可以随时在项目目录运行 docker-compose build 来重新构建服务 help 获得一个命令的信息 kill 通过发送 SIGKILL 信号来强制停止服务容器,支持通过参数来指定发送信号,例如docker-compose kill -s SIGINT logs 查看服务的输出 port 打印绑定的公共端口 ps 列出所有容器 pull 拉去服务镜像 rm 删除停止的服务容器 run 在一个服务上执行一个命令docker-compose run ubuntu ping docker.com scale 设置同一个服务运行的容器个数通过 service=num 的参数来设置数量docker-compose scale web=2 worker=3 start 启动一个已经存在的服务容器 stop 停止一个已经运行的容器,但不删除。可以通过 docker-compose start 再次启动 up 构建、创建、启动、链接一个服务相关的容器链接服务都将被启动,除非他们已经运行docker-compose up -d 将后台运行并启动docker-compose up 已存在容器将会重新创建docker-compose up --no-recreate 将不会重新创建容器 7.5 环境变量环境变量可以用来配置 Compose 的行为 以 Docker_ 开头的变量用来配置 Docker 命令行客户端使用的一样 COMPOSE_PROJECT_NAME 设置通过 Compose 启动的每一个容器前添加的项目名称,默认是当前工作目录的名字。 COMPOSE_FILE 设置要使用的 docker-compose.yml 的路径。默认路径是当前工作目录。 DOCKER_HOST 设置 Docker daemon 的地址。默认使用 unix:///var/run/docker.sock,与 Docker 客户端采用的默认值一致。 DOCKER_TLS_VERIFY 如果设置不为空,则与 Docker daemon 交互通过 TLS 进行。 DOCKER_CERT_PATH 配置 TLS 通信所需要的验证(ca.pem、cert.pem 和 key.pem)文件的路径,默认是 ~/.docker 。 7.6 docker-compose.yml默认模版文件是 docker-compose.yml ,启动定义了每个服务都必须经过 image 指令指定镜像或 build 指令(需要 Dockerfile) 来自动构建。 其他大部分指令跟 docker run 类似 如果使用 build 指令,在 Dockerfile 中设置的选项(如 CMD 、EXPOSE 等)将会被自动获取,无需在 docker-compose.yml 中再次设置。 **image** 指定镜像名称或镜像ID,如果本地仓库不存在,将尝试从远程仓库拉去此镜像 image: ubuntu image: orchardup/postgresql image: a4bc65fd **build** 指定 Dockerfile 所在文件的路径。Compose 将利用它自动构建这个镜像,然后使用这个镜像。 build: /path/to/build/dir **command** 覆盖容器启动默认执行命令 command: bundle exec thin -p 3000 **links** 链接到其他服务中的容器,使用服务名称或别名 links: - db - db:database - redis 别名会自动在服务器中的 /etc/hosts 里创建。例如: 172.17.2.186 db 172.17.2.186 database 172.17.2.187 redis **external_links** 连接到 docker-compose.yml 外部的容器,甚至并非 Compose 管理的容器。 external_links: - redis_1 - project_db_1:mysql - project_db_1:postgresql ports 暴露端口信息 HOST:CONTAINER 格式或者仅仅指定容器的端口(宿主机会随机分配端口) ports: - "3000" - "8000:8000" - "49100:22" - "127.0.0.1:8001:8001" 注:当使用 HOST:CONTAINER 格式来映射端口时,如果你使用的容器端口小于 60 你可能会得到错误得结果,因为 YAML 将会解析 xx:yy 这种数字格式为 60 进制。所以建议采用字符串格式。 **expose** 暴露端口,但不映射到宿主机,只被连接的服务访问 expose: - "3000" - "8000" volumes 卷挂载路径设置。可以设置宿主机路径 (HOST:CONTAINER) 或加上访问模式 (HOST:CONTAINER:ro)。 volumes: - /var/lib/mysql - cache/:/tmp/cache - ~/configs:/etc/configs/:ro **** volumes_from 从另一个服务或容器挂载它的所有卷。 volumes_from: - service_name - container_name environment 设置环境变量。你可以使用数组或字典两种格式。 只给定名称的变量会自动获取它在 Compose 主机上的值,可以用来防止泄露不必要的数据。 environment: RACK_ENV: development SESSION_SECRET: environment: - RACK_ENV=development - SESSION_SECRET env_file 从文件中获取环境变量,可以为单独的文件路径或列表。 如果通过 docker-compose -f FILE 指定了模板文件,则 env_file 中路径会基于模板文件路径。 如果有变量名称与 environment 指令冲突,则以后者为准。 env_file: .env env_file: - ./common.env - ./apps/web.env - /opt/secrets.env 环境变量文件中每一行必须符合格式,支持 # 开头的注释行。 # common.env: Set Rails/Rack environment RACK_ENV=development extends 基于已有的服务进行扩展。例如我们已经有了一个 webapp 服务,模板文件为 common.yml。 # common.yml webapp: build: ./webapp environment: - DEBUG=false - SEND_EMAILS=false 编写一个新的 development.yml 文件,使用 common.yml 中的 webapp 服务进行扩展。 # development.yml web: extends: file: common.yml service: webapp ports: - "8000:8000" links: - db environment: - DEBUG=true db: image: postgres 后者会自动继承 common.yml 中的 webapp 服务及相关环节变量。 **** net 设置网络模式。使用和 docker client 的 --net 参数一样的值。 net: "bridge" net: "none" net: "container:[name or id]" net: "host" **** pid 跟主机系统共享进程命名空间。打开该选项的容器可以相互通过进程 ID 来访问和操作。 pid: "host" dns 配置 DNS 服务器。可以是一个值,也可以是一个列表。 dns: 8.8.8.8 dns: - 8.8.8.8 - 9.9.9.9 cap_add, cap_drop 添加或放弃容器的 Linux 能力(Capabiliity)。 cap_add: - ALL cap_drop: - NET_ADMIN - SYS_ADMIN **** dns_search 配置 DNS 搜索域。可以是一个值,也可以是一个列表。 dns_search: example.com dns_search: - domain1.example.com - domain2.example.com **** working_dir, entrypoint, user, hostname, domainname, mem_limit, privileged, restart, stdin_open, tty, cpu_shares 这些都是和 docker run 支持的选项类似。 八、安全8.1 内核命名空间当使用 docker run 启动一个容器时,在后台 Docker 为容器创建一个独立的命名空间和控制集合。命名空间踢空了最基础的也是最直接的隔离,在容器中运行的进程不会被运行在主机上的进程和其他容器发现和作用。 8.2 控制组控制组是 Linux 容器机制的另一个关键组件,负责实现资源的审计和限制。 它提供了很多特性,确保哥哥容器可以公平地分享主机的内存、CPU、磁盘IO等资源;当然,更重要的是,控制组确保了当容器内的资源使用产生压力时不会连累主机系统。 8.3 内核能力机制能力机制是 Linux 内核的一个强大特性,可以提供细粒度的权限访问控制。 可以作用在进程上,也可以作用在文件上。 例如一个服务需要绑定低于 1024 的端口权限,并不需要 root 权限,那么它只需要被授权 net_bind_service 能力即可。 默认情况下, Docker 启动的容器被严格限制只允许使用内核的一部分能力。 使用能力机制加强 Docker 容器的安全有很多好处,可以按需分配给容器权限,这样,即便攻击者在容器中取得了 root 权限,也不能获取宿主机较高权限,能进行的破坏也是有限的。 参考资料https://docs.docker.com/engine/reference/commandline/images/ http://www.dockerinfo.net/","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://yelog.org/tags/linux/"},{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"3-hexo评论设置","slug":"3-hexo-comment","date":"2020-05-23T14:26:23.000Z","updated":"2024-07-05T11:10:22.503Z","comments":true,"path":"2020/05/23/3-hexo-comment/","permalink":"http://yelog.org/2020/05/23/3-hexo-comment/","excerpt":"","text":"前言目前 3-hexo 已经集成了评论系统有 gitalk 、gitment、 disqus 、来必力、utteranc 一、gitalkgitalk 是一款基于 Github Issue 和 Preact 开发的评论插件 官网: https://gitalk.github.io/ 1. 登录 github ,注册应用点击进行注册 ,如下 注册完后,可得到 Client ID 和 Client Secret 2. 新建存放评论的仓库因为 gitalk 是基于 Github 的 Issue 的,所以需要指定一个仓库,用来承接 gitalk 的评论,我们一般使用 Github Page 来做我们博客的评论,所以,新建仓库名为 xxx.github.io,其中 xxx 为你的 Github 用户名 3. 配置主题在主题下 _config.yml 中找到如下配置,启用评论,并使用 gitalk ##########评论设置############# comment: on: true type: gitalk 在主题下 _config.yml 中找到 gitalk 配置,将 第1步 得到的 Client ID 和 Client Secret 复制到如下位置 gitalk: githubID: # 填你的 github 用户名 repo: xxx.github.io # 承载评论的仓库,一般使用 Github Page 仓库 ClientID: # 第1步获得 Client ID ClientSecret: # 第1步获得 Client Secret adminUser: # Github 用户名 distractionFreeMode: true language: zh-CN perPage: 10 二、来必力1. 创建来必力账号,并选择 City 免费版官网http://livere.com/ ,创建账号,点击上面的安装,选择 City 免费版 复制获取到的代码中的 data-uid 2. 主题选择使用来必力评论在主题下 _config.yml 在找到来必力配置如下,第一步中复制的 data-uid 粘贴到下面 data_uid 处 livere: data_uid: xxxxxx 找到以下代码, 开启并选择 livere (来必力) ##########评论设置############# comment: on: true type: livere 三、utteranc官网地址:https://utteranc.es/ 1. 安装 utterances点我进行安装 2. 配置主题在主题下 _config.yml 中找到 utteranc 的配置 ,修改 repo 为自己的仓库名 utteranc: repo: xxx/xxx.github.io # 承载评论的仓库,填上自己的仓库 issue_term: pathname # Issue 与 博客文章 之间映射关系 label: utteranc # 创建的 Issue 添加的标签 theme: github-light # 主题,可选主题请查看官方文档 https://utteranc.es/#heading-theme # 官方文档 https://utteranc.es/ # 使用说明 https://yelog.org//2020/05/23/3-hexo-comment/ 在主题下 _config.yml 中找到如下配置,启用评论,并使用 utteranc comment: on: true type: utteranc","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"}]},{"title":"3-hexo支持mermaid图表","slug":"3-hexo-support-mermaid","date":"2019-11-12T01:55:37.000Z","updated":"2024-07-05T11:10:22.576Z","comments":true,"path":"2019/11/12/3-hexo-support-mermaid/","permalink":"http://yelog.org/2019/11/12/3-hexo-support-mermaid/","excerpt":"","text":"一、说明开启 安装hexo插件 npm install hexo-filter-mermaid-diagrams 修改themes/3-hexo/_config.yml 的 mermaid.on,开启主题支持 # Mermaid 支持 mermaid: on: true cdn: //cdn.jsdelivr.net/npm/mermaid@8.4.2/dist/mermaid.min.js #cdn: //cdnjs.cloudflare.com/ajax/libs/mermaid/8.3.1/mermaid.min.js options: # 更多配置信息可以参考 https://mermaidjs.github.io/#/mermaidAPI theme: 'default' startOnLoad: true flowchart: useMaxWidth: false htmlLabels: true 在markdown中,像写代码块一样写图表 二、示例以下示例源码可以在这边查看 本文源码更多示例可以查看官网:https://mermaidjs.github.io 1. flowchartgraph TD; A-->B; A-->C; B-->D; C-->D; graph TB c1-->a2 subgraph one a1-->a2 end subgraph two b1-->b2 end subgraph three c1-->c2 end 2.Sequence diagramssequenceDiagram participant Alice participant Bob Alice->>John: Hello John, how are you? loop Healthcheck John->>John: Fight against hypochondria end Note right of John: Rational thoughts prevail! John-->>Alice: Great! John->>Bob: How about you? Bob-->>John: Jolly good! 3.Class diagramsclassDiagram Animal NumLockOn : EvNumLockPressed NumLockOn --> NumLockOff : EvNumLockPressed -- [*] --> CapsLockOff CapsLockOff --> CapsLockOn : EvCapsLockPressed CapsLockOn --> CapsLockOff : EvCapsLockPressed -- [*] --> ScrollLockOff ScrollLockOff --> ScrollLockOn : EvCapsLockPressed ScrollLockOn --> ScrollLockOff : EvCapsLockPressed } 5.Gantt diagramsgantt dateFormat YYYY-MM-DD title Adding GANTT diagram functionality to mermaid section A section Completed task :done, des1, 2014-01-06,2014-01-08 Active task :active, des2, 2014-01-09, 3d Future task : des3, after des2, 5d Future task2 : des4, after des3, 5d section Critical tasks Completed task in the critical line :crit, done, 2014-01-06,24h Implement parser and jison :crit, done, after des1, 2d Create tests for parser :crit, active, 3d Future task in critical line :crit, 5d Create tests for renderer :2d Add to mermaid :1d section Documentation Describe gantt syntax :active, a1, after des1, 3d Add gantt diagram to demo page :after a1 , 20h Add another diagram to demo page :doc1, after a1 , 48h section Last section Describe gantt syntax :after doc1, 3d Add gantt diagram to demo page :20h Add another diagram to demo page :48h 6.Pie chart diagramspie \"Dogs\" : 386 \"Cats\" : 85 \"Rats\" : 15","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"3-hexo 添加音乐插件","slug":"3-hexo-add-music","date":"2019-10-08T02:44:30.000Z","updated":"2024-07-05T11:10:22.516Z","comments":true,"path":"2019/10/08/3-hexo-add-music/","permalink":"http://yelog.org/2019/10/08/3-hexo-add-music/","excerpt":"","text":"网易云音乐1. 复制网易云音乐插件代码前往网易云音乐官网,搜索一个作为背景音乐的歌曲,并进入播放页面,点击 生成外链播放器 设置好想要显示的样式后,复制 html 代码 最好外层在加一个 div,如下,可直接将上一步复制的 iframe 替换下方里面的 iframe <div id="musicMouseDrag" style="position:fixed; z-index: 9999; bottom: 0; right: 0;"> <div id="musicDragArea" style="position: absolute; top: 0; left: 0; width: 100%;height: 10px;cursor: move; z-index: 10;"></div> <iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=38592976&auto=1&height=66"></iframe> </div> 2. 将插件引入到主题中将上一步加过 div 的代码粘贴到主题下 layout/_partial/footer.ejs 的最后面 3. 调整位置默认给的样式是显示在右下角,可以通过调整上一步粘贴的 div 的 style 中 bottom 和 right 来调整位置。 4. 自由拖动如果需要自由拖动,在刚才添加的代码后面,再添加下面代码即可,鼠标就可以在音乐控件的 上边沿 点击拖动 <!--以下代码是为了支持随时拖动音乐控件的位置,如没有需求,可去掉下面代码--> <script> var $DOC = $(document) $('#musicMouseDrag').on('mousedown', function (e) { // 阻止文本选中 $DOC.bind("selectstart", function () { return false; }); $('#musicDragArea').css('height', '100%'); var $moveTarget = $('#musicMouseDrag'); $moveTarget.css('border', '1px dashed grey') var div_x = e.pageX - $moveTarget.offset().left; var div_y = e.pageY - $moveTarget.offset().top; $DOC.on('mousemove', function (e) { var targetX = e.pageX - div_x; var targetY = e.pageY - div_y; targetX = targetX < 0 ? 0 : (targetX + $moveTarget.outerWidth() >= window.innerWidth) ? window.innerWidth - $moveTarget.outerWidth() : targetX; targetY = targetY < 0 ? 0 : (targetY + $moveTarget.outerHeight() >= window.innerHeight) ? window.innerHeight - $moveTarget.outerHeight() : targetY; $moveTarget.css({'left': targetX + 'px', 'top': targetY + 'px', 'bottom': 'inherit', 'right': 'inherit'}) }).on('mouseup', function () { $DOC.unbind("selectstart"); $DOC.off('mousemove') $DOC.off('mouseup') $moveTarget.css('border', 'none') $('#musicDragArea').css('height', '10px'); }) }) </script>","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"3-hexo支持jsfiddle渲染","slug":"3-hexo-jsfiddle","date":"2019-09-24T01:59:37.000Z","updated":"2024-07-05T11:10:22.525Z","comments":true,"path":"2019/09/24/3-hexo-jsfiddle/","permalink":"http://yelog.org/2019/09/24/3-hexo-jsfiddle/","excerpt":"","text":"1. canvas 粒子效果 2. 复选框动画","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"3-hexo文章内toc生成","slug":"3-hexo-toc","date":"2019-09-24T01:48:20.000Z","updated":"2024-07-05T11:10:22.580Z","comments":true,"path":"2019/09/24/3-hexo-toc/","permalink":"http://yelog.org/2019/09/24/3-hexo-toc/","excerpt":"","text":"[toc] 1. 如何使用1.1 关键字只要在在文章中使用如下关键字,不区分大小写,便可以在相应位置显示目录导航,效果文章开头 1.2 小标题2jlksjdflksdjflksjdflksjdflkaj;sdfjka;lskdjfla;skjdf;lajsdflkjal;sdjkf;laskjdf占位占位 1.3 小标题占位占位占位 2. 标题二占位占位占位 2.1 小标题占位占位 2.2 小标题2占位占位占位占位占位 文末占位占位占位占位占位","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"[记]《知识分子的不幸》-王小波","slug":"misfortune-intellectual","date":"2019-06-09T13:31:09.000Z","updated":"2024-07-05T11:10:21.862Z","comments":true,"path":"2019/06/09/misfortune-intellectual/","permalink":"http://yelog.org/2019/06/09/misfortune-intellectual/","excerpt":"","text":"前言这篇文章发表于1996年第二期《东方》杂志,同样收录于《沉默的大多数》一书中。 所想文章一开头就抛出了一个问题:什么是知识分子最害怕的事?想起了高晓松在晓说中提到过这个问题,晓松肯定是看过这篇文章的。 王小波说:“知识分子最怕活在不理智的年代。”所谓不理智的年代,就是伽利略低头认罪,承认地球不转的年代,也是拉瓦斯上断头台的年代;是茨威格服毒自杀的年代,也是老舍跳太平湖的年代。“ 王小波和他的美国老师谈论了一个问题:”有信仰比无信仰要好。“,由于王小波是经历过文革的,所以王小波一开始是抵触这种思想的,尤其是 课间祷告 让王小波想起了文革中的 早请示。但老师最终说服了他,“不管是信神,还是自珍自重,人活在世界上总得有点信念才成。就我个人而言,虽是无神论者,我也有个人操守,从不逾越。” 国内的学者,只搞学术研究,不搞意识形态,这由不了自己。有朝一日它成了意识形态,你的话就是罪状。言论不自由,不理智,民族狂热,这不就是知识分子最怕的事情吗? 王小波崇拜墨子:其一,他思维缜密,其二,他敢赤裸裸地谈利害。(有了他,我也敢说自己是中华民族的赤诚分子,不怕国学家说我是全盘西化了。) 营造意识形态则是灭绝思想额丰饶。中国的人文知识分子,有种以天下为己任的使命感,总觉得自己该搞出些老百姓当信仰的东西。 国学,这种东西实在厉害。最可怕之处就在于那个“国”字。顶着这个字,谁敢有不同意见?抢到了这个制高点,就可以压制一切不同意见;所以很容易落入思想流氓的手中变成一种凶器。 目前正值 “中美贸易战”,各种自媒体为了点击量、关注度。煽动民族狂热情绪,导致民众根本容不得半点不同意见,不讲道理,“盲目爱国“。 认真思索,真诚的明辨是非,有这种态度大概就可算是善良了吧。 人活在世上,自会形成信念,一种学问、一本书,假如不对我的价值观发生变化,就不值得一学,不值得一看。","categories":[{"name":"读书","slug":"读书","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/"},{"name":"阅读笔记","slug":"读书/阅读笔记","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"reading","slug":"reading","permalink":"http://yelog.org/tags/reading/"}]},{"title":"人们在一本叫《活着》的书中纷纷死去","slug":"to-live","date":"2019-05-02T09:46:16.000Z","updated":"2024-07-05T11:10:21.870Z","comments":true,"path":"2019/05/02/to-live/","permalink":"http://yelog.org/2019/05/02/to-live/","excerpt":"","text":"有那么一个年代,离我们很近,它腥风血雨,连活着都是一件奢侈的事。 在富贵的一生中,每次出现看似被上天眷顾的福气后(如有庆长跑第一、凤霞嫁了人并怀了孩子),读者还在替富贵开心的时候,他们却以各种方式迅速死去,最终富贵亲手埋葬了他所有的亲人。 一本 12w 左右的小说,但是在没有华丽词藻的情况下,在顺畅流利的写作手法、跌宕起伏的剧情、第一人称的代入感下一口气读完了。期间多次痛哭流涕(一点儿没夸张),不得不放下书本,洗过脸后才能继续阅读。所以已经多年没写书评的我,还是忍不住为她写下书评。 人是为了活着本身而活着,而不是为了活着之外的任何事物所活着。 这是作者在中文序言中的一句话,在当今生活着的我,初读序言中的这句话,并无任何共鸣,甚至还行吐槽两句。随着富贵将他的”一生”娓娓道来,你就会明白在那样的时代背景下,活着已经是一件不容易的事。 所以作者在日文版序言中说到: 在旁人眼中富贵的一生是苦熬的一生;可是对于富贵自己,我相信他更多地感受到了幸福。 因为他相信自己的妻子是世上最好的妻子,他相信自己的子女也是世上最好的子女,还有他的女婿他的外孙,还有他的那头也叫富贵的牛,还有一起上火锅的朋友们,还有生活的点点滴滴…… 富贵的真是一路跌下去的一生,从”富家少爷”赌光了家产、气死了爹爹。由于母亲生病,为母亲求医路上被国民党抓壮丁,被俘虏后,放回家中。却发现母亲已死,女儿也由于生病变成了聋哑人。本想着大难之后必有后福,却只是悲惨一生的开端。儿子有庆由于和县长夫人血型匹配,遭抽血而亡、女儿凤霞产子大出血而亡、妻子家珍失去儿女后,失去了最后与病魔争斗的信念,也走了、女婿二喜在工地被水泥板拍死、外孙苦根难得吃到豆子,却被豆子撑死。最后只剩下自己和一个也叫作富贵的老牛。 春生想自杀前,找到富贵告别,在被家珍原谅,并答应不会自杀,在这种情况下坚持了一个月,最终还是自杀了。那种时代背景下的无奈,那种窒息感。。。 富贵的一生跨越了地主、解放战争、人民公社运动、大炼钢铁、自然灾害和文化大革命,从一个人的视角看到每个时代下的一个小小的缩影,但却比任何其他的描述更让人深刻了解到这些时代背景下人们的生活状态。 在那时,活着不仅是幸运,也更需要勇气。","categories":[{"name":"读书","slug":"读书","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/"},{"name":"阅读笔记","slug":"读书/阅读笔记","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"reading","slug":"reading","permalink":"http://yelog.org/tags/reading/"},{"name":"活着","slug":"活着","permalink":"http://yelog.org/tags/%E6%B4%BB%E7%9D%80/"}]},{"title":"shell速查表","slug":"shell速查表","date":"2018-09-08T03:48:24.000Z","updated":"2024-07-05T11:10:23.074Z","comments":true,"path":"2018/09/08/shell-command/","permalink":"http://yelog.org/2018/09/08/shell-command/","excerpt":"","text":"1. 变量#!/bin/bash msg="hello world" echo $msg 变量名的命名须遵循如下规则: 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。 中间不能有空格,可以使用下划线(_)。 不能使用标点符号。 不能使用bash里的关键字(可用help命令查看保留关键字)。 2. 传参#!/bin/bash echo "执行的文件名:$0"; echo "第一个参数为:$1"; echo "第二个参数为:$2"; echo "第三个参数为:$3"; 脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……另外,还有几个特殊字符用来处理参数: 参数 说明 $# 传递到脚本的参数个数 $* 以一个单字符串显示所有向脚本传递的参数。如"$*"用「”」括起来的情况、以”$1 $2 … $n”的形式输出所有参数。 $$ 脚本运行的当前进程ID号 $! 后台运行的最后一个进程的ID号 $@ 与$*相同,但是使用时加引号,并在引号中返回每个参数。如”$@”用「”」括起来的情况、以”$1” “$2” … “$n” 的形式输出所有参数。 $- 显示Shell使用的当前选项,与set命令功能相同。 $? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 3. 数组#!/bin/bash my_array=(A B "C" D) echo "第一个元素为: ${my_array[0]}" echo "第二个元素为: ${my_array[1]}" echo "第三个元素为: ${my_array[2]}" echo "第四个元素为: ${my_array[3]}" echo "数组的元素为: ${my_array[*]}" echo "数组的元素为: ${my_array[@]}" echo "数组元素个数为: ${#my_array[*]}" echo "数组元素个数为: ${#my_array[@]}" 执行结果如下: 第一个元素为: A 第二个元素为: B 第三个元素为: C 第四个元素为: D 数组的元素为: A B C D 数组的元素为: A B C D 数组元素个数为: 4 数组元素个数为: 4 4. 基本运算符 原生 bash 不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。 expr 是一款表达式计算工具,使用它能完成表达式的求值操作。 ① 算数运算符#!/bin/bash echo "2加2等于"`expr 2 + 2` echo "2减2等于"`expr 2 - 2` echo "2乘2等于"`expr 2 \\* 2` echo "2除2等于"`expr 2 / 2` echo "2除2取余"`expr 2 % 2` ② 关系运算符#!/bin/bash a=10 b=20 if [ $a -eq $b ] # 检测两个数是否相等,相等返回 true。 if [ $a -ne $b ] # 检测两个数是否不相等,不相等返回 true。 if [ $a -gt $b ] # 检测左边的数是否大于右边的,如果是,则返回 true。 if [ $a -lt $b ] # 检测左边的数是否小于右边的,如果是,则返回 true。 if [ $a -ge $b ] # 检测左边的数是否大于等于右边的,如果是,则返回 true。 if [ $a -le $b ] # 检测左边的数是否小于等于右边的,如果是,则返回 true。 ③ 布尔运算符#!/bin/bash if [ ! false ] # 非运算,返回 true if [ true -o false ] # 或运算,返回 true if [ true -a false ] # 与运算,返回 false ④ 逻辑运算符#!/bin/bash a=10 b=20 if [[ $a -lt $b && $a -gt $b ]] # 逻辑的 AND, 返回 false if [ $a -lt $b ] && [ $a -gt $b ] # 逻辑的 AND, 返回 false if [[ $a -lt $b || $a -gt $b ]] # 逻辑的 OR, 返回 true if [ $a -lt $b ] || [ $a -gt $b ] # 逻辑的 OR, 返回 true ⑤ 字符串运算符#!/bin/bash a="abc" b="efg" if [ $a = $b ] # 检测两个字符串是否相等,相等返回 true。 if [ $a != $b ] # 检测两个字符串是否相等,不相等返回 true。 if [ -z $a ] # 检测字符串长度是否为0,为0返回 true。 if [ -n "$a" ] # 检测字符串长度是否为0,不为0返回 true。 if [ $a ] # 检测字符串是否为空,不为空返回 true。 ⑥ 文件测试运算符文件测试运算符用于检测 Unix 文件的各种属性。 操作符 说明 -b file 检测文件是否是块设备文件,如果是,则返回 true。 -c file 检测文件是否是字符设备文件,如果是,则返回 true。 -d file 检测文件是否是目录,如果是,则返回 true。 -f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 -g file 检测文件是否设置了 SGID 位,如果是,则返回 true。 -k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 -p file 检测文件是否是有名管道,如果是,则返回 true。 -u file 检测文件是否设置了 SUID 位,如果是,则返回 true。 -r file 检测文件是否可读,如果是,则返回 true。 -w file 检测文件是否可写,如果是,则返回 true。 -x file 检测文件是否可执行,如果是,则返回 true。 -s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。 -e file 检测文件(包括目录)是否存在,如果是,则返回 true。 5. echo① 命令格式#!/bin/bash echo "It is a test" echo It is a test echo "\\"It is a test\\"" # 转义 name=Chris echo "$name is handsome" echo -e "OK! \\n" # 显示换行 -e 开启转义 echo "It is a test" > myfile # 显示结果定向至文件 echo '$name\\"' # 原样输入字符串,不进行转义或取变量(使用单引号) echo `date` # 显示命令执行结构 ② 颜色显示echo -e "\\033[字背景颜色;文字颜色m字符串\\033[0m" echo -e “\\033[30m 黑色字 \\033[0m” echo -e “\\033[31m 红色字 \\033[0m” echo -e “\\033[32m 绿色字 \\033[0m” echo -e “\\033[33m 黄色字 \\033[0m” echo -e “\\033[34m 蓝色字 \\033[0m” echo -e “\\033[35m 紫色字 \\033[0m” echo -e “\\033[36m 天蓝字 \\033[0m” echo -e “\\033[37m 白色字 \\033[0m” echo -e “\\033[40;37m 黑底白字 \\033[0m” echo -e “\\033[41;37m 红底白字 \\033[0m” echo -e “\\033[42;37m 绿底白字 \\033[0m” echo -e “\\033[43;37m 黄底白字 \\033[0m” echo -e “\\033[44;37m 蓝底白字 \\033[0m” echo -e “\\033[45;37m 紫底白字 \\033[0m” echo -e “\\033[46;37m 天蓝底白字 \\033[0m” echo -e “\\033[47;30m 白底黑字 \\033[0m” \\33[0m 关闭所有属性 \\33[1m 设置高亮度 \\33[4m 下划线 \\33[5m 闪烁 \\33[7m 反显 \\33[8m 消隐 \\33[30m — \\33[37m 设置前景色 \\33[40m — \\33[47m 设置背景色 \\33[nA 光标上移n行 \\33[nB 光标下移n行 \\33[nC 光标右移n行 \\33[nD 光标左移n行 \\33[y;xH设置光标位置 \\33[2J 清屏 \\33[K 清除从光标到行尾的内容 \\33[s 保存光标位置 \\33[u 恢复光标位置 \\33[?25l 隐藏光标 \\33[?25h 显示光标 6. sprintf#!/bin/bash printf "%-10s %-8s %-4s\\n" 姓名 性别 体重kg printf "%-10s %-8s %-4.2f\\n" 郭靖 男 66.1234 printf "%-10s %-8s %-4.2f\\n" 杨过 男 48.6543 printf "%-10s %-8s %-4.2f\\n" 郭芙 女 47.9876 结果: 姓名 性别 体重kg 郭靖 男 66.12 杨过 男 48.65 郭芙 女 47.99 %s %c %d %f 都是格式替代符d: Decimal 十进制整数 – 对应位置参数必须是十进制整数,否则报错!s: String 字符串 – 对应位置参数必须是字符串或者字符型,否则报错!c: Char 字符 – 对应位置参数必须是字符串或者字符型,否则报错!f: Float 浮点 – 对应位置参数必须是数字型,否则报错!%-10s 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。%-4.2f 指格式化为小数,其中.2指保留2位小数。 7. testShell中的 test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。 #!/bin/bash num1=100 num2=100 if test $[num1] -eq $[num2] 8. 流程控制① if-else#!/bin/bash a=10 b=20 if [ $a == $b ] then echo "a 等于 b" elif [ $a -gt $b ] then echo "a 大于 b" elif [ $a -lt $b ] then echo "a 小于 b" else echo "没有符合的条件" fi ② for#!/bin/bash for loop in 1 2 3 4 5 do echo "The value is: $loop" done ③ while#!/bin/bash int=1 while(( $int<=5 )) do echo $int let "int++" done ④ case#!/bin/bash echo '输入 1 到 4 之间的数字:' echo '你输入的数字为:' read aNum case $aNum in 1) echo '你选择了 1' ;; 2) echo '你选择了 2' ;; 3) echo '你选择了 3' ;; 4) echo '你选择了 4' ;; *) echo '你没有输入 1 到 4 之间的数字' ;; esac ⑤ breakbreak命令允许跳出所有循环(终止执行后面的所有循环)。 #!/bin/bash while : do echo -n "输入 1 到 5 之间的数字:" read aNum case $aNum in 1|2|3|4|5) echo "你输入的数字为 $aNum!" ;; *) echo "你输入的数字不是 1 到 5 之间的! 游戏结束" break ;; esac done ⑥ continue跳出当前循环。 #!/bin/bash while : do echo -n "输入 1 到 5 之间的数字: " read aNum case $aNum in 1|2|3|4|5) echo "你输入的数字为 $aNum!" ;; *) echo "你输入的数字不是 1 到 5 之间的!" continue echo "游戏结束" ;; esac done ⑦ until#!/bin/bash a=0 until [ ! $a -lt 10 ] do echo $a a=`expr $a + 1` done 9. 函数#!/bin/bash funWithParam(){ echo "第一个参数为 $1 !" echo "第二个参数为 $2 !" echo "第十个参数为 $10 !" echo "第十个参数为 ${10} !" echo "第十一个参数为 ${11} !" echo "参数总数有 $# 个!" echo "作为一个字符串输出所有参数 $* !" } funWithParam 1 2 3 4 5 6 7 8 9 34 73 结果: 第一个参数为 1 ! 第二个参数为 2 ! 第十个参数为 10 ! 第十个参数为 34 ! 第十一个参数为 73 ! 参数总数有 11 个! 作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 ! 10. 输入输出#!/bin/bash who > today.log # 执行结果覆盖到文件 today.log echo "菜鸟教程:www.runoob.com" >> today.log # 执行结果追加到文件 today.log wc -l < today.log # 统计 today.log 行数 wc -l << EOF 李白 苏轼 王勃 EOF 11. 文件包含test1.sh #!/bin/bash name="Chris" test2.sh #!/bin/bash #使用 . 号来引用test1.sh 文件 . ./test1.sh # 或者使用以下包含文件代码 # source ./test1.sh echo $name 注:被包含的文件 test1.sh 不需要可执行权限。 reference:[1] http://www.runoob.com/linux/linux-shell.html","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://yelog.org/tags/linux/"},{"name":"shell","slug":"shell","permalink":"http://yelog.org/tags/shell/"}]},{"title":"nginx配置记录","slug":"nginx配置记录","date":"2018-02-08T01:19:09.000Z","updated":"2024-07-05T11:10:22.281Z","comments":true,"path":"2018/02/08/nginx-config/","permalink":"http://yelog.org/2018/02/08/nginx-config/","excerpt":"","text":"启用https1.购买免费证书登录阿里云 -> 控制台 -> 安全(云盾) -> CA证书服务 -> 购买证书 2.补全证书信息点击补全,绑定域名 3.下载并配置选择下载 证书for nginx 上面这个页面有相关的配置信息,下面简单介绍: ① 将下载文件中的 *.pem、*.key, 拷贝到 nginx 目录下 的 cert , 当然也可以是其他目录② 修改 nginx.conf server { listen 443 ssl; server_name xiangzhangshugongyi.com; ssl_certificate cert/214487958220243.pem; ssl_certificate_key cert/214487958220243.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Proto https; proxy_redirect off; proxy_connect_timeout 240; proxy_send_timeout 240; proxy_read_timeout 240; # note, there is not SSL here! plain HTTP is used proxy_pass http://127.0.0.1:8080; } } ③ 重启 nginx,通过 证书绑定域名进行 https 访问到 服务器跑在 8080 的服务","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"nginx","slug":"nginx","permalink":"http://yelog.org/tags/nginx/"}]},{"title":"Mac神器-BTT(BetterTouchTool)不完全教程","slug":"Mac-BetterTouchTool","date":"2017-12-13T09:04:25.000Z","updated":"2024-07-05T11:10:22.324Z","comments":true,"path":"2017/12/13/Mac-BetterTouchTool/","permalink":"http://yelog.org/2017/12/13/Mac-BetterTouchTool/","excerpt":"","text":"介绍BetterTouchTool 是一款专为Mac用户开发的 窗口管理/Trackpad(触控板)/Magic Mouse(苹果鼠标)/Keyboard(键盘)/TouchBar 功能增强制作的软件。 这款软件不但可以设置全局的 手势/快捷键/TouchBar ,还可以给不同的应用定义不同的姿势,再配合上 Alfred 的 workflow,简直各种高难度姿势都能玩的出来。 本文主要介绍以下功能: 窗口管理 帮 Trackpad 定义各种姿势 帮 Magic Mouse 定义各种姿势 帮 Keyboard 定义各种姿势 帮任何应用自定义 TouchBar 本文以 macbook pro 2017 touchbar 版为例 1. 窗口管理这个功能无需过多配置,默认配置即可很好使用(和windows的理念相似) 将窗口移到左右边缘,最大化至半屏 将窗口移到上边缘,最大化至全屏 如果对默认配置不满意,也可以在如下图所示的位置来调整窗口展示: 2. 帮 Trackpad 定义各种姿势姿势选择在界面选择 Trackpad(触摸板) -> Add New Gesture(添加一个新姿势) 左边可以选择生效的范围:全局或者某个应用 如上图所示,姿势包括但不限于如: 单指:左下角单击、单指轻拍右上角、单指轻拍上边中点 双指:两个手指捏、张开两指以两指中心为圆轴逆时针、中指拍住中央食指轻拍面板、双指从上边缘下滑 三指:三指轻拍、三指拍顶端、三指点击并向上滑、两指轻拍住,拍左、右二指固定拍住,左一下滑 四指:四指双轻拍、中指无名小拍住,食单击、食中指无名拍住,小单击 五指:五手指轻拍、五手指上滑 上面只是列一些典型,更多姿势可以在上图中浏览。 绑定功能 选择过姿势之后,也可以选择在按住某个功能键的时候才能使用(左下角)。 右边是绑定功能:快捷键或动作。 绑定快捷键举例:比如 给chrome 设置 姿势(两指从触控板下边缘滑入),弹出开发者模式(快捷键绑定:command+option+i),如下图: 绑定动作举例:设置 在任何应用内,五指下滑 锁屏,如下图 3. 帮 Magic Mouse 定义各种姿势这个功能设置和 Trackpad 设置 大同小异,所以这边就不多讲,直接图示几个功能。 我快捷键设置了 option+E 鼠标取词翻译(欧陆词典),然后绑定到双指轻拍鼠标,即可触发翻译。 4. 帮 Keyboard 定义各种姿势这个功能比较简单,设置一些 键盘快捷键或录制案件序列 来触发 一些动作或者其他快捷键功能。 5. 帮任何应用自定义 TouchBar这个重磅功能,可以帮助不支持touchbar的软件定制 TouchBar,是不是有点厉害。 下面就以我给 IntelliJ IDEA 定制 TouchBar 为例 (没有F1 ~ F12 功能键,debug真的很痛苦,这个软件真的是雪中送炭),展示一下使用效果 如上图所示,我给 IntelliJ IDEA 添加了 四个功能 step over/step into/resume/evaluate 添加完之后,切到 IntelliJ IDEA 软件中时,TouchBar 就显示我们添加的四个功能键, 如下图所示 最后BTT还有其他很方便的功能,这盘就介绍到这里,等之后更新了 Alfred 的 workflow 开发指南之后,再一起更新一篇有意思的 BTT+Alfred 效率流。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"软件记录","slug":"工具/软件记录","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95/"}],"tags":[{"name":"mac","slug":"mac","permalink":"http://yelog.org/tags/mac/"},{"name":"efficiency","slug":"efficiency","permalink":"http://yelog.org/tags/efficiency/"}]},{"title":"[转]谈谈Java中的语法糖","slug":"转-谈谈Java中的语法糖","date":"2017-11-27T14:51:45.000Z","updated":"2024-07-05T11:10:22.636Z","comments":true,"path":"2017/11/27/java-grammatical-sugar/","permalink":"http://yelog.org/2017/11/27/java-grammatical-sugar/","excerpt":"","text":"语法糖(Syntactic Sugar),也称糖衣语法,指在计算机语言中添加的某种语法,这种语法对语言本身功能来说没有什么影响,只是为了方便程序员的开发,提高开发效率。说白了,语法糖就是对现有语法的一个封装。 Java作为一种与平台无关的高级语言,当然也含有语法糖,这些语法糖并不被虚拟机所支持,在编译成字节码阶段就自动转换成简单常用语法。一般来说Java中的语法糖主要有以下几种: 泛型与类型擦除 自动装箱与拆箱,变长参数、 增强for循环 内部类与枚举类 泛型与类型擦除Java语言并不是一开始就支持泛型的。在早期的JDK中,只能通过Object类是所有类型的父类和强制类型转换来实现泛型的功能。强制类型转换的缺点就是把编译期间的问题延迟到运行时,JVM并不能为我们提供编译期间的检查。 在JDK1.5中,Java语言引入了泛型机制。但是这种泛型机制是通过类型擦除来实现的,即Java中的泛型只在程序源代码中有效(源代码阶段提供类型检查),在编译后的字节码中自动用强制类型转换进行替代。也就是说,Java语言中的泛型机制其实就是一颗语法糖,相较与C++、C#相比,其泛型实现实在是不那么优雅。 /** * 在源代码中存在泛型 */ public static void main(String[] args) { Map<String,String> map = new HashMap<String,String>(); map.put("hello","你好"); String hello = map.get("hello"); System.out.println(hello); } 当上述源代码被编译为class文件后,泛型被擦除且引入强制类型转换 public static void main(String[] args) { HashMap map = new HashMap(); //类型擦除 map.put("hello", "你好"); String hello = (String)map.get("hello");//强制转换 System.out.println(hello); } 自动装箱与拆箱 Java中的自动装箱与拆箱指的是基本数据类型与他们的包装类型之间的相互转换。 我们知道Java是一门面向对象的语言,在Java世界中有一句话是这么说的:“万物皆对象”。但是Java中的基本数据类型却不是对象,他们不需要进行new操作,也不能调用任何方法,这在使用的时候有诸多不便。因此Java为这些基本类型提供了包装类,并且为了使用方便,提供了自动装箱与拆箱功能。自动装箱与拆箱在使用的过程中,其实是一个语法糖,内部还是调用了相应的函数进行转换。 下面代码演示了自动装箱和拆箱功能 public static void main(String[] args) { Integer a = 1; int b = 2; int c = a + b; System.out.println(c); } 经过编译后,代码如下 public static void main(String[] args) { Integer a = Integer.valueOf(1); // 自动装箱 byte b = 2; int c = a.intValue() + b;//自动拆箱 System.out.println(c); } 变长参数 所谓变长参数,就是方法可以接受长度不定确定的参数 变长参数特性是在JDK1.5中引入的,使用变长参数有两个条件,一是变长的那一部分参数具有相同的类型,二是变长参数必须位于方法参数列表的最后面。变长参数同样是Java中的语法糖,其内部实现是Java数组。 public class Varargs { public static void print(String... args) { for(String str : args){ System.out.println(str); } } public static void main(String[] args) { print("hello", "world"); } } 编译为class文件后如下,从中可以很明显的看出变长参数内部是通过数组实现的 public class Varargs { public Varargs() { } public static void print(String... args) { String[] var1 = args; int var2 = args.length; //增强for循环的数组实现方式 for(int var3 = 0; var3 < var2; ++var3) { String str = var1[var3]; System.out.println(str); } } public static void main(String[] args) { //变长参数转换为数组 print(new String[]{"hello", "world"}); } } 增强for循环 增强for循环与普通for循环相比,功能更强并且代码更简洁 增强for循环的对象要么是一个数组,要么实现了Iterable接口。这个语法糖主要用来对数组或者集合进行遍历,其在循环过程中不能改变集合的大小。 public static void main(String[] args) { String[] params = new String[]{"hello","world"}; //增强for循环对象为数组 for(String str : params){ System.out.println(str); } List<String> lists = Arrays.asList("hello","world"); //增强for循环对象实现Iterable接口 for(String str : lists){ System.out.println(str); } } 编译后的class文件为 public static void main(String[] args) { String[] params = new String[]{"hello", "world"}; String[] lists = params; int var3 = params.length; //数组形式的增强for退化为普通for for(int str = 0; str < var3; ++str) { String str1 = lists[str]; System.out.println(str1); } List var6 = Arrays.asList(new String[]{"hello", "world"}); Iterator var7 = var6.iterator(); //实现Iterable接口的增强for使用iterator接口进行遍历 while(var7.hasNext()) { String var8 = (String)var7.next(); System.out.println(var8); } } 内部类 内部类就是定义在一个类内部的类 Java语言中之所以引入内部类,是因为有些时候一个类只在另一个类中有用,我们不想让其在另外一个地方被使用。内部类之所以是语法糖,是因为其只是一个编译时的概念,一旦编译完成,编译器就会为内部类生成一个单独的class文件,名为outer$innter.class。 public class Outer { class Inner{ } } 使用javac编译后,生成两个class文件Outer.class和Outer$Inner.class,其中Outer$Inner.class的内容如下: class Outer$Inner { Outer$Inner(Outer var1) { this.this$0 = var1; } } 内部类分为四种:成员内部类、局部内部类、匿名内部类、静态内部类,每一种都有其用法,这里就不介绍了 枚举类型 枚举类型就是一些具有相同特性的类常量 java中类的定义使用class,枚举类的定义使用enum。在Java的字节码结构中,其实并没有枚举类型,枚举类型只是一个语法糖,在编译完成后被编译成一个普通的类。这个类继承java.lang.Enum,并被final关键字修饰。 public enum Fruit { APPLE,ORINGE } 使用jad对编译后的class文件进行反编译后得到 //继承java.lang.Enum并声明为final public final class Fruit extends Enum { public static Fruit[] values() { return (Fruit[])$VALUES.clone(); } public static Fruit valueOf(String s) { return (Fruit)Enum.valueOf(Fruit, s); } private Fruit(String s, int i) { super(s, i); } //枚举类型常量 public static final Fruit APPLE; public static final Fruit ORANGE; private static final Fruit $VALUES[];//使用数组进行维护 static { APPLE = new Fruit("APPLE", 0); ORANGE = new Fruit("ORANGE", 1); $VALUES = (new Fruit[] { APPLE, ORANGE }); } }","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"}]},{"title":"PostgreSQL事务及隔离级别","slug":"PostgreSQL事务及隔离级别","date":"2017-11-08T16:07:33.000Z","updated":"2024-07-05T11:10:22.297Z","comments":true,"path":"2017/11/09/PostgreSQL事物及隔离级别/","permalink":"http://yelog.org/2017/11/09/PostgreSQL%E4%BA%8B%E7%89%A9%E5%8F%8A%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB/","excerpt":"","text":"介绍PostgreSQL中提供了多种数据完整性的保证机制。如:约束、触发器、事务和锁管理等。 事务主要是为了保证一组相关数据库的操作能全部执行成功,从而保证数据的完整性。锁机制主要是控制多个用户对同一数据进行操作,使用锁机制可以解决并发问题。 事务事务是用户对一个数据库操作的一个序列,这些操作要么全做,要么全不做,是一个不可分割的单位。 事务管理的常用语句如下: BEGIN; SQL语句1; SQL语句2; ... COMMIT; 事务块是指包围在BEGIN和COMMIT之间的语句。在PostgreSQL9中,常用的事务块管理语句含义如下: START TRANSACTION:此命令表示开始一个新的事务块.BEGIN:初始化一个事务块。在BEGIN命令后的语句都将在一个事务里面执行,知道遇见COMMIT或ROLLBACK。它和START TRANSACTION是一样的。COMMIT:提交事务。ROLLBACK:事务失败时执行回滚操作。SET TRANSACTION:设置当前事务的特性。对后面的事务没有影响。 事务隔离及并发控制PostgreSQL是一个支持多用户的数据库,当多个用户操作同一数据库时,并发控制要保证所有用户可以高效的访问的同时不破坏数据的完整性。 数据库中数据的并发操作经常发生,而对数据的并发操作会带来下面的一些问题: 脏读一个事务读取了另一个未提交事务写入的数据。 不可重复读一个事务重新读取前面读取过的数据,发现该数据已经被另一个已经提交的事务修改。 幻读一个事务重新执行一个查询,返回符合查询条件的行的集合,发现满足查询条件的行的集合因为其它最近提交的事务而发生了改变。 SQL标准定义了四个级别的事务隔离。 | 隔离级别 | 脏读 | 幻读 | 不可重复性读取 || :- | :- ||读未提交 |可能 |可能 |可能||读已提交 |不可能| 可能 |可能||可重复读 |不可能 |可能 |不可能||可串行读 |不可能 |不可能 |不可能| 在PostgreSQL中,可以请求4种隔离级别中的任意一种。但是在内部,实际上只有两种独立的隔离级别,分别对应已提交和可串行化。如果选择了读未提交的级别,实际上使用的是读已提交,在选择可重复读级别的时候,实际上用的是可串行化,所以实际的隔离级别可能比选择的更严格。这是SQL标准允许的:4种隔离级别只定义了哪种现象不能发生,但是没有定义哪种现象一定发生。 PostgreSQL只提供两种隔离级别的原因是,这是把标准的隔离级别与多版本并发控制架构映射相关的唯一合理方法。 读已提交这是PostgreSQL中默认的隔离级别,当一个事务运行在这个隔离级别时,一个SELECT查询只能看到查询开始前已提交的数据,而无法看到未提交的数据或者在查询期间其他的事务已提交的数据。 可串行化可串行化提供最严格的事务隔离。这个级别模拟串行的事务执行,就好像事务是一个接着一个串行的执行。不过,这个级别的应用必须准备在串行化失败的时候重新启动事务。","categories":[{"name":"数据库","slug":"数据库","permalink":"http://yelog.org/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"}],"tags":[{"name":"PostgreSQL","slug":"PostgreSQL","permalink":"http://yelog.org/tags/PostgreSQL/"}]},{"title":"linux下修改按键ESC<=>CAPSLOCK和Control=>ALT_R","slug":"linux下修改按键ESC-CAPSLOCK和Control-ALT-R","date":"2017-10-20T09:38:49.000Z","updated":"2024-07-05T11:10:23.056Z","comments":true,"path":"2017/10/20/linux下修改按键ESC<=>CAPSLOCK和Control=>ALT_R/","permalink":"http://yelog.org/2017/10/20/linux%E4%B8%8B%E4%BF%AE%E6%94%B9%E6%8C%89%E9%94%AEESC%3C=%3ECAPSLOCK%E5%92%8CControl=%3EALT_R/","excerpt":"","text":"使用 vim 过程中发现 esc 和 ctrl 按键很难按,小拇指没有那么长啊~~,而 caps_lock 和 alt_r(右alt) 很少用。 本教程将 esc 和 caps_lock 两个按键交换, alt_r(右alt) 改为 ctrl。 一、 esc 与 caps_lock 按键交换①. 创建 .xmodmaprc 文件。②. 加入以下内容: remove Lock = Caps_Lock add Lock = Escape keysym Caps_Lock = Escape keysym Escape = Caps_Lock ③. 执行 xmodmap .xmodmaprc 使之生效。 二、 将 右alt 改为 ctrl①. 查看需要修改键位的 keysym通过 xev | grep keycode 获取右 alt 的 keysym 为 Alt_R。如下图所示: ②. 查看 Alt_R 是哪个 modifier 使用的通过 xmodmap -pm 查看,发现 Alt_R 是作为 modifier mod1 使用的。如下图所示: ③. 修改 modifier xmodmap -e 'remove mod1 = Alt_R' # 解除原来绑定 xmodmap -e 'add control = Alt_R' # 作为 control 使用","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://yelog.org/tags/linux/"},{"name":"vim","slug":"vim","permalink":"http://yelog.org/tags/vim/"},{"name":"keybord","slug":"keybord","permalink":"http://yelog.org/tags/keybord/"},{"name":"emacs","slug":"emacs","permalink":"http://yelog.org/tags/emacs/"}]},{"title":"[转]字符编解码的故事(ASCII,ANSI,Unicode,Utf-8区别)","slug":"转-字符编解码的故事(ASCII,ANSI,Unicode,Utf-8区别)","date":"2017-09-25T11:15:00.000Z","updated":"2024-07-05T11:10:23.096Z","comments":true,"path":"2017/09/25/ascii-ansi-unicode-utf-8/","permalink":"http://yelog.org/2017/09/25/ascii-ansi-unicode-utf-8/","excerpt":"","text":"很久很久以前,有一群人,他们决定用8个可以开合的晶体管来组合成不同的状态,以表示世界上的万物。他们认为8个开关状态作为原子单位很好,于是他们把这称为”字节”。 再后来,他们又做了一些可以处理这些字节的机器,机器开动了,可以用字节来组合出更多的状态,状态开始变来变去。他们看到这样是好的,于是它们就这机器称为”计算机”。 开始计算机只在美国用。八位的字节一共可以组合出256(2的8次方)种不同的状态。 他们把其中的编号从0开始的32种状态分别规定了特殊的用途,一但终端设备或者打印机遇上这些约定好的字节时,就要做一些约定的动作。遇上 00x10, 终端就换行,遇上0x07, 终端就向人们嘟嘟叫,例好遇上0x1b, 打印机就打印反白的字,对于终端就用彩色显示字母。他们看到这样很好,于是就把这些0x20(十进制32)以下的字节状态称为”控制码”。 他们又把所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第127号,这样计算机就可以用不同字节来存储英语的 文字了。大家看到这样,都感觉很好,于是大家都把这个方案叫做 ANSI 的”Ascii”编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都用同样的ASCII方案来保存英文文字。 后来,就像建造巴比伦塔一样,世界各地的都开始使用计算机,但是很多国家用的不是英文,他们用到的许多字母在ASCII中根本没有,为了也可以在计算机中保存他们的文字,他们决定采用127号之后的空位来表示这些新的字母、符号,还加入了很多画表格时需要用下到的横线、竖线、交叉等形状,一直把序号编到了最后一个状态255。从128到255这一页的字符集被称”扩展字符集”。从此之后,贪婪的人类再没有新的状态可以用了,美帝国主义可能没有想到还有第三世界国家的人们也希望可以用到计算机吧! 等中国人们得到计算机时,已经没有可以利用的字节状态来表示汉字,况且有6000多个常用汉字需要保存呢。但是这难不倒智慧的中国人民,我们不客气地把那些127号之后的奇异符号们直接取消掉,并且规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。 中国人民看到这样很不错,于是就把这种汉字方案叫做”GB2312”。GB2312 是对 ASCII 的中文扩展。 但是中国的汉字太多了,我们很快就就发现有许多人的人名没有办法在这里打出来,特别是某些很会麻烦别人的国家领导人(如朱镕基的“镕”字)。于是我们不得不继续把 GB2312 没有用到的码位找出来老实不客气地用上。 后来还是不够用,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为 GBK 标准,GBK 包括了 GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。 后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK 扩成了 GB18030。从此之后,中华民族的文化就可以在计算机时代中传承了。 中国的程序员们看到这一系列汉字编码的标准是好的,于是通称他们叫做 “DBCS”(Double Byte Charecter Set 双字节字符集)。在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。那时候凡是受过加持,会编程的计算机僧侣们都要每天念下面这个咒语数百遍: “一个汉字算两个英文字符!一个汉字算两个英文字符……” 因为当时各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码,连大陆和台湾这样只相隔了150海里,使用着同一种语言的兄弟地区,也分别采用了不同的 DBCS 编码方案——当时的中国人想让电脑显示汉字,就必须装上一个”汉字系统”,专门用来处理汉字的显示、输入的问题,但是那个台湾的愚昧封建人士写的算命程序就必须加装另一套支持 BIG5 编码的什么”倚天汉字系统”才可以用,装错了字符系统,显示就会乱了套!这怎么办?而且世界民族之林中还有那些一时用不上电脑的穷苦人民,他们的文字又怎么办? 真是计算机的巴比伦塔命题啊! 正在这时,大天使加百列及时出现了——一个叫 ISO (国际标谁化组织)的国际组织决定着手解决这个问题。他们采用的方法很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号的编码!他们打算叫它”Universal Multiple-Octet Coded Character Set”,简称 UCS, 俗称 “UNICODE”。 UNICODE 开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。于是 ISO 就直接规定必须用两个字节,也就是16位来统一表示所有的字符,对于ascii里的那些”半角”字符,UNICODE 包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于”半角”英文符号只需要用到低8位,所以其高 8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。 这时候,从旧社会里走过来的程序员开始发现一个奇怪的现象:他们的strlen函数靠不住了,一个汉字不再是相当于两个字符了,而是一个!是 的,从 UNICODE 开始,无论是半角的英文字母,还是全角的汉字,它们都是统一的”一个字符”!同时,也都是统一的”两个字节”,请注意”字符”和”字节”两个术语的不同, “字节”是一个8位的物理存贮单元,而”字符”则是一个文化相关的符号。在UNICODE 中,一个字符就是两个字节。一个汉字算两个英文字符的时代已经快过去了。 从前多种字符集存在时,那些做多语言软件的公司遇上过很大麻烦,他们为了在不同的国家销售同一套软件,就不得不在区域化软件时也加持那个双字节字符集咒语,不仅要处处小心不要搞错,还要把软件中的文字在不同的字符集中转来转去。UNICODE 对于他们来说是一个很好的一揽子解决方案,于是从 Windows NT 开始,MS 趁机把它们的操作系统改了一遍,把所有的核心代码都改成了用 UNICODE 方式工作的版本,从这时开始,WINDOWS 系统终于无需要加装各种本土语言系统,就可以显示全世界上所有文化的字符了。 但是,UNICODE 在制订时没有考虑与任何一种现有的编码方案保持兼容,这使得 GBK 与UNICODE 在汉字的内码编排上完全是不一样的,没有一种简单的算术方法可以把文本内容从UNICODE编码和另一种编码进行转换,这种转换必须通过查表来进行。 如前所述,UNICODE 是用两个字节来表示为一个字符,他总共可以组合出65535不同的字符,这大概已经可以覆盖世界上所有文化的符号。如果还不够也没有关系,ISO已经准备了UCS-4方案,说简单了就是四个字节来表示一个字符,这样我们就可以组合出21亿个不同的字符出来(最高位有其他用途),这大概可以用到银河联邦成立那一天吧! UNICODE 来到时,一起到来的还有计算机网络的兴起,UNICODE 如何在网络上传输也是一个必须考虑的问题,于是面向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF8就是每次8个位传输数据,而UTF16就是每次16个位,只不过为了传输时的可靠性,从UNICODE到 UTF时并不是直接的对应,而是要过一些算法和规则来转换。 受到过网络编程加持的计算机僧侣们都知道,在网络里传递信息时有一个很重要的问题,就是对于数据高低位的解读方式,一些计算机是采用低位先发送的方法,例如我们PC机采用的 INTEL 架构;而另一些是采用高位先发送的方式。在网络中交换数据时,为了核对双方对于高低位的认识是否是一致的,采用了一种很简便的方法,就是在文本流的开始时向对方发送一个标志符——如果之后的文本是高位在位,那就发送”FEFF”,反之,则发送”FFFE”。不信你可以用二进制方式打开一个UTF-X格式的文件,看看开头两个字节是不是这两个字节? 下面是Unicode和UTF-8转换的规则 Unicode UTF-8 0000 - 007F 0xxxxxxx 0080 - 07FF 110xxxxx 10xxxxxx 0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx 例如”汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以要用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 1100 0100 1001,将这个比特流按三字节模板的分段方法分为0110 110001 001001,依次代替模板中的x,得到:1110-0110 10-110001 10-001001,即E6 B1 89,这就是其UTF8的编码。 讲到这里,我们再顺便说说一个很著名的奇怪现象:当你在 windows 的记事本里新建一个文件,输入”联通”两个字之后,保存,关闭,然后再次打开,你会发现这两个字已经消失了,代之的是几个乱码!呵呵,有人说这就是联通之所以拼不过移动的原因。 其实这是因为GB2312编码与UTF8编码产生了编码冲撞的原因。 当一个软件打开一个文本时,它要做的第一件事是决定这个文本究竟是使用哪种字符集的哪种编码保存的。软件一般采用三种方式来决定文本的字符集和编码: 检测文件头标识,提示用户选择,根据一定的规则猜测 最标准的途径是检测文本最开头的几个字节,开头字节 Charset/encoding,如下表: EF BB BF UTF-8 FF FE UTF-16/UCS-2, little endian FE FF UTF-16/UCS-2, big endian FF FE 00 00 UTF-32/UCS-4, little endian. 00 00 FE FF UTF-32/UCS-4, big-endian. 当你新建一个文本文件时,记事本的编码默认是ANSI(代表系统默认编码,在中文系统中一般是GB系列编码), 如果你在ANSI的编码输入汉字,那么他实际就是GB系列的编码方式,在这种编码下,”联通”的内码是: c1 1100 0001 aa 1010 1010 cd 1100 1101 a8 1010 1000 注意到了吗?第一二个字节、第三四个字节的起始部分的都是”110”和”10”,正好与UTF8规则里的两字节模板是一致的, 于是当我们再次打开记事本时,记事本就误认为这是一个UTF8编码的文件,让我们把第一个字节的110和第二个字节的10去掉,我们就得到了”00001 101010”,再把各位对齐,补上前导的0,就得到了”0000 0000 0110 1010”,不好意思,这是UNICODE的006A,也就是小写的字母”j”,而之后的两字节用UTF8解码之后是0368,这个字符什么也不是。这就是只有”联通”两个字的文件没有办法在记事本里正常显示的原因。 而如果你在”联通”之后多输入几个字,其他的字的编码不见得又恰好是110和10开始的字节,这样再次打开时,记事本就不会坚持这是一个utf8编码的文件,而会用ANSI的方式解读之,这时乱码又不出现了。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"encoding","slug":"encoding","permalink":"http://yelog.org/tags/encoding/"}]},{"title":"搭建dubbo+zookeeper平台","slug":"搭建dubbo-zookeeper平台","date":"2017-09-25T08:29:07.000Z","updated":"2024-07-05T11:10:22.689Z","comments":true,"path":"2017/09/25/搭建dubbo+zookeeper平台/","permalink":"http://yelog.org/2017/09/25/%E6%90%AD%E5%BB%BAdubbo+zookeeper%E5%B9%B3%E5%8F%B0/","excerpt":"","text":"前言本文将介绍在SpringMVC+Spring+Mybatis项目中添加 dubbo 作为 rpc 服务。 文末有项目代码地址。 一.搭建zookeeper使用 docker 一句话创建: docker run -dit --name zookeeper --hostname zookeeper-host -v /data:/data -p 2181:2181 jplock/zookeeper:latest 二.安装zkui(非必须)这个项目为 zookeeper 提供一个 web 的管理界面。当然我们也可以直接在zookeeper中使用命令查看,所以此步骤可以忽略 在开始前需要安装 Java 环境、Maven 环境。 到 zkui 的项目中下载代码。 git clone https://github.com/DeemOpen/zkui.git 执行 mvn clean install 生成jar文件。 将config.cfg复制到上一步生成的jar文件所在目录,然后修改配置文件中的zookeeper地址。 执行 nohup java -jar zkui-2.0-SNAPSHOT-jar-with-dependencies.jar & 测试 http://localhost:9090,如果能看到如下页面,表示安装成功。 三.使用dubbo 在原来 SpringMVC+Spring+Mybatis 项目中,除了原来 spring 相关依赖外,还需要加入以下依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.5.5</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.9</version> </dependency> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.2</version> </dependency> 定义服务接口 public interface IPersonService { List<Person> listAll(); Person getById(Integer id); Integer delById(Person person); Integer updatePerson(Person person); } 定义服务实现类 @Service public class PersonService implements IPersonService { @Autowired PersonMapper personMapper; public List<Person> listAll() { return personMapper.findAll(); } public Person getById(Integer id) { return personMapper.findOneById(id); } public Integer delById(Person person) { return personMapper.del(person); } public Integer updatePerson(Person person) { return personMapper.update(person); } } 配置生产者,注册服务信息 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!--定义了提供方应用信息,用于计算依赖关系;--> <dubbo:application name="demotest-provider" /> <!-- 使用 zookeeper 注册中心暴露服务地址 --> <dubbo:registry address="zookeeper://192.168.0.86:2181"/> <!-- 用dubbo协议在20880端口暴露服务 --> <dubbo:protocol name="dubbo" port="20880"/> <!-- 和本地bean一样实现服务 --> <bean id="personService" class="com.ssm.service.PersonService"/> <!-- 声明需要暴露的服务接口 --> <dubbo:service interface="com.ssm.iservice.IPersonService" ref="personService"/> </beans> 配置消费者,订阅服务 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 --> <dubbo:application name="demo-consumer"/> <!-- 使用 zookeeper 注册中心暴露发现服务地址 --> <dubbo:registry address="zookeeper://192.168.0.86:2181"/> <!-- 生成远程服务代理,可以和本地bean一样使用demoService --> <dubbo:reference id="personService" check="false" interface="com.ssm.iservice.IPersonService"/> </beans> 调用远程服务配置完成后,我们就可以像使用本地 bean 一样,使用 rpc 的 service; @Controller public class IndexController { @Autowired IPersonService personService; @RequestMapping("/index.html") public String index(Model model) { RpcContext.getContext().setAttachment("index", "1");//测试ThreadLocal List<Person> list = personService.listAll(); model.addAttribute("command",list); return "index"; } } 最后至此,单机运行的 rpc 服务已搭建完成。 代码传送文 ssm","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"dubbo","slug":"dubbo","permalink":"http://yelog.org/tags/dubbo/"},{"name":"zookeeper","slug":"zookeeper","permalink":"http://yelog.org/tags/zookeeper/"}]},{"title":"docker报错集锦","slug":"docker报错集锦","date":"2017-09-25T02:03:50.000Z","updated":"2024-07-05T11:10:22.218Z","comments":true,"path":"2017/09/25/docker-errors/","permalink":"http://yelog.org/2017/09/25/docker-errors/","excerpt":"","text":"docker创建容器1. iptables failed创建 tale 容器时,如下命令: docker run -d --privileged --hostname tale --name tale \\ -v /etc/localtime:/etc/localtime:ro \\ -v /home/tale:/var/tale_home -p 127.0.0.1:234:9000 \\ -m 1024m --memory-swap -1 tale:1.0 然后就报了以下错误: docker: Error response from daemon: driver failed programming external connectivity on endpoint tale (263775ff559176224428ec44dcec416a1c20e6c69198d9760b38f35849914260): iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 127.0.0.1 --dport 234 -j DNAT --to-destination 172.17.0.4:9000 ! -i docker0: iptables: No chain/target/match by that name. (exit status 1). 解决办法:重启 docker 服务: $ service docker restart","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"Hexo加速渲染速度之fragment_cache","slug":"hexo-fragment_cache","date":"2017-09-21T11:34:37.000Z","updated":"2024-07-05T11:10:22.507Z","comments":true,"path":"2017/09/21/hexo-fragment_cache/","permalink":"http://yelog.org/2017/09/21/hexo-fragment_cache/","excerpt":"","text":"前文从开发 3-hexo 主题到现在已过去 9 个月时间了,累计在博客中写 132 篇文章了。 现在发现了严重的问题,hexo generate 渲染的速度越来越慢,现在132篇左右,每次渲染时间到达了 50+ s,相当不爽。 今日抽时间,查看了官方api,看到了 fragment_cache 局部缓存这个东西,解决了渲染速度的问题。 使用官方文档局部缓存。它储存局部内容,下次使用时就能直接使用缓存。 <%- fragment_cache(id, fn); %> 替换简单文本区域a. 我们可以将所有页面都一样的区域,如下所示,缓存下来。当下一篇文章在渲染到这个位置时,将不再渲染,直接拿缓存数据。 <%- fragment_cache('header', function(){ return partial('<head></head>'); }) %> b. 文章模块也可以使用,原来公共引用部分(没有和当前文章耦合的内容)使用下面的方式: <%- partial('_partial/header'); %> 改进为以下代码: <%- fragment_cache('header', function(){ return partial('_partial/header'); }) %> 最后这个语法只适用于所有页面都相同,不随文章内容变化的部分。 作者在 3-hexo 中加入了此语法,渲染132篇文章的速度已从 50+s 到现在 3s 左右了。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"},{"name":"fragment_cache","slug":"fragment-cache","permalink":"http://yelog.org/tags/fragment-cache/"}]},{"title":"[转]浏览器前进/后退缓存(BF Cache)","slug":"bf-cache","date":"2017-09-21T07:35:24.000Z","updated":"2024-07-05T11:10:22.424Z","comments":true,"path":"2017/09/21/bf-cache/","permalink":"http://yelog.org/2017/09/21/bf-cache/","excerpt":"","text":"[浏览器前进/后退缓存](https://developer.mozilla.org/en-US/docs/Working_with_BFCache)(Backward/Forward Cache,BF Cache)是指浏览器在前进后退过程中, 会应用更强的缓存策略,表现为 DOM、window、甚至 JavaScript 对象被缓存,以及同步 XHR 也被缓存。 这一现象在移动端浏览器尤为常见,除 Chrome for Android、Android Browser 之外的浏览器基本都会触发。 BF Cache 本来是一项浏览器优化,但在某些情况下(比如前端路由的 Web App)会引起困惑。 本文主要讨论 BF Cache 的行为、如何检测 BF Cache 缓存、以及如何 workaround。 缓存行为BF Cache 是一种浏览器优化,HTML 标准并未指定其如何进行缓存,因此缓存行为是与浏览器实现相关的。 User agents may discard the Document objects of entries other than the current entry that are not referenced from any script, reloading the pages afresh when the user or script navigates back to such pages. This specification does not specify when user agents should discard Document objects and when they should cache them. – Session history and navigation, WHATWG Desktop Chrome:阻塞的资源和同步发出的 XHR 都会被缓存,但不缓存渲染结果。因此可以看到明显的载入过程,此时脚本也会重新执行。 Chrome for Android:有些情况下不会缓存,缓存时与 Desktop Chrome 行为一致。 Desktop Firefox:页面会被 Frozen,定时器会被暂停,DOM、Window、JavaScript 对象会被缓存,返回时页面脚本重新开始运行。 iOS Safari:渲染结果也会被缓存,因此才能支持左右滑动手势来前进/后退。 Desktop Firefox 暂停计时器的行为非常有趣,以下 HTML 中显示一个每秒加一的数字。 当页面导航时就会暂停,返回时继续增加(因此直接使用 setInterval 倒计时不仅不精确,而且不可靠): <span id="timer-tick"></span> <a href="http://harttle.com">External Link</a> <script> var i = 0 setInterval(() => document.querySelector('#timer-tick').innerHTML = i++, 1000) </script> pagehide/pageshow 事件会话(Session)中的某一个页面显示/隐藏时,会触发 pagehide 和 pageshow 事件。 这两个事件都有一个 persisted 属性用来指示当前页面是否被 BF Cache 缓存。 因此可以通过 persisted 属性来达到禁用 BF Cache 的效果: window.onpageshow = function(event) { if (event.persisted) { window.location.reload() } }; 注意无论页面是否被缓存 pageshow 总会触发,因此需要检测器 persisted 属性。 另外 pageshow 的时机总是在 load 事件之后。 这一点很容易检测,下面的 pageshow 日志总在 load 之前: window.addEventListener('pageshow', function () { console.log('on pageshow') }) window.addEventListener('load', function () { console.log('load') }) XHR 缓存同步(阻塞加载的)脚本发出的 XMLHttpRequest 也会被 Chrome 强制缓存, 因此即使在断网的情况下后退到访问过的页面仍然是可以完美渲染的。 如果页面中有这样一段外部脚本: sendXHR(); function sendXHR () { var xhr = new XMLHttpRequest() xhr.open('GET', '/data.json') xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { console.log('xhr arrived', xhr.responseText) } } xhr.send() } 超链接跳转后回来,该 xhr 也会被缓存。注意下图中的 XHR 一项 size 为 “from disk cache”: 为了强制发送 xhr,可以将 xhr 改为异步发送,或者加一个不重要的 query。 setTimeout(sendXHR, 1000) 这样就能看到 xhr 真正发送出去了 :) 异步 xhr 缓存时机未经兼容性测试, 还是建议读者使用一个随机产生的 query。","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"浏览器","slug":"浏览器","permalink":"http://yelog.org/tags/%E6%B5%8F%E8%A7%88%E5%99%A8/"},{"name":"js","slug":"js","permalink":"http://yelog.org/tags/js/"}]},{"title":"解决iphone下后退不执行js的问题","slug":"解决iphone下后退不执行js的问题","date":"2017-09-21T07:25:32.000Z","updated":"2024-07-05T11:10:22.420Z","comments":true,"path":"2017/09/21/iphone-bf-no-run-js/","permalink":"http://yelog.org/2017/09/21/iphone-bf-no-run-js/","excerpt":"","text":"直接上解决方法不论页面是否被缓存,都会触发 pageshow,所以后退后需要执行的方法可以都放在下面事件内: window.addEventListener('pageshow', function () { console.log('on pageshow') }) 浏览器缓存行为 的详细介绍可以参考: [转]浏览器前进/后退缓存(BF Cache)","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"js","slug":"js","permalink":"http://yelog.org/tags/js/"}]},{"title":"CentOS7使用Firewalld","slug":"CentOS7使用Firewalld","date":"2017-09-19T01:53:53.000Z","updated":"2024-07-05T11:10:23.105Z","comments":true,"path":"2017/09/19/CentOS7使用Firewalld/","permalink":"http://yelog.org/2017/09/19/CentOS7%E4%BD%BF%E7%94%A8Firewalld/","excerpt":"","text":"介绍FirewallD 提供了支持网络/防火墙区域(zone)定义网络链接以及接口安全等级的动态防火墙管理工具。它支持 IPv4, IPv6 防火墙设置以及以太网桥接,并且拥有运行时配置和永久配置选项。它也支持允许服务或者应用程序直接添加防火墙规则的接口。 安装$ yum install firewalld # 如果需要图形界面的话,则再安装 $ yum install firewall-config zoneFirewall 能将不同的网络连接归类到不同的信任级别。 $ firewall-cmd --list-all-zones #查看所有zone信息 Zone 提供了以下几个级别: drop: 丢弃所有进入的包,而不给出任何响应 block: 拒绝所有外部发起的连接,允许内部发起的连接 public: 允许指定的进入连接 external: 同上,对伪装的进入连接,一般用于路由转发 dmz: 允许受限制的进入连接 work: 允许受信任的计算机被限制的进入连接,类似 workgroup home: 同上,类似 homegroup internal: 同上,范围针对所有互联网用户 trusted: 信任所有连接 过滤规则 source: 根据源地址过滤 interface: 根据网卡过滤 service: 根据服务名过滤 port: 根据端口过滤 icmp-block: icmp 报文过滤,按照 icmp 类型配置 masquerade: ip 地址伪装 forward-port: 端口转发 rule: 自定义规则 过滤规则的优先级遵循如下顺序 source interface firewalld.conf 使用$ systemctl start firewalld # 启动 $ systemctl stop firewalld # 关闭 $ systemctl enable firewalld # 开机启动 $ systemctl disable firewalld # 取消开机启动 具体的规则管理,可以使用 firewall-cmd,具体的使用方法 $ firewall-cmd --help --zone=NAME # 指定 zone --permanent # 永久修改,--reload 后生效 --timeout=seconds # 持续效果,到期后自动移除,用于调试,不能与 --permanent 同时使用 查看规则查看运行状态 $ firewall-cmd --state 查看已被激活的 Zone 信息 $ firewall-cmd --get-active-zones public interfaces: eth0 eth1 查看指定接口的 Zone 信息 $ firewall-cmd --get-zone-of-interface=eth0 public 查看指定级别的接口 $ firewall-cmd --zone=public --list-interfaces eth0 查看指定级别的所有信息,譬如 public $ firewall-cmd --zone=public --list-all public (default, active) interfaces: eth0 sources: services: dhcpv6-client http ssh ports: masquerade: no forward-ports: icmp-blocks: rich rules: 查看所有级别被允许的信息 $ firewall-cmd --get-service 查看重启后所有 Zones 级别中被允许的服务,即永久放行的服务 $ firewall-cmd --get-service --permanent 管理规则$ firewall-cmd --panic-on # 丢弃 $ firewall-cmd --panic-off # 取消丢弃 $ firewall-cmd --query-panic # 查看丢弃状态 $ firewall-cmd --reload # 更新规则,不重启服务 $ firewall-cmd --complete-reload # 更新规则,重启服务 添加某接口至某信任等级,譬如添加 eth0 至 public,永久修改 $ firewall-cmd --zone=public --add-interface=eth0 --permanent 设置 public 为默认的信任级别 $ firewall-cmd --set-default-zone=public a. 管理端口列出 dmz 级别的被允许的进入端口 $ firewall-cmd --zone=dmz --list-ports 允许 tcp 端口 8080 至 dmz 级别 $ firewall-cmd --zone=dmz --add-port=8080/tcp 允许某范围的 udp 端口至 public 级别,并永久生效 $ firewall-cmd --zone=public --add-port=5060-5059/udp --permanent b. 网卡接口列出 public zone 所有网卡 $ firewall-cmd --zone=public --list-interfaces 将 eth0 添加至 public zone,永久 $ firewall-cmd --zone=public --permanent --add-interface=eth0 eth0 存在与 public zone,将该网卡添加至 work zone,并将之从 public zone 中删除 $ firewall-cmd --zone=work --permanent --change-interface=eth0 删除 public zone 中的 eth0,永久 $ firewall-cmd --zone=public --permanent --remove-interface=eth0 c. 管理服务添加 smtp 服务至 work zone $ firewall-cmd --zone=work --add-service=smtp 移除 work zone 中的 smtp 服务 $ firewall-cmd --zone=work --remove-service=smtp d. 配置 external zone 中的 ip 地址伪装查看 $ firewall-cmd --zone=external --query-masquerade 打开伪装 $ firewall-cmd --zone=external --add-masquerade 关闭伪装 $ firewall-cmd --zone=external --remove-masquerade e. 配置 public zone 的端口转发要打开端口转发,则需要先 $ firewall-cmd --zone=public --add-masquerade 然后转发 tcp 22 端口至 3753 $ firewall-cmd --zone=public --add-forward-port=port=22:proto=tcp:toport=3753 转发 22 端口数据至另一个 ip 的相同端口上 $ firewall-cmd --zone=public --add-forward-port=port=22:proto=tcp:toaddr=192.168.1.100 转发 22 端口数据至另一 ip 的 2055 端口上 $ firewall-cmd --zone=public --add-forward-port=port=22:proto=tcp:toport=2055:toaddr=192.168.1.100 f. 配置 public zone 的 icmp查看所有支持的 icmp 类型 $ firewall-cmd --get-icmptypes destination-unreachable echo-reply echo-request parameter-problem redirect router-advertisement router-solicitation source-quench time-exceeded 列出 $ firewall-cmd --zone=public --list-icmp-blocks 添加 echo-request 屏蔽 $ firewall-cmd --zone=public --add-icmp-block=echo-request [--timeout=seconds] 移除 echo-reply 屏蔽 $ firewall-cmd --zone=public --remove-icmp-block=echo-reply g. IP 封禁 $ firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='222.222.222.222' reject" 当然,我们仍然可以通过 ipset 来封禁 ip 封禁 ip $ firewall-cmd --permanent --zone=public --new-ipset=blacklist --type=hash:ip $ firewall-cmd --permanent --zone=public --ipset=blacklist --add-entry=222.222.222.222 封禁网段 $ firewall-cmd --permanent --zone=public --new-ipset=blacklist --type=hash:net $ firewall-cmd --permanent --zone=public --ipset=blacklist --add-entry=222.222.222.0/24 倒入 ipset 规则 $ firewall-cmd --permanent --zone=public --new-ipset-from-file=/path/blacklist.xml 然后封禁 blacklist $ firewall-cmd --permanent --zone=public --add-rich-rule='rule source ipset=blacklist drop' 重新载入以生效 $ firewall-cmd --reload","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"firewall","slug":"firewall","permalink":"http://yelog.org/tags/firewall/"}]},{"title":"docker备份恢复之save与export","slug":"docker-save-与-docker-export","date":"2017-09-18T14:38:52.000Z","updated":"2024-07-05T11:10:22.253Z","comments":true,"path":"2017/09/18/docker-save-export/","permalink":"http://yelog.org/2017/09/18/docker-save-export/","excerpt":"","text":"docker save导出docker save 命令用于持久化 镜像,先获得镜像名称,再执行保存: # 通过此命令查出要持久化的镜像名称 $ docker images # 持久化镜像名为 image_name 的镜像, $ docker save image_name -o ~/save.tar 注意: 如果镜像是在远程仓库,执行保存镜像的时候可能会报 Cowardly refusing to save to a terminal. Use the -o flag or redirect. 的错,可以通过 docker save image_name > image_name.tar 将镜像从远程仓库持久化到本地。 导入# 导入 save.tar $ docker load < ~/save.tar # 查看镜像 $ docker images images docker export导出docker export 命令用于持久化 容器,先获取容器ID,再执行保存。 # 通过此命令查出要持久化的容器ID $ docker ps -a # 持久化容器id为 container_id 的容器 $ docker export container_id > ~/export.tar 导入# 从 export.tar 导入镜像 $ cat ~/export.tar | docker import - my-images:latest # 查看镜像 $ sudo docker images 不同通过 sudo docker images --tree 可以查看到镜像的所有层,就会发现, docker export 丢失了所有的历史,而docker save 则会保存所有历史。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"sudo命令免密码设置","slug":"sudo命令免密码设置","date":"2017-09-11T01:30:55.000Z","updated":"2024-07-05T11:10:23.052Z","comments":true,"path":"2017/09/11/sudo命令免密码设置/","permalink":"http://yelog.org/2017/09/11/sudo%E5%91%BD%E4%BB%A4%E5%85%8D%E5%AF%86%E7%A0%81%E8%AE%BE%E7%BD%AE/","excerpt":"","text":"如果某台linux只有自己在使用,比如个人系统,每次调用 sudo 时都需要输入密码,长期下来着实厌烦,因此本文介绍如何配置 sudo 命令,使其在运行时不需要输入密码。 步骤 执行命令 $ sudo visudo 添加以下两行, 下面的 sys 表示 sys 组成员不用密码使用sudo aaronkilik ALL=(ALL) NOPASSWD: ALL %sys ALL=(ALL) NOPASSWD: ALL 现在在使用 sudo 命令, 将不再需要输入密码。 扩展如果只允许用户使用 kill 和 rm 命令时,不需要输入密码,见如下配置 %sys ALL=(ALL) NOPASSWD: /bin/kill, /bin/rm","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://yelog.org/tags/linux/"}]},{"title":"搭建Maven私服-Nexus","slug":"搭建Maven私服-Nexus","date":"2017-09-06T15:01:31.000Z","updated":"2024-07-05T11:10:23.061Z","comments":true,"path":"2017/09/06/build-Maven-Nexus/","permalink":"http://yelog.org/2017/09/06/build-Maven-Nexus/","excerpt":"","text":"Maven 私服,可以代理远程仓库和部署自己或第三方构件。本文介绍使用最广泛搭建 Maven 私服的工具: Sonatype Nexus。 作者环境 本次搭建私服是在局域网的一台服务器上,操作系统为 CentOS 。 需要部署到私服的项目 soul ssm 项目需要引用 soul 安装Java 确保服务器已经安装了 java 环境,这个过程不是本文重点,安装过程自行百度。 安装Nexus 官网 pro 版本的是需要付费的。所以我们使用免费的 OSS 版本,下载地址 (https://www.sonatype.com/download-oss-sonatype) # 上传到服务器并解压 $ tar xvf nexus-3.5.1-02-unix.tar.gz 启动Nexus# 启动服务 $ cd /nexus-3.5.1-02/bin/ $ ./nexus start 验证打开网址:(http://192.168.0.86:8081/) , ip 为搭建私服的服务器 ip 。用户名/密码: admin/admin123出现一下画面,就说明安装成功了。 发布soul项目到私服创建仓库 创建yelog-release仓库(名字自定义), type选择 : release 创建yelog-snapshot仓库(名字自定义), type选择 : snapshot重复上面 ① 和 ② 步,根据下图选择类型: 两个都创建完成后,效果如下: pom中添加部署配置url 复制上图中新建的仓库的 copy 按钮,复制url。 <distributionManagement> <repository> <id>yelog-release</id> <name>Release Repository of yelog</name> <url>http://192.168.0.86:8081/repository/yelog-release/</url> </repository> <snapshotRepository> <id>yelog-snapshot</id> <name>Snapshot Repository of yelog</name> <url>http://192.168.0.86:8081/repository/yelog-snapshot/</url> </snapshotRepository> </distributionManagement> 在maven的 settings.xml 中配置这里配置 maven 的账号密码,id 要与 distributionManagement 中的id一致。默认账号/密码:admin/admin123 <servers> <server> <id>yelog-realease</id> <username>admin</username> <password>admin123</password> </server> <server> <id>yelog-snapshot</id> <username>admin</username> <password>admin123</password> </server> </servers> 执行maven命令部署项目到私服上我这里直接使用IDE的插件执行部署完成后,可以在 yelog-snapshot 仓库中,查看部署的情况,如下图所示 从私服拉去依赖库 上一步我们已经将项目 soul 部署到私服上了,这一步介绍项目 ssm 如何依赖引用 soul。私服中的 maven-central 可以链接远程仓库。这样,当有依赖在私服中找不到后,就可以通过远程仓库自动下载依赖。 pom 文件中添加如下配置 public库成员仓库中添加我们自定义的仓库 配置远程仓库为私服地址。 <repositories> <repository> <id>public</id> <name>public Repository</name> <url>http://192.168.0.86:8081/repository/maven-public/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>public</id> <name>Public Repositories</name> <url>http://192.168.0.86:8081/repository/maven-public/</url> </pluginRepository> </pluginRepositories> 引入依赖 <dependency> <groupId>org.soul</groupId> <artifactId>commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> ssm项目就可以引用到soul代码 本文结束。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"maven","slug":"maven","permalink":"http://yelog.org/tags/maven/"},{"name":"nexus","slug":"nexus","permalink":"http://yelog.org/tags/nexus/"}]},{"title":"Mybatis常用Mapper语句","slug":"mybatis-Mapper","date":"2017-08-04T07:54:47.000Z","updated":"2024-07-05T11:10:22.641Z","comments":true,"path":"2017/08/04/mybatis-Mapper/","permalink":"http://yelog.org/2017/08/04/mybatis-Mapper/","excerpt":"","text":"插入/* 简单插入 */ <insert id="insertOne" parameterType="Person"> insert into person (id, name, age) VALUES(#{id}, #{name}, #{age}); </insert> /* 插入并返回对象的主键(数据库序列) */ <insert id="insertOne" parameterType="Person" useGeneratedKeys="true" keyProperty="id"> insert into person (name, age) VALUES(#{name}, #{age}); </insert> 更新/* 简单更新 */ <update id="updateName"> update person set name = #{name} where id = #{id}; </update> /* 更新值并返回 */ <select id="updateAge" parameterType="Person"> update person set age = age + #{age} where id = #{id} returning age; </select> 插入或更新记录玩家在某种类型游戏下的统计记录: 如果没有记录,则从插入,count字段为1;如果有记录,则更新count字段+1; 方式一 <insert id="addCount" parameterType="CountRecord"> /*如果有记录,则更新;无记录,则noting*/ update count_record set "count" = "count"+1 where type_id = #{typeId} and user_id = #{userId}; /*如果有记录,则noting;无记录,则插入*/ insert into count_record(type_id, user_id, "count") select #{typeId}, #{userId}, 1 where not exists (select * from count where type_id = #{typeId} and user_id = #{userId}); </insert> 方式二 /* 利用 PostgreSQL 的 conflic 特性 */ <insert id="addCount" parameterType="CountRecord"> insert into count_record(type_id, user_id, "count") VALUES (#{typeId}, #{userId}, #{count}) on conflict(type_id,user_id) do update set "count" = count_record."count" + 1 </insert>","categories":[{"name":"数据库","slug":"数据库","permalink":"http://yelog.org/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"}],"tags":[{"name":"mybatis","slug":"mybatis","permalink":"http://yelog.org/tags/mybatis/"}]},{"title":"MathJax适配Pjax","slug":"MathJax-pjax","date":"2017-07-05T11:24:10.000Z","updated":"2024-07-05T11:10:22.403Z","comments":true,"path":"2017/07/05/MathJax-pjax/","permalink":"http://yelog.org/2017/07/05/MathJax-pjax/","excerpt":"","text":"hexo 添加 MathJax 的过程网上很多,这里就不细讲,这里贴一张写的不错的文章 Hexo博客(13)添加MathJax数学公式渲染 由于 3-hexo 这个主题使用了 pjax ,刷新和第一次加载没有问题,但是点到其他文章,再点回来,渲染就无效了。 这个问题和之前适配多说和高亮时,是同样的问题,只需要在下面配置即可。 $(document).on({ /*pjax请求回来页面后触发的事件*/ 'pjax:end': function () { /*渲染MathJax数学公式*/ $.getScript('//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML',function () { MathJax.Hub.Typeset(); }); } }); 这样就解决了pjax的适配问题。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"mathjax","slug":"mathjax","permalink":"http://yelog.org/tags/mathjax/"},{"name":"pjax","slug":"pjax","permalink":"http://yelog.org/tags/pjax/"}]},{"title":"3-hexo配置MathJax数学公式渲染","slug":"3-hexo-mathjax","date":"2017-07-05T07:09:42.000Z","updated":"2024-07-05T11:10:22.499Z","comments":true,"path":"2017/07/05/3-hexo-mathjax/","permalink":"http://yelog.org/2017/07/05/3-hexo-mathjax/","excerpt":"","text":"在用 markdown 写文档时,免不了碰到数学公式。 处理hexo的MarkDown渲染器与MathJax的冲突由于hexo的MarkDown渲染器与MathJax有冲突,所以在使用之前需要修改两个地方。 编辑 node_modules\\marked\\lib\\marked.js 脚本 将451行 ,这一步取消了对 \\\\,\\{,\\} 的转义(escape) escape: /^\\\\([\\\\`*{}\\[\\]()# +\\-.!_>])/, 改为 escape: /^\\\\([`*\\[\\]()# +\\-.!_>])/, 将459行,这一步取消了对斜体标记 _ 的转义 em: /^\\b_((?:[^_]|__)+?)_\\b|^\\*((?:\\*\\*|[\\s\\S])+?)\\*(?!\\*)/, 改为 em:/^\\*((?:\\*\\*|[\\s\\S])+?)\\*(?!\\*)/, 开启MathJax修改 3-hexo/_config.yml # MathJax 数学公式支持 mathjax: on: true #是否启用 per_page: false # 若只渲染单个页面,此选项设为false,页面内加入 mathjax: true 考虑到页面的加载速度,支持渲染单个页面。 设置 per_page: false ,在需要渲染的页面内 加入 mathjax: true 这样,就可以在页面内写MathJax公式了。 MathJax公式书写公式书写依然按照MarkDown语法来,基本上也和LaTeX相同,单 $ 符引住的是行内公式,双$符引住的是行间公式。 MathJax公式书写参考MathJax basic tutorial and quick reference 1.MathJax行内公式含有下划线 _ 的公式 $x_mu$ : $x_mu$ 希腊字符 $\\sigma$ : $\\sigma$ 双 \\\\ 公式内换行 $$ f(n) = \\begin{cases} n/2, & \\text{if $n$ is even} \\\\ 3n+1, & \\text{if $n$ is odd} \\end{cases} $$ $$f(n) =\\begin{cases}n/2, & \\text{if $n$ is even} \\3n+1, & \\text{if $n$ is odd}\\end{cases}$$ 行内公式 $y=ax+b$:$y=ax+b$ 行内公式 $\\cos 2\\theta = \\cos^2 \\theta - \\sin^2 \\theta = 2 \\cos^2 \\theta$:$\\cos 2\\theta = \\cos^2 \\theta - \\sin^2 \\theta = 2 \\cos^2 \\theta$ 行内公式 $M(\\beta^{\\ast}(D),D) \\subseteq C$ : $M(\\beta^{\\ast}(D),D) \\subseteq C$ 2.MathJax行间公式行间公式$$ \\sum_{i=0}^n i^2 = \\frac{(n^2+n)(2n+1)}{6} $$:$$ \\sum_{i=0}^n i^2 = \\frac{(n^2+n)(2n+1)}{6} $$ 行间公式$$ x = \\dfrac{-b \\pm \\sqrt{b^2 - 4ac}}{2a} $$:$$ x = \\dfrac{-b \\pm \\sqrt{b^2 - 4ac}}{2a} $$ 3.MathJax公式自动编号书写时使用 $$ \\begin{equation} \\end{equation} $$ 进行公式自动编号,同时会自动连续编号,例如: $$ \\begin{equation} \\sum_{i=0}^n F_i \\cdot \\phi (H, p_i) - \\sum_{i=1}^n a_i \\cdot ( \\tilde{x_i}, \\tilde{y_i}) + b_i \\cdot ( \\tilde{x_i}^2 , \\tilde{y_i}^2 ) \\end{equation} $$ $$ \\begin{equation} \\beta^*(D) = \\mathop{argmin} \\limits_{\\beta} \\lambda {||\\beta||}^2 + \\sum_{i=1}^n max(0, 1 - y_i f_{\\beta}(x_i)) \\end{equation} $$ $$\\begin{equation}\\sum_{i=0}^n F_i \\cdot \\phi (H, p_i) - \\sum_{i=1}^n a_i \\cdot ( \\tilde{x_i}, \\tilde{y_i}) + b_i \\cdot ( \\tilde{x_i}^2 , \\tilde{y_i}^2 )\\end{equation}$$$$\\begin{equation}\\beta^*(D) = \\mathop{argmin} \\limits_{\\beta} \\lambda {||\\beta||}^2 + \\sum_{i=1}^n max(0, 1 - y_i f_{\\beta}(x_i))\\end{equation}$$ MathJax公式手动编号可以在公式书写时使用 \\tag{手动编号} 添加手动编号,例如: $$ \\begin{equation} \\sum_{i=0}^n F_i \\cdot \\phi (H, p_i) - \\sum_{i=1}^n a_i \\cdot ( \\tilde{x_i}, \\tilde{y_i}) + b_i \\cdot ( \\tilde{x_i}^2 , \\tilde{y_i}^2 ) \\tag{1.2.3} \\end{equation} $$ $$\\begin{equation}\\sum_{i=0}^n F_i \\cdot \\phi (H, p_i) - \\sum_{i=1}^n a_i \\cdot ( \\tilde{x_i}, \\tilde{y_i}) + b_i \\cdot ( \\tilde{x_i}^2 , \\tilde{y_i}^2 ) \\tag{1.2.3}\\end{equation}$$ 不加 \\begin{equation} \\end{equation} 也可以,例如: $$ \\beta^*(D) = \\mathop{argmin} \\limits_{\\beta} \\lambda {||\\beta||}^2 + \\sum_{i=1}^n max(0, 1 - y_i f_{\\beta}(x_i)) \\tag{我的公式3} $$ $$\\beta^*(D) = \\mathop{argmin} \\limits_{\\beta} \\lambda {||\\beta||}^2 + \\sum_{i=1}^n max(0, 1 - y_i f_{\\beta}(x_i)) \\tag{我的公式3}$$ 行内公式加\\tag{}后会自动成为行间公式,例如: $z = (p_0, ..... , p_n) \\tag{公式21} $$z = (p_0, ….. , p_n) \\tag{公式21} $ 4.其他公式书写技巧如何将下标放到正下方?① 如果是数学符号,那么直接用 \\limits 命令放在正下方,如Max函数下面的取值范围,需要放在Max的正下方。可以如下实现:$ \\max \\limits_{a<x<b}\\{f(x)\\} $$ \\max \\limits_{a<x<b}{f(x)} $ ② 若是普通符号,那么要用 \\mathop 先转成数学符号再用 \\limits,如$ \\mathop{a}\\limits_{i=1} $$ \\mathop{a}\\limits_{i=1} $ MathJax矩阵输入无括号矩阵: $$ \\begin{matrix} 1 & x & x^2 \\\\ 1 & y & y^2 \\\\ 1 & z & z^2 \\\\ \\end{matrix} $$ $$\\begin{matrix}1 & x & x^2 \\1 & y & y^2 \\1 & z & z^2 \\\\end{matrix}$$ 有括号有竖线矩阵: $$ \\left[ \\begin{array}{cc|c} 1&2&3\\\\ 4&5&6 \\end{array} \\right] $$ $$\\left[ \\begin{array}{cc|c} 1&2&3\\ 4&5&6 \\end{array}\\right]$$ 行内小矩阵:$\\bigl( \\begin{smallmatrix} a & b \\\\ c & d \\end{smallmatrix} \\bigr)$$\\bigl( \\begin{smallmatrix} a & b \\ c & d \\end{smallmatrix} \\bigr)$ 这里有个问题,上面的写法在矩阵内没有换行,我看了下源码,双反斜杠\\又被MarkDown渲染引擎转义为单个反斜杠了,解决方法是写三个反斜杠\\\\或在双反斜杠后换行即可: $\\bigl( \\begin{smallmatrix} a & b \\\\\\ c & d \\end{smallmatrix} \\bigr)$$\\bigl( \\begin{smallmatrix} a & b \\\\ c & d \\end{smallmatrix} \\bigr)$ 参考Hexo博客(13)添加MathJax数学公式渲染在Hexo中渲染MathJax数学公式MathJax basic tutorial and quick reference","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"mathjax","slug":"mathjax","permalink":"http://yelog.org/tags/mathjax/"}]},{"title":"CentOS7安装配置匿名访问Samba","slug":"CentOS7-安装配置匿名访问Samba","date":"2017-07-03T11:40:14.000Z","updated":"2024-07-05T11:10:23.065Z","comments":true,"path":"2017/07/03/CentOS7-anonymous-Samba/","permalink":"http://yelog.org/2017/07/03/CentOS7-anonymous-Samba/","excerpt":"","text":"介绍 Samba,是种用来让UNIX系列的操作系统与微软Windows操作系统的SMB/CIFS(Server Message Block/Common Internet File System)网络协议做链接的自由软件 –wikipedia 本文就以 CentOS7 搭建 Samba 匿名完全访问(读/写)为目标,实现一个局域网内的文件共享平台。 1.安装Samba服务使用 yum 工具进行安装 $ yum install samba samba-client 2.检查是否安装成功$ rpm -qa | grep samba 3.防火墙开放端口在 /etc/sysconfig/iptables 中添加配置 -A INPUT -p tcp -m state --state NEW -m tcp --dport 137 -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 138 -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 139 -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 389 -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 445 -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 901 -j ACCEPT 重启 iptables 服务 $ service iptables restart 设置开机自启动 $ chkconfig --level 35 smb on 4.共享配置Samba Server的验证方式有四种: share:匿名访问共享,不需要提供用户名和口令, 安全性能较低。 user:共享目录只能被授权的用户访问,由Samba Server负责检查账号和密码的正确性。账号和密码要在本Samba Server中建立。 server:依靠其他Windows Server或Samba Server来验证用户的账号和密码,是一种代理验证。此种安全模式下,系统管理员可以把所有的Windows用户和口令集中到一个Server系统上,使用 Windows Server进行Samba认证, 远程服务器可以自动认证全部用户和口令,如果认证失败,Samba将使用用户级安全模式作为替代的方式。 domain:域安全级别,使用主域控制器(PDC)来完成认证。 创建一个匿名共享访问,需要使用share模式,但在CentOS安装的samba4中share 和 server验证方式已被弃用 配置如下: [global] workgroup = MYGROUP server string = Samba Server Version %v log file = /var/log/samba/log.%m max log size = 50 security = user map to guest = Bad User load printers = yes cups options = raw [share] comment = share path = /home/samba directory mask = 0777 create mask = 0777 #不可视目录 #browseable = yes guest ok=yes writable=yes 创建 /home/samba 共享目录 $ mkdir /home/samba 重启 smb 服务 $ service smb restart 检查服务是否在运行 $ pgrep smbd 检查配置参数 $ testparm Load smb config files from /etc/samba/smb.conf Processing section "[share]" Loaded services file OK. Server role: ROLE_STANDALONE Press enter to see a dump of your service definitions # Global parameters [global] server string = Samba Server Version %v workgroup = MYGROUP log file = /var/log/samba/log.%m max log size = 50 map to guest = Bad User security = USER idmap config * : backend = tdb cups options = raw [share] comment = share path = /home/samba create mask = 0777 directory mask = 0777 guest ok = Yes read only = No 访问以上就配置完成,如服务器地址为192.168.0.87 windows 系统访问,直接运行 \\\\192.168.0.87\\share linux 系统访问, smb://192.168.0.87/share 遇到的问题 linux 系统可以正常读写修改,但 windows 系统只可以读写,直接打开修改时就,就为只读文件了。解决办法:修改 /etc/samba/smb.conf ,在 [share] 中加入以下内容 create mask = 0777 访问部分文件可以正常访问,但部分文件无法访问。解决方法:修改文件访问权限 $ chmod -R 1777 /home/samba $ chown nobody:nobody 参考 CentOS7 安装Samba服务 CentOS7 安装配置匿名访问Samba","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"samba","slug":"samba","permalink":"http://yelog.org/tags/samba/"}]},{"title":"完美替代多说-gitment","slug":"gitment","date":"2017-06-26T04:08:13.000Z","updated":"2024-07-05T11:10:22.466Z","comments":true,"path":"2017/06/26/gitment/","permalink":"http://yelog.org/2017/06/26/gitment/","excerpt":"","text":"自从多说要停止服务时,就开始关注第三方评论系统,现在的评论系统都有这样或那样的问题,见 关于第三方评论系统 。忽然看到作者 孙士权 的一片文章 Gitment:使用 GitHub Issues 搭建评论系统 。 立即就将 gitment 集成到 3-hexo 主题内。本篇文章只讲在 3-hexo 内如何使用,如果想自定义,可以参考上面原文。 注册 OAuth Application点击此处 来注册一个新的 OAuth Application。其他内容可以随意填写,但要确保填入正确的 callback URL(一般是评论页面对应的域名,如 http://yelog.org)。 使用 gitment 评论系统修改主题 _config.yml gitment: on: true # 启用gitment评论系统 owner: yelog # 你的github账号 repo: yelog.github.io # 评论issue保存的仓库,我选择保存在blog仓库,也可以新建一个仓库 client_id: d64ceca0d8a4e8b1f5c9 # 上一步注册后生成的client_id client_secret: fb17d5f0aba31372f61a03df707bb20a39a73a06 # 上一步注册后生成的client_secret 部署并初始化1.发布 hexo $ hexo clear && hexo g && hexo d 2.打开发布的blog,登录github账号,并点击 Initialize Comments。 3.现在其他人就可以进行评论了 感受整体评论系统做的简洁,整体来说是个不错的系统。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"解决粘贴到vim缩进错乱问题","slug":"解决粘贴到vim缩进错乱问题","date":"2017-06-01T11:45:24.000Z","updated":"2024-07-05T11:10:22.192Z","comments":true,"path":"2017/06/01/vim-paste/","permalink":"http://yelog.org/2017/06/01/vim-paste/","excerpt":"","text":"遇见当我使用vim,想要粘贴下面这段脚本到 xx.sh 文件中 #!/bin/bash if [ $1 ] then if [ $1 == "help" ]; then echo -e "\\033[37m pay 参数1 [参数2] \\033[0m" else if [ $2 ]; then filename = $2 fi fi else echo -e "\\033[37m 缺少关键词,通过'pay help'查看帮助信息 \\033[0m" fi 却出现了错乱,如下图所示 分析vim 没有相应的程序来处理这个从其他应用复制粘贴的过程,所以Vim通过插入键盘输入的buffer来模拟这个粘贴的过程,这个时候Vim会以为这是用户输入的。 所以问题是:当上一行结束,光标进入下一行时Vim会自动以上一行的的缩进为初始位置。这样就会破坏原始文件的缩进。 解决问题经过一番google,发现vim提供了 paste 选项,进入 paste 模式后,就可以正常缩进了。 # 进入 paste 模式 :set paste # 退出 paste 模式 :set nopaste 如果不想每次都执行这个命令,可以在 ~/.vimrc 中添加一行配置 set pastetoggle=<F12> ,这样就可以通过F12快速在paste模式中切换。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"vim","slug":"vim","permalink":"http://yelog.org/tags/vim/"}]},{"title":"进入docker容器命令制作","slug":"进入docker容器命令制作","date":"2017-06-01T09:25:11.000Z","updated":"2024-07-05T11:10:23.070Z","comments":true,"path":"2017/06/01/entering-docker/","permalink":"http://yelog.org/2017/06/01/entering-docker/","excerpt":"","text":"通过attach进入容器# 进入容器(Docker自带的命令) $ sudo docker attach [name] 通过这命令进入容器后,执行ctrl+d退出容器后发现容器也停止了。所以可以通过 先按,ctrl+p 再按,ctrl+q 退出 制作进入容器的命令既然attach退出很麻烦,一不小心容器就down掉了 通过 docker exec 进入容器是安全的,但是命令过长 所以我们可以通过下面操作,简化命令 1.创建文件 /usr/bin/ctn,内容如下 docker exec -it $1 /bin/bash 2.检查环境变量有没有配置目录 /usr/bin $PATH bash: /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games: No such file or directory 配置环境变量的方式自行百度 3.完成上面两步即可通过命令 ctn 进入容器 $ ctn [name] 注意:如果是使用非root账号创建的命令,而docker命令是root权限,可能会存在权限问题可以设置 chmod 777 /usr/bin/ctn 设置权限使用 sudo ctn [name] 即可进入容器 4.自动补全docker名使用上面命令时,docker的名字都是手动输入,很麻烦,而且容易出错。 我们可以借助complete命令,来补全docker信息。 在~/.bashrc(作用于当前用户,如果所有用户,修改/etc/bashrc)文件中添加一行 # ctn auto complete complete -W "$(docker ps --format "{{.Names}}")" ctn 再执行 source .bashrc 使之生效。 这样我们输入 ctn 后,按 Tab 就会提示或自动补全了。 注意: 由于提示的docker名是 .bashrc 生效时的列表,所以如果之后docker列表有变动,需重新执行 source .bashrc 使之更新提示列表","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"tale博客搭建及体验","slug":"tale-build-experience","date":"2017-05-24T03:29:30.000Z","updated":"2024-07-05T11:10:22.462Z","comments":true,"path":"2017/05/24/tale-build-experience/","permalink":"http://yelog.org/2017/05/24/tale-build-experience/","excerpt":"","text":"不久之前在逛blog时,发现了这款tale,今天抽空搭建了一下,将搭建过程写于此。demo website:https://tale.biezhi.me 搭建思路看了tale作者的(github)[https://github.com/otale] 发现有建好docker,所以果断使用docker搭建tale的环境 构建docker镜像下载tale-docker到本地。 # 下载官方Dockerfile $ git clone https://github.com/otale/tale-docker.git # 构建 tale 镜像 $ docker build -t tale:1.0 . 下载tale博客文件# 下载压缩包 $ sudo wget http://7xls9k.dl1.z0.glb.clouddn.com/tale.zip # 讲解压出来的文件夹移入home目录 $ unzip tale.zip $ mv tale /home 构建tale镜像docker run -d --privileged --hostname tale --name tale \\ -v /etc/localtime:/etc/localtime:ro \\ -v /home/tale:/var/tale_home -p 80:9000 \\ -m 1024m --memory-swap -1 tale:1.0 访问浏览器进入 127.0.0.1 即可访问 体验管理后台 文章支持Markdown和富文本。 文章/评论/友链/标签管理/主题,设置简单,一目了然 支持插件扩展 博客 主题简洁(当然支持切换主题) 使用 instantclick ,页面切换流畅 评论系统,简洁易用 搜索只支持文章标题 整体 管理简单方便 使用docker后,迁移数据也方便 主题还不是很多 对于常年使用静态blog,手动渲染/发布,使用这个之后还有点小清新。 最后tale整体不错,值得入手。 不过目前没有笔者喜欢的主题(当然默认主题也不错),暂时不打算更换blog,笔者也打算过一段时间开发一个tale的主题,然后正式迁入tale。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"软件记录","slug":"工具/软件记录","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95/"}],"tags":[{"name":"tale","slug":"tale","permalink":"http://yelog.org/tags/tale/"}]},{"title":"docker数据管理","slug":"docker数据管理","date":"2017-05-23T13:43:06.000Z","updated":"2024-07-05T11:10:22.227Z","comments":true,"path":"2017/05/23/docker-data-manager/","permalink":"http://yelog.org/2017/05/23/docker-data-manager/","excerpt":"","text":"数据卷数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性: 数据卷可以在容器之间共享和重用 对数据卷的修改会立马生效 对数据卷的更新,不会影响镜像 数据卷默认会一直存在,即使容器被删除 注意:数据卷的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的数据卷。 创建一个数据卷在用 docker run 命令的时候,使用 -v 标记来创建一个数据卷并挂载到容器里。在一次 run 中多次使用可以挂载多个数据卷。 下面创建一个名为 web 的容器,并加载一个数据卷到容器的 /webapp 目录。 $ sudo docker run -d -P --name web -v /webapp training/webapp python app.py 注意:也可以在 Dockerfile 中使用 VOLUME 来添加一个或者多个新的卷到由该镜像创建的任意容器。 删除数据卷数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docker不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v 这个命令。无主的数据卷可能会占据很多空间,要清理会很麻烦。Docker官方正在试图解决这个问题,相关工作的进度可以查看这个PR。 挂载一个主机目录作为数据卷使用 -v 标记也可以指定挂载一个本地主机的目录到容器中去。 $ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py 上面的命令加载主机的 /src/webapp 目录到容器的 /opt/webapp 目录。这个功能在进行测试的时候十分方便,比如用户可以放置一些程序到本地目录中,来查看容器是否正常工作。本地目录的路径必须是绝对路径,如果目录不存在 Docker 会自动为你创建它。 注意:Dockerfile 中不支持这种用法,这是因为 Dockerfile 是为了移植和分享用的。然而,不同操作系统的路径格式不一样,所以目前还不能支持。 Docker 挂载数据卷的默认权限是读写,用户也可以通过 :ro 指定为只读。 $ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py 加了 :ro 之后,就挂载为只读了。 查看数据卷的具体信息在主机里使用以下命令可以查看指定容器的信息 $ docker inspect web 在输出的内容中找到其中和数据卷相关的部分,可以看到所有的数据卷都是创建在主机的/var/lib/docker/volumes/下面的 "Volumes": { "/webapp": "/var/lib/docker/volumes/fac362...80535" }, "VolumesRW": { "/webapp": true } ... 注:从Docker 1.8.0起,数据卷配置在”Mounts”Key下面,可以看到所有的数据卷都是创建在主机的/mnt/sda1/var/lib/docker/volumes/….下面了。 "Mounts": [ { "Name": "b53ebd40054dae599faf7c9666acfe205c3e922fc3e8bc3f2fd178ed788f1c29", "Source": "/mnt/sda1/var/lib/docker/volumes/b53ebd40054dae599faf7c9666acfe205c3e922fc3e8bc3f2fd178ed788f1c29/_data", "Destination": "/webapp", "Driver": "local", "Mode": "", "RW": true, "Propagation": "" } ] ... 挂载一个本地主机文件作为数据卷-v 标记也可以从主机挂载单个文件到容器中 $ sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash 这样就可以记录在容器输入过的命令了。 注意:如果直接挂载一个文件,很多文件编辑工具,包括 vi 或者 sed --in-place,可能会造成文件 inode 的改变,从 Docker 1.1 .0起,这会导致报错误信息。所以最简单的办法就直接挂载文件的父目录。 数据卷容器如果你有一些持续更新的数据需要在容器之间共享,最好创建数据卷容器。 数据卷容器,其实就是一个正常的容器,专门用来提供数据卷供其它容器挂载的。 首先,创建一个名为 dbdata 的数据卷容器: $ sudo docker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres 然后,在其他容器中使用 –volumes-from 来挂载 dbdata 容器中的数据卷。 $ sudo docker run -d --volumes-from dbdata --name db1 training/postgres $ sudo docker run -d --volumes-from dbdata --name db2 training/postgres 可以使用超过一个的 --volumes-from 参数来指定从多个容器挂载不同的数据卷。 也可以从其他已经挂载了数据卷的容器来级联挂载数据卷。 $ sudo docker run -d --name db3 --volumes-from db1 training/postgres 注意:使用 –volumes-from 参数所挂载数据卷的容器自己并不需要保持在运行状态。 如果删除了挂载的容器(包括 dbdata、db1 和 db2),数据卷并不会被自动删除。如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时使用 docker rm -v 命令来指定同时删除关联的容器。 这可以让用户在容器之间升级和移动数据卷。具体的操作将在下一节中进行讲解。 利用数据卷容器来备份、恢复、迁移数据卷可以利用数据卷对其中的数据进行进行备份、恢复和迁移。 备份首先使用 –volumes-from 标记来创建一个加载 dbdata 容器卷的容器,并从主机挂载当前目录到容器的 /backup 目录。命令如下: $ sudo docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata 容器启动后,使用了 tar 命令来将 dbdata 卷备份为容器中 /backup/backup.tar 文件,也就是主机当前目录下的名为 backup.tar 的文件。 恢复如果要恢复数据到一个容器,首先创建一个带有空数据卷的容器 dbdata2。 $ sudo docker run -v /dbdata --name dbdata2 ubuntu /bin/bash 然后创建另一个容器,挂载 dbdata2 容器卷中的数据卷,并使用 untar 解压备份文件到挂载的容器卷中。 $ sudo docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar 为了查看/验证恢复的数据,可以再启动一个容器挂载同样的容器卷来查看 $ sudo docker run --volumes-from dbdata2 busybox /bin/ls /dbdata","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"docker容器","slug":"docker容器","date":"2017-05-23T13:29:38.000Z","updated":"2024-07-05T11:10:22.231Z","comments":true,"path":"2017/05/23/docker-container/","permalink":"http://yelog.org/2017/05/23/docker-container/","excerpt":"","text":"容器镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。 容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。 命令# 创建一个名为myubuntu的容器 # -t:分配一个伪终端 -i:让容器的标准输入保持打开 $ docker run --name=myubuntu -t -i ubuntu /bin/bash # 创建一个名为webserver 的nginx容器,使用卷映射本机/home/faker/myspace/nginx目录到docker目录/usr/share/nginx/html $ docker run --name=webserver -d -v /home/faker/myspace/nginx:/usr/share/nginx/html -p 80:80 nginx # 查看容器的输出信息(打印信息,如 echo) # run的时候,使用-d将会不展示在宿主机上,可通过下面命令查看打印信息 $ docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done" $ docker logs [container ID or NAMES] # 启动容器 myubuntu $ docker start myubuntu # 关闭容器 myubuntu $ docker stop myubuntu # 查看已启动的容器 -a:查看包括未启动的容器在内的所有容器 $ docker ps [-a] # 进入容器(Docker自带的命令) $ docker attach [name] # 进入容器(通过exec) $ docker exec -it [name] /bin/bash # 导出容器快照到本地文件 $ docker export [container id] > ubuntu.tar # 将容器快照导入为镜像 $ cat ubuntu.tar | docker import - test/ubuntu:v1.0 # 从制定 URL 或者某个目录导入 $ docker import http://example.com/exampleimage.tgz example/imagerepo # 删除容器 -f:删除正在运行的容器 $ docker [-f] rm myubuntu # 删除所有已关闭的容器 $ docker rm $(docker ps -a -q) # 查询各容器资源使用情况 $ docker stats $(docker ps --format={{.Names}})","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"docker仓库","slug":"docker仓库","date":"2017-05-23T13:15:52.000Z","updated":"2024-07-05T11:10:22.214Z","comments":true,"path":"2017/05/23/docker-registry/","permalink":"http://yelog.org/2017/05/23/docker-registry/","excerpt":"","text":"Docker Hub目前 Docker 官方维护了一个公共仓库 Docker Hub,其中已经包括了超过 15,000 的镜像。大部分需求,都可以通过在 Docker Hub 中直接下载镜像来实现。 登录可以通过执行 docker login 命令来输入用户名、密码和邮箱来完成注册和登录。 注册成功后,本地用户目录的 .dockercfg 中将保存用户的认证信息。 基本操作用户无需登录即可通过 docker search 命令来查找官方仓库中的镜像,并利用 docker pull 命令来将它下载到本地。 例如以 centos 为关键词进行搜索: $ sudo docker search centos NAME DESCRIPTION STARS OFFICIAL AUTOMATED centos The official build of CentOS. 465 [OK] tianon/centos CentOS 5 and 6, created using rinse instea... 28 blalor/centos Bare-bones base CentOS 6.5 image 6 [OK] saltstack/centos-6-minimal 6 [OK] tutum/centos-6.4 DEPRECATED. Use tutum/centos:6.4 instead. ... 5 [OK] ... 可以看到返回了很多包含关键字的镜像,其中包括镜像名字、描述、星级(表示该镜像的受欢迎程度)、是否官方创建、是否自动创建。 官方的镜像说明是官方项目组创建和维护的,automated 资源允许用户验证镜像的来源和内容。 根据是否是官方提供,可将镜像资源分为两类。 一种是类似 centos 这样的基础镜像,被称为基础或根镜像。这些基础镜像是由 Docker 公司创建、验证、支持、提供。这样的镜像往往使用单个单词作为名字。 还有一种类型,比如 tianon/centos 镜像,它是由 Docker 的用户创建并维护的,往往带有用户名称前缀。可以通过前缀 user_name/ 来指定使用某个用户提供的镜像,比如 tianon 用户。 另外,在查找的时候通过 -s N 参数可以指定仅显示评价为 N 星以上的镜像。 下载官方 centos 镜像到本地。 $ sudo docker pull centos Pulling repository centos 0b443ba03958: Download complete 539c0211cd76: Download complete 511136ea3c5a: Download complete 7064731afe90: Download complete 用户也可以在登录后通过 docker push 命令来将镜像推送到 Docker Hub。 私有仓库安装 docker-registry容器运行在安装了 Docker 后,可以通过获取官方 registry 镜像来运行。 $ sudo docker run -d -p 5000:5000 registry 这将使用官方的 registry 镜像来启动本地的私有仓库。 用户可以通过指定参数来配置私有仓库位置,例如配置镜像存储到 Amazon S3 服务。 $ sudo docker run \\ -e SETTINGS_FLAVOR=s3 \\ -e AWS_BUCKET=acme-docker \\ -e STORAGE_PATH=/registry \\ -e AWS_KEY=AKIAHSHB43HS3J92MXZ \\ -e AWS_SECRET=xdDowwlK7TJajV1Y7EoOZrmuPEJlHYcNP2k4j49T \\ -e SEARCH_BACKEND=sqlalchemy \\ -p 5000:5000 \\ registry 此外,还可以指定本地路径(如 /home/user/registry-conf )下的配置文件。 $ sudo docker run -d -p 5000:5000 -v /home/user/registry-conf:/registry-conf -e DOCKER_REGISTRY_CONFIG=/registry-conf/config.yml registry 默认情况下,仓库会被创建在容器的 /tmp/registry 下。可以通过 -v 参数来将镜像文件存放在本地的指定路径。 例如下面的例子将上传的镜像放到 /opt/data/registry 目录。 $ sudo docker run -d -p 5000:5000 -v /opt/data/registry:/tmp/registry registry 本地安装对于 Ubuntu 或 CentOS 等发行版,可以直接通过源安装。1.Ubuntu $ sudo apt-get install -y build-essential python-dev libevent-dev python-pip liblzma-dev $ sudo pip install docker-registry 2.CentOS $ sudo yum install -y python-devel libevent-devel python-pip gcc xz-devel $ sudo python-pip install docker-registry 3.源码安装 $ sudo apt-get install build-essential python-dev libevent-dev python-pip libssl-dev liblzma-dev libffi-dev $ git clone https://github.com/docker/docker-registry.git $ cd docker-registry $ sudo python setup.py install 然后修改配置文件,主要修改 dev 模板段的 storage_path 到本地的存储仓库的路径。 $ cp config/config_sample.yml config/config.yml 之后启动 Web 服务。 $ sudo gunicorn -c contrib/gunicorn.py docker_registry.wsgi:application 或者 $ sudo gunicorn --access-logfile - --error-logfile - -k gevent -b 0.0.0.0:5000 -w 4 --max-requests 100 docker_registry.wsgi:application 此时使用 curl 访问本地的 5000 端口,看到输出 docker-registry 的版本信息说明运行成功。 注:config/config_sample.yml 文件是示例配置文件。 在私有仓库上传、下载、搜索镜像创建好私有仓库之后,就可以使用 docker tag 来标记一个镜像,然后推送它到仓库,别的机器上就可以下载下来了。例如私有仓库地址为 192.168.7.26:5000。 先在本机查看已有的镜像。 $ sudo docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE ubuntu latest ba5877dc9bec 6 weeks ago 192.7 MB ubuntu 14.04 ba5877dc9bec 6 weeks ago 192.7 MB 使用docker tag 将 ba58 这个镜像标记为 192.168.7.26:5000/test(格式为 docker tag IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG])。 $ sudo docker tag ba58 192.168.7.26:5000/test root ~ # docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE ubuntu 14.04 ba5877dc9bec 6 weeks ago 192.7 MB ubuntu latest ba5877dc9bec 6 weeks ago 192.7 MB 192.168.7.26:5000/test latest ba5877dc9bec 6 weeks ago 192.7 MB 使用 docker push 上传标记的镜像。 $ sudo docker push 192.168.7.26:5000/test The push refers to a repository [192.168.7.26:5000/test] (len: 1) Sending image list Pushing repository 192.168.7.26:5000/test (1 tags) Image 511136ea3c5a already pushed, skipping Image 9bad880da3d2 already pushed, skipping Image 25f11f5fb0cb already pushed, skipping Image ebc34468f71d already pushed, skipping Image 2318d26665ef already pushed, skipping Image ba5877dc9bec already pushed, skipping Pushing tag for rev [ba5877dc9bec] on {http://192.168.7.26:5000/v1/repositories/test/tags/latest} 用 curl 查看仓库中的镜像。 $ curl http://192.168.7.26:5000/v1/search {"num_results": 7, "query": "", "results": [{"description": "", "name": "library/miaxis_j2ee"}, {"description": "", "name": "library/tomcat"}, {"description": "", "name": "library/ubuntu"}, {"description": "", "name": "library/ubuntu_office"}, {"description": "", "name": "library/desktop_ubu"}, {"description": "", "name": "dockerfile/ubuntu"}, {"description": "", "name": "library/test"}]} 这里可以看到 {“description”: “”, “name”: “library/test”},表明镜像已经被成功上传了。 现在可以到另外一台机器去下载这个镜像。 $ sudo docker pull 192.168.7.26:5000/test Pulling repository 192.168.7.26:5000/test ba5877dc9bec: Download complete 511136ea3c5a: Download complete 9bad880da3d2: Download complete 25f11f5fb0cb: Download complete ebc34468f71d: Download complete 2318d26665ef: Download complete $ sudo docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE 192.168.7.26:5000/test latest ba5877dc9bec 6 weeks ago 192.7 MB 可以使用 这个脚本 批量上传本地的镜像到注册服务器中,默认是本地注册服务器 127.0.0.1:5000。例如: $ wget https://github.com/yeasy/docker_practice/raw/master/_local/push_images.sh; sudo chmod a+x push_images.sh $ ./push_images.sh ubuntu:latest centos:centos7 The registry server is 127.0.0.1 Uploading ubuntu:latest... The push refers to a repository [127.0.0.1:5000/ubuntu] (len: 1) Sending image list Pushing repository 127.0.0.1:5000/ubuntu (1 tags) Image 511136ea3c5a already pushed, skipping Image bfb8b5a2ad34 already pushed, skipping Image c1f3bdbd8355 already pushed, skipping Image 897578f527ae already pushed, skipping Image 9387bcc9826e already pushed, skipping Image 809ed259f845 already pushed, skipping Image 96864a7d2df3 already pushed, skipping Pushing tag for rev [96864a7d2df3] on {http://127.0.0.1:5000/v1/repositories/ubuntu/tags/latest} Untagged: 127.0.0.1:5000/ubuntu:latest Done Uploading centos:centos7... The push refers to a repository [127.0.0.1:5000/centos] (len: 1) Sending image list Pushing repository 127.0.0.1:5000/centos (1 tags) Image 511136ea3c5a already pushed, skipping 34e94e67e63a: Image successfully pushed 70214e5d0a90: Image successfully pushed Pushing tag for rev [70214e5d0a90] on {http://127.0.0.1:5000/v1/repositories/centos/tags/centos7} Untagged: 127.0.0.1:5000/centos:centos7 Done 仓库配置文件Docker 的 Registry 利用配置文件提供了一些仓库的模板(flavor),用户可以直接使用它们来进行开发或生产部署。 模板在 config_sample.yml 文件中,可以看到一些现成的模板段: common:基础配置 local:存储数据到本地文件系统 s3:存储数据到 AWS S3 中 dev:使用 local 模板的基本配置 test:单元测试使用 prod:生产环境配置(基本上跟s3配置类似) gcs:存储数据到 Google 的云存储 swift:存储数据到 OpenStack Swift 服务 glance:存储数据到 OpenStack Glance 服务,本地文件系统为后备 glance-swift:存储数据到 OpenStack Glance 服务,Swift 为后备 elliptics:存储数据到 Elliptics key/value 存储 用户也可以添加自定义的模版段。 默认情况下使用的模板是 dev,要使用某个模板作为默认值,可以添加 SETTINGS_FLAVOR 到环境变量中,例如 export SETTINGS_FLAVOR=dev 另外,配置文件中支持从环境变量中加载值,语法格式为 _env:VARIABLENAME[:DEFAULT]。 示例配置common: loglevel: info search_backend: "_env:SEARCH_BACKEND:" sqlalchemy_index_database: "_env:SQLALCHEMY_INDEX_DATABASE:sqlite:////tmp/docker-registry.db" prod: loglevel: warn storage: s3 s3_access_key: _env:AWS_S3_ACCESS_KEY s3_secret_key: _env:AWS_S3_SECRET_KEY s3_bucket: _env:AWS_S3_BUCKET boto_bucket: _env:AWS_S3_BUCKET storage_path: /srv/docker smtp_host: localhost from_addr: docker@myself.com to_addr: my@myself.com dev: loglevel: debug storage: local storage_path: /home/myself/docker test: storage: local storage_path: /tmp/tmpdockertmp","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"},{"name":"docker仓库","slug":"docker仓库","permalink":"http://yelog.org/tags/docker%E4%BB%93%E5%BA%93/"}]},{"title":"每天一个linux命令(58): sort","slug":"linux-command-58-sort","date":"2017-05-23T03:44:12.000Z","updated":"2024-07-05T11:10:23.047Z","comments":true,"path":"2017/05/23/linux-command-57-sort/","permalink":"http://yelog.org/2017/05/23/linux-command-57-sort/","excerpt":"","text":"sort是在Linux里非常常用的一个命令,管排序的。 1.工作原理sort将文件的每一行作为一个单位,相互比较,比较原则是从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出。 $ cat seq.txt apple allow photo bowl peal check cheese $ sort seq.txt allow apple bowl check cheese peal photo 2.sort的-u选项它的作用很简单,就是在输出行中去除重复行。 3.sort的-r选项sort默认的排序方式是升序,如果想改成降序,就加个-r就搞定了。 4.sort的-o选项由于sort默认是把结果输出到标准输出,所以需要用重定向才能将结果写入文件,形如sort filename > newfile。 但是,如果你想把排序结果输出到原文件中,用重定向可就不行了。 [rocrocket@rocrocket programming]$ sort -r number.txt > number.txt [rocrocket@rocrocket programming]$ cat number.txt [rocrocket@rocrocket programming]$ 看,竟然将number清空了。 就在这个时候,-o选项出现了,它成功的解决了这个问题,让你放心的将结果写入原文件。这或许也是-o比重定向的唯一优势所在。 5.sort的-n选项你有没有遇到过10比2小的情况。我反正遇到过。出现这种情况是由于排序程序将这些数字按字符来排序了,排序程序会先比较1和2,显然1小,所以就将10放在2前面喽。这也是sort的一贯作风。 我们如果想改变这种现状,就要使用-n选项,来告诉sort,“要以数值来排序”! 6.sort的-t选项和-k选项如果有一个文件的内容是这样: [rocrocket@rocrocket programming]$ cat facebook.txt banana:30:5.5 apple:10:2.5 pear:90:2.3 orange:20:3.4 这个文件有三列,列与列之间用冒号隔开了,第一列表示水果类型,第二列表示水果数量,第三列表示水果价格。 那么我想以水果数量来排序,也就是以第二列来排序,如何利用sort实现? 幸好,sort提供了-t选项,后面可以设定间隔符。(是不是想起了cut和paste的-d选项,共鸣~~) 指定了间隔符之后,就可以用-k来指定列数了。 [rocrocket@rocrocket programming]$ sort -n -k 2 -t : facebook.txt apple:10:2.5 orange:20:3.4 banana:30:5.5 pear:90:2.3 我们使用冒号作为间隔符,并针对第二列来进行数值升序排序,结果很令人满意。 7.其他的sort常用选项-f会将小写字母都转换为大写字母来进行比较,亦即忽略大小写 -c会检查文件是否已排好序,如果乱序,则输出第一个乱序的行的相关信息,最后返回1 -C会检查文件是否已排好序,如果乱序,不输出内容,仅返回1 -M会以月份来排序,比如JAN小于FEB等等 -b会忽略每一行前面的所有空白部分,从第一个可见字符开始比较。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"CentOS修改DNS/GW/IP","slug":"CentOS修改DNS-GW-IP","date":"2017-05-23T01:53:52.000Z","updated":"2024-07-05T11:10:23.083Z","comments":true,"path":"2017/05/23/CentOS-DNS-GW-IP/","permalink":"http://yelog.org/2017/05/23/CentOS-DNS-GW-IP/","excerpt":"","text":"1.修改DNS解决方案一:修改网卡的DNS的配置文件 $ vim /etc/resolv.conf 添加以下内容,设置两条dns nameserver 8.8.8.8 #google域名服务器 nameserver 8.8.4.4 #google域名服务器 若未生效,可执行 chattr +i /etc/resolv.conf 设置文件属性只有root用户才能修改然后执行 service NetworkManager restart 解决方案二:对接口添加dns信息;编辑/etc/sysconfig/network-scripts/ifcfg-xxx,xxx为你的网卡名,但一般是ifcfg-eth0的,具体的xxx根据你的网卡确定,在最下面添加: DNS1=8.8.8.8 #google dns服务器, 根据实际情况更换 DNS2=8.8.4.4 #google dns服务器, 根据实际情况更换 保存后重启网络 $ service network restart 2.修改网关修改网关的配置文件(第3部分也可以设置) $ vim /etc/sysconfig/network 修改为一下内容 NETWORKING=yes(表示系统是否使用网络,一般设置为yes。如果设为no,则不能使用网络,而且很多系统服务程序将无法启动) HOSTNAME=centos(设置本机的主机名,这里设置的主机名要和/etc/hosts中设置的主机名对应) GATEWAY=192.168.1.1(设置本机连接的网关的IP地址。例如,网关为10.0.0.2) 3.修改ip修改对应的网卡的IP地址的配置文件 $ vim /etc/sysconfig/network-scripts/ifcfg-eth0 修改为一下内容 DEVICE=eth0 #描述网卡对应的设备别名,例如ifcfg-eth0的文件中它为eth0 BOOTPROTO=static #设置网卡获得ip地址的方式,可能的选项为static,dhcp或bootp,分别对应静态指定的 ip地址,通过dhcp协议获得的ip地址,通过bootp协议获得的ip地址 BROADCAST=192.168.0.255 #对应的子网广播地址 HWADDR=00:07:E9:05:E8:B4 #对应的网卡物理地址 IPADDR=12.168.1.2 #如果设置网卡获得 ip地址的方式为静态指定,此字段就指定了网卡对应的ip地址 IPV6INIT=no IPV6_AUTOCONF=no NETMASK=255.255.255.0 #网卡对应的网络掩码 NETWORK=192.168.1.0 #网卡对应的网络地址 ONBOOT=yes #系统启动时是否设置此网络接口,设置为yes时,系统启动时激活此设备","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/tags/%E8%BF%90%E7%BB%B4/"},{"name":"centos","slug":"centos","permalink":"http://yelog.org/tags/centos/"}]},{"title":"Dockerfile指令详解","slug":"Dockerfile指令详解","date":"2017-05-22T11:11:42.000Z","updated":"2024-07-05T11:10:22.222Z","comments":true,"path":"2017/05/22/Dockerfile/","permalink":"http://yelog.org/2017/05/22/Dockerfile/","excerpt":"","text":"Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。 构建镜像命令格式: $ docker build [选项] <上下文路径/URL/-> 示例: # 构建一个名为 nginx:v3 的镜像 $ docker build -t nginx:v3 . RUN 执行命令 shell格式:RUN <命令>,就像直接在命令行中输入的命令一样。刚才写的 Dockrfile 中的 RUN 指令就是这种格式。 RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html exec格式:RUN [“可执行文件”, “参数1”, “参数2”],这更像是函数调用中的格式。 COPY 复制文件格式: COPY <源路径>... <目标路径> COPY ["<源路径1>",... "<目标路径>"]和 RUN 指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。比如: COPY package.json /usr/src/app/ <源路径> 可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match 规则,如: COPY hom* /mydir/ COPY hom?.txt /mydir/ <目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。 此外,还需要注意一点,使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。 ADD更高级的复制文件ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。 比如 <源路径> 可以是一个 URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600,如果这并不是想要的权限,那么还需要增加额外的一层 RUN 进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN 指令进行解压缩。所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。 如果 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去。 在 Docker 官方的最佳实践文档中要求,尽可能的使用 COPY 。 因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。 CMD 容器启动命令CMD 指令的格式和 RUN 相似,也是两种格式:1) shell 格式:CMD <命令>2) exec 格式:CMD ["可执行文件", "参数1", "参数2"...]3) 参数列表格式:CMD ["参数1", "参数2"...]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。 Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器主进程的启动命令的。 在运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如,ubuntu 镜像默认的 CMD 是 /bin/bash,如果我们直接 docker run -it ubuntu 的话,会直接进入 bash。我们也可以在运行时指定运行别的命令,如 docker run -it ubuntu cat /etc/os-release。这就是用 cat /etc/os-release 命令替换了默认的 /bin/bash 命令了,输出了系统版本信息。 在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 “,而不要使用单引号。 ENTRYPOINT 入口点ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 –entrypoint 来指定。 当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为: <ENTRYPOINT> "<CMD>" 场景一:让镜像变成像命令一样使用 FROM ubuntu:16.04 RUN apt-get update \\ && apt-get install -y curl \\ && rm -rf /var/lib/apt/lists/* ENTRYPOINT [ "curl", "-s", "http://ip.cn" ] $ docker run myip -i 这是因为当存在 ENTRYPOINT 后,CMD 的内容将会作为参数传给 ENTRYPOINT,而这里 -i 就是新的 CMD,因此会作为参数传给 curl,从而达到了我们预期的效果。 场景二:应用运行前的准备工作可以写一个脚本,然后放入 ENTRYPOINT 中去执行,而这个脚本会将接到的参数(也就是 )作为命令,在脚本最后执行。比如官方镜像 redis 中就是这么做的: FROM alpine:3.4 ... RUN addgroup -S redis && adduser -S -G redis redis ... ENTRYPOINT ["docker-entrypoint.sh"] EXPOSE 6379 CMD [ "redis-server" ] 可以看到其中为了 redis 服务创建了 redis 用户,并在最后指定了 ENTRYPOINT 为 docker-entrypoint.sh 脚本。 #!/bin/sh ... # allow the container to be started with `--user` if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then chown -R redis . exec su-exec redis "$0" "$@" fi exec "$@" 该脚本的内容就是根据 CMD 的内容来判断,如果是 redis-server 的话,则切换到 redis 用户身份启动服务器,否则依旧使用 root 身份执行。比如: $ docker run -it redis id uid=0(root) gid=0(root) groups=0(root) ENV 设置环境变量格式有两种: ENV <key> <value> ENV <key1>=<value1> <key2>=<value2>... 这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,如 RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。 ENV VERSION=1.0 DEBUG=on \\ NAME="Happy Feet" 这个例子中演示了如何换行,以及对含有空格的值用双引号括起来的办法,这和 Shell 下的行为是一致的。 定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。比如在官方 node 镜像 Dockerfile 中,就有类似这样的代码: ENV NODE_VERSION 7.2.0 RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \\ && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \\ && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \\ && grep " node-v$NODE_VERSION-linux-x64.tar.xz\\$" SHASUMS256.txt | sha256sum -c - \\ && tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \\ && rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \\ && ln -s /usr/local/bin/node /usr/local/bin/nodejs 在这里先定义了环境变量 NODE_VERSION,其后的 RUN 这层里,多次使用 $NODE_VERSION 来进行操作定制。可以看到,将来升级镜像构建版本的时候,只需要更新 7.2.0 即可,Dockerfile 构建维护变得更轻松了。 下列指令可以支持环境变量展开: ADD、COPY、ENV、EXPOSE、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD。 可以从这个指令列表里感觉到,环境变量可以使用的地方很多,很强大。通过环境变量,我们可以让一份 Dockerfile 制作更多的镜像,只需使用不同的环境变量即可。 ARG 构建参数格式:ARG <参数名>[=<默认值>] 构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。 Dockerfile 中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build 中用 –build-arg <参数名>=<值> 来覆盖。 在 1.13 之前的版本,要求 –build-arg 中的参数名,必须在 Dockerfile 中用 ARG 定义过了,换句话说,就是 –build-arg 指定的参数,必须在 Dockerfile 中使用了。如果对应参数没有被使用,则会报错退出构建。从 1.13 开始,这种严格的限制被放开,不再报错退出,而是显示警告信息,并继续构建。这对于使用 CI 系统,用同样的构建流程构建不同的 Dockerfile 的时候比较有帮助,避免构建命令必须根据每个 Dockerfile 的内容修改。 VOLUME 定义匿名卷格式为: VOLUME ["<路径1>", "<路径2>"...] VOLUME <路径> 之前我们说过,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,后面的章节我们会进一步介绍 Docker 卷的概念。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。 VOLUME /data 这里的 /data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如: $ docker run -d -v mydata:/data xxxx 在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。 EXPOSE 声明端口格式为 EXPOSE <端口1> [<端口2>...]。 EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。 此外,在早期 Docker 版本中还有一个特殊的用处。以前所有容器都运行于默认桥接网络中,因此所有容器互相之间都可以直接访问,这样存在一定的安全性问题。于是有了一个 Docker 引擎参数 –icc=false,当指定该参数后,容器间将默认无法互访,除非互相间使用了 –links 参数的容器才可以互通,并且只有镜像中 EXPOSE 所声明的端口才可以被访问。这个 –icc=false 的用法,在引入了 docker network 后已经基本不用了,通过自定义网络可以很轻松的实现容器间的互联与隔离。 要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。-p,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。 WORKDIR 指定工作目录格式为 WORKDIR <工作目录路径>。 使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。 之前提到一些初学者常犯的错误是把 Dockerfile 等同于 Shell 脚本来书写,这种错误的理解还可能会导致出现下面这样的错误: RUN cd /app RUN echo "hello" > world.txt 如果将这个 Dockerfile 进行构建镜像运行后,会发现找不到 /app/world.txt 文件,或者其内容不是 hello。原因其实很简单,在 Shell 中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令;而在 Dockerfile 中,这两行 RUN 命令的执行环境根本不同,是两个完全不同的容器。这就是对 Dokerfile 构建分层存储的概念不了解所导致的错误。 之前说过每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。第一层 RUN cd /app 的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。 因此如果需要改变以后各层的工作目录的位置,那么应该使用 WORKDIR 指令。 USER 指定当前用户格式:USER <用户名> USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。 当然,和 WORKDIR 一样,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。 RUN groupadd -r redis && useradd -r -g redis redis USER redis RUN [ "redis-server" ] 如果以 root 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用 su 或者 sudo,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu,可以从其项目网站看到进一步的信息:https://github.com/tianon/gosu # 建立 redis 用户,并使用 gosu 换另一个用户执行命令 RUN groupadd -r redis && useradd -r -g redis redis # 下载 gosu RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" \\ && chmod +x /usr/local/bin/gosu \\ && gosu nobody true # 设置 CMD,并以另外的用户执行 CMD [ "exec", "gosu", "redis", "redis-server" ] HEALTHCHECK 健康检查格式: HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令 HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令 HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12 引入的新指令。 在没有 HEALTHCHECK 指令前,Docker 引擎只可以通过容器内主进程是否退出来判断容器是否状态异常。很多情况下这没问题,但是如果程序进入死锁状态,或者死循环状态,应用进程并不退出,但是该容器已经无法提供服务了。在 1.12 以前,Docker 不会检测到容器的这种状态,从而不会重新调度,导致可能会有部分容器已经无法提供服务了却还在接受用户请求。 而自 1.12 之后,Docker 提供了 HEALTHCHECK 指令,通过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。 当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting,在 HEALTHCHECK 指令检查成功后变为 healthy,如果连续一定次数失败,则会变为 unhealthy。 HEALTHCHECK 支持下列选项: --interval=<间隔>:两次健康检查的间隔,默认为 30 秒; --timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;3)--retries=<次数>:当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次。 和 CMD, ENTRYPOINT 一样,HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。 在 HEALTHCHECK [选项] CMD 后面的命令,格式和 ENTRYPOINT 一样,分为 shell 格式,和 exec 格式。命令的返回值决定了该次健康检查的成功与否:0:成功;1:失败;2:保留,不要使用这个值。 假设我们有个镜像是个最简单的 Web 服务,我们希望增加健康检查来判断其 Web 服务是否在正常工作,我们可以用 curl 来帮助判断,其 Dockerfile 的 HEALTHCHECK 可以这么写: FROM nginx RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* HEALTHCHECK --interval=5s --timeout=3s \\ CMD curl -fs http://localhost/ || exit 1 这里我们设置了每 5 秒检查一次(这里为了试验所以间隔非常短,实际应该相对较长),如果健康检查命令超过 3 秒没响应就视为失败,并且使用 curl -fs http://localhost/ || exit 1 作为健康检查命令。 使用 docker build 来构建这个镜像: $ docker build -t myweb:v1 . 构建好了后,我们启动一个容器: $ docker run -d --name web -p 80:80 myweb:v1 当运行该镜像后,可以通过 docker ps 看到最初的状态为 (health: starting): $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 3 seconds ago Up 2 seconds (health: starting) 80/tcp, 443/tcp web 在等待几秒钟后,再次 docker ps,就会看到健康状态变化为了 (healthy): $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 18 seconds ago Up 16 seconds (healthy) 80/tcp, 443/tcp web 如果健康检查连续失败超过了重试次数,状态就会变为 (unhealthy)。 为了帮助排障,健康检查命令的输出(包括 stdout 以及 stderr)都会被存储于健康状态里,可以用 docker inspect 来查看。 $ docker inspect --format '{{json .State.Health}}' web | python -m json.tool { "FailingStreak": 0, "Log": [ { "End": "2016-11-25T14:35:37.940957051Z", "ExitCode": 0, "Output": "<!DOCTYPE html>\\n<html>\\n<head>\\n<title>Welcome to nginx!</title>\\n<style>\\n body {\\n width: 35em;\\n margin: 0 auto;\\n font-family: Tahoma, Verdana, Arial, sans-serif;\\n }\\n</style>\\n</head>\\n<body>\\n<h1>Welcome to nginx!</h1>\\n<p>If you see this page, the nginx web server is successfully installed and\\nworking. Further configuration is required.</p>\\n\\n<p>For online documentation and support please refer to\\n<a href=\\"http://nginx.org/\\">nginx.org</a>.<br/>\\nCommercial support is available at\\n<a href=\\"http://nginx.com/\\">nginx.com</a>.</p>\\n\\n<p><em>Thank you for using nginx.</em></p>\\n</body>\\n</html>\\n", "Start": "2016-11-25T14:35:37.780192565Z" } ], "Status": "healthy" } ONBUILD 为他人做嫁衣裳格式:ONBUILD <其它指令>。 ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。 Dockerfile 中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD 是为了帮助别人定制自己而准备的。 假设我们要制作 Node.js 所写的应用的镜像。我们都知道 Node.js 使用 npm 进行包管理,所有依赖、配置、启动信息等会放到 package.json 文件里。在拿到程序代码后,需要先进行 npm install 才可以获得所有需要的依赖。然后就可以通过 npm start 来启动应用。因此,一般来说会这样写 Dockerfile: FROM node:slim RUN "mkdir /app" WORKDIR /app COPY ./package.json /app RUN [ "npm", "install" ] COPY . /app/ CMD [ "npm", "start" ] 把这个 Dockerfile 放到 Node.js 项目的根目录,构建好镜像后,就可以直接拿来启动容器运行。但是如果我们还有第二个 Node.js 项目也差不多呢?好吧,那就再把这个 Dockerfile 复制到第二个项目里。那如果有第三个项目呢?再复制么?文件的副本越多,版本控制就越困难,让我们继续看这样的场景维护的问题。 如果第一个 Node.js 项目在开发过程中,发现这个 Dockerfile 里存在问题,比如敲错字了、或者需要安装额外的包,然后开发人员修复了这个 Dockerfile,再次构建,问题解决。第一个项目没问题了,但是第二个项目呢?虽然最初 Dockerfile 是复制、粘贴自第一个项目的,但是并不会因为第一个项目修复了他们的 Dockerfile,而第二个项目的 Dockerfile 就会被自动修复。 那么我们可不可以做一个基础镜像,然后各个项目使用这个基础镜像呢?这样基础镜像更新,各个项目不用同步 Dockerfile 的变化,重新构建后就继承了基础镜像的更新?好吧,可以,让我们看看这样的结果。那么上面的这个 Dockerfile 就会变为: FROM node:slim RUN "mkdir /app" WORKDIR /app CMD [ "npm", "start" ] 这里我们把项目相关的构建指令拿出来,放到子项目里去。假设这个基础镜像的名字为 my-node 的话,各个项目内的自己的 Dockerfile 就变为: FROM my-node COPY ./package.json /app RUN [ "npm", "install" ] COPY . /app/ 基础镜像变化后,各个项目都用这个 Dockerfile 重新构建镜像,会继承基础镜像的更新。 那么,问题解决了么?没有。准确说,只解决了一半。如果这个 Dockerfile 里面有些东西需要调整呢?比如 npm install 都需要加一些参数,那怎么办?这一行 RUN 是不可能放入基础镜像的,因为涉及到了当前项目的 ./package.json,难道又要一个个修改么?所以说,这样制作基础镜像,只解决了原来的 Dockerfile 的前4条指令的变化问题,而后面三条指令的变化则完全没办法处理。 ONBUILD 可以解决这个问题。让我们用 ONBUILD 重新写一下基础镜像的 Dockerfile: FROM node:slim RUN "mkdir /app" WORKDIR /app ONBUILD COPY ./package.json /app ONBUILD RUN [ "npm", "install" ] ONBUILD COPY . /app/ CMD [ "npm", "start" ] 这次我们回到原始的 Dockerfile,但是这次将项目相关的指令加上 ONBUILD,这样在构建基础镜像的时候,这三行并不会被执行。然后各个项目的 Dockerfile 就变成了简单地: FROM my-node 是的,只有这么一行。当在各个项目目录中,用这个只有一行的 Dockerfile 构建镜像时,之前基础镜像的那三行 ONBUILD 就会开始执行,成功的将当前项目的代码复制进镜像、并且针对本项目执行 npm install,生成应用镜像。 ReferenceDocker–从入门到实践: https://yeasy.gitbooks.io/docker_practice/content/Dockerfie 官方文档:https://docs.docker.com/engine/reference/builder/Dockerfile 最佳实践文档:https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"docker镜像","slug":"docker镜像","date":"2017-05-19T08:47:33.000Z","updated":"2024-07-05T11:10:22.236Z","comments":true,"path":"2017/05/19/docker-image/","permalink":"http://yelog.org/2017/05/19/docker-image/","excerpt":"","text":"WhatDocker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。 因为镜像包含操作系统完整的 root 文件系统,其体积往往是庞大的,因此在 Docker 设计时,就充分利用 Union FS 的技术,将其设计为分层存储的架构。所以严格来说,镜像并非是像一个 ISO 那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。 安装# 官方 的安装脚本 $ curl -sSL https://get.docker.com/ | sh # 阿里云 的安装脚本 $ curl -sSL http://acs-public-mirror.oss-cn-hangzhou.aliyuncs.com/docker-engine/internet | sh - # DaoCloud 的安装脚本 $ curl -sSL https://get.daocloud.io/docker | sh 镜像# 获取镜像,registry为空默认从Docker Hub上获取 docker pull [选项] [Docker Registry地址]<仓库名>:<标签> # 交互式运行,退出删除: -i:交互式 ,-t:终端,--rm 退出删除 ,bash 启动bash窗口 $ docker run -it --rm ubuntu:14.04 bash # 列出已下载的镜像(只显示顶层镜像) -a:显示所有镜像 image_name:指定列出某个镜像 $ docker images [-a] [image_name] # 只显示虚悬镜像(dangling image) -f:--filter 过滤 $ docker images -f dangling=true # 过滤从mongo:3.2建立之后的镜像 $ docker images -f since=mongo:3.2 # 通过label过滤 $ docker images -f label=com.example.version=0.1 # 只显示镜像id $ docker images -q # 只包含镜像ID和仓库名 $ docker images --format "{{.ID}}: {{.Repository}}" # 以表格等距显示 有标题行,和默认一样,不过自己定义列 $ docker images --format "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}" # 删除镜像ID为image_id的镜像 $ docker rmi <image_id> # 删除虚悬镜像 $ docker rmi $(docker images -q -f dangling=true) # 将容器保存为镜像 $ docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]] # 将容器保存为镜像 $ docker commit \\ --author "Tao Wang <twang2218@gmail.com>" \\ --message "修改了默认网页" \\ webserver \\ nginx:v2 $ docker history nginx:v2","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"docker初体验","slug":"docker初体验","date":"2017-05-19T08:32:23.000Z","updated":"2024-07-05T11:10:22.240Z","comments":true,"path":"2017/05/19/docker-first/","permalink":"http://yelog.org/2017/05/19/docker-first/","excerpt":"","text":"安装笔者环境操作系统:deepin 15.4 Desktop 64Bit 安装# 官方 的安装脚本 $ curl -sSL https://get.docker.com/ | sh # 阿里云 的安装脚本 $ curl -sSL http://acs-public-mirror.oss-cn-hangzhou.aliyuncs.com/docker-engine/internet | sh - # DaoCloud 的安装脚本 $ curl -sSL https://get.daocloud.io/docker | sh 获取镜像Docker Hub 上有大量的高质量的镜像可以用,这里我们就说一下怎么获取这些镜像并运行。从 Docker Registry 获取镜像的命令是 docker pull。其命令格式为: $ docker pull [选项] [Docker Registry地址]<仓库名>:<标签> 具体的选项可以通过 docker pull --help 命令看到,这里我们说一下镜像名称的格式。 Docker Registry地址:地址的格式一般是 <域名/IP>[:端口号]。默认地址是 Docker Hub。 仓库名:如之前所说,这里的仓库名是两段式名称,既 <用户名>/<软件名>。对于 Docker Hub,如果不给出用户名,则默认为 library,也就是官方镜像。 $ sudo docker pull ubuntu 运行有了镜像后,我们就可以以这个镜像为基础启动一个容器来运行。以上面的 ubuntu 为例,如果我们打算启动里面的 bash 并且进行交互式操作的话,可以执行下面的命令。 $ sudo docker run -it --rm ubuntu root@0ae011f7b5be:/# cat /etc/os-release NAME="Ubuntu" VERSION="16.04.2 LTS (Xenial Xerus)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 16.04.2 LTS" VERSION_ID="16.04" HOME_URL="http://www.ubuntu.com/" SUPPORT_URL="http://help.ubuntu.com/" BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/" VERSION_CODENAME=xenial UBUNTU_CODENAME=xenial docker run 就是运行容器的命令 -it:这是两个参数,一个是 -i:交互式操作,一个是 -t 终端。我们这里打算进入 bash 执行一些命令并查看返回结果,因此我们需要交互式终端。 --rm:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 –rm 可以避免浪费空间。 ubuntu:这是指用 ubuntu 镜像为基础来启动容器。 bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 bash。 进入容器后,我们可以在 Shell 下操作,执行任何所需的命令。这里,我们执行了 cat /etc/os-release,这是 Linux 常用的查看当前系统版本的命令,从返回的结果可以看到容器内是 Ubuntu 16.04.2 LTS 系统。 最后通过 exit 退出了这个容器。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"}]},{"title":"linux无损调整分区大小","slug":"linux无损调整分区大小","date":"2017-05-17T14:00:42.000Z","updated":"2024-07-05T11:10:23.078Z","comments":true,"path":"2017/05/17/linux无损调整分区大小/","permalink":"http://yelog.org/2017/05/17/linux%E6%97%A0%E6%8D%9F%E8%B0%83%E6%95%B4%E5%88%86%E5%8C%BA%E5%A4%A7%E5%B0%8F/","excerpt":"","text":"summary 系统环境: Red Hat 4.8.5-11 情况: home:500G root:50G root分区不够用 思路:把home分区的空间划一部分到root分区 # 设置home分区大小为200G,释放300G空间 $ lvreduce -L 200G /dev/centos/home # 将空闲空间扩展到root分区 $ lvextend -l +100%FREE /dev/centos/root # 使用XFS文件系统自带的命令集增加分区空间 $ xfs_growfs /dev/mapper/centos-root 实例situation挂载在根目录的分区 /dev/mapper/centos-root 爆满,占用100% $ df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/centos-root 50G 50G 19M 100% / devtmpfs 32G 0 32G 0% /dev tmpfs 32G 0 32G 0% /dev/shm tmpfs 32G 2.5G 29G 8% /run tmpfs 32G 0 32G 0% /sys/fs/cgroup /dev/mapper/centos-home 476G 33M 476G 1% /home /dev/sda1 497M 238M 259M 48% /boot tmpfs 6.3G 0 6.3G 0% /run/user/0 analyze挂载在根目录的分区空间太小,只有50G,而服务器 home 目录为非常用目录,挂在了近500G的空间。 思路:从 centos-home 分区划出300G空间到 centos-root 分区。 operation1.查看各分区信息$ lvdisplay --- Logical volume --- LV Path /dev/centos/home LV Name home VG Name centos LV UUID 1fAt1E-bQsa-1HXR-MCE2-5VZ1-xzBz-iI1SLv LV Write Access read/write LV Creation host, time localhost, 2016-10-26 17:23:47 +0800 LV Status available # open 0 LV Size 475.70 GiB Current LE 121778 Segments 1 Allocation inherit Read ahead sectors auto - currently set to 256 Block device 253:2 --- Logical volume --- LV Path /dev/centos/root LV Name root VG Name centos LV UUID lD64zY-yc3Z-SZaB-dAjK-03YM-2gM8-pfj4oo LV Write Access read/write LV Creation host, time localhost, 2016-10-26 17:23:48 +0800 LV Status available # open 1 LV Size 50.00 GiB Current LE 12800 Segments 1 Allocation inherit Read ahead sectors auto - currently set to 256 Block device 253:0 2.减少/home分区空间# 释放 /dev/centos/home 分区 300G 的空间 # 命令设置 /dev/centos/home 分区 200G空间 $ lvreduce -L 200G /dev/centos/home WARNING: Reducing active logical volume to 200.00 GiB. THIS MAY DESTROY YOUR DATA (filesystem etc.) Do you really want to reduce centos/home? [y/n]: y Size of logical volume centos/home changed from 475.70 GiB (121778 extents) to 200.00 GiB (51200 extents). Logical volume centos/home successfully resized. 3.增加/root分区空间$ lvextend -l +100%FREE /dev/centos/root Size of logical volume centos/root changed from 50.06 GiB (12816 extents) to 325.76 GiB (83394 extents). Logical volume centos/root successfully resized. 4.扩展XFS文件空间大小$ xfs_growfs /dev/mapper/centos-root meta-data=/dev/mapper/centos-root isize=256 agcount=4, agsize=3276800 blks = sectsz=512 attr=2, projid32bit=1 = crc=0 finobt=0 spinodes=0 data = bsize=4096 blocks=13107200, imaxpct=25 = sunit=0 swidth=0 blks naming =version 2 bsize=4096 ascii-ci=0 ftype=0 log =internal bsize=4096 blocks=6400, version=2 = sectsz=512 sunit=0 blks, lazy-count=1 realtime =none extsz=4096 blocks=0, rtextents=0 data blocks changed from 13107200 to 85395456 完成","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/tags/%E8%BF%90%E7%BB%B4/"},{"name":"磁盘分区","slug":"磁盘分区","permalink":"http://yelog.org/tags/%E7%A3%81%E7%9B%98%E5%88%86%E5%8C%BA/"}]},{"title":"Git统计操作","slug":"git-log","date":"2017-05-16T01:26:11.000Z","updated":"2024-07-05T11:10:22.612Z","comments":true,"path":"2017/05/16/git-log/","permalink":"http://yelog.org/2017/05/16/git-log/","excerpt":"","text":"按commit统计# 统计当前作者今天(从凌晨1点开始)提交次数 $ git log --author="$(git config --get user.name)" --no-merges --since=1am --stat # 按提交作者统计,按提交次数排序 $ git shortlog -sn $ git shortlog --numbered --summary # 只看某作者提交的commit数 $ git log --author="faker" --oneline --shortstat # 按提交作者统计,提交数量排名前5(看全部,去掉head管道即可) $ git log --pretty='%aN' | sort | uniq -c | sort -k1 -n -r | head -n 5 # 按提交者邮箱统计,提交数量排名前5 $ git log --pretty=format:%ae | gawk -- '{ ++c[$0]; } END { for(cc in c) printf "%5d %s\\n",c[cc],cc; }' | sort -u -n -r | head -n 5 # 统计贡献者数量 $ git log --pretty='%aN' | sort -u | wc -l # 统计提交数量 $ git log --oneline | wc -l 按代码行数统计# 统计指定作者增删行数 $ git log --author="faker" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\\n", add, subs, loc }' - # 统计当前作者增删行数 $ git log --author="$(git config --get user.name)" --pretty=tformat: --numstat | gawk '{ add += $1 ; subs += $2 ; loc += $1 - $2 } END { printf "added lines: %s removed lines : %s total lines: %s\\n",add,subs,loc }' - # 统计所有邮箱前缀的增删行数 -英文版 $ git log --shortstat --pretty="%cE" | sed 's/\\(.*\\)@.*/\\1/' | grep -v "^$" | awk 'BEGIN { line=""; } !/^ / { if (line=="" || !match(line, $0)) {line = $0 "," line }} /^ / { print line " # " $0; line=""}' | sort | sed -E 's/# //;s/ files? changed,//;s/([0-9]+) ([0-9]+ deletion)/\\1 0 insertions\\(+\\), \\2/;s/\\(\\+\\)$/\\(\\+\\), 0 deletions\\(-\\)/;s/insertions?\\(\\+\\), //;s/ deletions?\\(-\\)//' | awk 'BEGIN {name=""; files=0; insertions=0; deletions=0;} {if ($1 != name && name != "") { print name ": " files " files changed, " insertions " insertions(+), " deletions " deletions(-), " insertions-deletions " net"; files=0; insertions=0; deletions=0; name=$1; } name=$1; files+=$2; insertions+=$3; deletions+=$4} END {print name ": " files " files changed, " insertions " insertions(+), " deletions " deletions(-), " insertions-deletions " net";}' # 统计所有邮箱前缀的增删行数 -中文版 $ git log --shortstat --pretty="%cE" | sed 's/\\(.*\\)@.*/\\1/' | grep -v "^$" | awk 'BEGIN { line=""; } !/^ / { if (line=="" || !match(line, $0)) {line = $0 "," line }} /^ / { print line " # " $0; line=""}' | sort | sed -E 's/# //;s/ files? changed,//;s/([0-9]+) ([0-9]+ deletion)/\\1 0 insertions\\(+\\), \\2/;s/\\(\\+\\)$/\\(\\+\\), 0 deletions\\(-\\)/;s/insertions?\\(\\+\\), //;s/ deletions?\\(-\\)//' | awk 'BEGIN {name=""; files=0; insertions=0; deletions=0;} {if ($1 != name && name != "") { print name ": " files " 个文件被改变, " insertions " 行被插入(+), " deletions " 行被删除(-), " insertions-deletions " 行剩余"; files=0; insertions=0; deletions=0; name=$1; } name=$1; files+=$2; insertions+=$3; deletions+=$4} END {print name ": " files " 个文件被改变, " insertions " 行被插入(+), " deletions " 行被删除(-), " insertions-deletions " 行剩余";}' # 统计所有作者增删行数 --英文版 $ git log --format='%aN' | sort -u | while read name; do echo -en "$name\\t"; git log --author="$name" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\\n", add, subs, loc }' -; done # 统计所有作者增删行数 --中文版 $ git log --format='%aN' | sort -u | while read name; do echo -en "$name\\t"; git log --author="$name" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "添加行数: %s, 删除行数: %s, 总行数: %s\\n", add, subs, loc }' -; done git log 说明 git log 参数说明:--author 指定作者--stat 显示每次更新的文件修改统计信息,会列出具体文件列表--shortstat 统计每个commit 的文件修改行数,包括增加,删除,但不列出文件列表:--numstat 统计每个commit 的文件修改行数,包括增加,删除,并列出文件列表: -p 选项展开显示每次提交的内容差异,用 -2 则仅显示最近的两次更新 例如:git log -p -2--name-only 仅在提交信息后显示已修改的文件清单--name-status 显示新增、修改、删除的文件清单--abbrev-commit 仅显示 SHA-1 的前几个字符,而非所有的 40 个字符--relative-date 使用较短的相对时间显示(比如,“2 weeks ago”)--graph 显示 ASCII 图形表示的分支合并历史--pretty 使用其他格式显示历史提交信息。可用的选项包括 oneline,short,full,fuller 和 format(后跟指定格式) 例如: git log --pretty=oneline ; git log --pretty=short ; git log --pretty=full ; git log –pretty=fuller--pretty=tformat: 可以定制要显示的记录格式,这样的输出便于后期编程提取分析 例如: git log --pretty=format:""%h - %an, %ar : %s"" 下面列出了常用的格式占位符写法及其代表的意义。 选项 说明 %H 提交对象(commit)的完整哈希字串 %h 提交对象的简短哈希字串 %T 树对象(tree)的完整哈希字串 %t 树对象的简短哈希字串 %P 父对象(parent)的完整哈希字串 %p 父对象的简短哈希字串 %an 作者(author)的名字 %ae 作者的电子邮件地址 %ad 作者修订日期(可以用 -date= 选项定制格式) %ar 作者修订日期,按多久以前的方式显示 %cn 提交者(committer)的名字 %ce 提交者的电子邮件地址 %cd 提交日期 %cr 提交日期,按多久以前的方式显示 %s 提交说明--since 限制显示输出的范围, 例如: git log --since=2.weeks 显示最近两周的提交 选项 说明 -(n) 仅显示最近的 n 条提交 --since, --after 仅显示指定时间之后的提交。 --until, --before 仅显示指定时间之前的提交。 --author 仅显示指定作者相关的提交。 --committer 仅显示指定提交者相关的提交。 一些例子:git log --until=1.minute.ago // 一分钟之前的所有 loggit log --since=1.day.ago //一天之内的loggit log --since=1.hour.ago //一个小时之内的 loggit log --since=1.month.ago --until=2.weeks.ago //一个月之前到半个月之前的loggit log --since ==2013-08.01 --until=2013-09-07 //某个时间段的 loggit blame 看看某一个文件的相关历史记录例如:git blame index.html --date short","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"}],"tags":[{"name":"git","slug":"git","permalink":"http://yelog.org/tags/git/"}]},{"title":"解决linux下zip文件解压乱码","slug":"解决linux下zip文件解压乱码","date":"2017-04-25T01:10:40.000Z","updated":"2024-07-05T11:10:23.092Z","comments":true,"path":"2017/04/25/解决linux下zip文件解压乱码/","permalink":"http://yelog.org/2017/04/25/%E8%A7%A3%E5%86%B3linux%E4%B8%8Bzip%E6%96%87%E4%BB%B6%E8%A7%A3%E5%8E%8B%E4%B9%B1%E7%A0%81/","excerpt":"","text":"原因由于zip格式并没有指定编码格式,Windows下生成的zip文件中的编码是GBK/GB2312等,因此,导致这些zip文件在Linux下解压时出现乱码问题,因为Linux下的默认编码是UTF8。 解决方案使用7z解压。 安装p7zip和convmv # fedora $ su -c 'yum install p7zip convmv' # ubuntu $ sudo apt-get install p7zip convmv 执行一下命令解压缩 # 使用7z解压缩 $ LANG=C 7za x your-zip-file.zip # 递归转码 $ convmv -f GBK -t utf8 --notest -r .","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"加密算法简介","slug":"加密算法简介","date":"2017-04-22T01:30:41.000Z","updated":"2024-07-05T11:10:22.672Z","comments":true,"path":"2017/04/22/encryption-algorithm/","permalink":"http://yelog.org/2017/04/22/encryption-algorithm/","excerpt":"","text":"一、对称密钥算法概述对称加密(Symmetric-key algorithm)是指加解密用同一个密钥的算法,根据具体实现分为流加密和分组加密两种类型: 流加密(Stream cipher)是对称加密常用的一种实现方法,加密和解密双方使用相同伪随机加密数据流,一般都是逐位异或随机密码本的内容。 分组加密加密(Block cipher),也叫块加密,将明文分成多个等长的模块(block),使用确定的算法和对称密钥对每组分别加密解密。现代分组加密建立在迭代的思想上产生密文。迭代产生的密文在每一轮加密中使用不同的子密钥,而子密钥生成自原始密钥。 对称加密普遍比非对称加密速度要快,实现更简单,适合大量内容的加密 DESDES (Data Encryption Standard) 是一种分组加密算法 DES算法的入口参数有三个:Key,Data,Mode,Key是密钥密钥占7个字节56位(64位里另外8位是用来校验的),Data是加密内容,占8个字节64位,Mode是加密还是解密。 DES算法于1976被确定,现在已经被认为不够安全,主要原因是56位的密钥过短。据说这个算法因为包含一些机密设计元素,被怀疑内含美国国家安全局(NSA)的后门。 DES算法有个拓展算法叫3DES,就是对数据块进行三次DES加密,增加爆破成本,但本质上也不够安全。 RC4RC4 (Rivest Cipher 4) 是一种流加密算法 RC4起源于1987年,现在已经被认为不够安全。RC4由伪随机数生成器和异或运算组成。RC4的密钥长度可变,范围是[1,255]。RC4一个字节一个字节地加解密。给定一个密钥,伪随机数生成器接受密钥并产生一个S盒。S盒用来加密数据,而且在加密过程中S盒会变化。 由于异或运算的对合性,RC4加密解密使用同一套算法。这个算法实现起来很简单,只用了最基本的加、异或、循环,话说我大学时某个课程设计的做的加密算法就是简化版的RC4。 之后还出现了RC5、RC6加密算法,但RC5和RC6都是分组加密,和RC4原理并不一样。 RC5RC5 (Rivest Cipher 5) 是一种分组加密算法,它和RC2,RC4,RC6都是同一个叫Ronald Rivest的人设计的。 相比RC4,RC5的密钥成了128位,但RC5仍然只需要基础的加、异或、循环运算,可以在很多硬件上实现。RC5有三个参数:字的大小,循环轮数(round),密钥中的8位字节个数,所以可以说RC5是一种可变加密算法。实际上循环轮数12轮以下的RC5都被认为是不安全的,会被差分分析法(Differential cryptanalysis)攻击,18-20轮才足够安全。 目前来说,RC5还是挺安全的,因为实现简单,消耗资源少,在一些传感器、嵌入式设备上使用很合适。 RC6RC6 (Rivest Cipher 6) 是RC5的加强版,也属于分组加密算法。 RC6算法在RC5算法基础之上针对RC5算法中的漏洞,主要是循环移位的位移量并不取决于要移动次数的所有比特,通过采用引入乘法运算来决定循环移位次数的方法,对RC5算法进行了改进,从而大大提高了RC6算法的安全性。 RC6曾作为AES(高级加密标准)备选算法之一,但最终AES选择了Rijndael算法。 AES最后压轴出场的是最著名的单密钥对称加密算法AES (Rijndael),AES是Advanced Encryption Standard的缩写,是美国国家标准与技术研究院2001年发布的新加密标准。 AES现在就是指的限定了区块长度和密钥长度的Rijndael算法,同样属于分组加密算法,该算法是两位比利时学者1998年发布的。起初还有很多算法参与了AES甄选,最终Rijndael凭借高安全性和清晰的数学结构而被选用。 AES将Rijndael算法的区块长度固定为128位,密钥长度可选128,192或256比特(Rijndael原版支持128-256,n*32的区块长度和密钥长度)。 AES算法包括4个步骤: AddRoundKey—矩阵中的每一个字节都与该次回合密钥(round key)做XOR运算;每个子密钥由密钥生成方案产生。 SubBytes—通过一个非线性的替换函数,用查找表的方式把每个字节替换成对应的字节。 ShiftRows—将矩阵中的每个横列进行循环式移位。 MixColumns—为了充分混合矩阵中各个直行的操作。这个步骤使用线性转换来混合每内联的四个字节。最后一个加密循环中省略MixColumns步骤,而以另一个AddRoundKey替换。 截止现在(2016),AES在算法层面上是安全的。2005年有人公布过一种缓存时序攻击法,但使用场景非常极端。 二、非对称秘钥算法概述公钥加密的思想于1974年被提出,相比对称加密无需共享密钥,更加安全。但是没法加密大量数据,一般用来加密对称加密的密钥,而用对称加密加密大量数据。非对称加密的原理如下: 消息发送方A在本地构建密钥对,公钥和私钥; 消息发送方A将产生的公钥发送给消息接收方B; B向A发送数据时,通过公钥进行加密,A接收到数据后通过私钥进行解密,完成一次通信; 反之,A向B发送数据时,通过私钥对数据进行加密,B接收到数据后通过公钥进行解密。 RSARSA算法是最著名的非对称加密算法。RSA是1977年提出的,名字来源于Rivest、Shmir和Adleman三位作者。我们平时用到的SSL协议,TLS协议都采用了该算法加密,SSH(Secure Shell)也是基于RSA实现的。 RSA的数学基础是极大整数的因数分解,具体实现过程如下: 随意选择两个大的质数p和q,p不等于q,计算N=pq。 根据欧拉函数,求得r=varphi (N) = varphi(p) * varphi(q)=(p-1)(q-1) 选择一个小于r的整数e,使e与r互质。并求得e关于r的模反元素,命名为d。 (N,e)是公钥,(N,d)是私钥。 加密时,加密的块 n^e ≡ c(MOD N),得到的c就是密文。解密时,c^d ≡ n(MOD N)。 要破解RSA要解决怎么把一个极大数分解为两个质数p和q,然后通过欧拉函数再得到公钥和私钥。但极大数因数分解目前还没什么好办法,所以只要N足够大,RSA在算法层面上就是安全的。 当N的长度为256时,用普通电脑花几小时即可以分解,当N长度为512时需要花数月时间分解,1024时需要大型分布式系统才能分解,长度到2046则可以确保是完全安全的。目前已有记录里,被分解的极大数最大位数是768位,于2009年被分解。 RSA也常被用来做数字签名,在消息内附加一个私钥加密过的散列值(Message digest),以此来确保消息发送人是可靠的。公钥私钥对生成 # 1.该命令会生成1024位的私钥,此时我们就可以在当前路径下看到rsa_private_key.pem文件了. genrsa -out rsa_private_key.pem 1024 # 2.生成的密钥不是pcs8格式,我们需要转成pkcs8格式 pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt # 3.生成 rsa 公钥 rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem 椭圆曲线算法椭圆曲线算法(Elliptic curve cryptography)也是一种非对称加密算法,于1985年被提出,以下简称ECC。相比RSA,同等破解难度时ECC的秘钥更短。另外,ECC可定义椭圆曲线群的双线性映射,该特性可能将来被用来实现身份基加密体制(Identity-Based Encryption,IBE)。 ECC的数学基础是求椭圆曲线离散对数问题。实现比较复杂我就不写了,因为我也看不懂(⊙﹏⊙)b。 也正因为实现复杂,ECC的加解密速度慢,消耗资源也更多。 ECC也同样可以实现数字签名,叫做ECDSA。 ECC的秘钥长度最小要求是160位,建议是163位。目前已有的破解记录是109位,一万台机器破解了一年半。所以ECC在算法层面是可以保证安全的。 ElGamalElGamal加密算法是一种用于对采用Diff-Hellman方式进行交换的公钥进行加密,常被用于数字签名和密钥加密的算法,ElGamal的数学基础是有限域上的离散对数问题。 选择一个素数p和两个随机数g 、x (g、 x < p ),计算 y ≡ g^x( mod p ) ,则其公钥为 y, g 和p ,私钥是x ,g和p可由一组用户共享。 ElGamal方法中一个明文对应两个加密结果(g^a和g^b),因此密文空间的大小是明文空间大小的两倍,也就是说纵观整个通信过程,收发密文的大小是实际明文大小的两倍。 三、哈希算法概述我们经常说MD5加密,但追根究底的话,MD5应该是哈希函数(Hash Function),而哈希函数并不等同于加密(Encrypt),不过我们平常也把哈希叫做加密。哈希函数也叫散列函数,散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值(hash values,hash codes,hash sums,或hashes)的指纹。散列值通常用来代表一个短的随机字母和数字组成的字符串。 说人话就是哈希(Hash)是将目标文本转换成具有相同长度的、不可逆的杂凑字符串,而加密(Encrypt)是将目标文本转换成具有不同长度的、可逆的密文。 哈希主要用来校验身份,错误检查,完整性检查。 MD5MD5(Message-Digest5 Algorithm)即消息摘要算法,是最著名、应用最为广泛的一种哈希算法,于1992年被公开。MD5之前还有MD4、MD3、MD2等哥哥算法,MD5是最终的改进版。 MD5输入不定长度信息,输出固定长度为128-bits的散列 未完 待补充REFERENCE常见加密算法简介","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"encryption","slug":"encryption","permalink":"http://yelog.org/tags/encryption/"}]},{"title":"JSP操作记录","slug":"JSP操作记录","date":"2017-04-21T03:22:12.000Z","updated":"2024-07-05T11:10:22.368Z","comments":true,"path":"2017/04/21/jsp-use-record/","permalink":"http://yelog.org/2017/04/21/jsp-use-record/","excerpt":"","text":"问题EL表达式失效<!-- jsp渲染器不识别el表达式,结果页面展示效果如下 --> {person.id} {person.name} 解决方法:在页面内加入下面代码即可 <%@ page isELIgnored="false" %> Map遍历<c:forEach items="${map}" var="entry"> <c:out value="${entry.key}" /> <c:out value="${entry.value}" /> </c:forEach> 取值<c:out value="${map[key]}" />","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"jsp","slug":"jsp","permalink":"http://yelog.org/tags/jsp/"},{"name":"jstl","slug":"jstl","permalink":"http://yelog.org/tags/jstl/"}]},{"title":"[转]SpringMVC执行流程及源码解析","slug":"转-SpringMVC执行流程及源码解析","date":"2017-04-15T02:22:05.000Z","updated":"2024-07-05T11:10:22.679Z","comments":true,"path":"2017/04/15/SpringMVC-implementation-process/","permalink":"http://yelog.org/2017/04/15/SpringMVC-implementation-process/","excerpt":"","text":"在SpringMVC中主要是围绕着DispatcherServlet来设计,可以把它当做指挥中心。这里先说明一下SpringMVC文档给出的执行流程,然后是我们稍微具体的执行流程,最后是流程大致的源码跟踪。关于很很很详细的源码解析,这里暂先不做。 官方文档中的流程首先看下SpringMVC文档上给的流程图:这张图片给了我们大概的执行流程: 用户请求首先发送到前端控制器DispatcherServlet,DispatcherServlet根据请求的信息来决定使用哪个页面控制器Controller(也就是我们通常编写的Controller)来处理该请求。找到控制器之后,DispatcherServlet将请求委托给控制器去处理。 接下来页面控制器开始处理用户请求,页面控制器会根据请求信息进行处理,调用业务层等等,处理完成之后,会把结果封装成一个ModelAndView返回给DispatcherServlet。 前端控制器DispatcherServlet接到页面控制器的返回结果后,根据返回的视图名选择相应的试图模板,并根据返回的数据进行渲染。 最后前端控制器DispatcherServlet将结果返回给用户。 更具体的流程上面只是总体流程,接下来我们稍微深入一点,看下更具体的流程,这里没有图,只有步骤解析: 用户请求发送到前端控制器DispatcherServlet。 前端控制器DispatcherServlet接收到请求后,DispatcherServlet会使用HandlerMapping来处理,HandlerMapping会查找到具体进行处理请求的Handler对象。 HandlerMapping找到对应的Handler之后,并不是返回一个Handler原始对象,而是一个Handler执行链,在这个执行链中包括了拦截器和处理请求的Handler。HandlerMapping返回一个执行链给DispatcherServlet。 DispatcherServlet接收到执行链之后,会调用Handler适配器去执行Handler。 Handler适配器执行完成Handler(也就是我们写的Controller)之后会得到一个ModelAndView,并返回给DispatcherServlet。 DispatcherServlet接收到Handler适配器返回的ModelAndView之后,会根据其中的视图名调用视图解析器。 视图解析器根据逻辑视图名解析成一个真正的View视图,并返回给DispatcherServlet。 DispatcherServlet接收到视图之后,会根据上面的ModelAndView中的model来进行视图中数据的填充,也就是所谓的视图渲染。 渲染完成之后,DispatcherServlet就可以将结果返回给用户了。 源码DispatcherServlet是一个Servlet,我们知道在Servlet在处理一个请求的时候会交给service方法进行处理,这里也不例外,DispatcherServlet继承了FrameworkServlet,首先进入FrameworkServlet的service方法: protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //请求方法 String method = request.getMethod(); //PATCH方法单独处理 if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) { processRequest(request, response); } else {//其他的请求类型的方法经由父类,也就是HttpServlet处理 super.service(request, response); } } HttpServlet中会根据请求类型的不同分别调用doGet或者doPost等方法,FrameworkServlet中已经重写了这些方法,在这些方法中会调用processRequest进行处理,在processRequest中会调用doService方法,这个doService方法就是在DispatcherServlet中实现的。下面就看下DispatcherServlet中的doService方法的实现。 请求到达DispatcherServletdoService方法: protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { //给request中的属性做一份快照 Map<String, Object> attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { logger.debug("Taking snapshot of request attributes before include"); attributesSnapshot = new HashMap<String, Object>(); Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } //如果我们没有配置类似本地化或者主题的处理器之类的 //SpringMVC会使用默认的值 //默认配置文件是DispatcherServlet.properties request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); try { //开始处理 doDispatch(request, response); } finally { if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { return; } // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } DispatcherServlet开始真正的处理,doDispatch方法: protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; //SpringMVC中异步请求的相关知识,暂先不解释 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { //先检查是不是Multipart类型的,比如上传等 //如果是Multipart类型的,则转换为MultipartHttpServletRequest类型 processedRequest = checkMultipart(request); multipartRequestParsed = processedRequest != request; //获取当前请求的Handler mappedHandler = getHandler(processedRequest, false); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } //获取当前请求的Handler适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 对于header中last-modified的处理 String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } //拦截器的preHandle方法进行处理 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } try { //真正调用Handler的地方 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } //处理成默认视图名,就是添加前缀和后缀等 applyDefaultViewName(request, mv); //拦截器postHandle方法进行处理 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } //处理最后的结果,渲染之类的都在这里 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Error err) { triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); return; } // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } 可以看到大概的步骤还是按照我们上面分析的走的。 查找请求对应的Handler对象对应着这句代码 mappedHandler = getHandler(processedRequest, false);,看下具体的getHandler方法: protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception { return getHandler(request); } 继续往下看getHandler: protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //遍历所有的handlerMappings进行处理 //handlerMappings是在启动的时候预先注册好的 for (HandlerMapping hm : this.handlerMappings) { HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } return null; } 继续往下看getHandler,在AbstractHandlerMapping类中: public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //根据request获取handler Object handler = getHandlerInternal(request); if (handler == null) { //如果没有找到就使用默认的handler handler = getDefaultHandler(); } if (handler == null) { return null; } //如果Handler是String,表明是一个bean名称 //需要超照对应bean if (handler instanceof String) { String handlerName = (String) handler; handler = getApplicationContext().getBean(handlerName); } //封装Handler执行链 return getHandlerExecutionChain(handler, request); } 根据requrst获取handler首先看下根据requrst获取handler步骤getHandlerInternal方法,在AbstractHandlerMethodMapping中: protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { //获取request中的url,用来匹配handler String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); //根据路径寻找Handler HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); //根据handlerMethod中的bean来实例化Handler并添加进HandlerMethod return (handlerMethod != null) ? handlerMethod.createWithResolvedBean() : null; } 看下根据路径寻找handler的方法lookupHandlerMethod: protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<Match>(); //直接匹配 List<T> directPathMatches = this.urlMap.get(lookupPath); //如果有匹配的,就添加进匹配列表中 if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } //还没有匹配的,就遍历所有的处理方法查找 if (matches.isEmpty()) { // No choice but to go through all mappings addMatchingMappings(this.handlerMethods.keySet(), matches, request); } //找到了匹配的 if (!matches.isEmpty()) { Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); Collections.sort(matches, comparator); //排序之后,获取第一个 Match bestMatch = matches.get(0); //如果有多个匹配的,会找到第二个最合适的进行比较一下 if (matches.size() > 1) { Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.handlerMethod.getMethod(); Method m2 = secondBestMatch.handlerMethod.getMethod(); throw new IllegalStateException( "Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" + m1 + ", " + m2 + "}"); } } //设置request参数 handleMatch(bestMatch.mapping, lookupPath, request); //返回匹配的url的处理的方法 return bestMatch.handlerMethod; } else {//最后还没有找到,返回null return handleNoMatch(handlerMethods.keySet(), lookupPath, request); } } 获取默认Handler如果上面没有获取到Handler,就会获取默认的Handler。如果还获取不到就返回null。 处理String类型的Handler如果上面处理完的Handler是String类型的,就会根据这个handlerName获取bean。 封装Handler执行链上面获取完Handler,就开始封装执行链了,就是将我们配置的拦截器加入到执行链中去,getHandlerExecutionChain: protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { //如果当前Handler不是执行链类型,就使用一个新的执行链实例封装起来 HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain) ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler); //先获取适配类型的拦截器添加进去拦截器链 chain.addInterceptors(getAdaptedInterceptors()); //当前的url String lookupPath = urlPathHelper.getLookupPathForRequest(request); //遍历拦截器,找到跟当前url对应的,添加进执行链中去 for (MappedInterceptor mappedInterceptor : mappedInterceptors) { if (mappedInterceptor.matches(lookupPath, pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); } } return chain; } 获取对应请求的Handler适配器getHandlerAdapter: protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { //遍历所有的HandlerAdapter,找到和当前Handler匹配的就返回 //我们这里会匹配到RequestMappingHandlerAdapter for (HandlerAdapter ha : this.handlerAdapters) { if (ha.supports(handler)) { return ha; } } } 缓存的处理也就是对last-modified的处理 执行拦截器的preHandle方法就是遍历所有的我们定义的interceptor,执行preHandle方法 使用Handler适配器执行当前的Handlerha.handle执行当前Handler,我们这里使用的是RequestMappingHandlerAdapter,首先会进入AbstractHandlerMethodAdapter的handle方法: public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return handleInternal(request, response, (HandlerMethod) handler); } handleInternal方法,在RequestMappingHandlerAdapter中: protected final ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { // Always prevent caching in case of session attribute management. checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true); } else { // Uses configured default cacheSeconds setting. checkAndPrepare(request, response, true); } // Execute invokeHandlerMethod in synchronized block if required. if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { return invokeHandleMethod(request, response, handlerMethod); } } } //执行方法,封装ModelAndView return invokeHandleMethod(request, response, handlerMethod); } 组装默认视图名称前缀和后缀名都加上 执行拦截器的postHandle方法遍历intercepter的postHandle方法。 处理最后的结果,渲染之类的processDispatchResult方法: private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { boolean errorView = false; if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } // Did the handler return a view to render? if (mv != null && !mv.wasCleared()) { //渲染 render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; } if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, null); } } 重点看下render方法,进行渲染: protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { //设置本地化 Locale locale = this.localeResolver.resolveLocale(request); response.setLocale(locale); View view; if (mv.isReference()) { //解析视图名,得到视图 view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); } else { // No need to lookup: the ModelAndView object contains the actual View object. view = mv.getView(); if (view == null) { throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'"); } } //委托给视图进行渲染 view.render(mv.getModelInternal(), request, response); } view.render就是进行视图的渲染,然后跳转页面等处理。 到这里大概的流程就走完了。其中涉及到的东西还有很多,暂先不做详细处理。 原文:SpringMVC执行流程及源码解析","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"spring","slug":"spring","permalink":"http://yelog.org/tags/spring/"},{"name":"springmvc","slug":"springmvc","permalink":"http://yelog.org/tags/springmvc/"}]},{"title":"PostgreSQL常用SQL操作","slug":"PostgreSQL常用SQL操作","date":"2017-04-14T08:37:24.000Z","updated":"2024-07-05T11:10:22.289Z","comments":true,"path":"2017/04/14/postgres-sql-use/","permalink":"http://yelog.org/2017/04/14/postgres-sql-use/","excerpt":"","text":"说明:文章中实例均在 PostgreSQL 环境操作。 DDL数据定义语言数据库/角色/schema-- 创建一个数据库用户 create role "sp-boss" createdb createrole login password 'sp-boss'; -- 使用上面角色登录 postgres 数据库 psql -U sp-boss -d postgres -- 创建自己的数据库 create database "sp-boss" -- 登录自己的数据库 psql -U sp-boss -- 创建一个其他用户 create role "sp-manager" login password 'sp-manager'; -- 赋予 create 权限 grant create on database "sp-boss" to "sp-manager"; -- 使用 新用户 登录数据库 psql -U sp-manager -d sp-boss -- 创建自己的 schema create schema "sp-manager"; 表--创建表 create table user_info ( id serial primary key, name varchar(20), age integer, create_time timestamp, type integer, display boolean default true, unique (name, type) ); --删除表 drop table exists user_info; --重命名表 alter table user_info rename to user_infos; 字段(列)--添加一列 alter table user_info add [column] username varchar(50); --删除一列 alter table user_info drop [column] username; --重命名列 alter table user_info rename [column] username to name; --修改结构 alter table user_info alter [column] username set not null; -- 唯一约束-- 添加名为 uk_name 的联合唯一约束,组合列为column1和column2 alter table sys_theme add constraint uk_name unique(column1,column2); -- 删除名为 uk_name 的约束 alter table sys_theme drop constraint uk_name; DML数据库操作语言SELECT查询包含json格式的text类型的数据postgres=# select * from person; id | name | other ----+--------+---------------------------------------------------------- 1 | faker | {"gender":"male","address":"xiamen","college":"xmut"} 2 | watson | {"gender":"male","address":"shenzhen","college":"szu"} 3 | lance | {"gender":"male","address":"shenzhen","college":"xmut"} 4 | jine | {"gender":"female","address":"xiamen","college":"xmut"} 5 | jobs | {"gender":"male","address":"beijing","college":"xmu"} 6 | yak | {"gender":"female","address":"xiamen","college":"xmut"} 7 | alice | {"gender":"female","address":"shanghai","college":"thu"} 8 | anita | {"gender":"female","address":"xiongan","college":"hku"} (8 行记录) -- 查询深圳学生的高校分部情况 select other::json->>'college' college, count(1) from person where other::json->>'address'='shenzhen' group by other::json->>'college'; ___________________________ college | count ---------+------- szu | 1 xmut | 1 (1 行记录) --- 结果可得深圳一共有两个学生, --- 在深圳大学和厦门理工学院各一个。","categories":[{"name":"数据库","slug":"数据库","permalink":"http://yelog.org/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"}],"tags":[{"name":"postgres","slug":"postgres","permalink":"http://yelog.org/tags/postgres/"},{"name":"sql","slug":"sql","permalink":"http://yelog.org/tags/sql/"}]},{"title":"deepin系统使用记录","slug":"deepin-linux","date":"2017-03-30T10:58:50.000Z","updated":"2024-07-05T11:10:23.087Z","comments":true,"path":"2017/03/30/deepin-linux/","permalink":"http://yelog.org/2017/03/30/deepin-linux/","excerpt":"","text":"传送门 官网 论坛 deepin’wiki Deepin应用列表 输入法安装输入法,除了商店下载(好多输入法没有被收录进deepin商店),可以使用fcitx安装。如安装google拼音输入法: $ sudo aptitude install fcitx fcitx-googlepinyin 如果当前在使用ibus,而不是fcitx的话,看下面1)安装fcitx,并安装google拼音 $ sudo apt-get install fcitx fcitx-googlepinyin im-config 2)打开输入法配置 $ im-config 依次:ok->yes,选择fcitx为默认输入法框架,ok->ok 制作启动器图标以创建 atom 这款编辑器的启动器图标为例。1)进入 /usr/share/applications/ 目录,创建 atom.desktop 文件2)编辑 atom.desktop 文件 [Desktop Entry] Name=Atom Comment=A hackable text editor for the 21st century Exec=/opt/atom/atom %F Icon=/opt/atom/atom.png Type=Application StartupNotify=true Categories=TextEditor;Development;Utility; MimeType=text/plain; 解释:Name:创建的图标名称Comment:备注,随便填Exec:启动文件的位置Icon:图标位置Type:类型,启动程序就填ApplicationStartupNotify: 启动通知,填true就行了。详细可查 Startup notificationCategories: 分类,随便填,比如:Application;MimeType: 打开文件类型 修改apt源修改 /etc/apt/sources.list默认的源 deb [by-hash=force] http://packages.deepin.com/deepin/ unstable main contrib non-free 阿里云的源 deb [by-hash=force] http://mirrors.aliyun.com/deepin unstable main contrib non-free 更换文件管理器Nautilus 深度商店下载安装 Nautilus2)卸载深度任务管理器 $ sudo apt remove dde-file-manager Nautilus 常用的快捷键 快捷键 作用 F2 重命名 Ctrl + 1 图标视图 Ctrl + 2 列表视图 Ctrl + T 新建标签页 Ctrl + W 关闭标签页 Alt + 数字 切换到指定标签页 Ctrl + D 收藏到当前文件夹到书签 Shift + F10 打开鼠标右键菜单 Alt + 左方向键 后退 Alt + 右方向键 前进 Ctrl + Q 退出","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"软件记录","slug":"工具/软件记录","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95/"}],"tags":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/tags/%E8%BF%90%E7%BB%B4/"},{"name":"deepin","slug":"deepin","permalink":"http://yelog.org/tags/deepin/"}]},{"title":"hexo报错合集","slug":"hexo-error-collection","date":"2017-03-27T11:40:42.000Z","updated":"2024-07-05T11:10:22.553Z","comments":true,"path":"2017/03/27/hexo-error-collection/","permalink":"http://yelog.org/2017/03/27/hexo-error-collection/","excerpt":"","text":"hexo server时报错FATAL watch … ENOSPC日志:2017-03-27 执行 hexo server 后报错。如图:分析问题:node.js 中 watch 的文件数是有限制的。解决问题: $ echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"3-hexo快捷键说明","slug":"3-hexo-shortcuts","date":"2017-03-24T08:45:31.000Z","updated":"2024-07-05T11:10:22.540Z","comments":true,"path":"2017/03/24/3-hexo-shortcuts/","permalink":"http://yelog.org/2017/03/24/3-hexo-shortcuts/","excerpt":"","text":"今日公司断网了半个小时,就利用这段时间给主题添加了快捷键操作,方便使用。 快捷键为vim风格的。按键可能与vimium(chrome插件)的快捷键有冲突,插件设置屏蔽掉此站的快捷键即可 如果有比较好的建议,欢迎骚扰。 说明全局 Key Descption s/S 全屏/取消全屏 w/W 打开/关闭文章目录 i/I 获取搜索框焦点 j/J 向下滑动 k/K 向上滑动 gg/GG 到最顶端 shift+G/g 到最下端 搜索框 Key Descption ESC 1.如果输入框有内容,清除内容2.如果输入框无内容,失去焦点 下 向下选择文章 上 向上选择文章 回车 打开当前选中的文章,若没有,则默认打开第一个 关闭快捷键在主题下 _config.yml 中 找到 shortcutKey 设为 false shortcutKey: false","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"3-hexo使用说明","slug":"3-hexo-instruction","date":"2017-03-23T07:13:47.000Z","updated":"2024-07-05T11:10:22.512Z","comments":true,"path":"2017/03/23/3-hexo-instruction/","permalink":"http://yelog.org/2017/03/23/3-hexo-instruction/","excerpt":"","text":"下面如果没有特殊说明, _config.yml 都指主题配置文件,即 3-hexo 目录下 一、初始化博客下 _config.yml1.1 国际化language: zh-CN #支持 zh-CN、en 1.2 关掉 hexo 自带的代码高亮主题内置了主题高亮,所以需要禁用 hexo 自带的高亮 根据 hexo 版本, 禁用的方式发生了变化 禁用高亮 below v7.0.0 # _config.yaml highlight: enable: false 7.0.0+ # _config.yml syntax_highlighter: # empty 二、功能相关2.1 自定义首页可查看这篇文章: 3-hexo配置首页 2.3 blog快捷键可查看这篇文章: 3-hexo快捷键说明 2.4 多作者模式可查看这篇文章: 3-hexo多作者模式 2.5 开启关于页面 在 hexo 根目录执行以下,创建 关于 页面 hexo new page "about" 位置: source/aoubt/index.md ,根据需要进行编辑。 在主题中开启显示:修改主题根目录 _config.yml 中的 about 的 on 为 true,如下所示 menu: about: # '关于' 按钮 on: true # 是否显示 url: /about # 跳转链接 type: 1 # 跳转类型 1:站内异步跳转 2:当前页面跳转 3:打开新的tab页 2.6 添加音乐插件3-hexo 添加音乐插件 2.7 配置评论系统3-hexo评论设置 三、样式设置3.1 代码高亮首先要关闭hexo根目录下_config.yml中的高亮设置: highlight: enable: false 配置主题下_config.yml中的高亮设置:可以根据提示,配置喜欢的高亮主题 highlight: on: true # true开启代码高亮 lineNum: true # true显示行号 theme: darcula # 代码高亮主题,效果可以查看 https://highlightjs.org/static/demo/ # 支持主题: # sublime : 参考sublime的高亮主题 # darcula : 参考idea中的darcula的主题 # atom-dark : 参考Atom的dark主题 # atom-light : 参考Atom的light主题 # github : 参考GitHub版的高亮主题 # github-gist : GitHub-Gist主题 # brown-paper : 牛皮纸效果 # gruvbox-light : gruvbox的light主题 # gruvbox-dark : gruvbox的dark主题 # rainbow : # railscasts : # sunburst : # kimbie-dark : # kimbie-light : # school-book : 纸张效果 3.2 MathJax数学公式修改 _config.yml # MathJax 数学公式支持 mathjax: on: true #是否启用 per_page: false # 若只渲染单个页面,此选项设为false,页面内加入 mathjax: true 考虑到页面的加载速度,支持渲染单个页面。设置 per_page: false ,在需要渲染的页面内 加入 mathjax: true 注意: 由于hexo的MarkDown渲染器与MathJax有冲突,可能会造成矩阵等使用不正常。所以在使用之前需要修改两个地方编辑 node_modules\\marked\\lib\\marked.js 脚本 将451行 ,这一步取消了对 \\\\,\\{,\\} 的转义(escape) escape: /^\\\\([\\\\`*{}\\[\\]()# +\\-.!_>])/, 改为 escape: /^\\\\([`*\\[\\]()# +\\-.!_>])/, 将459行,这一步取消了对斜体标记 _ 的转义 em: /^\\b_((?:[^_]|__)+?)_\\b|^\\*((?:\\*\\*|[\\s\\S])+?)\\*(?!\\*)/, 改为 em:/^\\*((?:\\*\\*|[\\s\\S])+?)\\*(?!\\*)/, 3.3 表格样式目前提供了3中样式,修改 _config.yml table: green_title # table 的样式 # 为空时类似github的table样式 # green 绿色样式 # green_title 头部为青色的table样式 3.4 文章列表的hover样式鼠标移入的背景色和文字颜色变动,设置 _config.yml #文章列表 鼠标移上去的样式, 为空时使用默认效果 article_list: hover: background: '#e2e0e0' # 背景色:提供几种:'#c1bfc1' '#fbf4a8' color: # 文字颜色 提供几种:'#ffffff' # 注意:由于颜色如果包含#,使用单引号 ' 引起来 3.5 开启字数统计 开启此功能需先安装插件,在 hexo根目录 执行 npm i hexo-wordcount --save 修改 _config.yml word_count: true 3.6 更换头像两种方式: 替换 source/img/avatar.jpg 图片。 修改 _config.yml 中头像的配置记录 # 你的头像url avatar: /img/avatar.jpg favicon: /img/avatar.jpg 3.7 设置链接图标 如果需要自定义图标可以看这篇文章 3-hexo添加自定义图标 如下,如果没有连接,则不展示图标。 #链接图标,链接为空则不显示 link: rss: /atom.xml github: https://github.com/yelog facebook: https://www.facebook.com/faker.tops twitter: linkedin: instagram: reddit: https://www.reddit.com/user/yelog/ weibo: http://weibo.com/u/2307534817 email: jaytp@qq.com 四、排序及置顶4.1 分类排序默认按照首字母正序排序,由于中文在 nodejs 环境下不能按照拼音首字母排序,所以添加了自定义排序方式,在主题下 _config.yml 中找到如下配置,category.sort则是定义分类顺序的。 规则:在 sort中定义的 category 比 没有在 sort 中定义的更靠前 # 文章分类设置 category: num: true # 分类显示文章数 sub: true # 开启多级分类 sort: - 读书 - 大前端 - 后端 - 数据库 - 工具 - 运维 4.2 文章排序 2020-05-20 更新:无需安装插件或修改源码,主题已经内置排序算法 文章列表默认按照创建时间(如下文章内定义的date)倒序。 使用 top 将会置顶文章,多个置顶文章时,top 定义的值越大,越靠前。 --- title: 每天一个linux命令 date: 2017-01-23 11:41:48 top: 1 categories: - 运维 tags: - linux命令 --- 五、关于写文章5.1 如何写每篇文章最好写上文集和标签,方便筛选和查看。一般推荐一篇文章设置一个文集,一个或多个标签categories:文集,为左侧列表tags:标签,通过#来筛选例如 本篇文章的设置 --- title: 3-hexo使用说明 date: 2017-03-23 15:13:47 categories: - 工具 tags: - hexo - 3-hexo --- 5.2 写作1.设置模板,blog根目录 scaffolds/post.md加入categories,tags等,这样以后通过 hexo new 生成的模板就不用写这两个单词了。当然,你也可以写入任何你每个文章中都会有的部分。 --- title: {{ title }} date: {{ date }} categories: tags: --- 六、技巧6.1 快捷命令其实就通过alias,触发一些命令的集合在 ~/.bashrc 文件中添加 alias hs='hexo clean && hexo g && hexo s' #启动本地服务 alias hd='hexo clean && hexo g && hexo d' #部署博客 甚至你也可以加入备份文章的命令,可以自由发挥。 6.3 博客备份(快捷命令升级版)为了保证我们写的文章不丢失、快速迁移博客,都需要备份我们的blog。 博客根目录,执行 git init 创建 git 仓库。 在 github(或其他托管平台、自建远程仓库等) 创建仓库并和本地仓库建立联系。 在 ~/.bashrc 文件中添加 alias hs='hexo clean && hexo g && hexo s' alias hd='hexo clean && hexo g && hexo d && git add . && git commit -m "update" && git push -f' 这样,我们在执行 hd 进行部署时,就一同将博客进行备份了","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"关于第三方评论系统","slug":"the-third-comment","date":"2017-03-23T03:59:50.000Z","updated":"2024-07-05T11:10:22.470Z","comments":true,"path":"2017/03/23/the-third-comment/","permalink":"http://yelog.org/2017/03/23/the-third-comment/","excerpt":"","text":"前言昨天登陆blog看到了多说的通知:将于2017年6月1日正式关停服务,其实并没有太大的意外。 自从去年9月份disqus被GFW认证后,被迫转移到多说,一看就是很久没有维护了,感觉关闭就是迟早的事,没想到刚用5个月。。 不能用disqus不开心,然后就又开始调查第三方评论系统。 友言特点 界面挺像disqus的 查询最近的评论,需要打开新的页面 支持表情,不支持图片 支持自定义界面 注册即用 网易云跟帖特点 界面简洁 网易作为后台,不容易倒 不支持表情,不支持图片 支持自定义界面 我在国外加载巨慢,不过查了国内的延迟,平均20ms左右。 注册即用 畅言特点 界面简介 打印记的功能 支持表情和图片 支持获取评论数 需要ICP备案 最后想要尝试一下畅言,功能符合我的预期,不过要ICP备案,最近由于想要开启国内CDN加速,也需要备案。 蛋四!! 我在国外,等回国的时候再备案吧,暂时现使用 disqus,如果想评论,国内只能翻墙了。╮(╯_╰)╭ 最后,多说一路走好。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"windows环境下使用hexo搭建blog平台","slug":"windows-hexo","date":"2017-03-17T06:02:55.000Z","updated":"2024-07-05T11:10:22.521Z","comments":true,"path":"2017/03/17/windows-hexo/","permalink":"http://yelog.org/2017/03/17/windows-hexo/","excerpt":"","text":"之前已经出过几期搭建 hexo 的文章,但是有不少朋友私信说系统是windows的,希望出一期windows环境下的hexo搭建文章。 为此,确保可用性,笔者在linux(笔者的系统环境)环境下安装了windows虚拟机重新演练了一边,确保没有什么漏洞,才写下了此文。 本文会非常详细,以确保没有计算机背景的小白也可以轻松上手 环境搭建安装git1).下载:从官网下载windows版本的git,地址在下方。https://git-scm.com/download/win2).安装:双击安装,一直点击下一步即可 安装node.js1).下载:从官网下载windows版本的node.js安装包(.msi后缀),地址下方https://nodejs.org/zh-cn/download/2).安装:双击安装,一直点击下一步即可 安装hexo在任意目录如桌面,点击鼠标右键,选择Git Bash Here这一项,打开git bash命令框(前提是git安装成功),如下图在打开的命令窗内输入下面的命令进行安装 npm install hexo-cli -g 安装过后,输入 hexo -v,出现下面信息,则表示安装成功 $ hexo -v hexo-cli: 1.0.2 os: Windows_NT 6.1.7601 win32 x64 http_parser: 2.7.0 node: 6.10.0 v8: 5.1.281.93 uv: 1.9.1 zlib: 1.2.8 ares: 1.10.1-DEV icu: 58.2 modules: 48 openssl: 1.0.2k 初始化blog进入准备创建blog的目录,同样点击鼠标右键,打开git bash命令框,执行一下命令进行初始化 $ hexo init myblog 就会自动创建一个名字为myblog目录,这时本地blog就已经创建好了。进入blog目录,启动 blog $ cd myblog $ hexo server 在浏览器输入 127.0.0.1:4000就可以访问到刚刚创建好的blog了。 换皮肤如果觉的自带的皮肤太难看。可以根据以下步奏更换皮肤在官网 可以查看各种各样的皮肤,挑选自己喜欢的皮肤 这里以 3-hexo 这款皮肤为例(这款皮肤是笔者写的,效果可查看 yelog.org)1)进入皮肤的 github 官网,如3-hexo的网址找到 clone or download ,复制它的url:https://github.com/yelog/hexo-theme-3-hexo.git 2)进入 myblog 目录,打开 git bash 命令框,执行以下命令将皮肤下载到themes目录下 $ git clone https://github.com/yelog/hexo-theme-3-hexo.git themes/3-hexo 修改 myblog/_config.yml 中的 theme: landscape 为 theme: 3-hexo 如果使用 3-hexo 主题的话,还需要注意两点①因为主题使用了自己的高亮效果,还需要修改 highlight enable: true 的 true 改为 false。②由于主题启用了文章字数统计功能,需要安装一个插件,在 myblog 目录下,打开 git bash 命令框,执行 npm i --save hexo-wordcount 即可 重新渲染,启动服务器 $ hexo clean && hexo g && hexo s 打开浏览器查看效果,换肤成功 如何写文章文章在 myblog/source/_posts/ 下,以markdown格式写成,笔者推荐使用atom作为写作工具。可以通过 hexo new 文章名 来创建一篇文章,当然也可以直接在 _posts 目录下直接新建.md文件。执行命令 仍是在 myblog 目录下,打开 git bash 命令框。以下是常用命令,其他可以查阅官网。 # 创建一个标题为“git教程”的文章 $ hexo new "git教程" # 清除所有渲染的页面 $ hexo clean # 将markdown渲染成页面 $ hexo g # 启动hexo $ hexo s 发布到网上如果想要在github上搭建blog,或者在自己的购买的服务器上搭建blog可以查看笔者的往期文章 今天的教程就到这里,有什么问题可以在评论区交流。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"npm使用介绍","slug":"npm使用介绍","date":"2017-03-16T12:09:48.000Z","updated":"2024-07-05T11:10:22.390Z","comments":true,"path":"2017/03/16/npm/","permalink":"http://yelog.org/2017/03/16/npm/","excerpt":"","text":"Whatnpm(全称Node Package Manager,即node包管理器)是Node.js默认的、以JavaScript编写的软件包管理系统。作者:艾萨克·施吕特(Isaac Z. Schlueter) 安装npm 是随同node.js一起安装的,所以安装node.js即可。 使用# 查看版本 $ npm -v # 升级 $ sudo npm install npm -g # 安装模块 $ npm install <Module Name> #本地安装 # 本地安装:安装到./node_modules(命令运行目录) $ npm install <Module Name> -g #全局安装 # 全局安装:放在 /usr/local 下或者你 node 的安装目录。 # 卸载模块 $ npm uninstall <Module Name> # 更新模块 $ npm update <Module Name> # 查看所有安装的模块 $ npm ls #所有本地模块 $ npm ls -g #所有全局模块 # 搜索模块 $ npm search <Module Name>","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"node","slug":"node","permalink":"http://yelog.org/tags/node/"}]},{"title":"为Hexo添加RSS和Sitemap","slug":"Hexo-RSS-Sitemap","date":"2017-03-14T01:44:29.000Z","updated":"2024-07-05T11:10:22.567Z","comments":true,"path":"2017/03/14/Hexo-RSS-Sitemap/","permalink":"http://yelog.org/2017/03/14/Hexo-RSS-Sitemap/","excerpt":"","text":"添加RSS使用RSS是为自己的blog提供订阅功能。 1.用npm安装插件$ npm install hexo-generator-feed --save 2.配置根目录_config.yml# Extensions ## Plugins: http://hexo.io/plugins/ #RSS订阅 plugin: - hexo-generator-feed #Feed Atom feed: type: atom path: atom.xml limit: 20 3.验证配置是否成功执行 hexo g,查看一下public目录下,如果有 atom.xml 文件,则表明配置成功。 4.显示RSS图标这里以3-hexo主题为例,给rss添加链接/atom.xml修改/themes/3-hexo/_config.yml link: rss: /atom.xml 5.效果链接图标:链接地址效果 添加SitemapSitemap,网站地图,是网站优化中重要的一环,无论是对于访问者还是对于搜索引擎。 1.用npm安装插件$ npm install hexo-generator-sitemap --save 2.配置根目录_config.ymlplugin: - hexo-generator-feed - hexo-generator-sitemap 3.验证配置是否成功执行 hexo g,查看一下public目录下,如果有 sitemap.xml 文件,则表明配置成功。 4.效果访问 /sitemap.xml 就能看到生成的站点地图了","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"3-hexo开发日志-持续更新","slug":"3-hexo-logs","date":"2017-03-13T07:44:57.000Z","updated":"2024-07-05T11:10:22.530Z","comments":true,"path":"2017/03/13/3-hexo-logs/","permalink":"http://yelog.org/2017/03/13/3-hexo-logs/","excerpt":"","text":"2024年1月01-05 fix 修复代码块的行号错位问题 2023年11月11-22 fix 修复代码块, 复制时丢失缩进和空格问题 环境 - 改为实例 2023年6月06-16 fix 修复文章分类层级不能无限递归的问题 06-02 fix 修复由于层级不连续,导致的 toc 获取不完整的问题 2023年5月05-23 fix 修复设置宽度后,背景不全 fixes #123 05-19 fix 修复隐藏文章后, 左侧分类上的文章数量不对的问题 2022年6月06-27 add 添加隐藏文章参数 hidden, 设置为 true 将文章从列表中隐藏 2022年2月02-22 fix jquery dom selector contains special symbols cause errors fix Script could run successfully in the case of errors synchronizing TOC 02-16 fix 修复首页使用自定义 html 标题时,大纲和文章关联关系丢失报错的问题 2021年9月09-26 fix 修复 gitalk 代理节点的问题,并升级为官方最新的代码 2021年2月02-22 enhance 按标题搜索支持字符级别模糊查询,并高亮显示 2020年12月12-28 fix 修复了右下角按钮错位问题 enhance 优化图标样式,支持自定义图标引入 change 分类超出隐藏 2020年8月08-09 fix 修复了切换大纲时,未关闭全局搜索框(in:) 的问题2. add 代码块新增显示代码类型和复制代码功能 fix分类支持 \\/#.[]() 特殊字符 fix 大纲支持URI 编码,兼容 hexo5+ 2020年5月05-29 fix 修复了旧版 hexo 报错的问题 05-23 change 由于 cloudflare 国内访问不稳定,故 cdn 切换到 jsdelivr fix 修复文章大纲为空时,同步大纲报错的问题 change 文章内 toc 生成从 @【toc】 改为 【toc】 enhance 快捷键支持关闭 shortcutKey: false add 添加第三方评论 来必力 和 utteranc remove 移除网易云评论 05-21 add 添加备案号配置 05-20 enhance 内置文章排序,无需再引入排序插件或修改源代码 enhance 支持自定义分类的顺序, 具体可以查看 3-hexo使用说明 中的排序相关内容 05-19 change 重做了文章大纲 change 重做了搜索/标签页 style 优化了整体界面风格 fix修复了诸如 分类选中动画闪烁 等细节问题 2020年2月02-041.fix: 修复 gitalk 使用 app API query parameter 弃用的问题 2019年11月11-121.fix: 修复 hexo4.0 版本链接外跳的问题2.add: 支持 mermaid 3-hexo支持mermaid图表 09-241.add: 支持文章内 toc 生成 3-hexo文章内toc生成 09-051.fix: pjax 兼容 jsfiddle 的渲染 3-hexo支持jsfiddle渲染 2019年8月08-201.fix: 左下角菜单个数为一个时,在移动端出现的位置错乱的问题 08-13 add 添加自定义左侧分类栏宽度 category.width,详情见 _config.yml 08-01 fix 修复 友链 区域超出不滚动的问题 2019年7月07-22 add 新增修改文章列表颜色的参数 07-15 fix 修复 gitalk 由于作者停止了跨域的服务,借用其他人的跨域服务解决问题 07-12 add 新增图标:qq、酷狗、网易云音乐 fix 修复文末声明跨行的问题 2019年6月06-09 fix 修复了标签按钮在某些分辨率下错位问题。 fix 修复了代码行数超百行时,行号溢出的问题 2019年5月05-21 add 文章分类可以显示文章数 category>num add 文章分类支持多级显示 category>sub fix 修复gitalk显示评论数错误的问题 添加背景图设置: ① _config.xml 配置默认背景图片 首页背景图: index_bg_img: xxx.jpg 文章页面背景图:other_bg_img: xxx.jpg ② 我们还可以单独给某篇文章设置背景图(优先级最高) title: 3-hexo开发日志-持续更新 bgImg: xxx.jpg #设置这篇文章的背景图 05-05 add 添加代码段高亮样式配置,对应 markdown 语法 `` ,位置:_config.yml 关键字 code 2019年1月01-05 fix 修复Firefox下,Tags 图标失效问题 2018年11月11-30 add 应用彩色图标,新增简书、知乎、csdn、oschina等图标 fix 修复 gitment 登录报错的问题 enhance 升级gitalk插件,并跟随官方版本 2018年8月08-08 修复 fix : 修复左侧栏出现滚动条的问题 2018年4月04-18 修复 fix : 调整原文地址key,解决和 encrypt 的冲突 2017年12月12-31 优化 enhance : 关闭打赏,则屏蔽相关代码。 12-28 添加自定义菜单功能 add : 添加自定义菜单功能,见配置文件 menu: 相关 fix : 修复 photoSwipe 一些问题:手机端右上角图标遮挡、首页图片渲染失败、CDN引入配置文件 12-27 插件添加 add : 引入 photoSwipe 图片相册,可在 _config 中 配置 img_resize: photoSwipe fix : 左侧分类列表过多,则显示滚动条 change : 由于cdnjs最近在国内网络波动较大,将默认CDN改到bootcdn 12-26 功能优化 enhance : 全文检索支持通过方向键选择文章,回车跳转 12-24 功能添加 add : 添加全文检索功能, 输入in:开头即可开始检索 2017年10月10-21 样式调整 enhance : 优化了高亮样式 atom-light 10-20 样式调整与添加 add : 添加列表样式 thread add : 添加引用块样式 bracket add : 文章列表可加入背景图 10-07 add : 加入关于/友链页面 2017年9月09-21 polish : 引入fragment_cache局部缓存,大幅缩减渲染(hexo g)的时间 Hexo加速渲染速度之fragment_cache enhance : 加入 SEO ,tag转keywords , title转description add : 添加文末说明参数 lit : 头像跳转首页的请求也处理为 pjax 2017年7月07-05 添加MathJax数学公式支持 add : 添加MathJax数学公式支持 3-hexo配置MathJax数学公式渲染 2017年6月06-26 添加gitment评论系统 add : 添加gitment评论系统,具体可参考 完美替代多说-gitment 2017年4月04-27 调整样式 change:调整引用块内p的样式。 04-19 调整样式和修复bug change:调整文章目录间距 fix:修复可能出现的访问量不显示的问题 04-17 调整样式及修复bug change:调整大屏下文章最大宽度(从780调到900),代码字号调整为比文字小3px fix:修复在过滤条件下,鼠标上下键不能正常在第一个,最后一个进行跳转的问题 fix:修复首页可能出现的错误渲染,导致没有样式的情况。 2017年3月03-29 增强文章列表上下键功能 enhance:增强列表跟随选择的文章上下滚动 enhance:文章列表上下循环 03-24 修复搜索及添加快捷键 fix:修复title关键字和标签关键字冲突的情况 fix:修复前进后退时对图片的错误处理 enhance:在搜索时,可以键盘上下键来选择文章 enhance:添加了一些快捷键,详情查看 3-hexo快捷键说明 03-22 添加评论系统 enhance: 添加网易云跟帖评论系统 03-21 图片放大动画 enhance:增加图片放大动画,增加过度感,最大放大至原图大小(若尺寸超过屏幕,按屏幕大小限制) 03-20 评论添加锚点 enhance:添加文章meta(标题下)中的评论数点击事件,滑动到评论区,评论区若隐藏,则自动打开 03-19 修复firfox错位问题 fix:修复firefox错位问题 enhance:文章标题下的分类、标签、作者在文章列表隐藏的情况下(包括移动端)点击,自动呼出文章列表 03-18 动画优化及修复bug enhance:给所有锚点添加动画 fix:修复文章列表页自适应宽度,解决由于firefox不支持自定义滚动条导致的错位 change:改动页面内站点访问量统计的标签,改动查看 — 3-hexo配置首页 03-17 评论调整优化 修复预加载时的评论数 首页添加评论框 03-16 文章meta样式修改 将文章meta(包括文集、标签、时间、字数等)改到文章title下方 站点版权信息(@2017 Yelog),自定义,在主题 _config.yml 中配置 文章meta中添加评论数 03-15 增加样式 扩展了文章列表的移入样式,位置 _config.yml 中 article_list 03-14 打赏优化 加入切换二维码动画 移动端样式调整 03-13 首页重构 将首页改写为md格式,方便博主更改,位置:/layout/index.md 03-09 修复多说、添加流量统计 修复多说在pjax中的使用 添加字数统计功能 添加文章版权信息 03-06 添加打赏功能 添加打赏功能 添加table样式 03-04 添加作者和标签的提示 输入#或@显示下拉提示 03-03 修复多作者模式 修复开启多作者模式时,文章没有作者引起的异常 03-02 图片样式改动 alt显示在图片下方 图片放大功能 移动端图片宽度100% 2017年2月02-28 多作者模式 添加多作者模式 02-27 调整样式 修改a的样式 调整移动端宽度 02-25 修改github仓库名 修改github仓库名:从 3-hexo 到 hexo-theme-3-hexo 添加回到顶端功能:小火箭 修复safari滑动bug 02-24 站点版权、ICON和置顶 添加站点版权信息 更换icon到icoMoon 添加置顶功能 02-20 代码块高亮主题 添加十几种代码高亮主题 移动端适配(ipad、手机) 02-19 移动端适配 添加文章加载动画进度条 开始进行移动端适配 02-08 评论系统 添加多说评论 添加disqus评论 02-07 样式及动画新增修改 添加标题和时间的title 添加头像下外链图标(facebook等) 添加目录显示动画 02-06 功能和样式开发 添加全屏和目录功能 搜索框下添加tags的显示,并支持使用#搜索 CDN改为在_config.yml中配置 02-05 开源3-hexo主题 设计主题页面结构 分类过滤和标题关键字搜索 使用pjax方式加载页面 添加引用、表格等样式 使用highlight.js来处理代码块高亮 命名为3-hexo并在github上开源e","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"3-hexo配置首页","slug":"3-hexo-homepage","date":"2017-03-13T01:56:07.000Z","updated":"2024-07-05T11:10:22.585Z","comments":true,"path":"2017/03/13/3-hexo-homepage/","permalink":"http://yelog.org/2017/03/13/3-hexo-homepage/","excerpt":"","text":"今日将首页提到md文件中了,方便大家的更改。 首页文件位置 /layout/indexs.md ,既然是md格式,要怎么写大家应该都熟门熟路了,阿杰就不赘述了。 如果需要使用以下信息,可以按照下面的方式使用(以下内容不限首页使用) 文章数统计/字数统计加入含有 class="article_number"的html标签可显示文章数量。加入含有 class="site_word_count"的html标签可显示站点总字数。 <!-- 我这里是借用了code的样式,所以直接使用code标签。 自定义样式,可加入style属性设置--> <code class="article_number"></code> <code class="site_word_count"></code> 上面代码的效果:文章:篇;总字数:字; 流量统计 日志: 2017-03-18改动,由原来的 id 改为现在的 class,可在页面添加多个同类标签 加入含有 class="site_uv"的html标签可显示站点访问人次。加入含有 class="site_pv"的html标签可显示站点访问量。 <!-- 我这里是借用了code的样式,所以直接使用code标签。 自定义样式,可加入style属性设置--> <code class="site_uv"></code> <code class="site_pv"></code> 上面代码的效果:访问人数:人,访问量:次。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"不蒜子适配pjax","slug":"不蒜子适配pjax","date":"2017-03-09T12:15:51.000Z","updated":"2024-07-05T11:10:22.412Z","comments":true,"path":"2017/03/09/busuanzi-pjax/","permalink":"http://yelog.org/2017/03/09/busuanzi-pjax/","excerpt":"","text":"不蒜子一般配置加入脚本 <script async src="//dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js"></script> <!--pv方式 --> <span id="busuanzi_container_site_pv"> 本站总访问量<span id="busuanzi_value_site_pv"></span>次 </span> <!--uv方式 --> <span id="busuanzi_container_site_uv"> 本站访客数<span id="busuanzi_value_site_uv"></span>人次 </span> <!--pv方式 --> <span id="busuanzi_container_page_pv"> 本文总阅读量<span id="busuanzi_value_page_pv"></span>次 </span> 只安装脚本,不安装标签代码,即可实现只记数,不显示。 适配pjax最近开发3-hexo主题,由于主题使用的pjax,异步加载页面时不蒜子会出现加载不到多说js的问题。在pjax:end加载下面js代替标签即可 $.getScript("//dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js");","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"},{"name":"jQuery","slug":"jQuery","permalink":"http://yelog.org/tags/jQuery/"}]},{"title":"多说适配pjax","slug":"多说适配pjax","date":"2017-03-09T11:50:45.000Z","updated":"2024-07-05T11:10:22.355Z","comments":true,"path":"2017/03/09/duoshuo-pjax/","permalink":"http://yelog.org/2017/03/09/duoshuo-pjax/","excerpt":"","text":"最近开发3-hexo主题,由于主题使用的pjax,异步加载页面时多说会出现加载不到多说js的问题。 多说加载代码如下: //加载多说 function loadComment() { duoshuoQuery = {short_name: $(".theme_duoshuo_domain").val()}; var d = document, s = d.createElement('script'); s.src = 'https://static.duoshuo.com/embed.js?t='+new Date().getTime(); s.async = true; s.charset = 'UTF-8'; (d.head || d.body).appendChild(s); } 当局部加载页面时,就会无法加载多说。需要编写一个js方法,参考文档:(http://dev.duoshuo.com/docs/50b344447f32d30066000147) /** * pjax后需要回调函数.加载多说 */ function pajx_loadDuodsuo(){ if(typeof duoshuoQuery =="undefined"){ loadComment(); } else { var dus=$(".ds-thread"); if($(dus).length==1){ var el = document.createElement('div'); el.setAttribute('data-thread-key',$(dus).attr("data-thread-key"));//必选参数 el.setAttribute('data-url',$(dus).attr("data-url")); DUOSHUO.EmbedThread(el); $(dus).html(el); } } } 在pjax:end中调用此方法即可。","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"},{"name":"jQuery","slug":"jQuery","permalink":"http://yelog.org/tags/jQuery/"}]},{"title":"Hexo加入字数统计WordCount","slug":"hexo-wordcount","date":"2017-03-09T08:57:10.000Z","updated":"2024-07-05T11:10:22.549Z","comments":true,"path":"2017/03/09/hexo-wordcount/","permalink":"http://yelog.org/2017/03/09/hexo-wordcount/","excerpt":"","text":"只需要安装一个插件 WordCount 安装$ npm i hexo-wordcount --save 使用单篇文章字数 <%=wordcount(post.content) %> 所有文章的总字数 <%=totalcount(site) %> 日志2017年3月9日,给3-hexo添加字数统计功能","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"前端页面开发规范","slug":"font-develop-rule","date":"2017-03-08T00:58:21.000Z","updated":"2024-07-05T11:10:22.407Z","comments":true,"path":"2017/03/08/font-develop-rule/","permalink":"http://yelog.org/2017/03/08/font-develop-rule/","excerpt":"","text":"一、前言随着开发人员的不断增加,在没有规范的情况下,就会导致开发的页面不统一,不像是一个系统。为了解决这个问题,就有了此规范的出现,当然为了不影响各个功能的灵活性,此规范要求不高, 请耐心阅读,并应用到日常开发中。 当然,如果你有更好的建议,可以通过邮件联系 yangyj13@lenovo.com,进行沟通来完善此篇规范。 二、编程规范2.1 命名规范2.1.1 文件命名全部采用小写方式,以横杠分割。 正例: resource.vue、user-info.vue 反例: basic_data.vue、EventLog.vue 2.1.2 目录命名全部采用小写方式,以横杠分割。 正例: system、ship-support 反例: errorPage、Components 2.1.3 JS、CSS、SCSS、HTML、PNG文件命名全部采用小写方式,以横杠分割。 正例: btn.scss、element-ui.scss、lenovo-logo.png 反例: leftSearch.scss、LeGrid.js 2.1.4 命名规范性代码中命名严禁使用拼音和英文混合的方式,更不允许直接使用中文的方式。说明: 正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音的命名方式也要避免采用。 正例: loading、searchForm、tableHeight、dmsLoading、rmb 专有名词缩写,视同英文反例: getLiaoPanName、DMSLoading 2.2 插件使用2.2.1 eslint 代码规范注意:前端的代码格式化已经在 eslint 中声明了,所以确保自己已经启用了 eslint,并使 eslint 进行代码格式化。 2.2.2 i18n 国际化所有展示的内容都要支持国际化。国际化内容写到 /src/lang/ 下的对应模块,通过 this.$t('xx.xx.xx') 来使用。 英文国际化的列或标签,请使用开头字母大写的方式,如: UserId、Status、UserName。 2.3 组件使用2.3.1 table 表格表格组件推荐使用 vxe-table,功能更加全面,之后也会主力优化此表格。比如可编辑表格的样式经过优化:可编辑表格 2.3.2 dialog 弹窗弹窗组件推荐使用 vxe-modal,代码设计更加合理,功能也更加全面。 2.3.2 element-ui除 table 和 modal 外,其他组件比如 form、button、DateTimePicker 优先使用 element-ui 。 2.3.2.1 icon 图标图标优先使用 element-ui 的图标。如果没有合适的,可以在 iconfont 上寻找到合适的图标后,找 yangyj13@lenovo.com 进行添加。 2.3.2.2 button 按钮按钮大小:除了在表格中的按钮要使用 size="mini" 外,其他情况使用默认大小即可。 按钮颜色:普通的 查询/修改/操作 等按钮使用蓝色 type="primary",新增使用绿色 type="success",删除等“危险”操作使用红色 type="danger"。推荐给按钮添加图标,可在 element-ui-icon 寻找合适的图标。 2.3.3 其他组件如果上述组件并不能满足业务需求,可以优先在网上找到合适的组件后,与 yangyj13@lenoov.com 联系后添加。 2.4 页面布局2.4.1 新增/修改表单普通的表单,采用中间对其的方案,也就是整个表单的 label-width 设置为一样的。 注意:一般的,新增修改使用弹窗的方式,展示表单。新增/修改可以共用代码,具体可以参考 common/system/va-config.vue <el-form ref="dialogForm" v-loading="edit.loading" :model="edit.form" :rules="edit.formRules" label-width="150px" style="padding-right: 30px;" > ... </el-form> 2.4.2 查询表单+表格这种应该是最长间的需求方案了,可以参考 /common/system/user.vue,在写的时候注意以下几点: label-width 不要设置,保证标签文字开头和表格对齐。 el-form 使用 :inline="true" 设置表单内容行内显示。 设置 vxe-table 的 height 属性,保证表格底部贴住网页底部,又不会有滚动条(表格内允许有滚动条) 按钮也放到表单中,不要单独一行。 最终效果如下:","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"translation","slug":"translation","permalink":"http://yelog.org/tags/translation/"}]},{"title":"[译]理解浏览器关键渲染路径","slug":"理解浏览器关键渲染路径-译","date":"2017-03-08T00:58:21.000Z","updated":"2024-07-05T11:10:22.373Z","comments":true,"path":"2017/03/08/understanding-the-critical-rendering-path/","permalink":"http://yelog.org/2017/03/08/understanding-the-critical-rendering-path/","excerpt":"","text":"当一个浏览器接收到从服务器发来的html页面,在渲染并呈现到屏幕上之前,有很多步骤要做。浏览器渲染页面需要做的一系列行为被称作“关键渲染路径(Critical Rendering Path 简称CRP)”。 CRP 的知识对于如何提升网站性能是相当有用的。CRP有6个步骤: 构建DOM树 构建CSSOM树 运行JavaScript 创建渲染树 生成布局 绘制页面 构建DOM树DOM(Document Object Model)树是一个表示整个解析过的HTML页面的对象,从根节点<html>开始,会创建页面中的每个 元素/文本 节点。嵌套在其他元素中的元素作为字节点,每个节点都包含了其所有的元素属性,例如: 一个<a>节点将有 href 属性与其关联。 举个例子 <html> <head> <title>Understanding the Critical Rendering Path</title> <link rel="stylesheet" href="style.css"> </head> <body> <header> <h1>Understanding the Critical Rendering Path</h1> </header> <main> <h2>Introduction</h2> <p>Lorem ipsum dolor sit amet</p> </main> <footer> <small>Copyright 2017</small> </footer> </body> </html> 上面的 HTML 将会被解析成下面的DOM树HTML的优点在于它不必等待整个页面加载完成才呈现页面,可以解析一部分,显示一部分。但是像CSS、JavaScript等其他资源会阻止页面渲染。 构建CSSOM树CSSOM(CSS Object Model) 是一个跟DOM相关的样式对象。它跟DOM的表示方法是相似的,但是不论显式声明还是隐式继承,每个节点都存在关联样式。 In the style.css file from the document mentioned above, we have the folowing styles在上面提到的html页面的style.css中的样式如下 body { font-size: 18px; } header { color: plum; } h1 { font-size: 28px; } main { color: firebrick; } h2 { font-size: 20px; } footer { display: none; } 它会被构建成下面的CSSOM树CSS 被认为是 “渲染阻塞资源”,它意味着如果不首先完全解析资源,渲染树是无法构建的。CSS由于它的层叠继承的性质,不能像HTML一样解析一部分,显示一部分。定义在文档后面的样式会覆盖或改写之前定义的样式,因为在整个样式表都被解析之前,如果我们使用了在样式表中较早定义的样式,那错误的样式将被应用。这意味着CSS必须被全部解析之后,才能开始下一步。 如果CSS文件适用于当前设备的话,仅仅只是会阻塞渲染。<link rel="stylesheet">标签可以使用media属性,用来指定特定样式宽度的特定媒体查询。 举个例子,如果我们有一个包含媒体属性orientation:landscape的样式,我们使用纵向模式(portrait mode)查看页面,这个资源将不会阻塞渲染。 CSS 也会导致脚本阻塞。这是因为JavaScript文件必须等待CSSOM被构建后才能运行。 运行JavaScriptJavaScript被认为是解析阻塞资源,这意味着HTML的解析会被JavaScript阻塞。 当解析器解析到 <script> 标签时,无论该资源是内部还是外链的都会停止解析,先去下载资源。这也是为什么,当页面内有引用JavaScript文件时,引用标签要放到可视元素之后了。 为避免JavaScript解析阻塞,它可以通过设定 async 属性来要求其异步加载。 <script async src="script.js"> 创建渲染树渲染树是DOM和CSSOM的结合体,它代表最终会渲染在页面上的元素的结构对象。这意味着它只关注可见内容,对于被隐藏或者CSS属性 display:none 的属性,不会被包含在结构内。 使用上面例子的DOM和CSSOM,渲染树被创建如下: 生成布局布局决定了浏览器视窗的大小,它提供了上下文依赖的CSS样式,如百分比或窗口的单位。视窗尺寸通常通过 <head> 标签中的 <meta> 中的 viewport 设定来决定。如果不存在该标签,则通常默认为 980px 例如,最常用的 meta veiwport 的值将会被设置为和设备宽度相符: <meta name="viewport" content="width=device-width,initial-scale=1"> 如果用户访问网页的设备宽度为1000px。然后整体视窗尺寸就会基于这个宽度值了,比如 50% 就是500px, 10vw 就是100px 等等。 绘制页面最后,在绘制页面步骤。页面上的所有可见内容都会被转换为像素并呈现在屏幕上。 具体的绘制时间跟DOM数以及应用的样式有关。有些样式会花费更多的执行时间,比如复杂的渐变背景图片所需要的计算时间远超过简单固定背景色。 整合所有想要看到关键渲染路径的执行流程,可以使用DevTools,在Chrome中,它是根据时间轴展示的。 举个例子, 上面的页面加入<script>标签 <html> <head> <title>Understanding the Critical Rendering Path</title> <link rel="stylesheet" href="style.css"> </head> <body> <header> <h1>Understanding the Critical Rendering Path</h1> </header> <main> <h2>Introduction</h2> <p>Lorem ipsum dolor sit amet</p> </main> <footer> <small>Copyright 2017</small> </footer> <script src="main.js"></script> </body> </html> 可以看关于页面加载时的事件日志,以下是我们获得的: Send Request - 发送到index.html的GET请求 Parse HTML and Send Request - 开始解析HTML并构建DOM,然后发送 GET 请求style.css和main.js Parse Stylesheet - 根据 style.css 创建的CSSOM Evaluate Script - 执行 main.js Layout - 基于HTML的元视窗标签,生成布局 Paint - 绘制网页基于这些信息,我们可以知道如何优化关键渲染路径。 原文: Understanding the Critical Rendering Path","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"translation","slug":"translation","permalink":"http://yelog.org/tags/translation/"}]},{"title":"Hexo主题3-hexo","slug":"3-hexo","date":"2017-03-07T03:15:50.000Z","updated":"2024-07-05T11:10:22.558Z","comments":true,"path":"2017/03/07/3-hexo/","permalink":"http://yelog.org/2017/03/07/3-hexo/","excerpt":"","text":"阮一峰曾言:喜欢写blog的人,会经历三个阶段 第一阶段,刚接触Blog,觉得很新鲜,试着选择一个免费空间来写。第二阶段,发现免费空间限制太多,就自己购买域名和空间,搭建独立博客。第三阶段,觉得独立博客的管理太麻烦,最好在保留控制权的前提下,让别人来管,自己只负责写文章。 有对搭建个人blog有兴趣的朋友,可以翻看我往期文章。 笔者从去年开始通过hexo写blog,使用了yilia主题,但是随着文章数量的上升,检索等操作就显得特别笨重。 在遍寻无果的情况下,就写下了3-hexo主题。Demo:http://yelog.org 多图预警 ↓↓↓ 设计思路整体设计三段式设计: 通过分类过滤 通过标题关键字搜索 通过作者搜索若开启了多作者模式,则可以通过输入@,进行作者搜索,如下所示 通过标签搜索输入#,就会出现标签提示 评论功能 打赏功能 文章置顶 返回头部 使用1.安装$ git clone https://github.com/yelog/hexo-theme-3-hexo.git themes/3-hexo 2.配置1) 修改hexo根目录的_config.yml的两处,如下 theme: 3-hexo highlight: enable: false #关闭hexo渲染高亮,使用主题代码块高亮 2) 在hexo 根目录source下添加avatar.jpg文件,作为头像 安装字数统计(由于主题使用这个插件,必须安装,否则会报错) $ $ npm i --save hexo-wordcount 注意: 如果没有安装会在 hexo g 的时候报错 3.更新$ cd themes/3-hexo $ git pull","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"Vim命令速查表","slug":"Vim命令速查表","date":"2017-03-06T03:23:36.000Z","updated":"2024-07-05T11:10:22.174Z","comments":true,"path":"2017/03/06/Vim-command/","permalink":"http://yelog.org/2017/03/06/Vim-command/","excerpt":"","text":"去年上半年开始全面使用linux进行开发和娱乐了,现在已经回不去windows了。 话归正传,在linux上一直使用vim,慢慢熟悉了它的命令,才终于领悟了什么是编辑器之神。 最近抽空整理了这份速查表,收获颇丰,并分享给大家。 进入vim 命令 描述 vim filename 打开或新建文件,并将光标置于第一行首 vim +n filename 打开文件,并将光标置于第n行首 vim + filename 打开文件,并将光标置于最后一行首 vim +/pattern filename 打开文件,并将光标置于第一个与pattern匹配的串处 vim -r filename 在上次正用vim编辑时发生系统崩溃,恢复filename vim filename….filename 打开多个文件,依次编辑 vim配置 命令 描述 all 列出所有选项设置情况 term 设置终端类型 ignorance 在搜索中忽略大小写 list 显示制表位(Ctrl+I)和行尾标志($) number 显示行号 report 显示由面向行的命令修改过的数目 terse 显示简短的警告信息 warn 在转到别的文件时若没保存当前文件则显示NO write信息 nomagic 允许在搜索模式中,使用前面不带“\\”的特殊字符 nowrapscan 禁止vi在搜索到达文件两端时,又从另一端开始 mesg 允许vi显示其他用户用write写到自己终端上的信息 :set number / set nonumber 显示/不显示行号 :set ruler /set noruler 显示/不显示标尺 :set hlsearch 高亮显示查找到的单词 :set nohlsearch 关闭高亮显示 :syntax on 语法高亮 :set nu 显示行号 :set ignorecase 搜索时忽略大小写 :set smartcase 搜索时匹配大小写 :set ruler 显示光标位置坐标 :set hlsearch 搜索匹配全高亮 :set tabstop=8 设置tab大小,8为最常用最普遍的设置 :set softtabstop=8 4:4个空格,8:正常的制表符,12:一个制表符4个空格,16:两个制表符 :set autoindent 自动缩进 :set cindent C语言格式里面的自动缩进 移动光标 命令 描述 k nk 上 向上移动n行 j nj 下 向下移动n行 h nh 左 向左移动n行 l nl 右 向右移动n行 Space 光标右移一个字符 Backspace 光标左移一个字符 Enter 光标下移一行 w/W 光标右移一个字至字首 b/B 光标左移一个字至字首 e或E 光标右移一个字至字尾 ) 光标移至句尾 ( 光标移至句首 } 光标移至段落开头 { 光标移至段落结尾 n$ 光标移至第n行尾 H 光标移至屏幕顶行 M 光标移至屏幕中间行 L 光标移至屏幕最后行 0 (注意是数字零)光标移至当前行首 ^ 移动光标到行首第一个非空字符上去 $ 光标移至当前行尾 gg 移到第一行 G 移到最后一行 f 移动光标到当前行的字符a上 F 相反 % 移动到与制匹配的括号上去(),{},[],<>等 nG 移动到第n行上 G 到最后一行 屏幕滚动 命令 描述 Ctrl+e 向文件首翻一行 Ctrl+y 向文件尾翻一行 Ctrl+u 向文件首翻半屏 Ctrl+d 向文件尾翻半屏 Ctrl+f 向文件尾翻一屏 Ctrl+b 向文件首翻一屏 nz 将第n行滚至屏幕顶部,不指定n时将当前行滚至屏幕顶部 插入文本类 命令 描述 i 在光标前 I 在当前行首 a 光标后 A 在当前行尾 o 在当前行之下新开一行 O 在当前行之上新开一行 r 替换当前字符 R 替换当前字符及其后的字符,直至按ESC键 s 从当前光标位置处开始,以输入的文本替代指定数目的字符 S 删除指定数目的行,并以所输入文本代替之 ncw/nCW 修改指定数目的字 nCC 修改指定数目的行 删除命令 命令 描述 x/X 删除一个字符,x删除光标后的,而X删除光标前的 dw 删除一个单词(删除光标位置到下一个单词开始的位置) dnw 删除n个单词 dne 也可,只是删除到单词尾 do 删至行首 d$ 删至行尾 dd 删除一行 ndd 删除当前行及其后n-1行 dnl 向右删除n个字母 dnh 向左删除n个字母 dnj 向下删除n行,当前行+其上n行 dnk 向上删除n行,当期行+其下n行 cnw[word] 将n个word改变为word C$ 改变到行尾 cc 改变整行 shift+j 删除行尾的换行符,下一行接上来了 复制粘贴 命令 描述 p 粘贴用x或d删除的文本 ynw 复制n个单词 yy 复制一行 ynl 复制n个字符 y$ 复制当前光标至行尾处 nyy 拷贝n行 撤销 命令 描述 u 撤销前一次的操作 shif+u(U) 撤销对该行的所有操作 搜索及替换 命令 描述 /pattern 从光标开始处向文件尾搜索pattern ?pattern 从光标开始处向文件首搜索pattern n 在同一方向重复上一次搜索命令 N 在反方向上重复上一次搜索命令 cw newword 替换为newword n 继续查找 . 执行替换 :s/p1/p2/g 将当前行中所有p1均用p2替代,g表示执行 用c表示需要确认 :n1,n2 s/p1/p2/g 将第n1至n2行中所有p1均用p2替代 :g/p1/s//p2/g 将文件中所有p1均用p2替换 :1,$ s/string1/string2/g 在全文中将string1替换为string2 书签 命令 描述 m[a-z] 在文中做标记,标记号可为a-z的26个字母 `a 移动到标记a处 visual模式 命令 描述 v 进入visual 模式 V 进入行的visual 模式 ctrl+v 进如块操作模式用o和O改变选择的边的大小 在所有行插入相同的内容如include< 将光标移到开始插入的位置,按CTRL+V进入VISUAL模式,选择好模块后按I(shift+i),后插入要插入的文本,按[ESC]完成 行方式命令 命令 描述 :n1,n2 co n3 将n1行到n2行之间的内容拷贝到第n3行下 :n1,n2 m n3 将n1行到n2行之间的内容移至到第n3行下 :n1,n2 d 将n1行到n2行之间的内容删除 :n1,n2 w!command 将文件中n1行至n2行的内容作为command的输入并执行之若不指定n1,n2,则表示将整个文件内容作为command的输入 宏 命令 描述 q[a-z] 开始记录但前开始的操作为宏,名称可为【a-z】,然后用q终止录制宏 reg 显示当前定义的所有的宏,用@[a-z]来在当前光标处执行宏[a-z] 窗口操作 命令 描述 :split 分割一个窗口 :split file.c 为另一个文件file.c分隔窗口 :nsplit file.c 为另一个文件file.c分隔窗口,并指定其行数 ctrl+w 在窗口中切换 :close 关闭当前窗口 文件及其他 命令 描述 :q 退出vi :q! 不保存文件并退出vi :e filename 打开文件filename进行编辑 :e! 放弃修改文件内容,重新载入该文件编辑 :w 保存当前文件 :wq 存盘退出 :ZZ 保存当前文档并退出VIM :!command 执行shell命令command :r!command 将命令command的输出结果放到当前行 :n1,n2 write temp.c :read file.c 将文件file.c的内容插入到当前光标所在的下面","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"vim","slug":"vim","permalink":"http://yelog.org/tags/vim/"}]},{"title":"如何在linux中搭建ftp服务","slug":"如何在linux中搭建ftp服务","date":"2017-03-06T00:47:48.000Z","updated":"2024-07-05T11:10:23.101Z","comments":true,"path":"2017/03/06/linux-ftp/","permalink":"http://yelog.org/2017/03/06/linux-ftp/","excerpt":"","text":"什么是 FTPFTP 是文件传输协议File Transfer Protocol的缩写。顾名思义,FTP用于计算机之间通过网络进行文件传输。你可以通过FTP在计算机账户间进行文件传输,也可以在账户和桌面计算机之间传输文件,或者访问在线软件归档。但是,需要注意的是多数的FTP站点的使用率非常高,可能需要多次重连才能连接上。 FTP地址和HTTP地址(即网页地址)非常相似,只是FTP地址使用 ftp://前缀而不是http:// FTP 服务器是什么通常,拥有FTP地址的计算机是专用于接收FTP连接请求的。一台专用于接收FTP连接请求的计算机即为FTP服务器或者FTP站点。 现在,我们来开始一个特别的冒险,我们将会搭建一个FTP服务用于和家人、朋友进行文件共享。在本教程,我们将以vsftpd作为ftp服务。 VSFTPD是一个自称为最安全的FTP服务端软件。事实上VSFTPD的前两个字母表示“非常安全的very secure”。该软件的构建绕开了FTP协议的漏洞。 尽管如此,你应该知道还有更安全的方法进行文件管理和传输,如:SFTP(使用OpenSSH)。FTP协议对于共享非敏感数据是非常有用和可靠的。 安装 VSFTP#使用 rpm 安装 $ dnf -y install vsftpd #使用 deb 安装 $ sudo apt-get install vsftpd #在 Arch 中安装 $ sudo pacman -S vsftpd 配置 FTP 服务多数的VSFTPD配置项都在/etc/vsftpd.conf配置文件中。这个文件本身已经有非常良好的文档说明了,因此,在本节中,我只强调一些你可能进行修改的重要选项。使用man页面查看所有可用的选项和基本的 文档说明: $ man vsftpd.conf 根据文件系统层级标准,FTP共享文件默认位于/srv/ftp目录中。允许上传:为了允许ftp用户可以修改文件系统的内容,如上传文件等,“write_enable”标志必须设置为 YES write_enable=YES 允许本地(系统)用户登录:为了允许文件/etc/passwd中记录的用户可以登录ftp服务,“local_enable”标记必须设置为YES。 local_enable=YES 匿名用户登录下面配置内容控制匿名用户是否允许登录: # 允许匿名用户登录 anonymous_enable=YES # 匿名登录不需要密码(可选) no_anon_password=YES # 匿名登录的最大传输速率,Bytes/second(可选) anon_max_rate=30000 # 匿名登录的目录(可选) anon_root=/example/directory/ 根目录限制(Chroot Jail)( LCTT 译注:chroot jail是类unix系统中的一种安全机制,用于修改进程运行的根目录环境,限制该线程不能感知到其根目录树以外的其他目录结构和文件的存在。详情参看chroot jail) 有时我们需要设置根目录(chroot)环境来禁止用户离开他们的家(home)目录。在配置文件中增加/修改下面配置开启根目录限制(Chroot Jail): chroot_list_enable=YES chroot_list_file=/etc/vsftpd.chroot_list “chroot_list_file”变量指定根目录限制所包含的文件/目录( LCTT 译注:即用户只能访问这些文件/目录) 最后你必须重启ftp服务,在命令行中输入以下命令: $ sudo systemctl restart vsftpd 到此为止,你的ftp服务已经搭建完成并且启动了。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"ftp","slug":"ftp","permalink":"http://yelog.org/tags/ftp/"}]},{"title":"每天一个linux命令(57): sftp","slug":"linux-command-57-sftp","date":"2017-03-05T08:29:38.000Z","updated":"2024-07-05T11:10:22.992Z","comments":true,"path":"2017/03/05/linux-command-57-sftp/","permalink":"http://yelog.org/2017/03/05/linux-command-57-sftp/","excerpt":"","text":"sFTP(安全文件传输程序)是一种安全的交互式文件传输程序,其工作方式与 FTP(文件传输协议)类似。 然而,sFTP 比 FTP 更安全;它通过加密 SSH 传输处理所有操作。 它可以配置使用几个有用的 SSH 功能,如公钥认证和压缩。 它连接并登录到指定的远程机器,然后切换到交互式命令模式,在该模式下用户可以执行各种命令。 在本文中,我们将向你展示如何使用 sFTP 上传/下载整个目录(包括其子目录和子文件)。 How to use默认情况下,SFTP 协议采用和 SSH 传输协议一样的方式建立到远程服务器的安全连接。虽然,用户验证使用类似于 SSH 默认设置的密码方式,但是,建议创建和使用 SSH 无密码登录,以简化和更安全地连接到远程主机。 要连接到远程 sftp 服务器,如下建立一个安全 SSH 连接并创建 SFTP 会话: $ sftp root@server 登录到远程主机后,你可以如下运行交互式的 sFTP 命令: sftp> ls #列出服务器文件列表 sftp> lls #列出本地文件列表 sftp> pwd #当前服务器上路径 sftp> lpwd #当前本地路径 sftp> cd img #切换服务器路径 sftp> lcd img #切换本地路径 sftp> mkdir img #在服务器上创建一个目录 sftp> lmkdir img #在本地创建一个目录 上传文件sftp> put readme.md #上传单个文件 sftp> mput *.xls #上传多个文件 下载文件sftp> get readme.md #下载单个文件 sftp> mget *.xls #下载多个文件 上传文件夹使用put -r .但是远程服务器要提前创建一个相同名称的目录; -r 递归复制子目录和子文件 sftp> mkdir img sftp> put -r img 要保留修改时间、访问时间以及被传输的文件的模式,可使用 -p 。 sftp> put -pr img 下载文件夹sftp> get -r img 退出sftp> bye 或 sftp> exit 或 ctrl + d","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"AngularJs快速入门","slug":"AngularJs快速入门","date":"2017-03-04T00:40:58.000Z","updated":"2024-07-05T11:10:22.360Z","comments":true,"path":"2017/03/04/AngularJs/","permalink":"http://yelog.org/2017/03/04/AngularJs/","excerpt":"","text":"简介 AngularJS是一个JavaScript框架,为了克服HTML在构建应用上的不足而设计的。 AngularJS通过使用我们称为标识符(directives)的结构,让浏览器能够识别新的语法。 AngularJS 使得开发现代的单一页面应用程序(SPAs:Single Page Applications)变得更加容易。 表达式AngularJS 使用 表达式 把数据绑定到 HTML。 表达式AngularJS 表达式写在双大括号内:{{ expression }} 。AngularJS 表达式把数据绑定到 HTML,这与 ng-bind 指令有异曲同工之妙。AngularJS 将在表达式书写的位置”输出”数据。AngularJS 表达式 很像 JavaScript 表达式:它们可以包含文字、运算符和变量。实例: {{ 5 + 5 }} 或 {{ firstName + \" \" + lastName }} <div ng-app=""> <p>我的第一个表达式: {{ 5 + 5 }}</p> </div> 效果 数字<div ng-app="" ng-init="quantity=1;cost=5"> <p>总价: {{ quantity * cost }}</p> </div>","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"AngularJs","slug":"AngularJs","permalink":"http://yelog.org/tags/AngularJs/"}]},{"title":"3-hexo多作者模式","slug":"3-hexo-multiple-author","date":"2017-02-28T02:55:31.000Z","updated":"2024-07-05T11:10:22.563Z","comments":true,"path":"2017/02/28/3-hexo-multiple-author/","permalink":"http://yelog.org/2017/02/28/3-hexo-multiple-author/","excerpt":"","text":"尽管hexo是为个人blog而生的工具,但是有时也可能会有多作者需求,比如他人投稿等等,为此笔者在写3-hexo主题时,顺便添加了此功能 。 1.修改配置文件修改 3-hexo/_config.yml,开启多作者模式,并添加blog中出现的作者,为搜索提供数据 author: on: true #true:开启多作者模式 authors: author1: yelog #添加两个作者yelog、小马哥 author2: 小马哥 2.修改文章头部信息添加 author: yelog ,表示这篇文章的作者为yelog```xmltitle: reading-listdate: 2017-01-31 15:29:32author: yelogtop: 2categories:- 读书tags:- reading**效果:** ![](http://img.saodiyang.com/Fjq0M7pBzl6fsnC3ivpqMsdLdXc0.png) ## 搜索某个作者的所有文章 在搜索栏中输入`@小马哥`就可以显示出所有小马哥的文章。 如果你在_config.xml中配置了作者名,就可以出现`提示`,具体看第一部分 **效果如下:** ![](http://img.saodiyang.com/Fm2PK5W9Rd6ojYq055zZoWcbioAn.gif)","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"Hexo创建404页面","slug":"hexo-create-404-page","date":"2017-02-25T07:18:39.000Z","updated":"2024-07-05T11:10:22.535Z","comments":true,"path":"2017/02/25/hexo-create-404-page/","permalink":"http://yelog.org/2017/02/25/hexo-create-404-page/","excerpt":"","text":"对于github page来说,只要在根目录又404.html,当页面找不到时,就会被转发到/404.html页面,所以我们只要更改这个页面,就可以实现自定义404页面了。 但是我们通常会需要与本主题相符的404页面。那我们就需要以下操作 新建404页面 进入 Hexo 所在文件夹,输入 hexo new page 404 ; 打开刚新建的页面文件,默认在 Hexo 文件夹根目录下 /source/404/index.md; 在顶部插入一行,写上 enlink: /404,这表示指定该页固定链接为 http://"主页"/404.html --- title: 404 enlink: /404 date: 2016-09-27 11:31:01 --- --- ## 页面未找到! 效果 http://yelog.org/举个404例子","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"Hexo置顶及排序问题","slug":"hexo-top-sort","date":"2017-02-24T07:50:38.000Z","updated":"2024-07-05T11:10:22.545Z","comments":true,"path":"2017/02/24/hexo-top-sort/","permalink":"http://yelog.org/2017/02/24/hexo-top-sort/","excerpt":"","text":"近期在写3-hexo主题时,发现文章(site.posts)排序按照.md文件的创建时间排序,而没有按照文章中的date排序。 这就导致了一个问题,我重装了一次电脑,.md文件通过git备份了,还原回来的时候,md的创建时间都是一样的,所以文章列表就按照文章标题排序了 随后就想起了以前使用yilia主题时,设置过置顶文章。所以做了排序,顺便做了置顶的功能。 @牵猪的松鼠根据这篇文章写了一个npm插件 hexo-generator-topindex安装插件命令: npm install hexo-generator-topindex --save如果安装插件,可跳过第一部分 #修改hexo的js代码,直接看第二部分 #设置置顶 修改hexo的js代码直接上操作,修改node_modules/hexo-generator-index/lib/generator.js 'use strict'; var pagination = require('hexo-pagination'); module.exports = function(locals){ var config = this.config; var posts = locals.posts; posts.data = posts.data.sort(function(a, b) { if(a.top && b.top) { // 两篇文章top都有定义 if(a.top == b.top) return b.date - a.date; // 若top值一样则按照文章日期降序排 else return b.top - a.top; // 否则按照top值降序排 } else if(a.top && !b.top) { // 以下是只有一篇文章top有定义,那么将有top的排在前面(这里用异或操作居然不行233) return -1; } else if(!a.top && b.top) { return 1; } else return b.date - a.date; // 都没定义按照文章日期降序排 }); var paginationDir = config.pagination_dir || 'page'; return pagination('', posts, { perPage: config.index_generator.per_page, layout: ['index', 'archive'], format: paginationDir + '/%d/', data: { __index: true } }); }; 设置置顶给需要置顶的文章加入top参数,如下```xmltitle: 每天一个linux命令date: 2017-01-23 11:41:48top: 1categories:- 运维tags:- linux命令如果存在多个置顶文章,top后的参数越大,越靠前。 ## 2020-05-20 更新 3-hexo 主题已经内置排序算法,无需上面下载插件或修改源码,可以直接使用,具体可看 {% post_link 3-hexo-instruction %} 中的排序相关内容 ## References Netcan 的 [解决Hexo置顶问题](http://www.netcan666.com/2015/11/22/%E8%A7%A3%E5%86%B3Hexo%E7%BD%AE%E9%A1%B6%E9%97%AE%E9%A2%98/)","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"}]},{"title":"[译]Java内存泄露介绍","slug":"Java内存泄露介绍","date":"2017-02-21T07:05:39.000Z","updated":"2024-07-05T11:10:22.705Z","comments":true,"path":"2017/02/21/the-introduction-of-memory-leak-what-why-and-how/","permalink":"http://yelog.org/2017/02/21/the-introduction-of-memory-leak-what-why-and-how/","excerpt":"","text":"内存管理是Java最大的优势之一;你可以很简单的创建一个对象,内存的分配和释放则交给Java垃圾收集器处理;然而实际情况并非如此简单,因为在Java应用程序中会频繁的发生内存泄露。 这个教程将会说明内存泄露是什么?它为什么会发生?我们如何防止它? 内存泄露是什么内存泄露的定义:对象不再被应用程序使用,但是由于它们还在被引用,垃圾收集器不能清除掉它们。 为了理解这个定义,我们需要理解对象在内存中的状态;下面的图表说明什么是未被使用和未被引用。 图表中,有被引用的对象和未被引用的对象;未被引用的对象将会被当做垃圾回收,而被引用的对象将不会被当做垃圾回收;未被引用的对象由于没有被其他对象引用,它当然也是不被使用的对象,然而,不被使用的对象不全是不被引用的,它们中的一些是被引用的!这就是内存泄露的来源。 内存泄露为什么会发生让我们来看一下下面这个例子,它说明了内存泄露为什么会发生。在下面这个列子中,对象A引用了对象B,A的生命周期(t1t4)是比B(t2t3)的长;当B不再被应用程序使用时,A仍然在引用它;在这种情况下,垃圾收集器不能从内存中移除B;如果A引用了很多类似B这样的对象,它们不能被回收,又消耗着内存空间的资源,这样很有可能造成内存不足的问题。 还有一种可能的事情,B又引用了一些对象,这些被B引用的对象也不能被回收,那所有这些不被使用的对象将消耗大量宝贵的内存空间。 如何防止内存泄露下面有一些防止内存泄露的快速实践技巧 注意集合类,如:HashMap、ArrayList等等,因为它们是在常见的地方发生内存泄露;当它们被static声明时,它们和应用程序的生命周期是一样长的。 注意事件监听和回调,当一个监听事件被注册,而这个类再也没有被使用时可能会发生内存泄露。 “如果一个类管理自己的内存,程序员应该被提醒内存泄漏了”,通常,一个对象的指向其他对象的成员变量需要被置为null。 References:[1] Program Creek :The Introduction of Java Memory Leaks","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"translation","slug":"translation","permalink":"http://yelog.org/tags/translation/"}]},{"title":"每天一个linux命令(56): tailf","slug":"linux-command-56-tailf","date":"2017-02-20T07:11:06.000Z","updated":"2024-07-05T11:10:22.906Z","comments":true,"path":"2017/02/20/linux-command-56-tailf/","permalink":"http://yelog.org/2017/02/20/linux-command-56-tailf/","excerpt":"tailf 一个实时监听文件或日志的强大的命令","text":"tailf 一个实时监听文件或日志的强大的命令 命令格式$ tailf [option] file 命令描述 tailf 将会打印出一个文件的最后10行,等待并持续输出此文件的增长,它和tail -f相似,不同之处是当文件没有增长时,是不访问此文件的;但这会有一个副作用:不会更新文件的访问时间。当没有发生日志活动时,文件系统的冲洗(flush)不会定期发生。 tailf 对于打印日志不频繁,而又在使用笔记本电脑时是非常有用的,这样用户就能降低磁盘转速从而增加笔记本续航。 命令参数 参数 描述 -n,–lines=N,-N 输出最后N行,而不是默认的最后10行 命令实例例一:展示一个文件的最后5行并监听文件的新行(新增加的内容) $ tailf -n 5 myfile.txt $ tailf -5 myfile.txt $ tailf --lines=5 myfile.txt 注:这是一个实时监听文件或日志的强大的命令 例二:实时新增日志内容,并通过管道过滤出自己想要的内容 # 实时监听ip地址为24.10.160.10的访问日志 $ tailf access.log | grep 24.10.160.10","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"pjax用法","slug":"pjax用法","date":"2017-02-08T12:18:54.000Z","updated":"2024-07-05T11:10:22.385Z","comments":true,"path":"2017/02/08/pjax/","permalink":"http://yelog.org/2017/02/08/pjax/","excerpt":"最近在开发一款hexo主题3-hexo,其中使用了pjax大大提高了用户体验和加载速度,在此简单介绍一下pjax的用法github链接","text":"最近在开发一款hexo主题3-hexo,其中使用了pjax大大提高了用户体验和加载速度,在此简单介绍一下pjax的用法github链接 pjax是什么 pjax是一款jQuery插件,使用了ajax和pushState的技术,在保留真正永久链接,网页标题和可用的返回功能的情况下,带来一种快速的浏览体验。 –官方介绍 用人话说,就是当跳转过去的网页和当前网页的一部分是一样的,这时可以通过pjax就会从响应页面中取出 不同的那部分 (需指定),替换掉原来的内容。 如果在服务端判断处理,直接返回 不同的那部分内容,这样就可以减少带宽占用,提升加载速度。 这样做的优势: 由于从服务器取回的数据量变少,加载速度将会提升。 并且采用异步刷新页面中的不一样的地方,用户体验也是满满的。 保留了浏览器回退的功能(解决了ajax的尴尬) 好了,开始操作。 Demo第一步:引入jQuery和jQuery.pjax <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery.pjax/1.9.6/jquery.pjax.min.js"></script> 第二步:将指定的a的链接,转为pjax风格 /*将#menu中的a的链接的页面,只取回class=pjax元素中的内容,替换掉当前页面class=pjax元素中的内容*/ $(document).pjax('.#menu a', '.pjax', {fragment:'.pjax', timeout:8000}); 第三步:如果需要在请求的过程中做一些自定义的事件,可以使用下面的方法 $(document).on({ 'pjax:click': function() { //点击链接时,需要触发的事件写到这里 }, 'pjax:start': function() { //当开始获取请求时,需要触发的事件写在这里 }, 'pjax:end': function() { //当请求完成后,需要触发的事件写在这里 } }); 结束。 详细文档翻译于官方 参数$(document).pjax(selector, [container], options) selector 触发点击事件的选择器,String类型 container 一个选择器,为唯一的pjax容器 options 一个可以包含下面这些选项的对象 pjax options key default description timeout 650 ajax超时时间,单位毫秒,超时后将请求整个页面进行刷新 push true 使用 pushState 添加一个浏览器历史导航条目 replace false 替换URL,而不添加浏览器历史条目 maxCacheLength 20 历史内容 cache 的最大size version string : 当前pjax版本 scrollTo 0 垂直位置滚动,为了避免改变滚动条位置 type “GET” 可以查看jQuery.ajax() dataType “html” 可以查看jQuery.ajax() container css选择器,此元素内容将被替换 url link.href string: ajax 请求的URL target link eventually the relatedTarget value for pjax events fragment 从ajax响应的页面中抽取的‘片段’ Events除了pjax:click和pjax:clicked外的所有pjax事件从pjax容器中触发,是不需要点击链接的。所有事件的生命周期在通过pjax请求链接的过程中 event cancel arguments notes pjax:click ✔︎ options 在一个链接被激活(点击)时触发此事件,可以在此取消阻止pjax pjax:beforeSend ✔︎ xhr, options 可以设置 XHR 头 pjax:start xhr, options pjax:send xhr, options pjax:clicked options 当链接被点击,并且已经开始pjax请求后触发 pjax:beforeReplace contents, options 从服务器已经加载到HTML内容,在替换HTML内容之前触发 pjax:success data, status, xhr, options 从服务器已经加载到HTML内容,在替换HTML内容之后触发 pjax:timeout ✔︎ xhr, options 页面将会在options.timeout之后直接发起请求刷新页面,除非取消pjax pjax:error ✔︎ xhr, textStatus, error, options ajax 错误,将会请求刷新页面,除非取消pjax pjax:complete xhr, textStatus, options 不管结果是什么,在ajax后,都触发 pjax:end xhr, options 生命周期在浏览器返回或前进时触发 event cancel arguments notes pjax:popstate 事件方向(前进,后退)属性: “back”/“forward” pjax:start null, options 替换内容前 pjax:beforeReplace contents, options 从cache中读取内容后,替换html前 pjax:end null, options 替换内容后","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"jQuery","slug":"jQuery","permalink":"http://yelog.org/tags/jQuery/"}]},{"title":"reading-list","slug":"reading-list","date":"2017-01-31T07:29:32.000Z","updated":"2024-07-05T11:10:21.882Z","comments":true,"path":"2017/01/31/reading-list/","permalink":"http://yelog.org/2017/01/31/reading-list/","excerpt":"下面是一些我读过的书","text":"下面是一些我读过的书 ★ ★ ★ ★ ☆ ☆ ☆ :推荐指数,7星制。此乃余之私见,或显偏薄。 文学小说 《棋王 树王 孩子王》 by 阿城 2015年9月 ★ ★ ★ ★ ☆ ☆ ☆ 《达芬奇密码》 by 丹.布朗 2015年9月 ★ ★ ★ ★ ★ ★ ☆ 《追风筝的人》 by 卡勒德·胡赛尼 2016年6月 ★ ★ ★ ★ ★ ☆ ☆ 《霍乱时期的爱情》 by 加西亚·马尔克斯 2016年8月 ★ ★ ★ ★ ★ ☆ ☆ 《查令十字街84号》 by 海莲·汉芙 2016年9月 ★ ★ ★ ★ ☆ ☆ ☆ 《围城》 by 钱钟书 2017年1月 ★ ★ ★ ★ ★ ☆ ☆ 《一个陌生女人的来信》 by 茨威格 2017年1月 ★ ★ ★ ★ ★ ★ ☆ 《一颗心的沦亡》 by 茨威格 2017年1月 ★ ★ ★ ★ ☆ ☆ ☆ 《情感的迷茫》 by 茨威格 2017年1月 ★ ★ ★ ★ ★ ☆ ☆ 《一个女人一生中的二十四个小时》 by 茨威格 2017年1月 ★ ★ ★ ★ ☆ ☆ ☆ 《摆渡人》 by 克莱尔·麦克福尔 2019年3月28 ★ ☆ ☆ ☆ ☆ ☆ ☆ 《悟空传》 by 今何在 2016年8月 ★ ★ ★ ★ ★ ☆ ☆ 《岛上书店》 by 加·泽文 2019年4月 ★ ★ ★ ★ ☆ ☆ ☆ 《月亮与六便士》 by 毛姆 2019年4月 ★ ★ ★ ★ ★ ☆ ☆ 《活着》 by 余华 2019年5月★ ★ ★ ★ ★ ★ ☆ 《白夜行》 by 东野圭吾 2019年12月 ★ ★ ★ ★ ★ ☆ ☆ 旅行 《不去会死》 by 石田裕辅 2017年2月 ★ ★ ★ ☆ ☆ ☆ ☆ 历史 《秦迷·秦始皇的秘密》 by 李开元 2016年3月 ★ ★ ★ ★ ☆ ☆ ☆ 《鱼羊野史·第1卷》 by 高晓松 2016年9月 ★ ★ ★ ★ ★ ☆ ☆ 心理学 《天才在左,疯子在右》 by 高铭 2016年3月 ★ ★ ★ ★ ★ ★ ☆ 经济 《历代经济变革得失》 by 吴晓波 2017年2月 ★ ★ ★ ★ ★ ★ ☆ 科幻 《球状闪电》 by 刘慈欣 2016年3月 ★ ★ ★ ★ ☆ ☆ ☆ 《三体·“地球往事”三部曲之一》 by 刘慈欣 2016年4月 ★ ★ ★ ★ ★ ☆ ☆ 《流浪地球》 by 刘慈欣 2019年3月 ★ ★ ★ ★ ☆ ☆ ☆ 创业 《从零到一》 by 彼得·蒂尔 2015年6月 ★ ★ ★ ★ ☆ ☆ ☆ 《创业维艰》 by 本·霍洛维茨 2015年6月 ★ ★ ★ ★ ★ ☆ ☆ 《餐巾纸上的创业课》 by 神田昌典 2016年6月 ★ ★ ★ ★ ☆ ☆ ☆ 方法论 《如何高效学习》 by 斯科特·扬 2017年1月 ★ ★ ★ ★ ★ ☆ ☆ 《DISCover自我探索》 by 李海峰 2017年1月 ★ ★ ★ ★ ★ ★ ☆ 计算机 《淘宝技术这十年》 by 子柳 2015年6月 ★ ★ ★ ★ ★ ☆ ☆ 《写给大家看的设计书》 by Robin Williams 2023年6月 ★ ★ ★ ★ ★ ☆ ☆ 《黑客与画家》 by 保罗·格雷姆 2023年10月 ★ ★ ★ ★ ★ ☆ ☆ 待读/在读 《基督山伯爵》 by 大仲马 《程序员修炼之道》 by Andrew Hunt & David Thomas","categories":[{"name":"读书","slug":"读书","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/"},{"name":"书单","slug":"读书/书单","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/%E4%B9%A6%E5%8D%95/"}],"tags":[{"name":"reading","slug":"reading","permalink":"http://yelog.org/tags/reading/"}]},{"title":"每天一个linux命令","slug":"linux-command-list","date":"2017-01-23T03:41:48.000Z","updated":"2024-07-05T11:10:22.771Z","comments":true,"path":"2017/01/23/linux-command/","permalink":"http://yelog.org/2017/01/23/linux-command/","excerpt":"开始详细的系统的学习linux命令,坚持每天一个命令。","text":"开始详细的系统的学习linux命令,坚持每天一个命令。 此系列最初参考peida的“ 每天一个linux命令”,之后根据自己的见闻逐渐添加整理。 文件目录操作命令 每天一个linux命令(1): ls 每天一个linux命令(2): cd 每天一个linux命令(3): pwd 每天一个linux命令(4): mkdir 每天一个linux命令(5): rm 每天一个linux命令(6): rmdir 每天一个linux命令(7): mv 每天一个linux命令(8): cp 每天一个linux命令(9): touch 每天一个linux命令(10): cat 每天一个linux命令(11): nl 每天一个linux命令(12): more 每天一个linux命令(13): less 每天一个linux命令(14): head 每天一个linux命令(15): tail 每天一个linux命令(56): tailf 文件查找命令 每天一个linux命令(16): which 每天一个linux命令(17): whereis 每天一个linux命令(18): locate 每天一个linux命令(19): find命令概览 每天一个linux命令(20): find命令之exec 每天一个linux命令(21): find命令之xargs 每天一个linux命令(22): find命令的参数详解 文件打包上传和下载 每天一个linux命令(23): 用SecureCRT来上传和下载文件 每天一个linux命令(24): tar 每天一个linux命令(25): gzip linux文件权限设置 每天一个linux命令(26): chmod 每天一个linux命令(27): chgrp 每天一个linux命令(28): chown 每天一个linux命令(29): /etc/group文件详解 磁盘存储相关 每天一个linux命令(30): df 每天一个linux命令(31): du 性能监控和优化命令 每天一个linux命令(32): top 每天一个linux命令(33): free 每天一个linux命令(34): vmstat 每天一个linux命令(35): iostat 每天一个linux命令(36): lsof 网络命令 每天一个linux命令(37): ifconfig 每天一个linux命令(38): route 每天一个linux命令(39): ping 每天一个linux命令(40): traceroute 每天一个linux命令(41): netstat 每天一个linux命令(42): ss 每天一个linux命令(43): telnet 每天一个linux命令(44): rcp 每天一个linux命令(45): scp 其他命令 每天一个linux命令(46): ln 每天一个linux命令(47): diff 每天一个linux命令(48): date 每天一个linux命令(49): cal 每天一个linux命令(50): grep 每天一个linux命令(51): wc 每天一个linux命令(52): ps 每天一个linux命令(53): watch 每天一个linux命令(54): at 每天一个linux命令(55): crontab 每天一个linux命令(56): tailf 每天一个linux命令(57): sftp 每天一个linux命令(58): sort","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(55): crontab","slug":"linux-command-55-crontab","date":"2017-01-23T02:44:50.000Z","updated":"2024-07-05T11:10:22.850Z","comments":true,"path":"2017/01/23/linux-command-55-crontab/","permalink":"http://yelog.org/2017/01/23/linux-command-55-crontab/","excerpt":"前一天学习了 at 命令是针对仅运行一次的任务,循环运行的例行性计划任务,linux系统则是由 cron (crond) 这个系统服务来控制的。Linux 系统上面原本就有非常多的计划性工作,因此这个系统服务是默认启动的。另外, 由于使用者自己也可以设置计划任务,所以, Linux 系统也提供了使用者控制计划任务的命令 :crontab 命令。","text":"前一天学习了 at 命令是针对仅运行一次的任务,循环运行的例行性计划任务,linux系统则是由 cron (crond) 这个系统服务来控制的。Linux 系统上面原本就有非常多的计划性工作,因此这个系统服务是默认启动的。另外, 由于使用者自己也可以设置计划任务,所以, Linux 系统也提供了使用者控制计划任务的命令 :crontab 命令。 crond简介 crond是linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程,与windows下的计划任务类似,当安装完成操作系统后,默认会安装此服务工具,并且会自动启动crond进程,crond进程每分钟会定期检查是否有要执行的任务,如果有要执行的任务,则自动执行该任务。 Linux下的任务调度分为两类,系统任务调度和用户任务调度。 系统任务调度:系统周期性所要执行的工作,比如写缓存数据到硬盘、日志清理等。在/etc目录下有一个crontab文件,这个就是系统任务调度的配置文件。 /etc/crontab文件包括下面几行: # /etc/crontab: system-wide crontab # Unlike any other crontab you don't have to run the `crontab' # command to install the new version when you edit this file # and files in /etc/cron.d. These files also have username fields, # that none of the other crontabs do. SHELL=/bin/sh PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin # m h dom mon dow user command 17 * * * * root cd / && run-parts --report /etc/cron.hourly 25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily ) 47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly ) 52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly ) 前四行是用来配置crond任务运行的环境变量,第一行SHELL变量指定了系统要使用哪个shell,这里是bash,第二行PATH变量指定了系统执行命令的路径,第三行MAILTO变量指定了crond的任务执行信息将通过电子邮件发送给root用户,如果MAILTO变量的值为空,则表示不发送任务执行信息给用户,第四行的HOME变量指定了在执行命令或者脚本时使用的主目录。第六至九行表示的含义将在下个小节详细讲述。这里不在多说。 用户任务调度:用户定期要执行的工作,比如用户数据备份、定时邮件提醒等。用户可以使用 crontab 工具来定制自己的计划任务。所有用户定义的crontab 文件都被保存在 /var/spool/cron目录中。其文件名与用户名一致。 使用者权限文件文件:/etc/cron.deny说明:该文件中所列用户不允许使用crontab命令 文件:/etc/cron.allow说明:该文件中所列用户允许使用crontab命令 文件:/var/spool/cron/说明:所有用户crontab文件存放的目录,以用户名命名 crontab文件的含义 用户所建立的crontab文件中,每一行都代表一项任务,每行的每个字段代表一项设置,它的格式共分为六个字段,前五段是时间设定段,第六段是要执行的命令段,格式如下:minute hour day month week command其中: minute: 表示分钟,可以是从0到59之间的任何整数。 hour:表示小时,可以是从0到23之间的任何整数。 day:表示日期,可以是从1到31之间的任何整数。 month:表示月份,可以是从1到12之间的任何整数。 week:表示星期几,可以是从0到7之间的任何整数,这里的0或7代表星期日。 command:要执行的命令,可以是系统命令,也可以是自己编写的脚本文件。 在以上各个字段中,还可以使用以下特殊字符:星号(*):代表所有可能的值,例如month字段如果是星号,则表示在满足其它字段的制约条件后每月都执行该命令操作。逗号(,):可以用逗号隔开的值指定一个列表范围,例如,“1,2,5,7,8,9”中杠(-):可以用整数之间的中杠表示一个整数范围,例如“2-6”表示“2,3,4,5,6”正斜线(/):可以用正斜线指定时间的间隔频率,例如“0-23/2”表示每两小时执行一次。同时正斜线可以和星号一起使用,例如*/10,如果用在minute字段,表示每十分钟执行一次。 crond服务安装crontab: $ yum install crontabs 服务操作说明: $ /sbin/service crond start //启动服务 $ /sbin/service crond stop //关闭服务 $ /sbin/service crond restart //重启服务 $ /sbin/service crond reload //重新载入配置 查看crontab服务状态: $ service crond status 手动启动crontab服务: $ service crond start 查看crontab服务是否已设置为开机启动,执行命令: $ ntsysv 加入开机自动启动: $ chkconfig –level 35 crond on contab 命令详解命令格式$ crontab [-u user] file $ crontab [-u user] [ -e | -l | -r ] 命令功能 通过crontab 命令,我们可以在固定的间隔时间执行指定的系统指令或 shell script脚本。时间间隔的单位可以是分钟、小时、日、月、周及以上的任意组合。这个命令非常设合周期性的日志分析或数据备份等工作。 命令参数 参数 描述 -u user 用来设定某个用户的crontab服务,例如,“-u ixdba”表示设定ixdba用户的crontab服务,此参数一般有root用户来运行 file file是命令文件的名字,表示将file做为crontab的任务列表文件并载入crontab。如果在命令行中没有指定这个文件,crontab命令将接受标准输入(键盘)上键入的命令,并将它们载入crontab。 -e 编辑某个用户的crontab文件内容。如果不指定用户,则表示编辑当前用户的crontab文件 -l 显示某个用户的crontab文件内容,如果不指定用户,则表示显示当前用户的crontab文件内容 -r 从/var/spool/cron目录中删除某个用户的crontab文件,如果不指定用户,则默认删除当前用户的crontab文件 -i 在删除用户的crontab文件时给确认提示 常用方法例一:创建一个新的crontab文件在考虑向cron进程提交一个crontab文件之前,首先要做的一件事情就是设置环境变量EDITOR。cron进程根据它来确定使用哪个编辑器编辑crontab文件。9 9 %的UNIX和LINUX用户都使用vi,如果你也是这样,那么你就编辑$ HOME目录下的. profile文件,在其中加入这样一行:EDITOR=vi; export EDITOR然后保存并退出。不妨创建一个名为 cron的文件,其中是用户名,例如, davecron。在该文件中加入如下的内容。 # (put your own initials here)echo the date to the console every # 15minutes between 6pm and 6am 0,15,30,45 18-06 * * * /bin/echo ‘date’ > /dev/console 保存并退出。确信前面5个域用空格分隔。在上面的例子中,系统将每隔1 5分钟向控制台输出一次当前时间。如果系统崩溃或挂起,从最后所显示的时间就可以一眼看出系统是什么时间停止工作的。在有些系统中,用tty1来表示控制台,可以根据实际情况对上面的例子进行相应的修改。为了提交你刚刚创建的crontab文件,可以把这个新创建的文件作为cron命令的参数: $ crontab davecron现在该文件已经提交给cron进程,它将每隔1 5分钟运行一次。同时,新创建文件的一个副本已经被放在/var/spool/cron目录中,文件名就是用户名(即dave)。例二:列出crontab文件 $ crontab -l 说明:你将会看到和上面类似的内容。可以使用这种方法在$ H O M E目录中对crontab文件做一备份: $ crontab -l > $HOME/mycron这样,一旦不小心误删了crontab文件,可以用上一节所讲述的方法迅速恢复。 例三:编辑crontab文件 $ crontab -e 说明:可以像使用v i编辑其他任何文件那样修改crontab文件并退出。如果修改了某些条目或添加了新的条目,那么在保存该文件时, c r o n会对其进行必要的完整性检查。如果其中的某个域出现了超出允许范围的值,它会提示你。我们在编辑crontab文件时,没准会加入新的条目。例如,加入下面的一条: # DT:delete core files,at 3.30am on 1,7,14,21,26,26 days of each month 30 3 1,7,14,21,26 * * /bin/find -name “core’ -exec rm {} ;现在保存并退出。最好在crontab文件的每一个条目之上加入一条注释,这样就可以知道它的功能、运行时间,更为重要的是,知道这是哪位用户的作业。现在让我们使用前面讲过的crontab -l命令列出它的全部信息: $ crontab -l # (crondave installed on Tue May 4 13:07:43 1999) # DT:ech the date to the console every 30 minites 0,15,30,45 18-06 * * * /bin/echo date > /dev/tty1 # DT:delete core files,at 3.30am on 1,7,14,21,26,26 days of each month 30 3 1,7,14,21,26 * * /bin/find -name “core’ -exec rm {} ; 例四:删除crontab文件 $ crontab -r 例五:恢复丢失的crontab文件如果不小心误删了crontab文件,假设你在自己的$ H O M E目录下还有一个备份,那么可以将其拷贝到/var/spool/cron/,其中是用户名。如果由于权限问题无法完成拷贝,可以用: $ crontab 其中,是你在$ H O M E目录中副本的文件名。我建议你在自己的$ H O M E目录中保存一个该文件的副本。我就有过类似的经历,有数次误删了crontab文件(因为r键紧挨在e键的右边)。这就是为什么有些系统文档建议不要直接编辑crontab文件,而是编辑该文件的一个副本,然后重新提交新的文件。有些crontab的变体有些怪异,所以在使用crontab命令时要格外小心。如果遗漏了任何选项,crontab可能会打开一个空文件,或者看起来像是个空文件。这时敲delete键退出,不要按,否则你将丢失crontab文件。 使用实例例一:每1分钟执行一次command * * * * * command 例二:每小时的第3和第15分钟执行 3,15 * * * * command 例三:在上午8点到11点的第3和第15分钟执行 3,15 8-11 * * * command 例四:每隔两天的上午8点到11点的第3和第15分钟执行 3,15 8-11 */2 * * command 例五:每个星期一的上午8点到11点的第3和第15分钟执行 3,15 8-11 * * 1 command 例六:每晚的21:30重启smb 30 21 * * * /etc/init.d/smb restart 例七:每月1、10、22日的4 : 45重启smb 45 4 1,10,22 * * /etc/init.d/smb restart 例八:每周六、周日的1 : 10重启smb 10 1 * * 6,0 /etc/init.d/smb restart 例九:每天18 : 00至23 : 00之间每隔30分钟重启smb 0,30 18-23 * * * /etc/init.d/smb restart 例十:每星期六的晚上11 : 00 pm重启smb 0 23 * * 6 /etc/init.d/smb restart 例十一:每一小时重启smb * */1 * * * /etc/init.d/smb restart 例十二:晚上11点到早上7点之间,每隔一小时重启smb * 23-7/1 * * * /etc/init.d/smb restart 例十三:每月的4号与每周一到周三的11点重启smb 0 11 4 * mon-wed /etc/init.d/smb restart 例十四:一月一号的4点重启smb 0 4 1 jan * /etc/init.d/smb restart 例十五:每小时执行/etc/cron.hourly目录内的脚本 01 * * * * root run-parts /etc/cron.hourly 说明:run-parts这个参数了,如果去掉这个参数的话,后面就可以写要运行的某个脚本名,而不是目录名了 注意事项注意环境变量问题 有时我们创建了一个crontab,但是这个任务却无法自动执行,而手动执行这个任务却没有问题,这种情况一般是由于在crontab文件中没有配置环境变量引起的。 在crontab文件中定义多个调度任务时,需要特别注意的一个问题就是环境变量的设置,因为我们手动执行某个任务时,是在当前shell环境下进行的,程序当然能找到环境变量,而系统自动执行任务调度时,是不会加载任何环境变量的,因此,就需要在crontab文件中指定任务运行所需的所有环境变量,这样,系统执行任务调度时就没有问题了。 不要假定cron知道所需要的特殊环境,它其实并不知道。所以你要保证在shelll脚本中提供所有必要的路径和环境变量,除了一些自动设置的全局变量。所以注意如下3点: 1)脚本中涉及文件路径时写全局路径; 2)脚本执行要用到java或其他环境变量时,通过source命令引入环境变量,如: $ cat start_cbp.sh #!/bin/sh source /etc/profile export RUN_CONF=/home/d139/conf/platform/cbp/cbp_jboss.conf /usr/local/jboss-4.0.5/bin/run.sh -c mev & 3)当手动执行脚本OK,但是crontab死活不执行时。这时必须大胆怀疑是环境变量惹的祸,并可以尝试在crontab中直接引入环境变量解决问题。如: 0 * * * * . /etc/profile;/bin/sh /var/www/java/audit_no_count/bin/restart_audit.sh 注意清理系统用户的邮件日志 每条任务调度执行完毕,系统都会将任务输出信息通过电子邮件的形式发送给当前系统用户,这样日积月累,日志信息会非常大,可能会影响系统的正常运行,因此,将每条任务进行重定向处理非常重要。 例如,可以在crontab文件中设置如下形式,忽略日志输出: 0 */3 * * * /usr/local/apache2/apachectl restart >/dev/null 2>&1 “/dev/null 2>&1”表示先将标准输出重定向到/dev/null,然后将标准错误重定向到标准输出,由于标准输出已经重定向到了/dev/null,因此标准错误也会重定向到/dev/null,这样日志输出问题就解决了。 系统级任务调度与用户级任务调度 系统级任务调度主要完成系统的一些维护操作,用户级任务调度主要完成用户自定义的一些任务,可以将用户级任务调度放到系统级任务调度来完成(不建议这么做),但是反过来却不行,root用户的任务调度操作可以通过“crontab –uroot –e”来设置,也可以将调度任务直接写入/etc/crontab文件,需要注意的是,如果要定义一个定时重启系统的任务,就必须将任务放到/etc/crontab文件,即使在root用户下创建一个定时重启系统的任务也是无效的。 其他注意事项 新创建的cron job,不会马上执行,至少要过2分钟才执行。如果重启cron则马上执行。 当crontab突然失效时,可以尝试/etc/init.d/crond restart解决问题。或者查看日志看某个job有没有执行/报错tail -f /var/log/cron。 千万别乱运行crontab -r。它从Crontab目录(/var/spool/cron)中删除用户的Crontab文件。删除了该用户的所有crontab都没了。 在crontab中%是有特殊含义的,表示换行的意思。如果要用的话必须进行转义%,如经常用的date ‘+%Y%m%d’在crontab里是不会执行的,应该换成date ‘+%Y%m%d’。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(54): at","slug":"linux-command-54-at","date":"2017-01-22T02:23:44.000Z","updated":"2024-07-05T11:10:22.788Z","comments":true,"path":"2017/01/22/linux-command-54-at/","permalink":"http://yelog.org/2017/01/22/linux-command-54-at/","excerpt":"在windows系统中,windows提供了计划任务这一功能,在控制面板 -> 性能与维护 -> 任务计划, 它的功能就是安排自动运行的任务。 通过’添加任务计划’的一步步引导,则可建立一个定时执行的任务。","text":"在windows系统中,windows提供了计划任务这一功能,在控制面板 -> 性能与维护 -> 任务计划, 它的功能就是安排自动运行的任务。 通过’添加任务计划’的一步步引导,则可建立一个定时执行的任务。 在linux系统中你可能已经发现了为什么系统常常会自动的进行一些任务?这些任务到底是谁在支配他们工作的?在linux系统如果你想要让自己设计的备份程序可以自动在某个时间点开始在系统底下运行,而不需要手动来启动它,又该如何处置呢? 这些例行的工作可能又分为一次性定时工作与循环定时工作,在系统内又是哪些服务在负责? 还有,如果你想要每年在老婆的生日前一天就发出一封信件提醒自己不要忘记,linux系统下该怎么做呢? 今天我们主要学习一下一次性定时计划任务的at命令的用法! 命令格式$ at [参数] [时间] 命令功能 在一个指定的时间执行一个指定任务,只能执行一次,且需要开启atd进程(ps -ef | grep atd查看, 开启用/etc/init.d/atd start or restart; 开机即启动则需要运行 chkconfig –level 2345 atd on)。 命令参数 参数 描述 -m 当指定的任务被完成之后,将给用户发送邮件,即使没有标准输出 -I atq的别名 -d atrm的别名 -v 显示任务将被执行的时间 -c 打印任务的内容到标准输出 -V 显示版本信息 -q<列队> 使用指定的列队 -f<文件> 从指定文件读入任务而不是从标准输入读入 -t<时间参数> 以时间参数的形式提交要运行的任务at允许使用一套相当复杂的指定时间的方法。他能够接受在当天的hh:mm(小时:分钟)式的时间指定。假如该时间已过去,那么就放在第二天执行。当然也能够使用midnight(深夜),noon(中午),teatime(饮茶时间,一般是下午4点)等比较模糊的 词语来指定时间。用户还能够采用12小时计时制,即在时间后面加上AM(上午)或PM(下午)来说明是上午还是下午。 也能够指定命令执行的具体日期,指定格式为month day(月 日)或mm/dd/yy(月/日/年)或dd.mm.yy(日.月.年)。指定的日期必须跟在指定时间的后面。 上面介绍的都是绝对计时法,其实还能够使用相对计时法,这对于安排不久就要执行的命令是很有好处的。指定格式为:now + count time-units ,now就是当前时间,time-units是时间单位,这里能够是minutes(分钟)、hours(小时)、days(天)、weeks(星期)。count是时间的数量,究竟是几天,还是几小时,等等。 更有一种计时方法就是直接使用today(今天)、tomorrow(明天)来指定完成命令的时间。 TIME 时间格式,这里可以定义出什么时候要进行 at 这项任务的时间 TIME的格式: HH:MM ex> 04:00 在今日的 HH:MM 时刻进行,若该时刻已超过,则明天的 HH:MM 进行此任务。 HH:MM YYYY-MM-DDex> 04:00 2009-03-17强制规定在某年某月的某一天的特殊时刻进行该项任务 HH:MM[am|pm] [Month] [Date]ex> 04pm March 17也是一样,强制在某年某月某日的某时刻进行该项任务 HH:MM[am|pm] + number [minutes|hours|days|weeks]ex> now + 5 minutesex> 04pm + 3 days就是说,在某个时间点再加几个时间后才进行该项任务。 使用实例例一:三天后的下午 5 点锺执行 /bin/ls $ at 5pm+3 days at> /bin/ls at> <EOT> # 按一下Ctrl+d就会出现<EOT>结束符 job 2 at Thu Feb 2 17:00:00 2017 例二:明天17点钟,输出时间到指定文件内 $ at 17:20 tomorrow at> date >/root/2013.log at> <EOT> 例三:计划任务设定后,在没有执行之前我们可以用atq命令来查看系统没有执行工作任务 $ atq 2 Thu Feb 2 17:00:00 2017 a faker 例四:删除已经设置的任务 # 2 为atq查出来的最前面的任务id $ atrm 2 例五:显示已经设置的任务内容 $ at -c 2 #!/bin/sh # atrun uid=1000 gid=1000 # mail faker 0 umask 22 此处省略n个字符 /bin/ls atd 的启动与 at 运行的方式atd 的启动 要使用一次性计划任务时,我们的 Linux 系统上面必须要有负责这个计划任务的服务,那就是 atd 服务。 不过并非所有的 Linux distributions 都默认会把他打开的,所以,某些时刻我们需要手动将atd 服务激活才行。 激活的方法很简单,就是这样:命令: $ /etc/init.d/atd start $ /etc/init.d/atd restart 配置一下启动时就启动这个服务,免得每次重新启动都得再来一次 $ chkconfig atd on at 的运行方式 既然是计划任务,那么应该会有任务执行的方式,并且将这些任务排进行程表中。那么产生计划任务的方式是怎么进行的? 事实上,我们使用 at 这个命令来产生所要运行的计划任务,并将这个计划任务以文字档的方式写入 /var/spool/at/ 目录内,该工作便能等待 atd 这个服务的取用与运行了。就这么简单。 不过,并不是所有的人都可以进行 at 计划任务。为什么? 因为系统安全的原因。很多主机被所谓的攻击破解后,最常发现的就是他们的系统当中多了很多的黑客程序, 这些程序非常可能运用一些计划任务来运行或搜集你的系统运行信息,并定时的发送给黑客。 所以,除非是你认可的帐号,否则先不要让他们使用 at 命令。那怎么达到使用 at 的可控呢? 我们可以利用 /etc/at.allow 与 /etc/at.deny 这两个文件来进行 at 的使用限制。加上这两个文件后, at 的工作情况是这样的: 先找寻 /etc/at.allow 这个文件,写在这个文件中的使用者才能使用 at ,没有在这个文件中的使用者则不能使用 at (即使没有写在 at.deny 当中); 如果 /etc/at.allow 不存在,就寻找 /etc/at.deny 这个文件,若写在这个 at.deny 的使用者则不能使用 at ,而没有在这个 at.deny 文件中的使用者,就可以使用 at 命令了。 如果两个文件都不存在,那么只有 root 可以使用 at 这个命令。 透过这个说明,我们知道 /etc/at.allow 是管理较为严格的方式,而 /etc/at.deny 则较为松散 (因为帐号没有在该文件中,就能够运行 at 了)。在一般的 distributions 当中,由于假设系统上的所有用户都是可信任的, 因此系统通常会保留一个空的 /etc/at.deny 文件,意思是允许所有人使用 at 命令的意思 (您可以自行检查一下该文件)。 不过,万一你不希望有某些使用者使用 at 的话,将那个使用者的帐号写入 /etc/at.deny 即可! 一个帐号写一行。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(53): watch","slug":"linux-command-53-watch","date":"2017-01-21T02:12:30.000Z","updated":"2024-07-05T11:10:22.841Z","comments":true,"path":"2017/01/21/linux-command-53-watch/","permalink":"http://yelog.org/2017/01/21/linux-command-53-watch/","excerpt":"watch是一个非常实用的命令,基本所有的Linux发行版都带有这个小工具,如同名字一样,watch可以帮你监测一个命令的运行结果,省得你一遍遍的手动运行。在Linux下,watch是周期性的执行下个程序,并全屏显示执行结果。你可以拿他来监测你想要的一切命令的结果变化,比如 tail 一个 log 文件,ls 监测某个文件的大小变化,看你的想象力了!","text":"watch是一个非常实用的命令,基本所有的Linux发行版都带有这个小工具,如同名字一样,watch可以帮你监测一个命令的运行结果,省得你一遍遍的手动运行。在Linux下,watch是周期性的执行下个程序,并全屏显示执行结果。你可以拿他来监测你想要的一切命令的结果变化,比如 tail 一个 log 文件,ls 监测某个文件的大小变化,看你的想象力了! 命令格式$ watch[参数][命令] 命令功能 可以将命令的输出结果输出到标准输出设备,多用于周期性执行命令/定时执行命令 命令参数 参数 描述 -n或–interval watch缺省每2秒运行一下程序,可以用-n或-interval来指定间隔的时间 -d或–differences watch 会高亮显示变化的区域 -d=cumulative 会把变动过的地方(不管最近的那次有没有变动)都高亮显示出来 -t 或-no-title 会关闭watch命令在顶部的时间间隔,命令,当前时间的输出 -h, –help 查看帮助文档 使用实例例一:每隔一秒高亮显示网络链接数的变化情况 $ watch -n 1 -d netstat -ant 说明:其它操作:切换终端: Ctrl+x退出watch:Ctrl+g (deepin系统没效果,只能使用Ctrl+c退出了) 例二:每隔一秒高亮显示http链接数的变化情况 # 每隔一秒高亮显示http链接数的变化情况。 后面接的命令若带有管道符,需要加''将命令区域归整。 $ watch -n 1 -d 'pstree|grep http' 例三:实时查看模拟攻击客户机建立起来的连接数 $ watch 'netstat -an | grep:21 | \\ grep<模拟攻击客户机的IP>| wc -l' 例四:监测当前目录中 scf’ 的文件的变化 $ watch -d 'ls -l|grep scf' 例五:10秒一次输出系统的平均负载 $ watch -n 10 'cat /proc/loadavg'","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(52): ps","slug":"linux-command-52-ps","date":"2017-01-20T01:46:16.000Z","updated":"2024-07-05T11:10:23.016Z","comments":true,"path":"2017/01/20/linux-command-52-ps/","permalink":"http://yelog.org/2017/01/20/linux-command-52-ps/","excerpt":"Linux中的ps命令是Process Status的缩写。ps命令用来列出系统中当前运行的那些进程。ps命令列出的是当前那些进程的快照,就是执行ps命令的那个时刻的那些进程,如果想要动态的显示进程信息,就可以使用top命令。","text":"Linux中的ps命令是Process Status的缩写。ps命令用来列出系统中当前运行的那些进程。ps命令列出的是当前那些进程的快照,就是执行ps命令的那个时刻的那些进程,如果想要动态的显示进程信息,就可以使用top命令。 要对进程进行监测和控制,首先必须要了解当前进程的情况,也就是需要查看当前进程,而 ps 命令就是最基本同时也是非常强大的进程查看命令。使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等。总之大部分信息都是可以通过执行该命令得到的。 ps 为我们提供了进程的一次性的查看,它所提供的查看结果并不动态连续的;如果想对进程时间监控,应该用 top 工具。 kill 命令用于杀死进程。linux上进程有5种状态: 运行(正在运行或在运行队列中等待) 中断(休眠中, 受阻, 在等待某个条件的形成或接受到信号) 不可中断(收到信号不唤醒和不可运行, 进程必须等待直到有中断发生) 僵死(进程已终止, 但进程描述符存在, 直到父进程调用wait4()系统调用后释放) 停止(进程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信号后停止运行运行) ps工具标识进程的5种状态码:D 不可中断 uninterruptible sleep (usually IO)R 运行 runnable (on run queue)S 中断 sleepingT 停止 traced or stoppedZ 僵死 a defunct (”zombie”) process 命令格式$ ps [参数] 命令功能用来现实当前进程的状态 命令参数 参数 描述 a 显示所有进程 -a 显示同一终端下的所有程序 -A 显示所有进程 c 显示进程的真实名称 -N 反向选择 -e 等于“-A” e 显示环境变量 f 显示程序间的关系 -H 显示树状结构 r 显示当前终端的进程 T 显示当前终端的所有程序 u 指定用户的所有进程 -au 显示较详细的资讯 -aux 显示所有包含其他使用者的行程 -C<命令> 列出指定命令的状况 –lines<行数> 每页显示的行数 –width<字符数> 每页显示的字符数 –help 显示帮助信息 –version 显示版本显示 使用实例例一:显示所有进程信息 $ ps -A 例二:显示指定用户的进程信息 $ ps -u faker 例三:显示所有进程信息,连同命令行 $ ps -ef 例四:ps 与grep 常用组合用法,查找特定进程 $ ps -ef|grep ssh 例五:将目前属于您自己这次登入的 PID 与相关信息列示出来 $ ps -l 说明:各相关信息的意义: F 代表这个程序的旗标 (flag), 4 代表使用者为 super user S 代表这个程序的状态 (STAT),关于各 STAT 的意义将在内文介绍 UID 程序被该 UID 所拥有 PID 就是这个程序的 ID ! PPID 则是其上级父程序的ID C CPU 使用的资源百分比 PRI 这个是 Priority (优先执行序) 的缩写,详细后面介绍 NI 这个是 Nice 值,在下一小节我们会持续介绍 ADDR 这个是 kernel function,指出该程序在内存的那个部分。如果是个 running的程序,一般就是 “-“ SZ 使用掉的内存大小 WCHAN 目前这个程序是否正在运作当中,若为 - 表示正在运作 TTY 登入者的终端机位置 TIME 使用掉的 CPU 时间。 CMD 所下达的指令为何 在预设的情况下, ps 仅会列出与目前所在的 bash shell 有关的 PID 而已,所以, 当我使用 ps -l 的时候,只有三个 PID。 例六:列出目前所有的正在内存当中的程序 $ ps aux 说明: USER:该 process 属于那个使用者账号的 PID :该 process 的号码 %CPU:该 process 使用掉的 CPU 资源百分比 %MEM:该 process 所占用的物理内存百分比 VSZ :该 process 使用掉的虚拟内存量 (Kbytes) RSS :该 process 占用的固定的内存量 (Kbytes) TTY :该 process 是在那个终端机上面运作,若与终端机无关,则显示 ?,另外, tty1-tty6 是本机上面的登入者程序,若为 pts/0 等等的,则表示为由网络连接进主机的程序。 STAT:该程序目前的状态,主要的状态有 R :该程序目前正在运作,或者是可被运作 S :该程序目前正在睡眠当中 (可说是 idle 状态),但可被某些讯号 (signal) 唤醒。 T :该程序目前正在侦测或者是停止了 Z :该程序应该已经终止,但是其父程序却无法正常的终止他,造成 zombie (疆尸) 程序的状态 START:该 process 被触发启动的时间 TIME :该 process 实际使用 CPU 运作的时间 COMMAND:该程序的实际指令 例七:列出类似程序树的程序显示 $ ps -axjf 例八:找出与 cron 与 syslog 这两个服务有关的 PID 号码 $ ps aux | egrep '(cron|syslog)' 其他 # 可以用 | 管道和 more 连接起来分页查看 $ ps -aux |more # 把所有进程显示出来,并输出到ps001.txt文件 $ ps -aux > ps001.txt # 输出指定的字段 $ ps -o pid,ppid,pgrp,session,tpgid,comm","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(51): wc","slug":"linux-command-51-wc","date":"2017-01-19T01:33:59.000Z","updated":"2024-07-05T11:10:22.809Z","comments":true,"path":"2017/01/19/linux-command-51-wc/","permalink":"http://yelog.org/2017/01/19/linux-command-51-wc/","excerpt":"Linux系统中的wc(Word Count)命令的功能为统计指定文件中的字节数、字数、行数,并将统计结果显示输出。","text":"Linux系统中的wc(Word Count)命令的功能为统计指定文件中的字节数、字数、行数,并将统计结果显示输出。 命令格式$ wc [选项]文件... 命令功能 统计指定文件中的字节数、字数、行数,并将统计结果显示输出。该命令统计指定文件中的字节数、字数、行数。如果没有给出文件名,则从标准输入读取。wc同时也给出所指定文件的总统计数。 命令参数 参数 描述 -c 统计字节数 -l 统计行数 -m 统计字符数。这个标志不能与 -c 标志一起使用 -w 统计字数。一个字被定义为由空白、跳格或换行字符分隔的字符串 -L 打印最长行的长度 -help 显示帮助信息 –version 显示版本信息 使用实例例一:查看文件的字节数、字数、行数 $ wc 1.txt 5 19 105 1.txt 行数 单词数 字节数 文件名 例二:用wc命令怎么做到只打印统计数字不打印文件名 $ wc -l 1.txt 5 1.txt # 5行 $ cat 1.txt | wc -l 5 # 值输出数字 例三:用来统计当前目录下的文件和文件夹总数 # 数量中包含当前目录 $ ls -l | wc -l 10 # 7个文件 + 2个文件夹 + 1个当前目录","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(50): grep","slug":"linux-command-50-grep","date":"2017-01-18T02:12:46.000Z","updated":"2024-07-05T11:10:23.034Z","comments":true,"path":"2017/01/18/linux-command-50-grep/","permalink":"http://yelog.org/2017/01/18/linux-command-50-grep/","excerpt":"Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来。grep全称是Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户。","text":"Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来。grep全称是Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户。 grep的工作方式是这样的,它在一个或多个文件中搜索字符串模板。如果模板包括空格,则必须被引用,模板后的所有字符串被看作文件名。搜索的结果被送到标准输出,不影响原文件内容。 grep可用于shell脚本,因为grep通过返回一个状态值来说明搜索的状态,如果模板搜索成功,则返回0,如果搜索不成功,则返回1,如果搜索的文件不存在,则返回2。我们利用这些返回值就可进行一些自动化的文本处理工作。 命令格式$ grep [option] pattern file 命令功能 用于过滤/搜索的特定字符。可使用正则表达式能多种命令配合使用,使用上十分灵活。 命令参数 参数 描述 -a –text 不要忽略二进制的数据 -A<显示行数> –after-context=<显示行数> 除了显示符合范本样式的那一列之外,并显示该行之后的内容 -b –byte-offset 在显示符合样式的那一行之前,标示出该行第一个字符的编号 -B<显示行数> –before-context=<显示行数> 除了显示符合样式的那一行之外,并显示该行之前的内容 -c –count 计算符合样式的列数 -C<显示行数> –context=<显示行数>或-<显示行数> 显示上下文n行 -d <动作> –directories=<动作> 当指定要查找的是目录而非文件时,必须使用这项参数,否则grep指令将回报信息并停止动作 -e<范本样式> –regexp=<范本样式> 指定字符串做为查找文件内容的样式 -E –extended-regexp 将样式为延伸的普通表示法来使用 -f<规则文件> –file=<规则文件> 指定规则文件,其内容含有一个或多个规则样式,让grep查找符合规则条件的文件内容,格式为每行一个规则样式 -F –fixed-regexp 将样式视为固定字符串的列表 -G –basic-regexp 样式视为普通的表示法来使用 -h –no-filename 在显示符合样式的那一行之前,不标示该行所属的文件名称 -H –with-filename 在显示符合样式的那一行之前,表示该行所属的文件名称 -i –ignore-case 忽略字符的大小写 -l –file-with-matches 只列出匹配的文件名 -L –files-without-match 列出不匹配的文件名 -n –line-number 显示行号 -q –quiet或–silent 不显示任何信息 -r –recursive 递归查询, 此参数的效果和指定“-d recurse”参数相同 -s –no-messages 不显示错误信息 -v –revert-match 显示不包含匹配文本的所有行 -V –version 显示版本信息 -w –word-regexp 只显示全字符合的列 -x –line-regexp 只显示全列符合的列 -y 此参数的效果和指定“-i”参数相同 规则表达式grep的规则表达式 ^ #锚定行的开始 如:'^grep'匹配所有以grep开头的行。 $ #锚定行的结束 如:'grep$'匹配所有以grep结尾的行。 . #匹配一个非换行符的字符 如:'gr.p'匹配gr后接一个任意字符,然后是p。 * #匹配零个或多个先前字符 如:'*grep'匹配所有一个或多个空格后紧跟grep的行。 .* #一起用代表任意字符。 [] #匹配一个指定范围内的字符,如'[Gg]rep'匹配Grep和grep。 [^] #匹配一个不在指定范围内的字符,如:'[^A-FH-Z]rep'匹配不包含A-R和T-Z的一个字母开头,紧跟rep的行。 \\(..\\) #标记匹配字符,如'\\(love\\)',love被标记为1。 \\< #锚定单词的开始,如:'\\<grep'匹配包含以grep开头的单词的行。 \\> #锚定单词的结束,如'grep\\>'匹配包含以grep结尾的单词的行。 x\\{m\\} #重复字符x,m次,如:'o\\{5\\}'匹配包含5个o的行。 x\\{m,\\} #重复字符x,至少m次,如:'o\\{5,\\}'匹配至少有5个o的行。 x\\{m,n\\} #重复字符x,至少m次,不多于n次,如:'o\\{5,10\\}'匹配5–10个o的行。 \\w #匹配文字和数字字符,也就是[A-Za-z0-9],如:'G\\w*p'匹配以G后跟零个或多个文字或数字字符,然后是p。 \\W #\\w的反置形式,匹配一个或多个非单词字符,如点号句号等。 \\b #单词锁定符,如: '\\bgrep\\b'只匹配grep。POSIX字符 为了在不同国家的字符编码中保持一至,POSIX(The Portable Operating System Interface)增加了特殊的字符类,如[:alnum:]是[A-Za-z0-9]的另一个写法。要把它们放到[]号内才能成为正则表达式,如[A-Za-z0-9]或[[:alnum:]]。在linux下的grep除fgrep外,都支持POSIX的字符类。 [:alnum:] #文字数字字符 [:alpha:] #文字字符 [:digit:] #数字字符 [:graph:] #非空字符(非空格、控制字符) [:lower:] #小写字符 [:cntrl:] #控制字符 [:print:] #非空字符(包括空格) [:punct:] #标点符号 [:space:] #所有空白字符(新行,空格,制表符) [:upper:] #大写字符 [:xdigit:] #十六进制数字(0-9,a-f,A-F) 使用实例例一:查找指定进程 $ ps -ef|grep hexo faker 13401 19030 0 09:51 pts/2 00:00:15 hexo faker 15465 15449 0 10:34 pts/3 00:00:00 grep hexo 说明:第一条记录是查找出的进程;第二条结果是grep进程本身,并非真正要找的进程。 例二:查找指定进程数 $ ps -ef|grep hexo -c $ ps -ef|grep -c hexo 2 例三:从2.txt中读取关键词在1.txt中进行搜索 # -n显示行号 $ cat 1.txt | grep -nf 2.txt 1:If you please draw me a sheep! 2:What! 例四:从文件中查找关键词 $ grep 'jump' 1.txt I jumped to my feet,completely thunderstruck. 例五:从多个文件中查找关键词 $ grep 'jump' 1.txt 2.txt 1.txt:I jumped to my feet,completely thunderstruck. 2.txt:I jump 说明:多文件时,输出查询到的信息内容行时,会把文件的命名在行最前面输出并且加上”:”作为标示符 例六:grep不显示本身进程 $ ps aux|grep \\[s]sh $ ps aux | grep ssh | grep -v "grep" 例七:找出以u开头的行内容 $ cat 1.txt |grep ^u If you please draw me a sheep! I jumped to my feet,completely thunderstruck. 例八:输出非u开头的行内容 $ cat 1.txt | grep ^[^I] What! Draw me a sheep! 例九:输出以!结尾的行内容 $ cat 1.txt |grep \\!$ If you please draw me a sheep! What! Draw me a sheep! 例十:显示包含sh或者at字符的内容行 $ cat 1.txt |grep -E "sh|at" If you please draw me a sheep! What! Draw me a sheep! 例十一:显示当前目录下面以.txt 结尾的文件中的所有包含每个字符串至少有7个连续小写字符的字符串的行 $ grep '[a-z]\\{7\\}' *.txt 1.txt:I jumped to my feet,completely thunderstruck. 3.txt:kdfkksjdf112123 4.txt:kisdfsf","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(49): cal","slug":"linux-command-49-cal","date":"2017-01-17T01:38:32.000Z","updated":"2024-07-05T11:10:22.924Z","comments":true,"path":"2017/01/17/linux-command-49-cal/","permalink":"http://yelog.org/2017/01/17/linux-command-49-cal/","excerpt":"cal命令可以用来显示公历(阳历)日历。公历是现在国际通用的历法,又称格列历,通称阳历。“阳历”又名“太阳历”,系以地球绕行太阳一周为一年,为西方各国所通用,故又名“西历”。","text":"cal命令可以用来显示公历(阳历)日历。公历是现在国际通用的历法,又称格列历,通称阳历。“阳历”又名“太阳历”,系以地球绕行太阳一周为一年,为西方各国所通用,故又名“西历”。 命令格式$ cal [参数][月份][年份] 命令功能用于查看日历等时间信息,如只有一个参数,则表示年份(1-9999),如有两个参数,则表示月份和年份 命令参数 参数 描述 -1 显示一个月的月历 -3 显示系统前一个月,当前月,下一个月的月历 -s 显示星期天为一个星期的第一天,默认的格式 -m 显示星期一为一个星期的第一天 -j 显示在当年中的第几天(一年日期按天算,从1月1号算起,默认显示当前月在一年中的天数) -y 显示当前年份的日历 使用实例例一:显示当前月份日历 $ cal 例二:显示指定月份的日历 $ cal 6 2016 例三:显示2016年的日历 $ cal -y 2016 $ cal 2016 例四:显示自1月1日的天数 $ cal -j 例五:星期一显示在第一列 $ cal -m","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(48): date","slug":"linux-command-48-date","date":"2017-01-16T06:39:27.000Z","updated":"2024-07-05T11:10:22.855Z","comments":true,"path":"2017/01/16/linux-command-48-date/","permalink":"http://yelog.org/2017/01/16/linux-command-48-date/","excerpt":"在linux环境中,不管是编程还是其他维护,时间是必不可少的,也经常会用到时间的运算,熟练运用date命令来表示自己想要表示的时间,肯定可以给自己的工作带来诸多方便。","text":"在linux环境中,不管是编程还是其他维护,时间是必不可少的,也经常会用到时间的运算,熟练运用date命令来表示自己想要表示的时间,肯定可以给自己的工作带来诸多方便。 命令格式$ date [参数]... [+格式] 命令功能 date 可以用来显示或设定系统的日期与时间。 命令参数命令参数 参数 描述 %H 小时(以00-23来表示) %I 小时(以01-12来表示) %K 小时(以0-23来表示) %l 小时(以0-12来表示) %M 分钟(以00-59来表示) %P AM或PM %r 时间(含时分秒,小时以12小时AM/PM来表示) %s 总秒数。起算时间为1970-01-01 00:00:00 UTC %S 秒(以本地的惯用法来表示) %T 时间(含时分秒,小时以24小时制来表示) %X 时间(以本地的惯用法来表示) %Z 市区 %a 星期的缩写 %A 星期的完整名称 %b 月份英文名的缩写 %B 月份的完整英文名称 %c 日期与时间。只输入date指令也会显示同样的结果 %d 日期(以01-31来表示) %D 日期(含年月日) %j 该年中的第几天 %m 月份(以01-12来表示) %U 该年中的周数 %w 该周的天数,0代表周日,1代表周一,异词类推 %x 日期(以本地的惯用法来表示) %y 年份(以00-99来表示) %Y 年份(以四位数来表示) %n 在显示时,插入新的一行 %t 在显示时,插入tab MM 月份(必要) DD 日期(必要) hh 小时(必要) mm 分钟(必要) ss 秒(选择性) 选择参数 参数 描述 -d<字符串> 显示字符串所指的日期与时间。字符串前后必须加上双引号 -s<字符串> 根据字符串来设置日期与时间。字符串前后必须加上双引号 -u 显示GMT –help 在线帮助 –version 显示版本信息 使用说明1.在显示方面,使用者可以设定欲显示的格式,格式设定为一个加号后接数个标记,其中可用的标记列表如下: % : 打印出 %%n : 下一行%t : 跳格%H : 小时(00..23)%I : 小时(01..12)%k : 小时(0..23)%l : 小时(1..12)%M : 分钟(00..59)%p : 显示本地 AM 或 PM%r : 直接显示时间 (12 小时制,格式为 hh:mm:ss [AP]M)%s : 从 1970 年 1 月 1 日 00:00:00 UTC 到目前为止的秒数%S : 秒(00..61)%T : 直接显示时间 (24 小时制)%X : 相当于 %H:%M:%S%Z : 显示时区 %a : 星期几 (Sun..Sat)%A : 星期几 (Sunday..Saturday)%b : 月份 (Jan..Dec)%B : 月份 (January..December)%c : 直接显示日期与时间%d : 日 (01..31)%D : 直接显示日期 (mm/dd/yy)%h : 同 %b%j : 一年中的第几天 (001..366)%m : 月份 (01..12)%U : 一年中的第几周 (00..53) (以 Sunday 为一周的第一天的情形)%w : 一周中的第几天 (0..6)%W : 一年中的第几周 (00..53) (以 Monday 为一周的第一天的情形)%x : 直接显示日期 (mm/dd/yy)%y : 年份的最后两位数字 (00.99)%Y : 完整年份 (0000..9999)2.在设定时间方面date -s //设置当前时间,只有root权限才能设置,其他只能查看。date -s 20080523 //设置成20080523,这样会把具体时间设置成空00:00:00date -s 01:01:01 //设置具体时间,不会对日期做更改date -s “01:01:01 2008-05-23″ //这样可以设置全部时间date -s “01:01:01 20080523″ //这样可以设置全部时间date -s “2008-05-23 01:01:01″ //这样可以设置全部时间date -s “20080523 01:01:01″ //这样可以设置全部时间3.加减date +%Y%m%d //显示前天年月日date +%Y%m%d –date=”+1 day” //显示前一天的日期date +%Y%m%d –date=”-1 day” //显示后一天的日期date +%Y%m%d –date=”-1 month” //显示上一月的日期date +%Y%m%d –date=”+1 month” //显示下一月的日期date +%Y%m%d –date=”-1 year” //显示前一年的日期date +%Y%m%d –date=”+1 year” //显示下一年的日期 使用实例例一:显示当前时间 $ date 2017年 01月 28日 星期六 14:51:10 CST $ date '+%c' 2017年01月28日 星期六 14时51分35秒 $ date '+%D' 01/28/17 $ date '+%x' 2017年01月28日 $ date '+%T' 14:52:02 $ date '+%X' 14时52分06秒 例二:显示日期和设定时间 $ date --date 08:42:00 例三:date -d参数使用 $ date -d "nov 22" 2012年 11月 22日 星期四 00:00:00 CST $ date -d '2 weeks' 2012年 12月 22日 星期六 08:50:21 CST $ date -d 'next monday' 2012年 12月 10日 星期一 00:00:00 CST $ date -d next-day +%Y%m%d 20121209 $ date -d tomorrow +%Y%m%d 20121209 $ date -d last-day +%Y%m%d 20121207 $ date -d yesterday +%Y%m%d 20121207 $ date -d last-month +%Y%m 201211 $ date -d next-month +%Y%m 201301 $ date -d '30 days ago' 2012年 11月 08日 星期四 08:51:37 CST $ date -d '-100 days' 2012年 08月 30日 星期四 08:52:03 CST $ date -d 'dec 14 -2 weeks' 2012年 11月 30日 星期五 00:00:00 CST $ date -d '50 days' 2013年 01月 27日 星期日 08:52:27 CST 说明: date 命令的另一个扩展是 -d 选项,该选项非常有用。使用这个功能强大的选项,通过将日期作为引号括起来的参数提供,您可以快速地查明一个特定的日期。-d 选项还可以告诉您,相对于当前日期若干天的究竟是哪一天,从现在开始的若干天或若干星期以后,或者以前(过去)。通过将这个相对偏移使用引号括起来,作为 -d 选项的参数,就可以完成这项任务。 具体说明如下: date -d “nov 22” 今年的 11 月 22 日是星期三 date -d ‘2 weeks’ 2周后的日期 date -d ‘next monday’ (下周一的日期) date -d next-day +%Y%m%d(明天的日期)或者:date -d tomorrow +%Y%m%d date -d last-day +%Y%m%d(昨天的日期) 或者:date -d yesterday +%Y%m%d date -d last-month +%Y%m(上个月是几月) date -d next-month +%Y%m(下个月是几月) 使用 ago 指令,您可以得到过去的日期: date -d ‘30 days ago’ (30天前的日期) 使用负数以得到相反的日期: date -d ‘dec 14 -2 weeks’ (相对:dec 14这个日期的两周前的日期) date -d ‘-100 days’ (100天以前的日期) date -d ‘50 days’(50天后的日期) 例四:显示月份和日数 $ date '+%B %d' 一月 28 例五:显示时间后跳行,再显示目前日期 $ date '+%T%n%D' 14:58:23 01/28/17","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(47): diff","slug":"linux-command-47-diff","date":"2017-01-15T06:08:08.000Z","updated":"2024-07-05T11:10:22.979Z","comments":true,"path":"2017/01/15/linux-command-47-diff/","permalink":"http://yelog.org/2017/01/15/linux-command-47-diff/","excerpt":"diff 命令是 linux上非常重要的工具,用于比较文件的内容,特别是比较两个版本不同的文件以找到改动的地方。diff在命令行中打印每一个行的改动。最新版本的diff还支持二进制文件。diff程序的输出被称为补丁 (patch),因为Linux系统中还有一个patch程序,可以根据diff的输出将a.c的文件内容更新为b.c。diff是svn、cvs、git等版本控制工具不可或缺的一部分。","text":"diff 命令是 linux上非常重要的工具,用于比较文件的内容,特别是比较两个版本不同的文件以找到改动的地方。diff在命令行中打印每一个行的改动。最新版本的diff还支持二进制文件。diff程序的输出被称为补丁 (patch),因为Linux系统中还有一个patch程序,可以根据diff的输出将a.c的文件内容更新为b.c。diff是svn、cvs、git等版本控制工具不可或缺的一部分。 命令格式$ diff [参数] [文件1或目录1] [文件2或目录2] 命令功能 diff命令能比较单个文件或者目录内容。如果指定比较的是文件,则只有当输入为文本文件时才有效。以逐行的方式,比较文本文件的异同处。如果指定比较的是目录的的时候,diff 命令会比较两个目录下名字相同的文本文件。列出不同的二进制文件、公共子目录和只在一个目录出现的文件。 命令参数 参数 描述 - 指定要显示多少行的文本。此参数必须与-c或-u参数一并使用 -a或–text diff预设只会逐行比较文本文件 -b或–ignore-space-change 不检查空格字符的不同 -B或–ignore-blank-lines 不检查空白行 -c 显示全部内文,并标出不同之处 -C或–context 与执行”-c”指令相同 -d或–minimal 使用不同的演算法,以较小的单位来做比较 -D或ifdef 此参数的输出格式可用于前置处理器巨集 -e或–ed 此参数的输出格式可用于ed的script文件 -f或-forward-ed 输出的格式类似ed的script文件,但按照原来文件的顺序来显示不同处 -H或–speed-large-files 比较大文件时,可加快速度 -l或–ignore-matching-lines 若两个文件在某几行有所不同,而这几行同时都包含了选项中指定的字符或字符串,则不显示这两个文件的差异 -i或–ignore-case 不检查大小写的不同 -l或–paginate 将结果交由pr程序来分页 -n或–rcs 将比较结果以RCS的格式来显示 -N或–new-file 在比较目录时,若文件A仅出现在某个目录中,预设会显示:Only in目录:文件A若使用-N参数,则diff会将文件A与一个空白的文件比较 -p 若比较的文件为C语言的程序码文件时,显示差异所在的函数名称 -P或–unidirectional-new-file 与-N类似,但只有当第二个目录包含了一个第一个目录所没有的文件时,才会将这个文件与空白的文件做比较 -q或–brief 仅显示有无差异,不显示详细的信息 -r或–recursive 比较子目录中的文件 -s或–report-identical-files 若没有发现任何差异,仍然显示信息 -S或–starting-file 在比较目录时,从指定的文件开始比较 -t或–expand-tabs 在输出时,将tab字符展开 -T或–initial-tab 在每行前面加上tab字符以便对齐 -u,-U或–unified= 以合并的方式来显示文件内容的不同 -v或–version 显示版本信息 -w或–ignore-all-space 忽略全部的空格字符 -W或–width 在使用-y参数时,指定栏宽 -x或–exclude 不比较选项中所指定的文件或目录 -X或–exclude-from 您可以将文件或目录类型存成文本文件,然后在=中指定此文本文件 -y或–side-by-side 以并列的方式显示文件的异同之处 –left-column 在使用-y参数时,若两个文件某一行内容相同,则仅在左侧的栏位显示该行内容 –suppress-common-lines 在使用-y参数时,仅显示不同之处 –help 显示帮助 使用实例例一:比较两个文件```bash$ diff 1.txt 2.txt1c1< ii iii >**说明:** 上面的“1c1”表示第一个文件和第二个文件的第1行内容有所不同; diff 的normal 显示格式有三种提示: a - add c - change d - delete **`例二`:并排格式输出** ```bash $ diff 1.txt 2.txt -y -W 50 ii | iii iii iii iiii iiii iiiii iiiii 说明: “|”表示前后2个文件内容有不同 “<”表示后面文件比前面文件少了1行内容 “>”表示后面文件比前面文件多了1行内容 例三:上下文输出格式 $ diff 1.txt 2.txt -c *** 1.txt 2017-01-28 14:24:13.744538252 +0800 --- 2.txt 2017-01-28 14:24:59.096124066 +0800 *************** *** 1,4 **** ! ii iii iiii iiiii --- 1,4 ---- ! iii iii iiii iiiii 说明: 这种方式在开头两行作了比较文件的说明,这里有三中特殊字符: “+” 比较的文件的后者比前着多一行 “-” 比较的文件的后者比前着少一行 “!” 比较的文件两者有差别的行 例四:统一格式输出 $ diff 1.txt 2.txt -u --- 1.txt 2017-01-28 14:24:13.744538252 +0800 +++ 2.txt 2017-01-28 14:24:59.096124066 +0800 @@ -1,4 +1,4 @@ -ii +iii iii iiii iiiii 例五:比较文件夹不同 $ diff test3 test6 例六:比较两个文件不同,并生产补丁 $ diff -ruN 1.txt 2.txt >patch.log 例七:打补丁 $ 1.txt patch","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(46): ln","slug":"linux-command-46-ln","date":"2017-01-14T03:00:33.000Z","updated":"2024-07-05T11:10:22.969Z","comments":true,"path":"2017/01/14/linux-command-46-ln/","permalink":"http://yelog.org/2017/01/14/linux-command-46-ln/","excerpt":"ln是linux中又一个非常重要命令,它的功能是为某一个文件在另外一个位置建立一个同步的链接.当我们需要在不同的目录,用到相同的文件时,我们不需要在每一个需要的目录下都放一个必须相同的文件,我们只要在某个固定的目录,放上该文件,然后在 其它的目录下用ln命令链接(link)它就可以,不必重复的占用磁盘空间。","text":"ln是linux中又一个非常重要命令,它的功能是为某一个文件在另外一个位置建立一个同步的链接.当我们需要在不同的目录,用到相同的文件时,我们不需要在每一个需要的目录下都放一个必须相同的文件,我们只要在某个固定的目录,放上该文件,然后在 其它的目录下用ln命令链接(link)它就可以,不必重复的占用磁盘空间。 命令格式$ ln [参数][源文件或目录][目标文件或目录] 命令功能 Linux文件系统中,有所谓的链接(link),我们可以将其视为档案的别名,而链接又可分为两种 : 硬链接(hard link)与软链接(symbolic link),硬链接的意思是一个档案可以有多个名称,而软链接的方式则是产生一个特殊的档案,该档案的内容是指向另一个档案的位置。硬链接是存在同一个文件系统中,而软链接却可以跨越不同的文件系统。软连接 1.软链接,以路径的形式存在。类似于Windows操作系统中的快捷方式 2.软链接可以 跨文件系统 ,硬链接不可以 3.软链接可以对一个不存在的文件名进行链接 4.软链接可以对目录进行链接硬链接 1.硬链接,以文件副本的形式存在。但不占用实际空间。 2.不允许给目录创建硬链接 3.硬链接只有在同一个文件系统中才能创建两点注意 第一,ln命令会保持每一处链接文件的同步性,也就是说,不论你改动了哪一处,其它的文件都会发生相同的变化; 第二,ln的链接又分软链接和硬链接两种,软链接就是ln –s 源文件 目标文件,它只会在你选定的位置上生成一个文件的镜像,不会占用磁盘空间,硬链接 ln 源文件 目标文件,没有参数-s, 它会在你选定的位置上生成一个和源文件大小相同的文件,无论是软链接还是硬链接,文件都保持同步变化。 ln指令用在链接文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录,则会把前面指定的所有文件或目录复制到该目录中。若同时指定多个文件或目录,且最后的目的地并非是一个已存在的目录,则会出现错误信息。 命令参数必要参数 参数 描述 -b 删除,覆盖以前建立的链接 -d 允许超级用户制作目录的硬链接 -f 强制执行 -i 交互模式,文件存在则提示用户是否覆盖 -n 把符号链接视为一般目录 -s 软链接(符号链接) -v 显示详细的处理过程 选择参数 参数 描述 -S “-S<字尾备份字符串> ”或 “–suffix=<字尾备份字符串>” -V “-V<备份方式>”或“–version-control=<备份方式>” –help 显示帮助信息 –version 显示版本信息 使用实例例一:给文件创建软链接 # 为2.txt文件创建软链接2,如果2.txt丢失,2将失效 $ ln -s 2.txt 2 例二:给文件创建硬链接 # 为1.txt创建硬链接1,1.txt与1的各项属性相同,删除1.txt,1仍能使用 $ ln 1.txt 1 例三:接上面两实例,链接完毕后,删除和重建链接原文件 $ ll -rw-r--r-- 2 faker faker 10 1月 22 11:28 1 -rw-r--r-- 2 faker faker 10 1月 22 11:28 1.txt lrwxrwxrwx 1 faker faker 5 1月 28 11:15 2 -> 2.txt -rwxrwxrwx 1 faker faker 14 1月 18 10:06 2.txt $ rm 1.txt 2.txt -rw-r--r-- 1 faker faker 10 1月 22 11:28 1 lrwxrwxrwx 1 faker faker 5 1月 28 11:15 2 -> 2.txt $ cat 1 sdfiskdlf $ cat 2 cat: 2: 没有那个文件或目录 说明: 1.源文件被删除后,并没有影响硬链接文件;软链接文件在centos系统下不断的闪烁,提示源文件已经不存在 2.重建源文件后,软链接不在闪烁提示,说明已经链接成功,找到了链接文件系统;重建后,硬链接文件并没有受到源文件影响,硬链接文件的内容还是保留了删除前源文件的内容,说明硬链接已经失效 例三:将文件链接为另一个目录中的相同名字 # 在ig文件夹中创建一个1.txt的链接 $ ln 1.txt ig/ $ ll ig -rw-r--r-- 2 faker faker 10 1月 28 13:40 1.txt 例五:给目录创建软连接 $ ln -s ig gi 说明: 1.目录只能创建软链接 2.目录创建链接必须用绝对路径,相对路径创建会不成功,会提示:符号连接的层数过多 这样的错误(测试并不会出现这样的问题) 3.在链接目标目录中修改文件都会在源文件目录中同步变化","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(45): scp","slug":"linux-command-45-scp","date":"2017-01-13T02:53:48.000Z","updated":"2024-07-05T11:10:23.002Z","comments":true,"path":"2017/01/13/linux-command-45-scp/","permalink":"http://yelog.org/2017/01/13/linux-command-45-scp/","excerpt":"scp是secure copy的简写,用于在Linux下进行远程拷贝文件的命令,和它类似的命令有cp,不过cp只是在本机进行拷贝不能跨服务器,而且scp传输是加密的。可能会稍微影响一下速度。当你服务器硬盘变为只读 read only system时,用scp可以帮你把文件移出来。另外,scp还非常不占资源,不会提高多少系统负荷,在这一点上,rsync就远远不及它了。虽然 rsync比scp会快一点,但当小文件众多的情况下,rsync会导致硬盘I/O非常高,而scp基本不影响系统正常使用。","text":"scp是secure copy的简写,用于在Linux下进行远程拷贝文件的命令,和它类似的命令有cp,不过cp只是在本机进行拷贝不能跨服务器,而且scp传输是加密的。可能会稍微影响一下速度。当你服务器硬盘变为只读 read only system时,用scp可以帮你把文件移出来。另外,scp还非常不占资源,不会提高多少系统负荷,在这一点上,rsync就远远不及它了。虽然 rsync比scp会快一点,但当小文件众多的情况下,rsync会导致硬盘I/O非常高,而scp基本不影响系统正常使用。 命令格式$ scp [参数] [原路径] [目标路径] 命令功能 scp是 secure copy的缩写, scp是linux系统下基于ssh登陆进行安全的远程文件拷贝命令。linux的scp命令可以在linux服务器之间复制文件和目录。 命令参数 参数 描述 -1 强制scp命令使用协议ssh1 -2 强制scp命令使用协议ssh2 -4 强制scp命令只使用IPv4寻址 -6 强制scp命令只使用IPv6寻址 -B 使用批处理模式(传输过程中不询问传输口令或短语) -C 允许压缩。(将-C标志传递给ssh,从而打开压缩功能) -p 保留原文件的修改时间,访问时间和访问权限 -q 不显示传输进度条 -r 递归复制整个目录 -v 详细方式显示输出。scp和ssh(1)会显示出整个过程的调试信息。这些信息用于调试连接,验证和配置问题 -c cipher 以cipher将数据传输进行加密,这个选项将直接传递给ssh -F ssh_config 指定一个替代的ssh配置文件,此参数直接传递给ssh -i identity_file 从指定文件中读取传输时使用的密钥文件,此参数直接传递给ssh -l limit 限定用户所能使用的带宽,以Kbit/s为单位 -o ssh_option 如果习惯于使用ssh_config(5)中的参数传递方式 -P port 注意是大写的P, port是指定数据传输用到的端口号 -S program 指定加密传输时所使用的程序。此程序必须能够理解ssh(1)的选项 使用实例从本地服务器复制到远程服务器: # 指定了用户名,命令执行后需输入密码 $ scp -r img/* root@server:/var/project/img/ # 没有指定用户名,命令执行后需要输入用户名密码 $ scp -r img/* server:/var/project/img/ 从远程服务器复制到本地当前目录: $ scp -r server:/var/project/img/* .","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(44): rcp","slug":"linux-command-44-rcp","date":"2017-01-12T02:36:06.000Z","updated":"2024-07-05T11:10:22.896Z","comments":true,"path":"2017/01/12/linux-command-44-rcp/","permalink":"http://yelog.org/2017/01/12/linux-command-44-rcp/","excerpt":"rcp代表“remote file copy”(远程文件拷贝)。该命令用于在计算机之间拷贝文件。rcp命令有两种格式。第一种格式用于文件到文件的拷贝;第二种格式用于把文件或目录拷贝到另一个目录中。","text":"rcp代表“remote file copy”(远程文件拷贝)。该命令用于在计算机之间拷贝文件。rcp命令有两种格式。第一种格式用于文件到文件的拷贝;第二种格式用于把文件或目录拷贝到另一个目录中。 命令格式$ rcp [参数] [源文件] [目标文件] 命令功能 rcp命令用在远端复制文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录,则它会把前面指定的所有文件或目录复制到该目录中。 命令参数 命令 描述 -r 递归地把源目录中的所有内容拷贝到目的目录中。要使用这个选项,目的必须是一个目录 -p 试图保留源文件的修改时间和模式,忽略umask -k 请求rcp获得在指定区域内的远程主机的Kerberos 许可,而不是获得由krb_relmofhost⑶确定的远程主机区域内的远程主机的Kerberos许可。 -x 为传送的所有数据打开DES加密。这会影响响应时间和CPU利用率,但是可以提高安全性。如果在文件名中指定的路径不是完整的路径名,那么这个路径被解释为相对远程机上同名用户的主目录。如果没有给出远程用户名,就使用当前用户名。如果远程机上的路径包含特殊shell字符,需要用反斜线(\\)、双引号(”)或单引号(’)括起来,使所有的shell元字符都能被远程地解释。需要说明的是,rcp不提示输入口令,它通过rsh命令来执行拷贝。 directory 每个文件或目录参数既可以是远程文件名也可以是本地文件名。远程文件名具有如下形式:rname@rhost:path,其中rname是远程用户名,rhost是远程计算机名,path是这个文件的路径。 使用实例使用rcp,需要具备的条件 如果系统中有 /etc/hosts 文件,系统管理员应确保该文件包含要与之进行通信的远程主机的项。 /etc/hosts 文件中有一行文字,其中包含每个远程系统的以下信息: internet_address official_name alias 例如: 9.186.10.*** webserver1.com.58.webserver.rhosts 文件 .rhosts 文件位于远程系统的主目录下,其中包含本地系统的名称和本地登录名。 例如,远程系统的 .rhosts 文件中的项可能是: webserver1 root 其中,webserver1 是本地系统的名称,root 是本地登录名。这样,webserver1 上的 root 即可在包含.rhosts 文件的远程系统中来回复制文件。配置过程:只对root用户生效 在双方root用户根目录下建立.rhosts文件,并将双方的hostname加进去.在此之前应在双方的 /etc/hosts文件中加入对方的IP和hostname 把rsh服务启动起来,redhat默认是不启动的。方法:用执行ntsysv命令,在rsh选项前用空格键选中,确定退出。然后执行:service xinetd restart即可。 3.到/etc/pam.d/目录下,把rsh文件中的auth required /lib/security/pam_securetty.so一行用“#”注释掉即可。(只有注释掉这一行,才能用root用户登录) 例一:将本地img文件夹内的所有内容 复制到服务器相应的img目录下 # -r 递归子目录 $ rcp -r img/* webserver1:/var/project/img/ 例二:将服务器的img文件夹内的所有内容 复制到本地目录下 # -r 递归子目录 $ rcp -r webserver1:/var/project/img/* img/ 例三:将目录复制到远程系统:要将本地目录及其文件和子目录复制到远程系统 # 将本地的img目录复制到服务器的project目录下 $ rcp -r img/ webserver1:/var/project/","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(43): telnet","slug":"linux-command-43-telnet","date":"2017-01-12T01:24:01.000Z","updated":"2024-07-05T11:10:22.919Z","comments":true,"path":"2017/01/12/linux-command-43-telnet/","permalink":"http://yelog.org/2017/01/12/linux-command-43-telnet/","excerpt":"telnet命令通常用来远程登录。telnet程序是基于TELNET协议的远程登录客户端程序。Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的 能力。在终端使用者的电脑上使用telnet程序,用它连接到服务器。终端使用者可以在telnet程序中输入命令,这些命令会在服务器上运行,就像直接在服务器的控制台上输入一样。可以在本地就能控制服务器。要开始一个 telnet会话,必须输入用户名和密码来登录服务器。Telnet是常用的远程控制Web服务器的方法。","text":"telnet命令通常用来远程登录。telnet程序是基于TELNET协议的远程登录客户端程序。Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的 能力。在终端使用者的电脑上使用telnet程序,用它连接到服务器。终端使用者可以在telnet程序中输入命令,这些命令会在服务器上运行,就像直接在服务器的控制台上输入一样。可以在本地就能控制服务器。要开始一个 telnet会话,必须输入用户名和密码来登录服务器。Telnet是常用的远程控制Web服务器的方法。 但是,telnet因为采用明文传送报文,安全性不好,很多Linux服务器都不开放telnet服务,而改用更安全的ssh方式了。但仍然有很多别的系统可能采用了telnet方式来提供远程登录,因此弄清楚telnet客户端的使用方式仍是很有必要的。 telnet命令还可做别的用途,比如确定远程服务的状态,比如确定远程服务器的某个端口是否能访问。 命令格式$ telnet [参数][主机] 命令功能 执行telnet指令开启终端机阶段作业,并登入远端主机。 命令参数 命令 描述 -8 允许使用8位字符资料,包括输入与输出 -a 尝试自动登入远端系统 -b<主机别名> 使用别名指定远端主机名称 -c 不读取用户专属目录里的.telnetrc文件 -d 启动排错模式 -e<脱离字符> 设置脱离字符 -E 滤除脱离字符 -f 此参数的效果和指定”-F”参数相同 -F 使用Kerberos V5认证时,加上此参数可把本地主机的认证数据上传到远端主机 -k<域名> 使用Kerberos认证时,加上此参数让远端主机采用指定的领域名,而非该主机的域名 -K 不自动登入远端主机 -l<用户名称> 指定要登入远端主机的用户名称 -L 允许输出8位字符资料 -n<记录文件> 指定文件记录相关信息 -r 使用类似rlogin指令的用户界面 -S<服务类型> 设置telnet连线所需的IP TOS信息 -x 假设主机有支持数据加密的功能,就使用它 -X<认证形态> 关闭指定的认证形态 使用实例例一:远程服务器无法访问 $ telnet 192.168.120.206 Trying 192.168.120.209... telnet: connect to address 192.168.120.209: No route to host telnet: Unable to connect to remote host: No route to host 说明:处理这种情况方法: (1)确认ip地址是否正确? (2)确认ip地址对应的主机是否已经开机? (3)如果主机已经启动,确认路由设置是否设置正确?(使用route命令查看) (4)如果主机已经启动,确认主机上是否开启了telnet服务?(使用netstat命令查看,TCP的23端口是否有LISTEN状态的行) (5)如果主机已经启动telnet服务,确认防火墙是否放开了23端口的访问?(使用iptables-save查看) 例二:域名无法解析 $ telnet www.baidu.com www.baidu.com/telnet: Temporary failure in name resolution 说明:处理这种情况方法: (1)确认域名是否正确 (2)确认本机的域名解析有关的设置是否正确(/etc/resolv.conf中nameserver的设置是否正确,如果没有,可以使用nameserver 8.8.8.8) (3)确认防火墙是否放开了UDP53端口的访问(DNS使用UDP协议,端口53,使用iptables-save查看) 例三:连接被拒绝 $ telnet 192.168.120.206 Trying 192.168.120.206... telnet: connect to address 192.168.120.206: Connection refused telnet: Unable to connect to remote host: Connection refused 说明:处理这种情况: (1)确认ip地址或者主机名是否正确? (2)确认端口是否正确,是否默认的23端口 例四:正常telnet $ telnet 192.168.120.204 Trying 192.168.120.204... Connected to 192.168.120.204 (192.168.120.204). Escape character is '^]'. localhost (Linux release 2.6.18-274.18.1.el5 #1 SMP Thu Feb 9 12:45:44 EST 2012) (1) login: root Password: Login incorrect 说明: 一般情况下不允许root从远程登录,可以先用普通账号登录,然后再用su -切到root用户。 例五:测试服务器8888端口是否可用 $ telnet 192.168.0.88 8888","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(42): ss","slug":"linux-command-42-ss","date":"2017-01-11T02:34:47.000Z","updated":"2024-07-05T11:10:22.872Z","comments":true,"path":"2017/01/11/linux-command-42-ss/","permalink":"http://yelog.org/2017/01/11/linux-command-42-ss/","excerpt":"ss是Socket Statistics的缩写。顾名思义,ss命令可以用来获取socket统计信息,它可以显示和netstat类似的内容。但ss的优势在于它能够显示更多更详细的有关TCP和连接状态的信息,而且比netstat更快速更高效。","text":"ss是Socket Statistics的缩写。顾名思义,ss命令可以用来获取socket统计信息,它可以显示和netstat类似的内容。但ss的优势在于它能够显示更多更详细的有关TCP和连接状态的信息,而且比netstat更快速更高效。 当服务器的socket连接数量变得非常大时,无论是使用netstat命令还是直接cat /proc/net/tcp,执行速度都会很慢。可能你不会有切身的感受,但请相信我,当服务器维持的连接达到上万个的时候,使用netstat等于浪费 生命,而用ss才是节省时间。 天下武功唯快不破。ss快的秘诀在于,它利用到了TCP协议栈中tcp_diag。tcp_diag是一个用于分析统计的模块,可以获得Linux 内核中第一手的信息,这就确保了ss的快捷高效。当然,如果你的系统中没有tcp_diag,ss也可以正常运行,只是效率会变得稍慢。(但仍然比 netstat要快。) 命令格式$ ss [参数] $ ss[参数] [过滤] 命令功能 ss(Socket Statistics的缩写)命令可以用来获取 socket统计信息,此命令输出的结果类似于 netstat输出的内容,但它能显示更多更详细的 TCP连接状态的信息,且比 netstat 更快速高效。它使用了 TCP协议栈中 tcp_diag(是一个用于分析统计的模块),能直接从获得第一手内核信息,这就使得 ss命令快捷高效。在没有 tcp_diag,ss也可以正常运行。 命令参数 命令 描述 -h, –help 帮助信息 -V, –version 程序版本信息 -n, –numeric 不解析服务名称 -r, –resolve 解析主机名 -a, –all 显示所有套接字(sockets) -l, –listening 显示监听状态的套接字(sockets) -o, –options 显示计时器信息 -e, –extended 显示详细的套接字(sockets)信息 -m, –memory 显示套接字(socket)的内存使用情况 -p, –processes 显示使用套接字(socket)的进程 -i, –info 显示 TCP内部信息 -s, –summary 显示套接字(socket)使用概况 -4, –ipv4 仅显示IPv4的套接字(sockets) -6, –ipv6 仅显示IPv6的套接字(sockets) -0, –packet 显示 PACKET 套接字(socket) -t, –tcp 仅显示 TCP套接字(sockets) -u, –udp 仅显示 UCP套接字(sockets) -d, –dccp 仅显示 DCCP套接字(sockets) -w, –raw 仅显示 RAW套接字(sockets) -x, –unix 仅显示 Unix套接字(sockets) -f, –family=FAMILY 显示 FAMILY类型的套接字(sockets),FAMILY可选,支持 unix, inet, inet6, link, netlink -A, –query=QUERY, –socket=QUERYQUERY := {all inet -D, –diag=FILE 将原始TCP套接字(sockets)信息转储到文件 -F, –filter=FILE 从文件中都去过滤器信息 FILTER := [ state TCP-STATE ] [ EXPRESSION ] 使用实例例一:显示TCP连接 $ ss -t -a 例二:显示 Sockets 摘要 $ ss -s Total: 1385 (kernel 0) TCP: 199 (estab 64, closed 76, orphaned 0, synrecv 0, timewait 1/0), ports 0 Transport Total IP IPv6 * 0 - - RAW 2 1 1 UDP 29 21 8 TCP 123 47 76 INET 154 69 85 FRAG 0 0 0 说明: 列出当前的established, closed, orphaned and waiting TCP sockets 例三:列出所有打开的网络连接端口 $ ss -l 例四:查看进程使用的socket $ ss -pl 例五:找出打开套接字/端口应用程序 $ ss -lp | grep 3306 例六:显示所有UDP Sockets $ ss -u -a 例七:显示所有状态为established的SMTP连接 $ ss -o state established '( dport = :smtp or sport = :smtp )' 例八:显示所有状态为Established的HTTP连接 $ ss -o state established '( dport = :http or sport = :http )' 例九:列举出处于 FIN-WAIT-1状态的源端口为 80或者 443,目标网络为 193.233.7/24所有 tcp套接字命令 $ ss -o state fin-wait-1 '( sport = :http or sport = :https )' dst 193.233.7/24 例十:用TCP 状态过滤Sockets $ ss -4 state FILTER-NAME-HERE $ ss -6 state FILTER-NAME-HERE 说明:FILTER-NAME-HERE 可以代表以下任何一个: established syn-sent syn-recv fin-wait-1 fin-wait-2 time-wait closed close-wait last-ack listen closing all : 所有以上状态 connected : 除了listen and closed的所有状态 synchronized :所有已连接的状态除了syn-sent bucket : 显示状态为maintained as minisockets,如:time-wait和syn-recv. big : 和bucket相反. 例十一:匹配远程地址和端口号 $ ss dst ADDRESS_PATTERN $ ss dst 192.168.1.5 $ ss dst 192.168.119.113:http $ ss dst 192.168.119.113:smtp $ ss dst 192.168.119.113:443 例十二:匹配本地地址和端口号 $ ss src ADDRESS_PATTERN $ ss src 192.168.119.103 $ ss src 192.168.119.103:http $ ss src 192.168.119.103:80 $ ss src 192.168.119.103:smtp $ ss src 192.168.119.103:25 例十三:将本地或者远程端口和一个数比较 $ ss dport OP PORT $ ss sport OP PORT 说明: ss dport OP PORT 远程端口和一个数比较;ss sport OP PORT 本地端口和一个数比较。 OP 可以代表以下任意一个: <= or le : 小于或等于端口号 >= or ge : 大于或等于端口号 == or eq : 等于端口号 != or ne : 不等于端口号 < or gt : 小于端口号 > or lt : 大于端口号 例十四:ss 和 netstat 效率对比 $ time netstat -at $ time ss 说明: 用time 命令分别获取通过netstat和ss命令获取程序和概要占用资源所使用的时间。在服务器连接数比较多的时候,netstat的效率完全没法和ss比。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(41): netstat","slug":"linux-command-41-netstat","date":"2017-01-10T01:54:16.000Z","updated":"2024-07-05T11:10:22.954Z","comments":true,"path":"2017/01/10/linux-command-41-netstat/","permalink":"http://yelog.org/2017/01/10/linux-command-41-netstat/","excerpt":"netstat命令用于显示与IP、TCP、UDP和ICMP协议相关的统计数据,一般用于检验本机各端口的网络连接情况。netstat是在内核中访问网络及相关信息的程序,它能提供TCP连接,TCP和UDP监听,进程内存管理的相关报告。","text":"netstat命令用于显示与IP、TCP、UDP和ICMP协议相关的统计数据,一般用于检验本机各端口的网络连接情况。netstat是在内核中访问网络及相关信息的程序,它能提供TCP连接,TCP和UDP监听,进程内存管理的相关报告。 如果你的计算机有时候接收到的数据报导致出错数据或故障,你不必感到奇怪,TCP/IP可以容许这些类型的错误,并能够自动重发数据报。但如果累计的出错情况数目占到所接收的IP数据报相当大的百分比,或者它的数目正迅速增加,那么你就应该使用netstat查一查为什么会出现这些情况了。 命令格式$ netstat [-acCeFghilMnNoprstuvVwx][-A<网络类型>][--ip] 命令功能 netstat用于显示与IP、TCP、UDP和ICMP协议相关的统计数据,一般用于检验本机各端口的网络连接情况。 命令参数 命令 描述 -a或–all 显示所有连线中的Socket -A<网络类型>或–<网络类型> 列出该网络类型连线中的相关地址 -c或–continuous 持续列出网络状态 -C或–cache 显示路由器配置的快取信息 -e或–extend 显示网络其他相关信息 -F或–fib 显示FIB -g或–groups 显示多重广播功能群组组员名单 -h或–help 在线帮助 -i或–interfaces 显示网络界面信息表单 -l或–listening 显示监控中的服务器的Socket -M或–masquerade 显示伪装的网络连线 -n或–numeric 直接使用IP地址,而不通过域名服务器 -N或–netlink或–symbolic 显示网络硬件外围设备的符号连接名称 -o或–timers 显示计时器 -p或–programs 显示正在使用Socket的程序识别码和程序名称 -r或–route 显示Routing Table -s或–statistice 显示网络工作信息统计表 -t或–tcp 显示TCP传输协议的连线状况 -u或–udp 显示UDP传输协议的连线状况 -v或–verbose 显示指令执行过程 -V或–version 显示版本信息 -w或–raw 显示RAW传输协议的连线状况 -x或–unix 此参数的效果和指定”-A unix”参数相同 –ip或–inet 此参数的效果和指定”-A inet”参数相同 使用实例例一:无参数使用 $ netstat Active Internet connections (w/o servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 268 192.168.120.204:ssh 10.2.0.68:62420 ESTABLISHED udp 0 0 192.168.120.204:4371 10.58.119.119:domain ESTABLISHED Active UNIX domain sockets (w/o servers) Proto RefCnt Flags Type State I-Node Path unix 2 [ ] DGRAM 1491 @/org/kernel/udev/udevd unix 4 [ ] DGRAM 7337 /dev/log unix 2 [ ] DGRAM 708823 unix 2 [ ] DGRAM 7539 unix 3 [ ] STREAM CONNECTED 7287 unix 3 [ ] STREAM CONNECTED 7286 说明: 从整体上看,netstat的输出结果可以分为两个部分: 一个是Active Internet connections,称为有源TCP连接,其中”Recv-Q”和”Send-Q”指的是接收队列和发送队列。这些数字一般都应该是0。如果不是则表示软件包正在队列中堆积。这种情况只能在非常少的情况见到。 另一个是Active UNIX domain sockets,称为有源Unix域套接口(和网络套接字一样,但是只能用于本机通信,性能可以提高一倍)。 Proto显示连接使用的协议,RefCnt表示连接到本套接口上的进程号,Types显示套接口的类型,State显示套接口当前的状态,Path表示连接到套接口的其它进程使用的路径名。套接口类型: -t :TCP -u :UDP -raw :RAW类型 –unix :UNIX域类型 –ax25 :AX25类型 –ipx :ipx类型 –netrom :netrom类型状态说明: LISTEN:侦听来自远方的TCP端口的连接请求 SYN-SENT:再发送连接请求后等待匹配的连接请求(如果有大量这样的状态包,检查是否中招了) SYN-RECEIVED:再收到和发送一个连接请求后等待对方对连接请求的确认(如有大量此状态,估计被flood攻击了) ESTABLISHED:代表一个打开的连接 FIN-WAIT-1:等待远程TCP连接中断请求,或先前的连接中断请求的确认 FIN-WAIT-2:从远程TCP等待连接中断请求 CLOSE-WAIT:等待从本地用户发来的连接中断请求 CLOSING:等待远程TCP对连接中断的确认 LAST-ACK:等待原来的发向远程TCP的连接中断请求的确认(不是什么好东西,此项出现,检查是否被攻击) TIME-WAIT:等待足够的时间以确保远程TCP接收到连接中断请求的确认 CLOSED:没有任何连接状态 例二:列出所有端口 $ netstat -a Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 localhost:smux *:* LISTEN tcp 0 0 *:svn *:* LISTEN tcp 0 0 *:ssh *:* LISTEN tcp 0 284 192.168.120.204:ssh 10.2.0.68:62420 ESTABLISHED udp 0 0 localhost:syslog *:* udp 0 0 *:snmp *:* Active UNIX domain sockets (servers and established) Proto RefCnt Flags Type State I-Node Path unix 2 [ ACC ] STREAM LISTENING 708833 /tmp/ssh-yKnDB15725/agent.15725 unix 2 [ ACC ] STREAM LISTENING 7296 /var/run/audispd_events unix 2 [ ] DGRAM 1491 @/org/kernel/udev/udevd unix 4 [ ] DGRAM 7337 /dev/log unix 2 [ ] DGRAM 708823 unix 2 [ ] DGRAM 7539 unix 3 [ ] STREAM CONNECTED 7287 unix 3 [ ] STREAM CONNECTED 7286 说明: 显示一个所有的有效连接信息列表,包括已建立的连接(ESTABLISHED),也包括监听连接请(LISTENING)的那些连接 例三:显示当前UDP连接状况 $ netstat -nu 例四:显示UDP端口号的使用情况 $ netstat -apu 例五:显示UDP端口号的使用情况 $ netstat -i 例六:显示组播组的关系 $ netstat -g 例七:显示网络统计信息 $ netstat -s 说明: 按照各个协议分别显示其统计数据。如果我们的应用程序(如Web浏览器)运行速度比较慢,或者不能显示Web页之类的数据,那么我们就可以用本选项来查看一下所显示的信息。我们需要仔细查看统计数据的各行,找到出错的关键字,进而确定问题所在。 例八:显示监听的套接口 $ netstat -l 例九:显示所有已建立的有效连接 $ netstat -n 例十:显示关于以太网的统计数据 $ netstat -e 说明: 用于显示关于以太网的统计数据。它列出的项目包括传送的数据报的总字节数、错误数、删除数、数据报的数量和广播的数量。这些统计数据既有发送的数据报数量,也有接收的数据报数量。这个选项可以用来统计一些基本的网络流量) 例十一:显示关于路由表的信息 $ netstat -r 例十二:列出所有 tcp 端口 $ netstat -at 例十三:统计机器中网络连接各个状态个数 $ netstat -a | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' 例十四:把状态全都取出来后使用uniq -c统计后再进行排序 $ netstat -nat |awk '{print $6}'|sort|uniq -c 例十五:查看连接某服务端口最多的的IP地址 $ netstat -nat | grep "192.168.120.20:16067" |awk '{print $5}'|awk -F: '{print $4}'|sort|uniq -c|sort -nr|head -20 例十六:找出程序运行的端口 $ netstat -ap | grep ssh 例十七:在 netstat 输出中显示 PID 和进程名称 $ netstat -pt 说明: netstat -p 可以与其它开关一起使用,就可以添加 “PID/进程名称” 到 netstat 输出中,这样 debugging 的时候可以很方便的发现特定端口运行的程序 例十八:查看所有端口使用情况 $ netstat -tln 例十九:找出运行在指定端口的进程 $ netstat -anpt | grep ':4000' (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 0.0.0.0:4000 0.0.0.0:* LISTEN 5362/hexo tcp 0 0 127.0.0.1:4000 127.0.0.1:45884 ESTABLISHED 5362/hexo tcp 0 0 127.0.0.1:4000 127.0.0.1:45886 ESTABLISHED 5362/hexo 说明: 运行在端口4000的进程id为5362,再通过ps命令就可以找到具体的应用程序了","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(40): traceroute","slug":"linux-command-40-traceroute","date":"2017-01-09T02:56:53.000Z","updated":"2024-07-05T11:10:22.997Z","comments":true,"path":"2017/01/09/linux-command-40-traceroute/","permalink":"http://yelog.org/2017/01/09/linux-command-40-traceroute/","excerpt":"通过traceroute我们可以知道信息从你的计算机到互联网另一端的主机是走的什么路径。当然每次数据包由某一同样的出发点(source)到达某一同样的目的地(destination)走的路径可能会不一样,但基本上来说大部分时候所走的路由是相同的。linux系统中,我们称之为traceroute,在MS Windows中为tracert。 traceroute通过发送小的数据包到目的设备直到其返回,来测量其需要多长时间。一条路径上的每个设备traceroute要测3次。输出结果中包括每次测试的时间(ms)和设备的名称(如有的话)及其IP地址。","text":"通过traceroute我们可以知道信息从你的计算机到互联网另一端的主机是走的什么路径。当然每次数据包由某一同样的出发点(source)到达某一同样的目的地(destination)走的路径可能会不一样,但基本上来说大部分时候所走的路由是相同的。linux系统中,我们称之为traceroute,在MS Windows中为tracert。 traceroute通过发送小的数据包到目的设备直到其返回,来测量其需要多长时间。一条路径上的每个设备traceroute要测3次。输出结果中包括每次测试的时间(ms)和设备的名称(如有的话)及其IP地址。 在大多数情况下,我们会在linux主机系统下,直接执行命令行: traceroute hostname 而在Windows系统下是执行tracert的命令: tracert hostname 命令格式$ traceroute [参数] [主机] 命令功能 traceroute指令让你追踪网络数据包的路由途径,预设数据包大小是40Bytes,用户可另行设置。 具体参数格式:traceroute [-dFlnrvx][-f<存活数值>][-g<网关>...][-i<网络界面>][-m<存活数值>][-p<通信端口>][-s<来源地址>][-t<服务类型>][-w<超时秒数>][主机名称或IP地址][数据包大小] 命令参数 命令 描述 -d 使用Socket层级的排错功能 -f 设置第一个检测数据包的存活数值TTL的大小 -F 设置勿离断位 -g 设置来源路由网关,最多可设置8个 -i 使用指定的网络界面送出数据包 -I 使用ICMP回应取代UDP资料信息 -m 设置检测数据包的最大存活数值TTL的大小 -n 直接使用IP地址而非主机名称 -p 设置UDP传输协议的通信端口 -r 忽略普通的Routing Table,直接将数据包送到远端主机上 -s 设置本地主机送出数据包的IP地址 -t 设置检测数据包的TOS数值 -v 详细显示指令的执行过程 -w 设置等待远端主机回报的时间 -x 开启或关闭数据包的正确性检验 使用实例例一:traceroute 用法简单、最常用的用法 $ traceroute yelog.github.com traceroute to yelog.github.com (151.101.192.133), 30 hops max, 60 byte packets 1 vrouter (192.168.0.1) 0.443 ms 0.565 ms 0.684 ms 2 112.208.32.1.pldt.net (112.208.32.1) 14.518 ms 22.454 ms 23.080 ms 3 119.93.255.197 (119.93.255.197) 24.492 ms 25.380 ms 26.328 ms 4 210.213.131.66.static.pldt.net (210.213.131.66) 29.942 ms 210.213.131.70.static.pldt.net (210.213.131.70) 28.209 ms 28.992 ms 5 122.2.175.30.static.pldt.net (122.2.175.30) 32.429 ms 32.765 ms 210.213.128.29.static.pldt.net (210.213.128.29) 35.165 ms 6 210.213.130.162.static.pldt.net (210.213.130.162) 32.147 ms 31.403 ms 32.107 ms 7 las-b3-link.telia.net (62.115.13.128) 198.546 ms 190.829 ms 191.039 ms 8 las-b21-link.telia.net (213.155.131.82) 194.301 ms las-b21-link.telia.net (62.115.116.179) 191.927 ms las-b21-link.telia.net (213.155.131.84) 194.433 ms 9 * * * 10 * * * 说明: 记录按序列号从1开始,每个纪录就是一跳 ,每跳表示一个网关,我们看到每行有三个时间,单位是 ms,其实就是-q的默认参数。探测数据包向每个网关发送三个数据包后,网关响应后返回的时间;如果您用 traceroute -q 4 www.58.com ,表示向每个网关发送4个数据包。 有时我们traceroute 一台主机时,会看到有一些行是以星号表示的。出现这样的情况,可能是防火墙封掉了ICMP的返回信息,所以我们得不到什么相关的数据包返回数据。 有时我们在某一网关处延时比较长,有可能是某台网关比较阻塞,也可能是物理设备本身的原因。当然如果某台DNS出现问题时,不能解析主机名、域名时,也会 有延时长的现象;您可以加-n 参数来避免DNS解析,以IP格式输出数据。 如果在局域网中的不同网段之间,我们可以通过traceroute 来排查问题所在,是主机的问题还是网关的问题。如果我们通过远程来访问某台服务器遇到问题时,我们用到traceroute 追踪数据包所经过的网关,提交IDC服务商,也有助于解决问题;但目前看来在国内解决这样的问题是比较困难的,就是我们发现问题所在,IDC服务商也不可能帮助我们解决。 例二:跳数设置 $ traceroute -m 10 www.baidu.com 例三:显示IP地址,不查主机名 $ traceroute -n www.baidu.com 例四:探测包使用的基本UDP端口设置6888 $ traceroute -p 6888 www.baidu.com 例五:把探测包的个数设置为值4 $ traceroute -q 4 www.baidu.com 例六:绕过正常的路由表,直接发送到网络相连的主机 $ traceroute -r www.baidu.com 例七:把对外发探测包的等待响应时间设置为3秒 $ traceroute -w 3 www.baidu.com Traceroute的工作原理 Traceroute最简单的基本用法是:traceroute hostname Traceroute程序的设计是利用ICMP及IP header的TTL(Time To Live)栏位(field)。首先,traceroute送出一个TTL是1的IP datagram(其实,每次送出的为3个40字节的包,包括源地址,目的地址和包发出的时间标签)到目的地,当路径上的第一个路由器(router)收到这个datagram时,它将TTL减1。此时,TTL变为0了,所以该路由器会将此datagram丢掉,并送回一个「ICMP time exceeded」消息(包括发IP包的源地址,IP包的所有内容及路由器的IP地址),traceroute 收到这个消息后,便知道这个路由器存在于这个路径上,接着traceroute 再送出另一个TTL是2 的datagram,发现第2 个路由器…… traceroute 每次将送出的datagram的TTL 加1来发现另一个路由器,这个重复的动作一直持续到某个datagram 抵达目的地。当datagram到达目的地后,该主机并不会送回ICMP time exceeded消息,因为它已是目的地了,那么traceroute如何得知目的地到达了呢? Traceroute在送出UDP datagrams到目的地时,它所选择送达的port number 是一个一般应用程序都不会用的号码(30000 以上),所以当此UDP datagram 到达目的地后该主机会送回一个「ICMP port unreachable」的消息,而当traceroute 收到这个消息时,便知道目的地已经到达了。所以traceroute 在Server端也是没有所谓的Daemon 程式。 Traceroute提取发 ICMP TTL到期消息设备的IP地址并作域名解析。每次 ,Traceroute都打印出一系列数据,包括所经过的路由设备的域名及 IP地址,三个包每次来回所花时间。 windows之tracert 格式: $ tracert [-d] [-h maximum_hops] [-j host-list] [-w timeout] target_name 参数说明 tracert [-d] [-h maximum_hops] [-j computer-list] [-w timeout] target_name 该诊断实用程序通过向目的地发送具有不同生存时间 (TL) 的 Internet 控制信息协议 (CMP) 回应报文,以确定至目的地的路由。路径上的每个路由器都要在转发该 ICMP 回应报文之前将其 TTL 值至少减 1,因此 TTL 是有效的跳转计数。当报文的 TTL 值减少到 0 时,路由器向源系统发回 ICMP 超时信息。通过发送 TTL 为 1 的第一个回应报文并且在随后的发送中每次将 TTL 值加 1,直到目标响应或达到最大 TTL 值,Tracert 可以确定路由。通过检查中间路由器发发回的 ICMP 超时 (ime Exceeded) 信息,可以确定路由器。注意,有些路由器“安静”地丢弃生存时间 (TLS) 过期的报文并且对 tracert 无效。参数:-d 指定不对计算机名解析地址。-h maximum_hops 指定查找目标的跳转的最大数目。-jcomputer-list 指定在 computer-list 中松散源路由。-w timeout 等待由 timeout 对每个应答指定的毫秒数。target_name 目标计算机的名称。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(39): ping","slug":"linux-command-39-ping","date":"2017-01-08T02:24:50.000Z","updated":"2024-07-05T11:10:22.794Z","comments":true,"path":"2017/01/08/linux-command-39-ping/","permalink":"http://yelog.org/2017/01/08/linux-command-39-ping/","excerpt":"Linux系统的ping命令是常用的网络命令,它通常用来测试与目标主机的连通性,我们经常会说“ping一下某机器,看是不是开着”、不能打开网页时会说“你先ping网关地址192.168.1.1试试”。它通过发送ICMP ECHO_REQUEST数据包到网络主机(send ICMP ECHO_REQUEST to network hosts),并显示响应情况,这样我们就可以根据它输出的信息来确定目标主机是否可访问(但这不是绝对的)。有些服务器为了防止通过ping探测到,通过防火墙设置了禁止ping或者在内核参数中禁止ping,这样就不能通过ping确定该主机是否还处于开启状态。","text":"Linux系统的ping命令是常用的网络命令,它通常用来测试与目标主机的连通性,我们经常会说“ping一下某机器,看是不是开着”、不能打开网页时会说“你先ping网关地址192.168.1.1试试”。它通过发送ICMP ECHO_REQUEST数据包到网络主机(send ICMP ECHO_REQUEST to network hosts),并显示响应情况,这样我们就可以根据它输出的信息来确定目标主机是否可访问(但这不是绝对的)。有些服务器为了防止通过ping探测到,通过防火墙设置了禁止ping或者在内核参数中禁止ping,这样就不能通过ping确定该主机是否还处于开启状态。 linux下的ping和windows下的ping稍有区别,linux下ping不会自动终止,需要按ctrl+c终止或者用参数-c指定要求完成的回应次数。 命令格式$ ping [参数] [主机名或IP地址] 命令功能 ping命令用于:确定网络和各外部主机的状态;跟踪和隔离硬件和软件问题;测试、评估和管理网络。如果主机正在运行并连在网上,它就对回送信号进行响应。每个回送信号请求包含一个网际协议(IP)和 ICMP 头,后面紧跟一个 tim 结构,以及来填写这个信息包的足够的字节。缺省情况是连续发送回送信号请求直到接收到中断信号(Ctrl-C)。 ping 命令每秒发送一个数据报并且为每个接收到的响应打印一行输出。ping 命令计算信号往返时间和(信息)包丢失情况的统计信息,并且在完成之后显示一个简要总结。ping 命令在程序超时或当接收到 SIGINT 信号时结束。Host 参数或者是一个有效的主机名或者是因特网地址。 命令参数 命令 描述 -d 使用Socket的SO_DEBUG功能 -f 极限检测。大量且快速地送网络封包给一台机器,看它的回应 -n 只输出数值 -q 不显示任何传送封包的信息,只显示最后的结果 -r 忽略普通的Routing Table,直接将数据包送到远端主机上。通常是查看本机的网络接口是否有问题 -R 记录路由过程 -v 详细显示指令的执行过程 -c 数目 在发送指定数目的包后停止 -i 秒数 设定间隔几秒送一个网络封包给一台机器,预设值是一秒送一次 -I 网络界面 使用指定的网络界面送出数据包 -l 前置载入 设置在送出要求信息之前,先行发出的数据包 -p 范本样式 设置填满数据包的范本样式 -s 字节数 指定发送的数据字节数,预设值是56,加上8字节的ICMP头,一共是64ICMP数据字节 -t 存活数值 设置存活数值TTL的大小 使用实例例一:ping通的情况 $ ping 192.168.0.1 PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data. 64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=0.238 ms 64 bytes from 192.168.0.1: icmp_seq=2 ttl=64 time=0.202 ms 64 bytes from 192.168.0.1: icmp_seq=3 ttl=64 time=0.232 ms 64 bytes from 192.168.0.1: icmp_seq=4 ttl=64 time=0.210 ms --- 192.168.0.1 ping statistics --- 6 packets transmitted, 6 received, 0% packet loss, time 4997ms 例二:ping不通的情况 $ ping 192.168.0.222 PING 192.168.0.222 (192.168.0.222) 56(84) bytes of data. From 192.168.0.101 icmp_seq=1 Destination Host Unreachable From 192.168.0.101 icmp_seq=2 Destination Host Unreachable From 192.168.0.101 icmp_seq=3 Destination Host Unreachable --- 192.168.0.222 ping statistics --- 7 packets transmitted, 0 received, +6 errors, 100% packet loss, time 6032ms pipe 3 例二:ping指定次数 $ ping -c 192.168.0.1 例三:时间间隔和次数限制的ping $ ping -c 10 -i 0.5 192.168.120.206 例四:通过域名ping公网上的站点 $ ping -c 5 yelog.github.com 例五:多参数使用 # -i 3 发送周期为 3秒 -s 设置发送包的大小为1024 -t 设置TTL值为 255 $ ping -i 3 -s 1024 -t yelog.github.com PING github.map.fastly.net (151.101.192.133) 1024(1052) bytes of data. 1032 bytes from 151.101.192.133 (151.101.192.133): icmp_seq=1 ttl=56 time=191 ms 1032 bytes from 151.101.192.133 (151.101.192.133): icmp_seq=2 ttl=56 time=190 ms 1032 bytes from 151.101.192.133 (151.101.192.133): icmp_seq=3 ttl=56 time=189 ms 1032 bytes from 151.101.192.133 (151.101.192.133): icmp_seq=4 ttl=56 time=190 ms","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(38): route","slug":"linux-command-38-route","date":"2017-01-07T02:16:34.000Z","updated":"2024-07-05T11:10:22.939Z","comments":true,"path":"2017/01/07/linux-command-38-route/","permalink":"http://yelog.org/2017/01/07/linux-command-38-route/","excerpt":"Linux系统的route命令用于显示和操作IP路由表(show / manipulate the IP routing table)。要实现两个不同的子网之间的通信,需要一台连接两个网络的路由器,或者同时位于两个网络的网关来实现。在Linux系统中,设置路由通常是为了解决以下问题:该Linux系统在一个局域网中,局域网中有一个网关,能够让机器访问Internet,那么就需要将这台机器的IP地址设置为Linux机器的默认路由。要注意的是,直接在命令行下执行route命令来添加路由,不会永久保存,当网卡重启或者机器重启之后,该路由就失效了;可以在/etc/rc.local中添加route命令来保证该路由设置永久有效。","text":"Linux系统的route命令用于显示和操作IP路由表(show / manipulate the IP routing table)。要实现两个不同的子网之间的通信,需要一台连接两个网络的路由器,或者同时位于两个网络的网关来实现。在Linux系统中,设置路由通常是为了解决以下问题:该Linux系统在一个局域网中,局域网中有一个网关,能够让机器访问Internet,那么就需要将这台机器的IP地址设置为Linux机器的默认路由。要注意的是,直接在命令行下执行route命令来添加路由,不会永久保存,当网卡重启或者机器重启之后,该路由就失效了;可以在/etc/rc.local中添加route命令来保证该路由设置永久有效。 命令格式route [-f] [-p] [Command [Destination] [mask Netmask] [Gateway] [metric Metric]] [if Interface]] 命令功能 Route命令是用于操作基于内核ip路由表,它的主要作用是创建一个静态路由让指定一个主机或者一个网络通过一个网络接口,如eth0。当使用”add”或者”del”参数时,路由表被修改,如果没有参数,则显示路由表当前的内容。 命令参数 命令 描述 -c 显示更多信息 -n 不解析名字 -v 显示详细的处理信息 -F 显示发送信息 -C 显示路由缓存 -f 清除所有网关入口的路由表 -p 与 add 命令 -p 与 add 命令一起使用时使路由具有永久性。 add 添加一条新路由 del 删除一条路由 -net 目标地址是一个网络 -host 目标地址是一个主机 netmask 当添加一个网络路由时,需要使用网络掩码 gw 路由数据包通过网关。注意,你指定的网关必须能够达到 metric 设置路由跳数 Command 指定您想运行的命令 (Add/Change/Delete/Print) Destination 指定该路由的网络目标 mask Netmask 指定与网络目标相关的网络掩码(也被称作子网掩码) Gateway 指定网络目标定义的地址集和子网掩码可以到达的前进或下一跃点 IP 地址 metric Metric 为路由指定一个整数成本值标(从 1 至 9999),当在路由表(与转发的数据包目标地址最匹配)的多个路由中进行选择时可以使用 if Interface 为可以访问目标的接口指定接口索引。若要获得一个接口列表和它们相应的接口索引,使用 route print 命令的显示功能。可以使用十进制或十六进制值进行接口索引 使用实例例一:显示当前路由 $ route $ route -n [root@localhost ~]# route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 192.168.120.0 * 255.255.255.0 U 0 0 0 eth0 e192.168.0.0 192.168.120.1 255.255.0.0 UG 0 0 0 eth0 10.0.0.0 192.168.120.1 255.0.0.0 UG 0 0 0 eth0 default 192.168.120.240 0.0.0.0 UG 0 0 0 eth0 [root@localhost ~]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 192.168.120.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 192.168.0.0 192.168.120.1 255.255.0.0 UG 0 0 0 eth0 10.0.0.0 192.168.120.1 255.0.0.0 UG 0 0 0 eth0 0.0.0.0 192.168.120.240 0.0.0.0 UG 0 0 0 eth0 说明: 第一行表示主机所在网络的地址为192.168.120.0,若数据传送目标是在本局域网内通信,则可直接通过eth0转发数据包; 第四行表示数据传送目的是访问Internet,则由接口eth0,将数据包发送到网关192.168.120.240 其中Flags为路由标志,标记当前网络节点的状态。 Flags标志说明: U Up表示此路由当前为启动状态 H Host,表示此网关为一主机 G Gateway,表示此网关为一路由器 R Reinstate Route,使用动态路由重新初始化的路由 D Dynamically,此路由是动态性地写入 M Modified,此路由是由路由守护程序或导向器动态修改 ! 表示此路由当前为关闭状态备注: route -n (-n 表示不解析名字,列出速度会比route 快) 例二:添加网关/设置网关 # 增加一条 到达244.0.0.0的路由 $ route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0 例三:屏蔽一条路由 # 增加一条屏蔽的路由,目的地址为 224.x.x.x 将被拒绝 $ route add -net 224.0.0.0 netmask 240.0.0.0 reject 例四:删除路由记录 $ route del -net 224.0.0.0 netmask 240.0.0.0 $ route del -net 224.0.0.0 netmask 240.0.0.0 reject 例五:删除和添加设置默认网关 $ route del default gw 192.168.120.240 $ route add default gw 192.168.120.240","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(37): ifconfig","slug":"linux-command-37-ifconfig","date":"2017-01-06T01:57:01.000Z","updated":"2024-07-05T11:10:22.892Z","comments":true,"path":"2017/01/06/linux-command-37-ifconfig/","permalink":"http://yelog.org/2017/01/06/linux-command-37-ifconfig/","excerpt":"许多windows非常熟悉ipconfig命令行工具,它被用来获取网络接口配置信息并对此进行修改。Linux系统拥有一个类似的工具,也就是ifconfig(interfaces config)。通常需要以root身份登录或使用sudo以便在Linux机器上使用ifconfig工具。依赖于ifconfig命令中使用一些选项属性,ifconfig工具不仅可以被用来简单地获取网络接口配置信息,还可以修改这些配置。","text":"许多windows非常熟悉ipconfig命令行工具,它被用来获取网络接口配置信息并对此进行修改。Linux系统拥有一个类似的工具,也就是ifconfig(interfaces config)。通常需要以root身份登录或使用sudo以便在Linux机器上使用ifconfig工具。依赖于ifconfig命令中使用一些选项属性,ifconfig工具不仅可以被用来简单地获取网络接口配置信息,还可以修改这些配置。 命令格式$ ifconfig [网络设备] [参数] 命令功能 ifconfig 命令用来查看和配置网络设备。当网络环境发生改变时可通过此命令对网络进行相应的配置。 注意: 用ifconfig命令配置的网卡信息,在网卡重启后机器重启后,配置就不存在。要想将上述的配置信息永远的存的电脑里,那就要修改网卡的配置文件了。 命令参数 命令 描述 up 启动指定网络设备/网卡 down 关闭指定网络设备/网卡。该参数可以有效地阻止通过指定接口的IP信息流,如果想永久地关闭一个接口,我们还需要从核心路由表中将该接口的路由信息全部删除 arp 设置指定网卡是否支持ARP协议 -promisc 设置是否支持网卡的promiscuous模式,如果选择此参数,网卡将接收网络中发给它所有的数据包 -allmulti 设置是否支持多播模式,如果选择此参数,网卡将接收网络中所有的多播数据包 -a 显示全部接口信息 -s 显示摘要信息(类似于 netstat -i) add 给指定网卡配置IPv6地址 del 删除指定网卡的IPv6地址 <硬件地址> 配置网卡最大的传输单元 mtu<字节数> 设置网卡的最大传输单元 (bytes) netmask<子网掩码> 设置网卡的子网掩码。掩码可以是有前缀0x的32位十六进制数,也可以是用点分开的4个十进制数。如果不打算将网络分成子网,可以不管这一选项;如果要使用子网,那么请记住,网络中每一个系统必须有相同子网掩码 tunel 建立隧道 dstaddr 设定一个远端地址,建立点对点通信 -broadcast<地址> 为指定网卡设置广播协议 -pointtopoint<地址> 为网卡设置点对点通讯协议 multicast 为网卡设置组播标志 address 为网卡设置IPv4地址 txqueuelen<长度> 为网卡设置传输列队的长度 使用实例例一:显示网络设备信息(激活状态的) $ ifconfig 说明: eth0 表示第一块网卡, 其中 HWaddr 表示网卡的物理地址,可以看到目前这个网卡的物理地址(MAC地址)是 00:50:56:BF:26:20 inet addr 用来表示网卡的IP地址,此网卡的 IP地址是 192.168.120.204,广播地址, Bcast:192.168.120.255,掩码地址Mask:255.255.255.0 lo 是表示主机的回坏地址,这个一般是用来测试一个网络程序,但又不想让局域网或外网的用户能够查看,只能在此台主机上运行和查看所用的网络接口。比如把 HTTPD服务器的指定到回坏地址,在浏览器输入 127.0.0.1 就能看到你所架WEB网站了。但只是您能看得到,局域网的其它主机或用户无从知道。 第一行:连接类型:Ethernet(以太网)HWaddr(硬件mac地址) 第二行:网卡的IP地址、子网、掩码 第三行:UP(代表网卡开启状态)RUNNING(代表网卡的网线被接上)MULTICAST(支持组播)MTU:1500(最大传输单元):1500字节 第四、五行:接收、发送数据包情况统计 第七行:接收、发送数据字节数统计信息。 例二:启动关闭指定网卡 # 启动eth0网卡 $ ifconfig eth0 up # 关闭eth0网卡 $ ifconfig eth0 down 注意: ssh登陆linux服务器操作要小心,关闭了就不能开启了,除非你有多网卡。 例三:为网卡配置和删除IPv6地址 # 配置IPv6的地址 $ ifconfig eth0 add 33ffe:3240:800:1005::2/64 # 删除IPv6的地址 $ ifconfig eth0 del 33ffe:3240:800:1005::2/64 例四:用ifconfig修改MAC地址 $ ifconfig eth0 hw ether 00:AA:BB:CC:DD:EE 例五:配置IP地址 # 配置ip $ ifconfig eth0 192.168.120.56 # 配置ip和掩码地址 $ ifconfig eth0 192.168.120.56 netmask 255.255.255.0 # 配置ip、掩码地址和广播地址 $ ifconfig eth0 192.168.120.56 netmask 255.255.255.0 broadcast 192.168.120.255 例六:启用和关闭ARP协议 # 启用eth0的ARP协议 $ ifconfig eth0 arp # 关闭eth0的ARP协议 $ ifconfig eth0 -arp 例七:设置最大传输单元 # 设置能通过的最大数据包大小为 1500 bytes $ ifconfig eth0 mtu 1500","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(36): lsof","slug":"linux-command-36-lsof","date":"2017-01-05T02:28:13.000Z","updated":"2024-07-05T11:10:22.776Z","comments":true,"path":"2017/01/05/linux-command-36-lsof/","permalink":"http://yelog.org/2017/01/05/linux-command-36-lsof/","excerpt":"lsof(list open files)是一个列出当前系统打开文件的工具。在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件。所以如传输控制协议 (TCP) 和用户数据报协议 (UDP) 套接字等,系统在后台都为该应用程序分配了一个文件描述符,无论这个文件的本质如何,该文件描述符为应用程序与基础操作系统之间的交互提供了通用接口。因为应用程序打开文件的描述符列表提供了大量关于这个应用程序本身的信息,因此通过lsof工具能够查看这个列表对系统监测以及排错将是很有帮助的。","text":"lsof(list open files)是一个列出当前系统打开文件的工具。在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件。所以如传输控制协议 (TCP) 和用户数据报协议 (UDP) 套接字等,系统在后台都为该应用程序分配了一个文件描述符,无论这个文件的本质如何,该文件描述符为应用程序与基础操作系统之间的交互提供了通用接口。因为应用程序打开文件的描述符列表提供了大量关于这个应用程序本身的信息,因此通过lsof工具能够查看这个列表对系统监测以及排错将是很有帮助的。 命令格式$ lsof [参数][文件] 命令功能 用于查看你进程开打的文件,打开文件的进程,进程打开的端口(TCP、UDP)。找回/恢复删除的文件。是十分方便的系统监视工具,因为 lsof 需要访问核心内存和各种文件,所以需要root用户执行。lsof打开的文件可以是: 普通文件 目录 网络文件系统的文件 字符或设备文件 (函数)共享库 管道,命名管道 符号链接 网络文件(例如:NFS file、网络socket,unix域名socket) 还有其它类型的文件,等等 命令参数 命令 描述 -a 列出打开文件存在的进程 -c<进程名> 列出指定进程所打开的文件 -g 列出GID号进程详情 -d<文件号> 列出占用该文件号的进程 +d<目录> 列出目录下被打开的文件 +D<目录> 递归列出目录下被打开的文件 -n<目录> 列出使用NFS的文件 -i<条件> 列出符合条件的进程。(4、6、协议、:端口、 @ip ) -p<进程号> 列出指定进程号所打开的文件 -u 列出UID号进程详情 -h 显示帮助信息 -v 显示版本信息 使用实例例一:无任何参数 $ lsof COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME init 1 root cwd DIR 8,2 4096 2 / init 1 root rtd DIR 8,2 4096 2 / init 1 root txt REG 8,2 43496 6121706 /sbin/init init 1 root mem REG 8,2 143600 7823908 /lib64/ld-2.5.so init 1 root mem REG 8,2 1722304 7823915 /lib64/libc-2.5.so init 1 root mem REG 8,2 23360 7823919 /lib64/libdl-2.5.so init 1 root mem REG 8,2 95464 7824116 /lib64/libselinux.so.1 init 1 root mem REG 8,2 247496 7823947 /lib64/libsepol.so.1 init 1 root 10u FIFO 0,17 1233 /dev/initctl migration 2 root cwd DIR 8,2 4096 2 / migration 2 root rtd DIR 8,2 4096 2 / migration 2 root txt unknown /proc/2/exe ksoftirqd 3 root cwd DIR 8,2 4096 2 / ksoftirqd 3 root rtd DIR 8,2 4096 2 / ksoftirqd 3 root txt unknown /proc/3/exe migration 4 root cwd DIR 8,2 4096 2 / migration 4 root rtd DIR 8,2 4096 2 / migration 4 root txt unknown /proc/4/exe ksoftirqd 5 root cwd DIR 8,2 4096 2 / ksoftirqd 5 root rtd DIR 8,2 4096 2 / ksoftirqd 5 root txt unknown /proc/5/exe events/0 6 root cwd DIR 8,2 4096 2 / events/0 6 root rtd DIR 8,2 4096 2 / events/0 6 root txt unknown /proc/6/exe events/1 7 root cwd DIR 8,2 4096 2 / 说明:lsof输出各列信息的意义如下:COMMAND:进程的名称PID:进程标识符PPID:父进程标识符(需要指定-R参数)USER:进程所有者PGID:进程所属组FD:文件描述符,应用程序通过文件描述符识别该文件。如cwd、txt等(1)cwd:表示current work dirctory,即:应用程序的当前工作目录,这是该应用程序启动的目录,除非它本身对这个目录进行更改(2)txt :该类型的文件是程序代码,如应用程序二进制文件本身或共享库,如上列表中显示的 /sbin/init 程序(3)lnn:library references (AIX);(4)er:FD information error (see NAME column);(5)jld:jail directory (FreeBSD);(6)ltx:shared library text (code and data);(7)mxx :hex memory-mapped type number xx.(8)m86:DOS Merge mapped file;(9)mem:memory-mapped file;(10)mmap:memory-mapped device;(11)pd:parent directory;(12)rtd:root directory;(13)tr:kernel trace file (OpenBSD);(14)v86 VP/ix mapped file;(15)0:表示标准输出(16)1:表示标准输入(17)2:表示标准错误一般在标准输出、标准错误、标准输入后还跟着文件状态模式:r、w、u等(1)u:表示该文件被打开并处于读取/写入模式(2)r:表示该文件被打开并处于只读模式(3)w:表示该文件被打开并处于(4)空格:表示该文件的状态模式为unknow,且没有锁定(5)-:表示该文件的状态模式为unknow,且被锁定同时在文件状态模式后面,还跟着相关的锁(1)N:for a Solaris NFS lock of unknown type;(2)r:for read lock on part of the file;(3)R:for a read lock on the entire file;(4)w:for a write lock on part of the file;(文件的部分写锁)(5)W:for a write lock on the entire file;(整个文件的写锁)(6)u:for a read and write lock of any length;(7)U:for a lock of unknown type;(8)x:for an SCO OpenServer Xenix lock on part of the file;(9)X:for an SCO OpenServer Xenix lock on the entire file;(10)space:if there is no lock.TYPE:文件类型,如DIR、REG等,常见的文件类型(1)DIR:表示目录(2)CHR:表示字符类型(3)BLK:块设备类型(4)UNIX: UNIX 域套接字(5)FIFO:先进先出 (FIFO) 队列(6)IPv4:网际协议 (IP) 套接字DEVICE:指定磁盘的名称SIZE:文件的大小NODE:索引节点(文件在磁盘上的标识)NAME:打开文件的确切名称 例二:查看谁正在使用某个文件,也就是说查找某个文件相关的进程 $ lsof /bin/bash COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME bash 24159 root txt REG 8,2 801528 5368780 /bin/bash bash 24909 root txt REG 8,2 801528 5368780 /bin/bash bash 24941 root txt REG 8,2 801528 5368780 /bin/bash 例三:递归查看某个目录的文件信息 $ lsof test/test3 COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME bash 24941 root cwd DIR 8,2 4096 2258872 test/test3 vi 24976 root cwd DIR 8,2 4096 2258872 test/test3 说明: 使用了+D,对应目录下的所有子目录和文件都会被列出 例四:不使用+D选项,遍历查看某个目录的所有文件信息的方法 $ lsof |grep 'test/test3' 例五:列出某个用户打开的文件信息 # -u 选项,u其实是user的缩写 $ lsof -u username 例六:列出某个程序进程所打开的文件信息 $ lsof -c mysql 说明:-c 选项将会列出所有以mysql这个进程开头的程序的文件,其实你也可以写成 lsof | grep mysql, 但是第一种方法明显比第二种方法要少打几个字符了 例七:列出多个进程多个打开的文件信息 $ lsof -c mysql -c apache 例八:列出某个用户以及某个进程所打开的文件信息 $ lsof -u test -c mysql 例九:列出除了某个用户外的被打开的文件信息 # ^这个符号在用户名之前,将会把是root用户打开的进程不让显示 $ lsof -u ^root 例十:通过某个进程号显示该进行打开的文件 $ lsof -p 1 例十一:列出多个进程号对应的文件信息 $ lsof -p 1,2,3 例十二:列出除了某个进程号,其他进程号所打开的文件信息 $ lsof -p ^1 例十三:列出所有的网络连接 $ lsof -i 例十四:列出所有tcp 网络连接信息 $ lsof -i tcp 例十五:列出所有udp网络连接信息 $ lsof -i udp 例十六:列出谁在使用某个端口 $ lsof -i :3306 例十七:列出谁在使用某个特定的udp/tcp端口 $ lsof -i udp:55 $ lsof -i tcp:80 例十八:列出某个用户的所有活跃的网络端口 $ lsof -a -u test -i 例十九:列出所有网络文件系统 $ lsof -N 例二十:域名socket文件 $ lsof -u 例二十一:某个用户组所打开的文件信息 $ lsof -g 5555 例二十二:根据文件描述列出对应的文件信息 $ lsof -d description(like 2) 例如:lsof -d txt 例如:lsof -d 1 例如:lsof -d 2 说明:0表示标准输入,1表示标准输出,2表示标准错误,从而可知:所以大多数应用程序所打开的文件的 FD 都是从 3 开始 例二十三:根据文件描述范围列出文件信息 $ lsof -d 2-3 例二十四:列出COMMAND列中包含字符串” sshd”,且文件描符的类型为txt的文件信息 $ lsof -c sshd -a -d txt 例二十五:列出被进程号为1234的进程所打开的所有IPV4 network files $ lsof -i 4 -a -p 1234 例二十六:列出目前连接主机peida.linux上端口为:20,21,22,25,53,80相关的所有文件信息,且每隔3秒不断的执行lsof指令 $ lsof -i @peida.linux:20,21,22,25,53,80 -r 3","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(35): iostat","slug":"linux-command-35-iostat","date":"2017-01-04T02:10:02.000Z","updated":"2024-07-05T11:10:22.983Z","comments":true,"path":"2017/01/04/linux-command-35-iostat/","permalink":"http://yelog.org/2017/01/04/linux-command-35-iostat/","excerpt":"Linux系统中的 iostat是I/O statistics(输入/输出统计)的缩写,iostat工具将对系统的磁盘操作活动进行监视。它的特点是汇报磁盘活动统计情况,同时也会汇报出CPU使用情况。同vmstat一样,iostat也有一个弱点,就是它不能对某个进程进行深入分析,仅对系统的整体情况进行分析。iostat属于sysstat软件包。可以用yum install sysstat 直接安装。","text":"Linux系统中的 iostat是I/O statistics(输入/输出统计)的缩写,iostat工具将对系统的磁盘操作活动进行监视。它的特点是汇报磁盘活动统计情况,同时也会汇报出CPU使用情况。同vmstat一样,iostat也有一个弱点,就是它不能对某个进程进行深入分析,仅对系统的整体情况进行分析。iostat属于sysstat软件包。可以用yum install sysstat 直接安装。 命令格式$ iostat [参数][时间][次数] 命令功能 通过iostat方便查看CPU、网卡、tty设备、磁盘、CD-ROM 等等设备的活动情况, 负载信息。 命令参数 命令 描述 -C 显示CPU使用情况 -d 显示磁盘使用情况 -k 以 KB 为单位显示 -m 以 M 为单位显示 -N 显示磁盘阵列(LVM) 信息 -n 显示 NFS 使用情况 -p[磁盘] 显示磁盘和分区的情况 -t 显示终端和CPU的信息 -x 显示详细信息 -V 显示版本信息 使用实例例一:显示所有设备负载情况 $ iostat Linux 3.10.0-327.el7.x86_64 (s88) 2017年01月22日 _x86_64_ (24 CPU) avg-cpu: %user %nice %system %iowait %steal %idle 0.62 0.00 0.20 1.46 0.00 97.72 Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn sda 64.59 1726.21 255.56 3159941 467823 dm-0 3.55 141.11 4.46 258319 8162 dm-1 0.10 0.83 0.00 1520 0 dm-2 0.10 2.78 1.14 5080 2082 dm-3 60.44 1565.98 248.84 2866640 455511 dm-4 27.54 463.29 105.38 848088 192897 dm-5 1.25 25.57 17.57 46804 32170 dm-6 0.64 12.86 2.07 23535 3786 dm-7 4.14 80.43 36.60 147240 67004 dm-8 1.13 20.52 2.42 37566 4428 dm-9 1.13 21.18 2.40 38766 4396 dm-10 1.15 21.35 2.41 39082 4412 dm-11 0.70 14.40 2.21 26355 4043 dm-12 1.42 22.42 6.85 41035 12541 dm-13 0.46 12.17 1.25 22275 2289 dm-14 1.15 20.47 2.42 37470 4432 dm-15 8.28 101.07 16.51 185018 30220 dm-16 1.10 20.02 2.45 36646 4488 dm-17 1.81 29.08 4.15 53232 7591 dm-18 0.68 18.40 1.43 33689 2611 dm-19 2.33 43.89 4.63 80340 8483 说明:cpu属性值说明:%user:CPU处在用户模式下的时间百分比。%nice:CPU处在带NICE值的用户模式下的时间百分比。%system:CPU处在系统模式下的时间百分比。%iowait:CPU等待输入输出完成时间的百分比。%steal:管理程序维护另一个虚拟处理器时,虚拟CPU的无意识等待时间百分比。%idle:CPU空闲时间百分比。备注:如果%iowait的值过高,表示硬盘存在I/O瓶颈,%idle值高,表示CPU较空闲,如果%idle值高但系统响应慢时,有可能是CPU等待分配内存,此时应加大内存容量。%idle值如果持续低于10,那么系统的CPU处理能力相对较低,表明系统中最需要解决的资源是CPU。disk属性值说明:rrqm/s: 每秒进行 merge 的读操作数目。即 rmerge/swrqm/s: 每秒进行 merge 的写操作数目。即 wmerge/sr/s: 每秒完成的读 I/O 设备次数。即 rio/sw/s: 每秒完成的写 I/O 设备次数。即 wio/srsec/s: 每秒读扇区数。即 rsect/swsec/s: 每秒写扇区数。即 wsect/srkB/s: 每秒读K字节数。是 rsect/s 的一半,因为每扇区大小为512字节。wkB/s: 每秒写K字节数。是 wsect/s 的一半。avgrq-sz: 平均每次设备I/O操作的数据大小 (扇区)。avgqu-sz: 平均I/O队列长度。await: 平均每次设备I/O操作的等待时间 (毫秒)。svctm: 平均每次设备I/O操作的服务时间 (毫秒)。%util: 一秒中有百分之多少的时间用于 I/O 操作,即被io消耗的cpu百分比备注:如果 %util 接近 100%,说明产生的I/O请求太多,I/O系统已经满负荷,该磁盘可能存在瓶颈。如果 svctm 比较接近 await,说明 I/O 几乎没有等待时间;如果 await 远大于 svctm,说明I/O 队列太长,io响应太慢,则需要进行必要优化。如果avgqu-sz比较大,也表示有当量io在等待。 例二:定时显示所有信息 # 每隔 2秒刷新显示,且显示3次 $ iostat 2 3 例三:显示指定磁盘信息 $ iostat -d sda1 例四:显示tty和Cpu信息 $ iostat -t 例五:以M为单位显示所有信息 $ iostat -m 例六:查看TPS和吞吐量信息 $ iostat -d -k 1 1 说明:tps:该设备每秒的传输次数(Indicate the number of transfers per second that were issued to the device.)。“一次传输”意思是“一次I/O请求”。多个逻辑请求可能会被合并为“一次I/O请求”。“一次传输”请求的大小是未知的。kB_read/s:每秒从设备(drive expressed)读取的数据量;kB_wrtn/s:每秒向设备(drive expressed)写入的数据量;kB_read:读取的总数据量;kB_wrtn:写入的总数量数据量;这些单位都为Kilobytes。上面的例子中,我们可以看到磁盘sda以及它的各个分区的统计数据,当时统计的磁盘总TPS是22.73,下面是各个分区的TPS。(因为是瞬间值,所以总TPS并不严格等于各个分区TPS的总和) 例七:查看设备使用率(%util)、响应时间(await) $ iostat -d -x -k 1 1 说明:rrqm/s: 每秒进行 merge 的读操作数目.即 delta(rmerge)/swrqm/s: 每秒进行 merge 的写操作数目.即 delta(wmerge)/sr/s: 每秒完成的读 I/O 设备次数.即 delta(rio)/sw/s: 每秒完成的写 I/O 设备次数.即 delta(wio)/srsec/s: 每秒读扇区数.即 delta(rsect)/swsec/s: 每秒写扇区数.即 delta(wsect)/srkB/s: 每秒读K字节数.是 rsect/s 的一半,因为每扇区大小为512字节.(需要计算)wkB/s: 每秒写K字节数.是 wsect/s 的一半.(需要计算)avgrq-sz:平均每次设备I/O操作的数据大小 (扇区).delta(rsect+wsect)/delta(rio+wio)avgqu-sz:平均I/O队列长度.即 delta(aveq)/s/1000 (因为aveq的单位为毫秒).await: 平均每次设备I/O操作的等待时间 (毫秒).即 delta(ruse+wuse)/delta(rio+wio)svctm: 平均每次设备I/O操作的服务时间 (毫秒).即 delta(use)/delta(rio+wio)%util: 一秒中有百分之多少的时间用于 I/O 操作,或者说一秒中有多少时间 I/O 队列是非空的,即 delta(use)/s/1000 (因为use的单位为毫秒) 如果 %util 接近 100%,说明产生的I/O请求太多,I/O系统已经满负荷,该磁盘可能存在瓶颈。 idle小于70% IO压力就较大了,一般读取速度有较多的wait。同时可以结合vmstat 查看查看b参数(等待资源的进程数)和wa参数(IO等待所占用的CPU时间的百分比,高过30%时IO压力高)。 另外 await 的参数也要多和 svctm 来参考。差的过高就一定有 IO 的问题。 avgqu-sz 也是个做 IO 调优时需要注意的地方,这个就是直接每次操作的数据的大小,如果次数多,但数据拿的小的话,其实 IO 也会很小。如果数据拿的大,才IO 的数据会高。也可以通过 avgqu-sz × ( r/s or w/s ) = rsec/s or wsec/s。也就是讲,读定速度是这个来决定的。 svctm 一般要小于 await (因为同时等待的请求的等待时间被重复计算了),svctm 的大小一般和磁盘性能有关,CPU/内存的负荷也会对其有影响,请求过多也会间接导致 svctm 的增加。await 的大小一般取决于服务时间(svctm) 以及 I/O 队列的长度和 I/O 请求的发出模式。如果 svctm 比较接近 await,说明 I/O 几乎没有等待时间;如果 await 远大于 svctm,说明 I/O 队列太长,应用得到的响应时间变慢,如果响应时间超过了用户可以容许的范围,这时可以考虑更换更快的磁盘,调整内核 elevator 算法,优化应用,或者升级 CPU。 队列长度(avgqu-sz)也可作为衡量系统 I/O 负荷的指标,但由于 avgqu-sz 是按照单位时间的平均值,所以不能反映瞬间的 I/O 洪水。形象的比喻:r/s+w/s 类似于交款人的总数平均队列长度(avgqu-sz)类似于单位时间里平均排队人的个数平均服务时间(svctm)类似于收银员的收款速度平均等待时间(await)类似于平均每人的等待时间平均I/O数据(avgrq-sz)类似于平均每人所买的东西多少I/O 操作率 (%util)类似于收款台前有人排队的时间比例 设备IO操作:总IO(io)/s = r/s(读) +w/s(写) =1.46 + 25.28=26.74 平均每次设备I/O操作只需要0.36毫秒完成,现在却需要10.57毫秒完成,因为发出的 请求太多(每秒26.74个),假如请求时同时发出的,可以这样计算平均等待时间: 平均等待时间=单个I/O服务器时间*(1+2+…+请求总数-1)/请求总数 每秒发出的I/0请求很多,但是平均队列就4,表示这些请求比较均匀,大部分处理还是比较及时。 例八:查看cpu状态 $ iostat -c 1 3","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"人生若只如初见-《围城》","slug":"Fortress-Besieged","date":"2017-01-03T03:27:04.000Z","updated":"2024-07-05T11:10:21.875Z","comments":true,"path":"2017/01/03/Fortress-Besieged/","permalink":"http://yelog.org/2017/01/03/Fortress-Besieged/","excerpt":"婚姻是被围困的城堡,城外的人想冲进去,城里的人想逃出来 –法国谚语","text":"婚姻是被围困的城堡,城外的人想冲进去,城里的人想逃出来 –法国谚语 方鸿渐对于鲍小姐,不堪抵抗;对于苏小姐的垂青,已再纠缠不清(导致方唐感情破裂的导火索);最后与孙柔嘉的婚姻,因属于现实的无奈吧;至于唐晓芙,方对她应该有着最纯粹的爱意。 整部小说给我留下深刻印象的就是唐小姐,她在整部小说的占比是非常少的。 总而言之,唐小姐是摩登文明社会里的那桩罕物――一个真正的女孩子。有许多都市女孩子已经是装模作样的早熟的女人,算不得孩子;有许多女孩子只是浑沌痴顽的无性别的孩子,还说不上女人。 唐晓芙的聪明漂亮、活泼可爱,令方鸿渐一见倾心。 当初,是苏小姐的干预,激起了唐晓芙的逆反心理,不让接近我偏接近,书信往来,见面谈话,时间推移,俩人都投入了真正的感情。 作为苏的姐妹,唐晓芙骨子里也有跟苏小姐的高傲。 方先生的过去太丰富了!我爱的人,我要能够占领他整个生命,他在碰见我以前,没有过去,留着空白等待我—— 方下定决心写信给苏撇清两人关系,苏的狭隘的心理从中挑拨导致方与唐之间的破裂 他象一个受了责骂的孩子那样,泪水在眼睛里打转,却一句话也说不出口。 所以当唐得知方的一些列斑斑劣迹后,愈心痛愈心恨,最后一次见方,连珠炮的发问,又恨方鸿渐为什么不辩护,她的心溶成了苦水。而方鸿渐的悲剧在于,他再次懦弱,该辩解时不辩解,该在屋外多淋雨时而过早走开,接到电话后,不问来人就大声呵斥,俩人都是好面子的人,看不到俩人为这段感情而去采取任何补救措施,而是各自松开了手。两人年轻,都不知退让,任彼此失之交臂。 从此直到最后,唐也没有再次出现。 但唐给我们的留下了一个近乎完美的形象,正是因为她没有和方走在一起,没有真实世俗的一面,才能留下那种只如初见的模样。 生活亦是如此,我们心目中的“女神”、“男神”,完美无缺的人,是那些我们曾经追求不得的人。想想若是得之,经过世俗的一面,ta的完美的形象,还会在你的心中站的稳吗。 唐晓芙这个角色是钱老钟爱的角色,是钱老心中完美的女性形象,简单说就是女神!是围城里任何男人都配不上的,所以不舍得把她许配给任何人。 --杨绛先生 《围城》写出了婚姻的一方面,但不是全部,很多人要冲进这座城,自有其道理,城中有争吵,但更有温情。愿诸位在现实生活中,相互欣赏,相敬如宾,如初见一样,相互爱戴,生活一定更加美好 在新春佳节祝大家幸福美满,阖家欢乐。","categories":[{"name":"读书","slug":"读书","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/"},{"name":"阅读笔记","slug":"读书/阅读笔记","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"reading","slug":"reading","permalink":"http://yelog.org/tags/reading/"}]},{"title":"每天一个linux命令(34): vmstat","slug":"linux-command-34-vmstat","date":"2017-01-03T01:46:57.000Z","updated":"2024-07-05T11:10:22.929Z","comments":true,"path":"2017/01/03/linux-command-34-vmstat/","permalink":"http://yelog.org/2017/01/03/linux-command-34-vmstat/","excerpt":"vmstat是Virtual Meomory Statistics(虚拟内存统计)的缩写,可对操作系统的虚拟内存、进程、CPU活动进行监控。他是对系统的整体情况进行统计,不足之处是无法对某个进程进行深入分析。vmstat 工具提供了一种低开销的系统性能观察方式。因为 vmstat 本身就是低开销工具,在非常高负荷的服务器上,你需要查看并监控系统的健康情况,在控制窗口还是能够使用vmstat 输出结果。在学习vmstat命令前,我们先了解一下Linux系统中关于物理内存和虚拟内存相关信息。","text":"vmstat是Virtual Meomory Statistics(虚拟内存统计)的缩写,可对操作系统的虚拟内存、进程、CPU活动进行监控。他是对系统的整体情况进行统计,不足之处是无法对某个进程进行深入分析。vmstat 工具提供了一种低开销的系统性能观察方式。因为 vmstat 本身就是低开销工具,在非常高负荷的服务器上,你需要查看并监控系统的健康情况,在控制窗口还是能够使用vmstat 输出结果。在学习vmstat命令前,我们先了解一下Linux系统中关于物理内存和虚拟内存相关信息。 物理内存和虚拟内存区别 我们知道,直接从物理内存读写数据要比从硬盘读写数据要快的多,因此,我们希望所有数据的读取和写入都在内存完成,而内存是有限的,这样就引出了物理内存与虚拟内存的概念。 物理内存就是系统硬件提供的内存大小,是真正的内存,相对于物理内存,在linux下还有一个虚拟内存的概念,虚拟内存就是为了满足物理内存的不足而提出的策略,它是利用磁盘空间虚拟出的一块逻辑内存,用作虚拟内存的磁盘空间被称为交换空间(Swap Space)。 作为物理内存的扩展,linux会在物理内存不足时,使用交换分区的虚拟内存,更详细的说,就是内核会将暂时不用的内存块信息写到交换空间,这样以来,物理内存得到了释放,这块内存就可以用于其它目的,当需要用到原始的内容时,这些信息会被重新从交换空间读入物理内存。 linux的内存管理采取的是分页存取机制,为了保证物理内存能得到充分的利用,内核会在适当的时候将物理内存中不经常使用的数据块自动交换到虚拟内存中,而将经常使用的信息保留到物理内存。 要深入了解linux内存运行机制,需要知道下面提到的几个方面: 首先,Linux系统会不时的进行页面交换操作,以保持尽可能多的空闲物理内存,即使并没有什么事情需要内存,Linux也会交换出暂时不用的内存页面。这可以避免等待交换所需的时间。 其次,linux进行页面交换是有条件的,不是所有页面在不用时都交换到虚拟内存,linux内核根据”最近最经常使用“算法,仅仅将一些不经常使用的页面文件交换到虚拟内存,有时我们会看到这么一个现象:linux物理内存还有很多,但是交换空间也使用了很多。其实,这并不奇怪,例如,一个占用很大内存的进程运行时,需要耗费很多内存资源,此时就会有一些不常用页面文件被交换到虚拟内存中,但后来这个占用很多内存资源的进程结束并释放了很多内存时,刚才被交换出去的页面文件并不会自动的交换进物理内存,除非有这个必要,那么此刻系统物理内存就会空闲很多,同时交换空间也在被使用,就出现了刚才所说的现象了。关于这点,不用担心什么,只要知道是怎么一回事就可以了。 最后,交换空间的页面在使用时会首先被交换到物理内存,如果此时没有足够的物理内存来容纳这些页面,它们又会被马上交换出去,如此以来,虚拟内存中可能没有足够空间来存储这些交换页面,最终会导致linux出现假死机、服务异常等问题,linux虽然可以在一段时间内自行恢复,但是恢复后的系统已经基本不可用了。 因此,合理规划和设计linux内存的使用,是非常重要的。 虚拟内存原理: 在系统中运行的每个进程都需要使用到内存,但不是每个进程都需要每时每刻使用系统分配的内存空间。当系统运行所需内存超过实际的物理内存,内核会释放某些进程所占用但未使用的部分或所有物理内存,将这部分资料存储在磁盘上直到进程下一次调用,并将释放出的内存提供给有需要的进程使用。 在Linux内存管理中,主要是通过“调页Paging”和“交换Swapping”来完成上述的内存调度。调页算法是将内存中最近不常使用的页面换到磁盘上,把活动页面保留在内存中供进程使用。交换技术是将整个进程,而不是部分页面,全部交换到磁盘上。 分页(Page)写入磁盘的过程被称作Page-Out,分页(Page)从磁盘重新回到内存的过程被称作Page-In。当内核需要一个分页时,但发现此分页不在物理内存中(因为已经被Page-Out了),此时就发生了分页错误(Page Fault)。 当系统内核发现可运行内存变少时,就会通过Page-Out来释放一部分物理内存。经管Page-Out不是经常发生,但是如果Page-out频繁不断的发生,直到当内核管理分页的时间超过运行程式的时间时,系统效能会急剧下降。这时的系统已经运行非常慢或进入暂停状态,这种状态亦被称作thrashing(颠簸)。 命令格式$ vmstat [-a] [-n] [-S unit] [delay [ count]] $ vmstat [-s] [-n] [-S unit] $ vmstat [-m] [-n] [delay [ count]] $ vmstat [-d] [-n] [delay [ count]] $ vmstat [-p disk partition] [-n] [delay [ count]] $ vmstat [-f] $ vmstat [-V] 命令功能 用来显示虚拟内存的信息 命令参数 命令 描述 -a 显示活跃和非活跃内存 -f 显示从系统启动至今的fork数量 -m 显示slabinfo -n 只在开始时显示一次各字段名称 -s 显示内存相关统计信息及多种系统活动数量 delay 刷新时间间隔。如果不指定,只显示一条结果 count 刷新次数。如果不指定刷新次数,但指定了刷新时间间隔,这时刷新次数为无穷 -d 显示磁盘相关统计信息 -p 显示指定磁盘分区统计信息 -S 使用指定单位显示。参数有 k 、K 、m 、M ,分别代表1000、1024、1000000、1048576字节(byte)。默认单位为K(1024 bytes) -V 显示vmstat版本信息 使用实例例一:显示虚拟内存使用情况 $ vmstat procs -----------memory---------- ---swap-- -----io--- --system--- -----cpu--- r b swpd free buff cache si so bi bo in cs us sy id wa st 0 0 0 7108340 129544 3155916 0 0 184 53 203 995 4 1 95 0 0 说明:Procs(进程):r: 运行队列中进程数量b: 等待IO的进程数量Memory(内存):swpd: 使用虚拟内存大小free: 可用内存大小buff: 用作缓冲的内存大小cache: 用作缓存的内存大小Swap:si: 每秒从交换区写到内存的大小so: 每秒写入交换区的内存大小IO:(现在的Linux版本块的大小为1024bytes)bi: 每秒读取的块数bo: 每秒写入的块数系统:in: 每秒中断数,包括时钟中断。cs: 每秒上下文切换数。CPU(以百分比表示):us: 用户进程执行时间(user time)sy: 系统进程执行时间(system time)id: 空闲时间(包括IO等待时间),中央处理器的空闲时间 。以百分比表示。wa: 等待IO时间备注: 如果 r经常大于 4 ,且id经常少于40,表示cpu的负荷很重。如果pi,po 长期不等于0,表示内存不足。如果disk 经常不等于0, 且在 b中的队列 大于3, 表示 io性能不好。Linux在具有高稳定性、可靠性的同时,具有很好的可伸缩性和扩展性,能够针对不同的应用和硬件环境调整,优化出满足当前应用需要的最佳性能。因此企业在维护Linux系统、进行系统调优时,了解系统性能分析工具是至关重要的。命令:vmstat 5 5表示在5秒时间内进行5次采样。将得到一个数据汇总他能够反映真正的系统情况。 例二:显示活跃和非活跃内存 $ vmstat -a 2 5 说明:使用-a选项显示活跃和非活跃内存时,所显示的内容除增加inact和active外,其他显示内容与例子1相同。Memory(内存):inact: 非活跃内存大小(当使用-a选项时显示)active: 活跃的内存大小(当使用-a选项时显示) 例三:查看系统已经fork了多少次 $ vmstat -f 说明:这个数据是从/proc/stat中的processes字段里取得的 例四:查看内存使用的详细信息 $ vmstat -s 说明:这些信息的分别来自于/proc/meminfo,/proc/stat和/proc/vmstat。 例五:查看磁盘的读/写 $ vmstat -d 说明:这些信息主要来自于/proc/diskstats.merged:表示一次来自于合并的写/读请求,一般系统会把多个连接/邻近的读/写请求合并到一起来操作. 例六:查看/dev/sda1磁盘的读/写 $ vmstat -p /dev/sda1 说明:这些信息主要来自于/proc/diskstats。reads:来自于这个分区的读的次数。read sectors:来自于这个分区的读扇区的次数。writes:来自于这个分区的写的次数。requested writes:来自于这个分区的写请求次数。 例七:查看系统的slab信息 $ vmstat -m 说明:这组信息来自于/proc/slabinfo。slab:由于内核会有许多小对象,这些对象构造销毁十分频繁,比如i-node,dentry,这些对象如果每次构建的时候就向内存要一个页(4kb),而其实只有几个字节,这样就会非常浪费,为了解决这个问题,就引入了一种新的机制来处理在同一个页框中如何分配小存储区,而slab可以对小对象进行分配,这样就不用为每一个对象分配页框,从而节省了空间,内核对一些小对象创建析构很频繁,slab对这些小对象进行缓冲,可以重复利用,减少内存分配次数。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(33): free","slug":"linux-command-33-free","date":"2017-01-02T13:43:26.000Z","updated":"2024-07-05T11:10:22.823Z","comments":true,"path":"2017/01/02/linux-command-33-free/","permalink":"http://yelog.org/2017/01/02/linux-command-33-free/","excerpt":"free命令可以显示Linux系统中空闲的、已用的物理内存及swap内存,及被内核使用的buffer。在Linux系统监控的工具中,free命令是最经常使用的命令之一。","text":"free命令可以显示Linux系统中空闲的、已用的物理内存及swap内存,及被内核使用的buffer。在Linux系统监控的工具中,free命令是最经常使用的命令之一。 命令格式$ free [参数] 命令功能 free 命令显示系统使用和空闲的内存情况,包括物理内存、交互区内存(swap)和内核缓冲区内存。共享内存将被忽略 命令参数 命令 描述 -b 以Byte为单位显示内存使用情况 -k 以KB为单位显示内存使用情况 -m 以MB为单位显示内存使用情况 -g 以GB为单位显示内存使用情况 -o 不显示缓冲区调节列 -s<间隔秒数> 持续观察内存使用状况 -t 显示内存总和列 -V 显示版本信息 使用实例例一:显示内存使用情况 $ free total used free shared buff/cache available Mem: 12095180 8362640 198460 1379116 3534080 2100004 Swap: 8185112 40008 8145104 说明:total:总计物理内存的大小。used:已使用多大。free:可用有多少。Shared:多个进程共享的内存总额。Buffers/cached:磁盘缓存的大小。 例二:显示内存使用情况 $ free -t 例三:周期性的查询内存使用信息 # 每10s 执行一次命令 $ free -s 10","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(32): top","slug":"linux-command-32-top","date":"2017-01-01T11:31:28.000Z","updated":"2024-07-05T11:10:22.799Z","comments":true,"path":"2017/01/01/linux-command-32-top/","permalink":"http://yelog.org/2017/01/01/linux-command-32-top/","excerpt":"top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。下面详细介绍它的使用方法。top是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直到用户终止该程序为止.比较准确的说,top命令提供了实时的对系统处理器的状态监视.它将显示系统中CPU最“敏感”的任务列表.该命令可以按CPU使用.内存使用和执行时间对任务进行排序;而且该命令的很多特性都可以通过交互式命令或者在个人定制文件中进行设定。","text":"top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。下面详细介绍它的使用方法。top是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直到用户终止该程序为止.比较准确的说,top命令提供了实时的对系统处理器的状态监视.它将显示系统中CPU最“敏感”的任务列表.该命令可以按CPU使用.内存使用和执行时间对任务进行排序;而且该命令的很多特性都可以通过交互式命令或者在个人定制文件中进行设定。 命令格式$ top [参数] 命令功能 显示当前系统正在执行的进程的相关信息,包括进程ID、内存占用率、CPU占用率等 命令参数 参数 描述 -b 批处理 -c 显示完整的治命令 -I 忽略失效过程 -s 保密模式 -S 累积模式 -i<时间> 设置间隔时间 -u<用户名> 指定用户名 -p<进程号> 指定进程 -n<次数> 循环显示的次数 top交互命令 在top 命令执行过程中可以使用的一些交互命令。这些命令都是单字母的,如果在命令行中使用了s 选项, 其中一些命令可能会被屏蔽。 参数 描述 h 显示帮助画面,给出一些简短的命令总结说明 k 终止一个进程。 i 忽略闲置和僵死进程。这是一个开关式命令 q 退出程序 r 重新安排一个进程的优先级别 S 切换到累计模式 s 改变两次刷新之间的延迟时间(单位为s),如果有小数,就换算成m s。输入0值则系统将不断刷新,默认值是5 s f或者F 从当前显示中添加或者删除项目 o或者O 改变显示项目的顺序 l 切换显示平均负载和启动时间信息 m 切换显示内存信息 t 切换显示进程和CPU状态信息 c 切换显示命令名称和完整命令行 M 根据驻留内存大小进行排序 P 根据CPU使用百分比大小进行排序 T 根据时间/累计时间进行排序 W 将当前设置写入~/.toprc文件中 使用实例例一:显示进程信息 $ top top讲解其他技巧 数字1,可监控每个逻辑CPU的状况 键盘b(打开/关闭加亮效果),运行状态的进程 键盘x 打开/关闭排序列的加亮效果 shift + >或shift + <改变排序列 例二:显示 完整命令 $ top -c 例三:以批处理模式显示程序信息 $ top -b 例四:以累积模式显示程序信息 $ top -S 例五:设置信息更新次数 # 表示更新两次后终止更新显示 $ top -n 2 例六:设置信息更新时间 # 表示更新周期为3秒 $ top -d 3 例七:显示指定的进程信息 $ top -p 574","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(31): du","slug":"linux-command-31-du","date":"2016-12-31T06:36:01.000Z","updated":"2024-07-05T11:10:22.819Z","comments":true,"path":"2016/12/31/linux-command-31-du/","permalink":"http://yelog.org/2016/12/31/linux-command-31-du/","excerpt":"Linux du命令也是查看使用空间的,但是与df命令不同的是Linux du命令是对文件和目录磁盘使用的空间的查看,还是和df命令有一些区别的.","text":"Linux du命令也是查看使用空间的,但是与df命令不同的是Linux du命令是对文件和目录磁盘使用的空间的查看,还是和df命令有一些区别的. 命令格式$ du [选项][文件] 命令功能 显示每个文件和目录的磁盘使用空间。 命令参数 参数 描述 -a或-all 显示目录中个别文件的大小。 -b或-bytes 显示目录或文件大小时,以byte为单位。 -c或–total 除了显示个别目录或文件的大小外,同时也显示所有目录或文件的总和。 -k或–kilobytes 以KB(1024bytes)为单位输出。 -m或–megabytes 以MB为单位输出。 -s或–summarize 仅显示总计,只列出最后加总的值。 -h或–human-readable 以K,M,G为单位,提高信息的可读性。 -x或–one-file-xystem 以一开始处理时的文件系统为准,若遇上其它不同的文件系统目录则略过。 -L<符号链接>或–dereference<符号链接> 显示选项中所指定符号链接的源文件大小。 -S或–separate-dirs 显示个别目录的大小时,并不含其子目录的大小。 -X<文件>或–exclude-from=<文件> 在<文件>指定目录或文件。 –exclude=<目录或文件> 略过指定的目录或文件。 -D或–dereference-args 显示指定符号链接的源文件大小。 -H或–si 与-h参数相同,但是K,M,G是以1000为换算单位。 -l或–count-links 重复计算硬件链接的文件。 使用实例例一:显示目录或者文件所占空间 $ du 说明: 只显示当前目录下面的子目录的目录大小和当前目录的总的大小,最下面的1288为当前目录的总大小 例二:显示指定文件所占空间 $ du log2012.log 例三:查看指定目录的所占空间 $ du scf 例四:显示多个文件所占空间 $ du log30.tar.gz log31.tar.gz 例五:只显示总和的大小 $ du -s 例六:方便阅读的格式显示 $ du -h test 例七:文件和目录都显示 $ du -ah test 例八:显示几个文件或目录各自占用磁盘空间的大小,还统计它们的总和 $ du -c log30.tar.gz log31.tar.gz 说明: 加上-c选项后,du不仅显示两个目录各自占用磁盘空间的大小,还在最后一行统计它们的总和。 例九:按照空间大小排序 $ du|sort -nr|more 例十:输出当前目录下各个子目录所使用的空间 $ du -h --max-depth=1","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(30): df","slug":"linux-command-30-df","date":"2016-12-30T06:23:31.000Z","updated":"2024-07-05T11:10:22.782Z","comments":true,"path":"2016/12/30/linux-command-30-df/","permalink":"http://yelog.org/2016/12/30/linux-command-30-df/","excerpt":"linux中df命令的功能是用来检查linux服务器的文件系统的磁盘空间占用情况。可以利用该命令来获取硬盘被占用了多少空间,目前还剩下多少空间等信息。","text":"linux中df命令的功能是用来检查linux服务器的文件系统的磁盘空间占用情况。可以利用该命令来获取硬盘被占用了多少空间,目前还剩下多少空间等信息。 命令格式$ df [选项] [文件] 命令功能 显示指定磁盘文件的可用空间。如果没有文件名被指定,则所有当前被挂载的文件系统的可用空间将被显示。默认情况下,磁盘空间将以 1KB 为单位进行显示,除非环境变量 POSIXLY_CORRECT 被指定,那样将以512字节为单位进行显示 命令参数必要参数 参数 描述 -a 全部文件系统列表 -h 方便阅读方式显示 -H 等于“-h”,但是计算式,1K=1000,而不是1K=1024 -i 显示inode信息 -k 区块为1024字节 -l 只显示本地文件系统 -m 区块为1048576字节 –no-sync 忽略 sync 命令 -P 输出格式为POSIX –sync 在取得磁盘信息前,先执行sync命令 -T 文件系统类型 选择参数 参数 描述 –block-size=<区块大小> 指定区块大小 -t<文件系统类型> 只显示选定文件系统的磁盘信息 -x<文件系统类型> 不显示选定文件系统的磁盘信息 –help 显示帮助信息 –version 显示版本信息 使用实例例一:显示磁盘使用情况 $ df 说明: linux中df命令的输出清单的第1列是代表文件系统对应的设备文件的路径名(一般是硬盘上的分区);第2列给出分区包含的数据块(1024字节)的数目;第3,4列分别表示已用的和可用的数据块数目。用户也许会感到奇怪的是,第3,4列块数之和不等于第2列中的块数。这是因为缺省的每个分区都留了少量空间供系统管理员使用。即使遇到普通用户空间已满的情况,管理员仍能登录和留有解决问题所需的工作空间。清单中Use% 列表示普通用户空间使用的百分比,即使这一数字达到100%,分区仍然留有系统管理员使用的空间。最后,Mounted on列表示文件系统的挂载点。 例二:以inode模式来显示磁盘使用情况 $ df -i 例三:显示指定类型磁盘 $ df -t ext3 例四:列出各文件系统的i节点使用情况 $ df -ia 例五:列出文件系统的类型 $ df -T 例六:以更易读的方式显示目前磁盘空间和使用情况 $ df -h 说明:-h更具目前磁盘空间和使用情况 以更易读的方式显示-H根上面的-h参数相同,不过在根式化的时候,采用1000而不是1024进行容量转换-k以单位显示磁盘的使用情况-l显示本地的分区的磁盘空间使用率,如果服务器nfs了远程服务器的磁盘,那么在df上加上-l后系统显示的是过滤nsf驱动器后的结果-i显示inode的使用情况。linux采用了类似指针的方式管理磁盘空间影射.这也是一个比较关键应用","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(29): /etc/group文件详解","slug":"linux-command-29-group","date":"2016-12-29T06:13:05.000Z","updated":"2024-07-05T11:10:22.837Z","comments":true,"path":"2016/12/29/linux-command-29-group/","permalink":"http://yelog.org/2016/12/29/linux-command-29-group/","excerpt":"Linux /etc/group文件与/etc/passwd和/etc/shadow文件都是有关于系统管理员对用户和用户组管理时相关的文件。linux /etc/group文件是有关于系统管理员对用户和用户组管理的文件,linux用户组的所有信息都存放在/etc/group文件中。具有某种共同特征的用户集合起来就是用户组(Group)。用户组(Group)配置文件主要有 /etc/group和/etc/gshadow,其中/etc/gshadow是/etc/group的加密信息文件。","text":"Linux /etc/group文件与/etc/passwd和/etc/shadow文件都是有关于系统管理员对用户和用户组管理时相关的文件。linux /etc/group文件是有关于系统管理员对用户和用户组管理的文件,linux用户组的所有信息都存放在/etc/group文件中。具有某种共同特征的用户集合起来就是用户组(Group)。用户组(Group)配置文件主要有 /etc/group和/etc/gshadow,其中/etc/gshadow是/etc/group的加密信息文件。 将用户分组是Linux系统中对用户进行管理及控制访问权限的一种手段。每个用户都属于某个用户组;一个组中可以有多个用户,一个用户也可以属于不 同的组。当一个用户同时是多个组中的成员时,在/etc/passwd文件中记录的是用户所属的主组,也就是登录时所属的默认组,而其他组称为附加组。 用户组的所有信息都存放在/etc/group文件中。此文件的格式是由冒号(:)隔开若干个字段,这些字段具体如下: 组名:口令:组标识号:组内用户列表 解释组名: 组名是用户组的名称,由字母或数字构成。与/etc/passwd中的登录名一样,组名不应重复。口令: 口令字段存放的是用户组加密后的口令字。一般Linux系统的用户组都没有口令,即这个字段一般为空,或者是*。组标识号: 组标识号与用户标识号类似,也是一个整数,被系统内部用来标识组。别称GID.组内用户列表: 是属于这个组的所有用户的列表,不同用户之间用逗号(,)分隔。这个用户组可能是用户的主组,也可能是附加组。 使用实例$ cat /etc/group 说明: 我们以root:x:0:root,linuxsir 为例: 用户组root,x是密码段,表示没有设置密码,GID是0,root用户组下包括root、linuxsir以及GID为0的其它用户。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(28): chown","slug":"linux-command-28-chown","date":"2016-12-28T01:51:30.000Z","updated":"2024-07-05T11:10:23.011Z","comments":true,"path":"2016/12/28/linux-command-28-chown/","permalink":"http://yelog.org/2016/12/28/linux-command-28-chown/","excerpt":"chown将指定文件的拥有者改为指定的用户或组,用户可以是用户名或者用户ID;组可以是组名或者组ID;文件是以空格分开的要改变权限的文件列表,支持通配符。系统管理员经常使用chown命令,在将文件拷贝到另一个用户的名录下之后,让用户拥有使用该文件的权限。","text":"chown将指定文件的拥有者改为指定的用户或组,用户可以是用户名或者用户ID;组可以是组名或者组ID;文件是以空格分开的要改变权限的文件列表,支持通配符。系统管理员经常使用chown命令,在将文件拷贝到另一个用户的名录下之后,让用户拥有使用该文件的权限。 命令格式$ chown [选项]... [所有者][:[组]] 文件... 命令功能 通过chown改变文件的拥有者和群组。在更改文件的所有者或所属群组时,可以使用用户名称和用户识别码设置。普通用户不能将自己的文件改变成其他的拥有者。其操作权限一般为管理员。 命令参数必要参数 参数 描述 -c 显示更改的部分的信息 -f 忽略错误信息 -h 修复符号链接 -R 处理指定目录以及其子目录下的所有文件 -v 显示详细的处理信息 -deference 作用于符号链接的指向,而不是链接文件本身 选择参数 参数 描述 –reference=<目录或文件> 把指定的目录/文件作为参考,把操作的文件/目录设置成参考文件/目录相同拥有者和群组 –from=<当前用户:当前群组> 只有当前用户和群组跟指定的用户和群组相同时才进行改变 –help 显示帮助信息 –version 显示版本信息 命令实例例一:改变拥有者和群组 $ chown mail:mail log2012.log 例二:改变文件拥有者和群组 # 组可为空,默认为root所在组 $ chown root: log2012.log 例三:改变文件群组 # 只改变所在组 $ chown :mail log2012.log 例四:改变指定目录以及其子目录下的所有文件的拥有者和群组 $ chown -R -v root:mail test6","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(27): chgrp","slug":"linux-command-27-chgrp","date":"2016-12-27T01:40:06.000Z","updated":"2024-07-05T11:10:22.882Z","comments":true,"path":"2016/12/27/linux-command-27-chgrp/","permalink":"http://yelog.org/2016/12/27/linux-command-27-chgrp/","excerpt":"在lunix系统里,文件或目录的权限的掌控以拥有者及所诉群组来管理。可以使用chgrp指令取变更文件与目录所属群组,这种方式采用群组名称或群组识别码都可以。Chgrp命令就是change group的缩写!要被改变的组名必须要在/etc/group文件内存在才行。","text":"在lunix系统里,文件或目录的权限的掌控以拥有者及所诉群组来管理。可以使用chgrp指令取变更文件与目录所属群组,这种方式采用群组名称或群组识别码都可以。Chgrp命令就是change group的缩写!要被改变的组名必须要在/etc/group文件内存在才行。 命令格式$ chgrp [选项] [组] [文件] 命令功能 chgrp命令可采用群组名称或群组识别码的方式改变文件或目录的所属群组。使用权限是超级用户。 命令参数必要参数 参数 描述 -c 当发生改变时,报告处理信息 -f 不显示错误信息 -R 处理指定目录以及其子目录下的所有文件 -v 运行时显示详细的处理信息 –dereference 作用于符号链接的指向,而不是符号链接本身 –no-dereference 作用于符号链接本身 选择参数 参数 描述 –reference=<文件或者目录> 设置为和指定的文件或目录的权限一样 –help 显示帮助信息 –version 显示版本信息 命令实例例一:改变文件的群组属性 # 将log2012.log文件由root群组改为bin群组 $ chgrp -v bin log2012.log “log2012.log” 的所属组已更改为 bin 例二:根据指定文件改变文件的群组属性 # 改变文件log2013.log 的群组属性,使得文件log2013.log的群组属性和参考文件log2012.log的群组属性相同 $ chgrp --reference=log2012.log log2013.log 例三:改变指定目录以及其子目录下的所有文件的群组属性 # 改变指定目录以及其子目录下的所有文件的群组属性 $ chgrp -R bin test6 例四:通过群组识别码改变文件群组属性 # 通过群组识别码改变文件群组属性,100为users群组的识别码,具体群组和群组识别码可以去/etc/group文件中查看 $ chgrp -R 100 test6","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(26): chmod","slug":"linux-command-26-chmod","date":"2016-12-26T12:22:43.000Z","updated":"2024-07-05T11:10:22.901Z","comments":true,"path":"2016/12/26/linux-command-26-chmod/","permalink":"http://yelog.org/2016/12/26/linux-command-26-chmod/","excerpt":"chmod命令用于改变linux系统文件或目录的访问权限。用它控制文件或目录的访问权限。该命令有两种用法。一种是包含字母和操作符表达式的文字设定法;另一种是包含数字的数字设定法。","text":"chmod命令用于改变linux系统文件或目录的访问权限。用它控制文件或目录的访问权限。该命令有两种用法。一种是包含字母和操作符表达式的文字设定法;另一种是包含数字的数字设定法。 Linux系统中的每个文件和目录都有访问许可权限,用它来确定谁可以通过何种方式对文件和目录进行访问和操作。 文件或目录的访问权限分为只读,只写和可执行三种。以文件为例,只读权限表示只允许读其内容,而禁止对其做任何的更改操作。可执行权限表示允许将该文件作为一个程序执行。文件被创建时,文件所有者自动拥有对该文件的读、写和可执行权限,以便于对文件的阅读和修改。用户也可根据需要把访问权限设置为需要的任何组合。 有三种不同类型的用户可对文件或目录进行访问:文件所有者,同组用户、其他用户。所有者一般是文件的创建者。所有者可以允许同组用户有权访问文件,还可以将文件的访问权限赋予系统中的其他用户。在这种情况下,系统中每一位用户都能访问该用户拥有的文件或目录。 每一文件或目录的访问权限都有三组,每组用三位表示,分别为文件属主的读、写和执行权限;与属主同组的用户的读、写和执行权限;系统中其他用户的读、写和执行权限。当用ls -l命令显示文件或目录的详细信息时,最左边的一列为文件的访问权限。 例如: $ ls -al -rw-r--r-- 1 root root 296K 11-13 06:03 log2012.log 第一列共有10个位置,第一个字符指定了文件类型。在通常意义上,一个目录也是一个文件。如果第一个字符是横线,表示是一个非目录的文件。如果是d,表示是一个目录。从第二个字符开始到第十个共9个字符,3个字符一组,分别表示了3组用户对文件或者目录的权限。权限字符用横线代表空许可,r代表只读,w代表写,x代表可执行。 - rw- r-- r-- 表示log2012.log是一个普通文件;log2012.log的属主有读写权限;与log2012.log属主同组的用户只有读权限;其他用户也只有读权限。 确定了一个文件的访问权限后,用户可以利用Linux系统提供的chmod命令来重新设定不同的访问权限。也可以利用chown命令来更改某个文件或目录的所有者。利用chgrp命令来更改某个文件或目录的用户组。 chmod命令是非常重要的,用于改变文件或目录的访问权限。用户用它控制文件或目录的访问权限。chmod命令详细情况如下。 命令格式$ chmod [-cfvR] [--help] [--version] mode file 命令功能用于改变文件或目录的访问权限,用它控制文件或目录的访问权限。 命令参数必要参数 参数 描述 -c 当发生改变时,报告处理信息 -f 错误信息不输出 -R 处理指定目录以及其子目录下的所有文件 -v 运行时显示详细处理信息 选择参数 参数 描述 –reference=<目录或者文件> 设置成具有指定目录或者文件具有相同的权限 –version 显示版本信息 <权限范围>+<权限设置> 使权限范围内的目录或者文件具有指定的权限 <权限范围>-<权限设置> 删除权限范围的目录或者文件的指定权限 <权限范围>=<权限设置> 设置权限范围内的目录或者文件的权限为指定的值 权限范围 参数 描述 u 目录或者文件的当前的用户 g 目录或者文件的当前的群组 o 除了目录或者文件的当前用户或群组之外的用户或者群组 a 所有的用户及群组 权限代号 参数 描述 r 读权限,用数字4表示 w 写权限,用数字2表示 x 执行权限,用数字1表示 - 删除权限,用数字0表示 s 特殊权限》该命令有两种用法。一种是包含字母和操作符表达式的文字设定法;另一种是包含数字的数字设定法。 文字设定法 $ chmod [who] [+ | - | =] [mode] 文件名 数字设定法 我们必须首先了解用数字表示的属性的含义:0表示没有权限,1表示可执行权限,2表示可写权限,4表示可读权限,然后将其相加。所以数字属性的格式应为3个从0到7的八进制数,其顺序是(u)(g)(o)。 例如,如果想让某个文件的属主有“读/写”二种权限,需要把4(可读)+2(可写)=6(读/写)。 数字设定法的一般形式为: chmod [mode] 文件名数字与字符对应关系如下:r=4,w=2,x=1若要rwx属性则4+2+1=7若要rw-属性则4+2=6;若要r-x属性则4+1=7。 命令实例例一:增加文件所有用户组可执行权限 $ chmod a+x log2012.log 例二:同时修改不同用户权限 $ chmod ug+w,o-x log2012.log 例三:删除文件权限 $ chmod a-x log2012.log 例四:使用“=”设置权限 $ chmod u=x log2012.log 例五:其他 # 给file的属主分配读、写、执行(7)的权限,给file的所在组分配读、执行(5)的权限,给其他用户分配执行(1)的权限 $ chmod 751 file # 上例的另一种形式 $ chmod u=rwx,g=rx,o=x file # 为所有用户分配读权限 $ chmod =r file # 同上例 $ chmod 444 file # 同上例 $ chmod a-wx,a+r file","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(25): gzip","slug":"linux-command-25-gzip","date":"2016-12-25T08:55:15.000Z","updated":"2024-07-05T11:10:22.805Z","comments":true,"path":"2016/12/25/linux-command-25-gzip/","permalink":"http://yelog.org/2016/12/25/linux-command-25-gzip/","excerpt":"减少文件大小有两个明显的好处,一是可以减少存储空间,二是通过网络传输文件时,可以减少传输的时间。gzip是在Linux系统中经常使用的一个对文件进行压缩和解压缩的命令,既方便又好用。gzip不仅可以用来压缩大的、较少使用的文件以节省磁盘空间,还可以和tar命令一起构成Linux操作系统中比较流行的压缩文件格式。据统计,gzip命令对文本文件有60%~70%的压缩率。","text":"减少文件大小有两个明显的好处,一是可以减少存储空间,二是通过网络传输文件时,可以减少传输的时间。gzip是在Linux系统中经常使用的一个对文件进行压缩和解压缩的命令,既方便又好用。gzip不仅可以用来压缩大的、较少使用的文件以节省磁盘空间,还可以和tar命令一起构成Linux操作系统中比较流行的压缩文件格式。据统计,gzip命令对文本文件有60%~70%的压缩率。 命令格式$ gzip [参数] [文件或者目录] 命令功能 gzip是个使用广泛的压缩程序,文件经它压缩过后,其名称后面会多出”.gz”的扩展名。 命令参数 参数 描述 -a或–ascii 使用ASCII文字模式。 -c或–stdout或–to-stdout 把压缩后的文件输出到标准输出设备,不去更动原始文件。 -d或–decompress或—-uncompress 解开压缩文件。 -f或–force 强行压缩文件。不理会文件名称或硬连接是否存在以及该文件是否为符号连接。 -h或–help 在线帮助。 -l或–list 列出压缩文件的相关信息。 -L或–license 显示版本与版权信息。 -n或–no-name 压缩文件时,不保存原来的文件名称及时间戳记。 -N或–name 压缩文件时,保存原来的文件名称及时间戳记。 -q或–quiet 不显示警告信息 -r或–recursive 递归处理,将指定目录下的所有文件及子目录一并处理。 -S<压缩字尾字符串>或—-suffix<压缩字尾字符串> 更改压缩字尾字符串。 -t或–test 测试压缩文件是否正确无误。 -v或–verbose 显示指令执行过程。 -V或–version 显示版本信息。 -num 用指定的数字num调整压缩的速度,-1或–fast表示最快压缩方法(低压缩比),-9或–best表示最慢压缩方法(高压缩比)。系统缺省值为6。 命令实例例一:把test目录下的每个文件压缩成.gz文件 # 忽略目录,只打包其中文件 $ gzip * 例二:把例1中每个压缩的文件解压,并列出详细的信息 $ gzip -dv * 例三:详细显示例1中每个压缩的文件的信息,并不解压 $ gzip -l * 例四:压缩一个tar备份文件,此时压缩文件的扩展名为.tar.gz $ gzip -r log.tar 例五:递归的压缩目录 $ gzip -rv test6 例六:递归地解压目录 $ gzip -dr test6","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"Git之reset揭秘","slug":"git-reset","date":"2016-12-24T03:19:24.000Z","updated":"2024-07-05T11:10:22.598Z","comments":true,"path":"2016/12/24/git-reset/","permalink":"http://yelog.org/2016/12/24/git-reset/","excerpt":"本文主要选自于《Pro Git》这本书,加上自己平时使用时的理解整理于此,以此给大家借鉴。本文主要讨论 reset 与 checkout。它们能做很多事情,所以我们要真正理解他们到底在底层做了哪些工作,以便能够恰当的运用它们。","text":"本文主要选自于《Pro Git》这本书,加上自己平时使用时的理解整理于此,以此给大家借鉴。本文主要讨论 reset 与 checkout。它们能做很多事情,所以我们要真正理解他们到底在底层做了哪些工作,以便能够恰当的运用它们。 三棵树理解 reset 和 checkout 的最简方法,就是以 Git 的思维框架(将其作为内容管理器)来管理三棵不同的树。 “树” 在我们这里的实际意思是 “文件的集合”,而不是指特定的数据结构。 (在某些情况下索引看起来并不像一棵树,不过我们现在的目的是用简单的方式思考它。) 树 描述 HEAD 上一次提交的快照,下一次提交的父结点 Index 预期的下一次提交的快照 Working Directory 沙盒 HEADHEAD 是当前分支引用的指针,它总是指向该分支上的最后一次提交。 这表示 HEAD 将是下一次提交的父结点。 通常,理解 HEAD 的最简方式,就是将它看做 你的上一次提交 的快照。 其实,查看快照的样子很容易。 下例就显示了 HEAD 快照实际的目录列表,以及其中每个文件的 SHA-1 校验和: $ git cat-file -p HEAD tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf author Scott Chacon 1301511835 -0700 committer Scott Chacon 1301511835 -0700 initial commit $ git ls-tree -r HEAD 100644 blob a906cb2a4a904a152... README 100644 blob 8f94139338f9404f2... Rakefile 040000 tree 99f1a6d12cb4b6f19... lib cat-file 与 ls-tree 是底层命令,它们一般用于底层工作,在日常工作中并不使用。不过它们能帮助我们了解到底发生了什么。 索引(Index)索引是你的 预期的下一次提交。 我们也会将这个概念引用为 Git 的 “暂存区域”,这就是当你运行 git commit 时 Git 看起来的样子。 Git 将上一次检出到工作目录中的所有文件填充到索引区,它们看起来就像最初被检出时的样子。 之后你会将其中一些文件替换为新版本,接着通过 git commit 将它们转换为树来用作新的提交。 $ git ls-files -s 100644 a906cb2a4a904a152e80877d4088654daad0c859 0 README 100644 8f94139338f9404f26296befa88755fc2598c289 0 Rakefile 100644 47c6340d6459e05787f644c2447d2595f5d3a54b 0 lib/simplegit.rb 再说一次,我们在这里又用到了 ls-files 这个幕后的命令,它会显示出索引当前的样子。 确切来说,索引并非技术上的树结构,它其实是以扁平的清单实现的。不过对我们而言,把它当做树就够了。 工作目录(Working Directory)最后,你就有了自己的工作目录。 另外两棵树以一种高效但并不直观的方式,将它们的内容存储在 .git 文件夹中。 工作目录会将它们解包为实际的文件以便编辑。 你可以把工作目录当做 沙盒。在你将修改提交到暂存区并记录到历史之前,可以随意更改。 $ tree . ├── README ├── Rakefile └── lib └── simplegit.rb 1 directory, 3 files 工作流程Git 主要的目的是通过操纵这三棵树来以更加连续的状态记录项目的快照。让我们来可视化这个过程:假设我们进入到一个新目录,其中有一个文件。 我们称其为该文件的 v1 版本,将它标记为蓝色。 现在运行 git init,这会创建一个 Git 仓库,其中的 HEAD 引用指向未创建的分支(master 还不存在)。此时,只有工作目录有内容。 现在我们想要提交这个文件,所以用 git add 来获取工作目录中的内容,并将其复制到索引中。接着运行 git commit,它首先会移除索引中的内容并将它保存为一个永久的快照,然后创建一个指向该快照的提交对象,最后更新 master 来指向本次提交。此时如果我们运行 git status,会发现没有任何改动,因为现在三棵树完全相同。 现在我们想要对文件进行修改然后提交它。 我们将会经历同样的过程;首先在工作目录中修改文件。 我们称其为该文件的 v2 版本,并将它标记为红色。如果现在运行 git status,我们会看到文件显示在 “Changes not staged for commit,” 下面并被标记为红色,因为该条目在索引与工作目录之间存在不同。 接着我们运行 git add 来将它暂存到索引中。此时,由于索引和 HEAD 不同,若运行 git status 的话就会看到 “Changes to be committed” 下的该文件变为绿色 ——也就是说,现在预期的下一次提交与上一次提交不同。 最后,我们运行 git commit 来完成提交。现在运行 git status 会没有输出,因为三棵树又变得相同了。 切换分支或克隆的过程也类似。 当检出一个分支时,它会修改 HEAD 指向新的分支引用,将 索引 填充为该次提交的快照,然后将 索引 的内容复制到 工作目录 中。 重置的作用在以下情景中观察 reset 命令会更有意义。 为了演示这些例子,假设我们再次修改了 file.txt 文件并第三次提交它。 现在的历史看起来是这样的:让我们跟着 reset 看看它都做了什么。 它以一种简单可预见的方式直接操纵这三棵树。 它做了三个基本操作。 1.移动 HEADreset 做的第一件事是移动 HEAD 的指向。 这与改变 HEAD 自身不同(checkout 所做的);reset 移动 HEAD 指向的分支。 这意味着如果 HEAD 设置为 master 分支(例如,你正在 master 分支上),运行 git reset 9e5e64a 将会使 master 指向 9e5e64a。无论你调用了何种形式的带有一个提交的 reset,它首先都会尝试这样做。 使用 reset --soft,它将仅仅停在那儿。 现在看一眼上图,理解一下发生的事情:它本质上是撤销了上一次 git commit 命令。 当你在运行 git commit 时,Git 会创建一个新的提交,并移动 HEAD 所指向的分支来使其指向该提交。 当你将它 reset 回 HEAD~(HEAD 的父结点)时,其实就是把该分支移动回原来的位置,而不会改变索引和工作目录。 现在你可以更新索引并再次运行 git commit 来完成 git commit --amend 所要做的事情了。 2.更新索引(–mixed)注意,如果你现在运行 git status 的话,就会看到新的 HEAD 和以绿色标出的它和索引之间的区别。 接下来,reset 会用 HEAD 指向的当前快照的内容来更新索引。如果指定 --mixed 选项,reset 将会在这时停止。 这也是默认行为,所以如果没有指定任何选项(在本例中只是 git reset HEAD~),这就是命令将会停止的地方。 现在再看一眼上图,理解一下发生的事情:它依然会撤销一上次 提交,但还会 取消暂存 所有的东西。 于是,我们回滚到了所有 git add 和 git commit 的命令执行之前。 3.更新工作目录reset 要做的的第三件事情就是让工作目录看起来像索引。 如果使用 –hard 选项,它将会继续这一步。现在让我们回想一下刚才发生的事情。 你撤销了最后的提交、git add 和 git commit 命令以及工作目录中的所有工作。 必须注意,--hard 标记是 reset 命令唯一的危险用法,它也是 Git 会真正地销毁数据的仅有的几个操作之一。 其他任何形式的 reset 调用都可以轻松撤消,但是 --hard 选项不能,因为它强制覆盖了工作目录中的文件。 在这种特殊情况下,我们的 Git 数据库中的一个提交内还留有该文件的 v3 版本,我们可以通过 reflog 来找回它。但是若该文件还未提交,Git 仍会覆盖它从而导致无法恢复。 回顾reset 命令会以特定的顺序重写这三棵树,在你指定以下选项时停止: 移动 HEAD 分支的指向 (若指定了 --soft,则到此停止) 使索引看起来像 HEAD (不带参数或 --mixed,则到此停止) 使工作目录看起来像索引 (指定了 --hard) 通过路径来重置前面讲述了 reset 基本形式的行为,不过你还可以给它提供一个作用路径。 若指定了一个路径,reset 将会跳过第 1 步,并且将它的作用范围限定为指定的文件或文件集合。 这样做自然有它的道理,因为 HEAD 只是一个指针,你无法让它同时指向两个提交中各自的一部分。 不过索引和工作目录 可以部分更新,所以重置会继续进行第 2、3 步。 现在,假如我们运行 git reset file.txt (这其实是 git reset --mixed HEAD file.txt 的简写形式,因为你既没有指定一个提交的 SHA-1 或分支,也没有指定 --soft 或 --hard),它会: 移动 HEAD 分支的指向 (已跳过) 让索引看起来像 HEAD (到此处停止) 所以它本质上只是将 file.txt 从 HEAD 复制到索引中。它还有 取消暂存文件 的实际效果。 如果我们查看该命令的示意图,然后再想想 git add 所做的事,就会发现它们正好相反。 这就是为什么 git status 命令的输出会建议运行此命令来取消暂存一个文件。 (查看 取消暂存的文件 来了解更多。) 我们可以不让 Git 从 HEAD 拉取数据,而是通过具体指定一个提交来拉取该文件的对应版本。 我们只需运行类似于 git reset eb43bf file.txt 的命令即可。它其实做了同样的事情,也就是把工作目录中的文件恢复到 v1 版本,运行 git add 添加它,然后再将它恢复到 v3 版本(只是不用真的过一遍这些步骤)。 如果我们现在运行 git commit,它就会记录一条“将该文件恢复到 v1 版本”的更改,尽管我们并未在工作目录中真正地再次拥有它。 还有一点同 git add 一样,就是 reset 命令也可以接受一个 --patch 选项来一块一块地取消暂存的内容。 这样你就可以根据选择来取消暂存或恢复内容了。 压缩我们来看看如何利用这种新的功能来做一些有趣的事情 - 压缩提交。 假设你的一系列提交信息中有 “oops.”、“WIP” 和 “forgot this file”, 聪明的你就能使用 reset 来轻松快速地将它们压缩成单个提交,也显出你的聪明。 (压缩提交 展示了另一种方式,不过在本例中用 reset 更简单。) 假设你有一个项目,第一次提交中有一个文件,第二次提交增加了一个新的文件并修改了第一个文件,第三次提交再次修改了第一个文件。 由于第二次提交是一个未完成的工作,因此你想要压缩它。那么可以运行 git reset --soft HEAD~2 来将 HEAD 分支移动到一个旧一点的提交上(即你想要保留的第一个提交):然后只需再次运行 git commit:现在你可以查看可到达的历史,即将会推送的历史,现在看起来有个 v1 版 file-a.txt 的提交,接着第二个提交将 file-a.txt 修改成了 v3 版并增加了 file-b.txt。 包含 v2 版本的文件已经不在历史中了。 checkout最后,你大概还想知道 checkout 和 reset 之间的区别。 和 reset 一样,checkout 也操纵三棵树,不过它有一点不同,这取决于你是否传给该命令一个文件路径。 不带路径运行 git checkout [branch] 与运行 git reset --hard [branch] 非常相似,它会更新所有三棵树使其看起来像 [branch],不过有两点重要的区别。 首先不同于 reset --hard,checkout 对工作目录是安全的,它会通过检查来确保不会将已更改的文件吹走。 其实它还更聪明一些。它会在工作目录中先试着简单合并一下,这样所有_还未修改过的_文件都会被更新。 而 reset --hard 则会不做检查就全面地替换所有东西。 第二个重要的区别是如何更新 HEAD。 reset 会移动 HEAD 分支的指向,而 checkout 只会移动 HEAD 自身来指向另一个分支。 例如,假设我们有 master 和 develop 分支,它们分别指向不同的提交;我们现在在 develop 上(所以 HEAD 指向它)。 如果我们运行 git reset master,那么 develop 自身现在会和 master 指向同一个提交。 而如果我们运行 git checkout master 的话,develop 不会移动,HEAD 自身会移动。 现在 HEAD 将会指向 master。 所以,虽然在这两种情况下我们都移动 HEAD 使其指向了提交 A,但_做法_是非常不同的。 reset 会移动 HEAD 分支的指向,而 checkout 则移动 HEAD 自身。 带路径运行 checkout 的另一种方式就是指定一个文件路径,这会像 reset 一样不会移动 HEAD。 它就像 git reset [branch] file 那样用该次提交中的那个文件来更新索引,但是它也会覆盖工作目录中对应的文件。 它就像是 git reset --hard [branch] file(如果 reset 允许你这样运行的话)- 这样对工作目录并不安全,它也不会移动 HEAD。 此外,同 git reset 和 git add 一样,checkout 也接受一个 –patch 选项,允许你根据选择一块一块地恢复文件内容。 总结希望你现在熟悉并理解了 reset 命令,不过关于它和 checkout 之间的区别,你可能还是会有点困惑,毕竟不太可能记住不同调用的所有规则。 下面的速查表列出了命令对树的影响。 “HEAD” 一列中的 “REF” 表示该命令移动了 HEAD 指向的分支引用,而‘HEAD’ 则表示只移动了 HEAD 自身。 特别注意 WD Safe? 一列 - 如果它标记为 NO,那么运行该命令之前请考虑一下。 **head****index****workdir****wd safe****commit level**`reset --soft [commit]`refnonoyes`reset [commit]`refyesnoyes`reset --hard [commit]`refyesyesno`checkout [commit]`headyesyesyes**file level**`reset (commit) [file]`noyesnoyes`checkout (commit) [file]`noyesyesno","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"}]},{"title":"每天一个linux命令(24): tar","slug":"linux-command-24-tar","date":"2016-12-24T02:24:17.000Z","updated":"2024-07-05T11:10:22.846Z","comments":true,"path":"2016/12/24/linux-command-24-tar/","permalink":"http://yelog.org/2016/12/24/linux-command-24-tar/","excerpt":"通过SSH访问服务器,难免会要用到压缩,解压缩,打包,解包等,这时候tar命令就是是必不可少的一个功能强大的工具。linux中最流行的tar是麻雀虽小,五脏俱全,功能强大。","text":"通过SSH访问服务器,难免会要用到压缩,解压缩,打包,解包等,这时候tar命令就是是必不可少的一个功能强大的工具。linux中最流行的tar是麻雀虽小,五脏俱全,功能强大。 tar命令可以为linux的文件和目录创建档案。利用tar,可以为某一特定文件创建档案(备份文件),也可以在档案中改变文件,或者向档案中加入新的文件。tar最初被用来在磁带上创建档案,现在,用户可以在任何设备上创建档案。利用tar命令,可以把一大堆的文件和目录全部打包成一个文件,这对于备份文件或将几个文件组合成为一个文件以便于网络传输是非常有用的。 首先要弄清两个概念:打包和压缩。打包是指将一大堆文件或目录变成一个总的文件;压缩则是将一个大的文件通过一些压缩算法变成一个小文件。 为什么要区分这两个概念呢?这源于Linux中很多压缩程序只能针对一个文件进行压缩,这样当你想要压缩一大堆文件时,你得先将这一大堆文件先打成一个包(tar命令),然后再用压缩程序进行压缩(gzip bzip2命令)。 linux下最常用的打包程序就是tar了,使用tar程序打出来的包我们常称为tar包,tar包文件的命令通常都是以.tar结尾的。生成tar包后,就可以用其它的程序来进行压缩。 命令格式$ tar [必要参数] [选择参数] [文件] 命令功能 用来压缩和解压文件。tar本身不具有压缩功能。他是调用压缩功能实现的 命令参数必要参数 参数 描述 -A 新增压缩文件到已存在的压缩 -B 设置区块大小 -c 建立新的压缩文件 -d 记录文件的差别 -r 添加文件到已经压缩的文件 -u 添加改变了和现有的文件到已经存在的压缩文件 -x 从压缩的文件中提取文件 -t 显示压缩文件的内容 -z 支持gzip解压文件 -j 支持bzip2解压文件 -Z 支持compress解压文件 -v 显示操作过程 -l 文件系统边界设置 -k 保留原有文件不覆盖 -m 保留文件不被覆盖 -W 确认压缩文件的正确性 可选参数 参数 描述 -b 设置区块数目 -C 切换到指定目录 -f 指定压缩文件 –help 显示帮助信息 –version 显示版本信息 使常见解压/压缩命令例一:.tar文件 $ tar xvf FileName.tar # 解包 $ tar cvf FileName.tar DirName # 打包 # 注:tar是打包,不是压缩! 例二:.gz文件 # 解压 $ gunzip FileName.gz $ gzip -d FileName.gz # 压缩 gzip FileName 例三:.tar.gz和.tgz文件 $ tar xvf FileName.tar.gz # 解包 $ tar cvf FileName.tar.gz DirName # 打包 # 注:tar是打包,不是压缩! 例四:.bz2文件 # 解压 $ bzip2 -d FileName.bz2 $ bunzip2 FileName.bz2 # 压缩 $ bzip2 -z FileName 例五:.tar.bz2文件 $ tar jxvf FileName.tar.bz2 # 解压 $ tar jcvf FileName.tar.bz2 DirName # 压缩 例六:.bz文件 # 解压 $ bzip2 -d FileName.bz $ bunzip2 FileName.bz 例七:.tar.bz文件 $ tar jxvf FileName.tar.bz # 解压 例八:.Z文件 $ uncompress FileName.Z # 解压 $ compress FileName # 压缩 例九:.tar.Z文件 $ tar Zxvf FileName.tar.Z # 解压 $ tar Zcvf FileName.tar.Z DirName # 压缩 例十:.zip文件 $ unzip FileName.zip # 解压 $ zip FileName.zip DirName # 压缩 例十一:.rar文件 $ rar x FileName.rar # 解压 $ rar a FileName.rar DirName # 压缩 使用实例例一:将文件全部打包成tar包 $ tar -cvf log.tar log2012.log # 仅打包,不压缩! $ tar -zcvf log.tar.gz log2012.log # 打包后,以 gzip 压缩 $ tar -jcvf log.tar.bz2 log2012.log # 打包后,以 bzip2 压缩 在参数 f 之后的文件档名是自己取的,我们习惯上都用 .tar 来作为辨识。 如果加 z 参数,则以 .tar.gz 或 .tgz 来代表 gzip 压缩过的 tar包; 如果加 j 参数,则以 .tar.bz2 来作为tar包名。 例二:查阅上述 tar包内有哪些文件 $ tar -ztvf log.tar.gz # 由于我们使用 gzip 压缩的log.tar.gz,所以要查阅log.tar.gz包内的文件时,就得要加上 z 这个参数了 例三:将tar 包解压缩 $ tar -zxvf /opt/soft/test/log.tar.gz # 在预设的情况下,我们可以将压缩档在任何地方解开的 例四:只将 /tar 内的 部分文件解压出来 $ tar -zxvf /opt/soft/test/log30.tar.gz log2013.log # 我可以透过 tar -ztvf 来查阅 tar 包内的文件名称,如果单只要一个文件,就可以透过这个方式来解压部分文件! 例五:文件备份下来,并且保存其权限 $ tar -zcvpf log31.tar.gz log2014.log log2015.log log2016.log # 这个 -p 的属性是很重要的,尤其是当您要保留原本文件的属性时 例六:在 文件夹当中,比某个日期新的文件才备份 $ tar -N "2012/11/13" -zcvf log17.tar.gz test 例七:备份文件夹内容是排除部分文件 $ tar --exclude scf/service -zcvf scf.tar.gz scf/*","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"Git操作之高手过招","slug":"git-master","date":"2016-12-23T02:59:01.000Z","updated":"2024-07-05T11:10:22.602Z","comments":true,"path":"2016/12/23/git-master/","permalink":"http://yelog.org/2016/12/23/git-master/","excerpt":"在使用git的过程中,总有一天你会遇到下面的问题:)这些也是在开发过程中很常见的问题,以下也是作者的经验之谈,有不对的地方还请指出。","text":"在使用git的过程中,总有一天你会遇到下面的问题:)这些也是在开发过程中很常见的问题,以下也是作者的经验之谈,有不对的地方还请指出。 最后一次commit信息写错了如果只是提交信息写错了信息,可以通过以下命令单独修改提交信息 $ git commit --amend 注意: 通过这样的过程修改提交信息后,相当于删除原来的提交,重新提交了一次。所有如果你在修改前已经将错误的那次提交push到服务端,那在修改后就需要通过 git pull 来合并代码(类似于两个分支了)。通过 git log --graph --oneline 查看就会发现两个分支合并的痕迹 最后一次commit少添加一个文件$ git add file1 $ git commit --amend 最后一次commit多添加一个文件$ git rm --cached file1 $ git commit --amend 移除add过的文件#方法一 $ git rm --cache [文件名] #方法二 $ git reset head [文件/文件夹] 回退本地commit(还未push)这种情况发生在你的本地仓库,可能你add,commit以后发现代码有点问题,打算取消提交,用到下面命令 #只会保留源码(工作区),回退commit(本地仓库)与index(暂存区)到某个版本 $ git reset <commit_id> #默认为 --mixed模式 $ git reset --mixed <commit_id> #保留源码(工作区)和index(暂存区),只回退commit(本地仓库)到某个版本 $ git reset --soft <commit_id> #源码(工作区)、commit(本地仓库)与index(暂存区)都回退到某个版本 $ git reset --hard <commit_id> 当然有人在push代码以后,也是用reset –hard回退代码到某个版本之前,但是这样会有一个问题,你线上的代码没有变化。 !!!可以通过 git push –force 将本地的回退推送到服务端,但是除非你很清楚在这么做, 不推荐. 所以,这种情况你要使用下面的方式了。 回退本地commit(已经push)对于已经把代码push到线上仓库,你回退本地代码其实也想同时回退线上代码,回滚到某个指定的版本,线上,线下代码保持一致.你要用到下面的命令 $ git revert <commit_id> 注意: git revert 用于反转提交,执行命令时要求工作树必须是干净的。 git revert 用一个新的提交来消除一个历时提交所做出的修改 回退单个文件的历史版本#查看历史版本 git log 1.txt #回退该文件到指定版本 git reset [commit_id] 1.txt git checkout 1.txt #提交 git commit -m "回退1.txt的历史版本" 修改提交历史中的author和email旧的:author:Old-Author email:old@mail.com新的:author:New-Author email:new@mail.com1.在git仓库内创建下面的脚本,如change.sh # !/bin/sh git filter-branch --env-filter ' an="$GIT_AUTHOR_NAME" am="$GIT_AUTHOR_EMAIL" cn="$GIT_COMMITTER_NAME" cm="$GIT_COMMITTER_EMAIL" if [ "$GIT_COMMITTER_EMAIL" = "old@mail.com" ] then cn="New-Author" cm="new@mail.com" fi if [ "$GIT_AUTHOR_EMAIL" = "old@mail.com" ] then an="New-Author" am="new@mail.com" fi export GIT_AUTHOR_NAME="$an" export GIT_AUTHOR_EMAIL="$am" export GIT_COMMITTER_NAME="$cn" export GIT_COMMITTER_EMAIL="$cm" ' 2.运行脚本 $ sh change.sh 忽略已提交的文件(.iml) 删除已提交的文件 # 删除项目中所有的.iml后缀的文件 $ find . -name "*.iml" | xargs rm -f 添加.gitignore文件 *.iml /**/*.iml 持续更新中~~~","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"}]},{"title":"每天一个linux命令(23): 用SecureCRT来上传和下载文件","slug":"linux-command-23-secureCRT","date":"2016-12-23T01:48:15.000Z","updated":"2024-07-05T11:10:22.974Z","comments":true,"path":"2016/12/23/linux-command-23-用SecureCRT来上传和下载文件/","permalink":"http://yelog.org/2016/12/23/linux-command-23-%E7%94%A8SecureCRT%E6%9D%A5%E4%B8%8A%E4%BC%A0%E5%92%8C%E4%B8%8B%E8%BD%BD%E6%96%87%E4%BB%B6/","excerpt":"用SSH管理linux服务器时经常需要远程与本地之间交互文件.而直接用SecureCRT自带的上传下载功能无疑是最方便的,SecureCRT下的文件传输协议有ASCII、Xmodem、Zmodem。","text":"用SSH管理linux服务器时经常需要远程与本地之间交互文件.而直接用SecureCRT自带的上传下载功能无疑是最方便的,SecureCRT下的文件传输协议有ASCII、Xmodem、Zmodem。 文件传输协议 文件传输是数据交换的主要形式。在进行文件传输时,为使文件能被正确识别和传送,我们需要在两台计算机之间建立统一的传输协议。这个协议包括了文件的识别、传送的起止时间、错误的判断与纠正等内容。常见的传输协议有以下几种: ASCII:这是最快的传输协议,单只能传输文本文件。 Xmodem:这种古老的传输协议速度较慢,但由于使用了CRC错误侦测方法,传输的准确率可高达99.6%。 Ymodem:这是Xmodem的改良版,使用了1024位区段传送,速度比Xmodem要快 Zmodem:Zmodem采用了串流式(streaming)传输方式,传输速度较快,而且还具有自动改变区段大小和断点续传、快速错误侦测等功能。这是目前最流行的文件传输协议。 除以上几种外,还有Imodem、Jmodem、Bimodem、Kermit、Lynx等协议,由于没有多数厂商支持,这里就略去不讲。 SecureCRT可以使用linux下的zmodem协议来快速的传送文件,使用非常方便.具体步骤: 在使用SecureCRT上传下载之前需要给服务器安装lrzsz 从下面的地址下载 lrzsz-0.12.20.tar.gz 我是下载地址 查看里面的INSTALL文档了解安装参数说明和细节 解压文件 $ tar zxvf lrzsz-0.12.20.tar.gz 进入目录,配置编译 $ cd lrzsz-0.12.20 $ ./configure --prefix=/usr/local/lrzsz $ make $ make install 建立软链接 $ cd /usr/bin $ ln -s /usr/local/lrzsz/bin/lrz rz $ ln -s /usr/local/lrzsz/bin/lsz sz 测试 运行 rz 弹出 SecureCrt上传窗口,用SecureCRT来上传和下载文件。 设置SecureCRT上传和下载的默认目录 options->session options ->Terminal->Xmodem/Zmodem 右栏directory设置上传和下载的目录 使用Zmodem从客户端上传文件到linux服务器 用SecureCRT登陆linux终端 选中你要放置上传文件的路径,在目录下然后输入rz命令,SecureCRT会弹出文件选择对话框,在查找范围中找到你要上传的文件,按Add按钮。然后OK就可以把文件上传到linux上了。 或者在Transfer->Zmodem Upoad list弹出文件选择对话框,选好文件后按Add按钮。然后OK窗口自动关闭。然后在linux下选中存放文件的目录,输入rz命令。liunx就把那个文件上传到这个目录下了。 使用Zmodem下载文件到客户端$ sz filename zmodem 接收可以自行启动.下载的文件存放在你设定的默认下载目录下 rz,sz 是 Linux/Unix 同 Windows 进行 ZModem 文件传输的命令行工具 , windows 端需要支持ZModem的telnet/ssh客户端,SecureCRT 就可以用 SecureCRT 登陆到 Unix/Linux 主机(telnet或ssh均可)O 运行命令rz,即是接收文件,SecureCRT就会弹出文件选择对话框,选好文件之后关闭对话框,文件就会上传到当前目录 O 运行命令sz file1 file2就是发文件到windows上(保存的目录是可以配置) 比ftp命令方便多了,而且服务器不用再开FTP服务了","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(22): find命令的参数详解","slug":"linux-command-22-find-args","date":"2016-12-22T03:44:14.000Z","updated":"2024-07-05T11:10:22.910Z","comments":true,"path":"2016/12/22/linux-command-22-find命令的参数详解/","permalink":"http://yelog.org/2016/12/22/linux-command-22-find%E5%91%BD%E4%BB%A4%E7%9A%84%E5%8F%82%E6%95%B0%E8%AF%A6%E8%A7%A3/","excerpt":"find一些常用参数的一些常用实例和一些具体用法和注意事项。","text":"find一些常用参数的一些常用实例和一些具体用法和注意事项。 使用name选项 文件名选项是find命令最常用的选项,要么单独使用该选项,要么和其他选项一起使用。可以使用某种文件名模式来匹配文件,记住要用引号将文件名模式引起来。 # 在自己的根目录$HOME中查找文件名符合*.log的文件,使用~作为 'pathname'参数,波浪号~代表了你的$HOME目录。 $ find ~ -name "*.log" -print # 在当前目录及子目录中查找所有的‘ *.log‘文件 $ find . -name "*.log" -print # 当前目录及子目录中查找文件名以一个大写字母开头的文件 $ find . -name "[A-Z]*" -print # 在/etc目录中查找文件名以host开头的文件 $ find /etc -name "host*" -print # 想要查找$HOME目录中的文件 $ find ~ -name "*" -print $ find . -print # 让系统高负荷运行,就从根目录开始查找所有的文件 $ find / -name "*" -print # 在当前目录查找文件名以一个个小写字母开头,最后是4到9加上.log结束的文件 $ find . -name "[a-z]*[4-9].log" -print 用perm选项 按照文件权限模式用-perm选项,按文件权限模式来查找文件的话。最好使用八进制的权限表示法 # 在当前目录下查找文件权限位为755的文件 $ find . -perm 755 -print 还有一种表达方法:在八进制数字前面要加一个横杠-,表示都匹配,如-007就相当于777,-005相当于555, $ find . -perm -005 忽略某个目录 如果在查找文件时希望忽略某个目录,因为你知道那个目录中没有你所要查找的文件,那么可以使用-prune选项来指出需要忽略的目录。在使用-prune选项时要当心,因为如果你同时使用了-depth选项,那么-prune选项就会被find命令忽略。如果希望在test目录下查找文件,但不希望在test/test3目录下查找,可以用: $ find test -path "test/test3" -prune -o -print 使用find查找文件的时候怎么避开某个文件目录例一:在test 目录下查找不在test4子目录之内的所有文件 $ find test -path "test/test4" -prune -o -print 说明:find [-path ..] [expression]在路径列表的后面的是表达式-path “test” -prune -o -print 是 -path “test” -a -prune -o -print 的简写表达式按顺序求值, -a 和 -o 都是短路求值,与 shell 的 && 和 || 类似如果-path “test” 为真,则求值 -prune , -prune 返回真,与逻辑表达式为真;否则不求值 -prune,与逻辑表达式为假。如果 -path “test” -a -prune 为假,则求值 -print ,-print返回真,或逻辑表达式为真;否则不求值 -print,或逻辑表达式为真。这个表达式组合特例可以用伪码写为:if -path “test” then-pruneelse-print 例二:避开多个文件夹 # 圆括号表示表达式的结合。\\ 表示引用,即指示 shell 不对后面的字符作特殊解释,而留给 find 命令去解释其意义 $ find test \\( -path test/test4 -o -path test/test3 \\) -prune -o -print 例三:查找某一确定文件,-name等选项加在-o 之后 $ find test \\(-path test/test4 -o -path test/test3 \\) -prune -o -name "*.log" -print 使用user和nouser选项# 在$HOME目录中查找文件属主为peida的文件 $ find ~ -user peida -print # 在/etc目录下查找文件属主为peida的文件 $ find /etc -user peida -print # 为了查找属主帐户已经被删除的文件,可以使用-nouser选项。在/home目录下查找所有的这类文件 $ find /home -nouser -print 使用group和nogroup选项# 在/apps目录下查找属于gem用户组的文件 $ find /apps -group gem -print # 查找没有有效所属用户组的所有文件 $ find / -nogroup-print 按照更改时间或访问时间等查找文件 如果希望按照更改时间来查找文件,可以使用mtime,atime或ctime选项。如果系统突然没有可用空间了,很有可能某一个文件的长度在此期间增长迅速,这时就可以用mtime选项来查找这样的文件。用减号-来限定更改时间在距今n日以内的文件,而用加号+来限定更改时间在距今n日以前的文件 # 在系统根目录下查找更改时间在5日以内的文件 $ find / -mtime -5 -print # 在/var/adm目录下查找更改时间在3日以前的文件 $ find /var/adm -mtime +3 -print 查找比某个文件新或旧的文件 如果希望查找更改时间比某个文件新但比另一个文件旧的所有文件,可以使用-newer选项。 # 查找更改时间比文件log2012.log新但比文件log2017.log旧的文件 $ find -newer log2012.log ! -newer log2017.log # 查找更改时间在比log2012.log文件新的文件 $ find . -newer log2012.log -print 使用type选项# 在/etc目录下查找所有的目录 $ find /etc -type d -print # 在当前目录下查找除目录以外的所有类型的文件 $ find . ! -type d -print # 在/etc目录下查找所有的符号链接文件 $ find /etc -type l -print 使用size选项 可以按照文件长度来查找文件,这里所指的文件长度既可以用块(block)来计量,也可以用字节来计量。以字节计量文件长度的表达形式为N c;以块计量文件长度只用数字表示即可。在按照文件长度查找文件时,一般使用这种以字节表示的文件长度,在查看文件系统的大小,因为这时使用块来计量更容易转换。 # 在当前目录下查找文件长度大于1 M字节的文件 $ find . -size +1000000c -print # 在/home/apache目录下查找文件长度恰好为100字节的文件 $ find /home/apache -size 100c -print # 在当前目录下查找长度超过10块的文件(一块等于512字节) $ find . -size +10 -print 使用depth选项 在使用find命令时,可能希望先匹配所有的文件,再在子目录中查找。使用depth选项就可以使find命令这样做。这样做的一个原因就是,当在使用find命令向磁带上备份文件系统时,希望首先备份所有的文件,其次再备份子目录中的文件。 # find命令从文件系统的根目录开始,查找一个名为CON.FILE的文件 # 它将首先匹配所有的文件然后再进入子目录中查找 $ find / -name "CON.FILE" -depth -print 使用mount选项 在当前的文件系统中查找文件(不进入其他文件系统),可以使用find命令的mount选项 # 从当前目录开始查找位于本文件系统中文件名以XC结尾的文件 $ find . -name "*.XC" -mount -print","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"Git之SSH与HTTPS免密码配置","slug":"git-ssh-https-verify-configuration","date":"2016-12-21T07:31:55.000Z","updated":"2024-07-05T11:10:22.617Z","comments":true,"path":"2016/12/21/git-ssh-https-verify-configuration/","permalink":"http://yelog.org/2016/12/21/git-ssh-https-verify-configuration/","excerpt":"Git作为当前最受欢迎的版本控制软件,使用是很频繁的。但每次使用git push等操作时都要输入密码,实在是挺麻烦的。本文对使用ssh与https两种通讯协议讨论一下免密码配置。注:这个过程在所有操作系统上都是相似的:)","text":"Git作为当前最受欢迎的版本控制软件,使用是很频繁的。但每次使用git push等操作时都要输入密码,实在是挺麻烦的。本文对使用ssh与https两种通讯协议讨论一下免密码配置。注:这个过程在所有操作系统上都是相似的:) SSH通信协议GitHub版许多Git服务器都使用SSH公钥进行认证,当然也包括github。首先你需要确认一下自己是否已经拥有密钥了,默认情况下,用户的 SSH 密钥存储在其 ~/.ssh 目录下。进入该目录并列出其中内容,你变可以下快速确认自己是否已经拥有密钥: $ cd ~/.ssh $ ls authorized_keys2 id_rsa known_hosts config id_rsa.pub 我们需要寻找一对 id_rsa 或 id_dsa 命名的文件,其中一个带 .pub 扩展名。 ‘.pub’文件是你的公钥,另一个则是私钥。如果没有找不到这样的文件(或者根本就没有.ssh目录),我们可以通过 ssh-keygen 程序来创建它们。 #邮箱可以随便填 $ ssh-keygen -t rsa -C "xx@xx.com" 首先 ssh-keygen 会确认密钥的存储位置和文件名(默认是 .ssh/id_rsa),然后他会要求你输入两次密钥口令,留空即可。所以一般选用默认,全部回车即可。 接下来我们登陆到GitHub上,右上角小头像->Setting->SSH and GPG keys中,点击new SSH key。Title:可以随便填写,但最好起的名字能让自己知道这个公钥是哪个设备的。Key:将上面生成的.pub文件中的所有内容复制到这里。点击下面的Add SSH key即可。然后你就会发现可以免密码访问了 Git服务器如果服务端是自己搭建的git服务器,生成密钥公钥对的步骤是一样的。然后将生成的 .pub 文件内容,导入到git服务器 /home/git/.ssh/authorized_keys 文件内,一行一个。然后你就会发现git push 不再需要密码了搭建git服务器和相关免登陆的详细步骤可参考我的另一篇 搭建Git服务器 HTTPS通信协议上面讲了SSH方式的免密码,接下来讲一下越来越常用的HTTPS方式的免密码新建文件并保存密码 $ touch ~/.git-credentials $ vim ~/.git-credentials 添加内容 https://{username}:{passwd}@github.com 添加git配置 $ git config --global credential.helper store 查看~/.gitconfig文件变化 [credential] helper = store 然后再尝试一下git push不再在需要密码了","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"}]},{"title":"每天一个linux命令(21): find命令之xargs","slug":"linux-command-21-find-xargs","date":"2016-12-21T03:08:01.000Z","updated":"2024-07-05T11:10:22.832Z","comments":true,"path":"2016/12/21/linux-command-21-find命令之xargs/","permalink":"http://yelog.org/2016/12/21/linux-command-21-find%E5%91%BD%E4%BB%A4%E4%B9%8Bxargs/","excerpt":"在使用 find命令的-exec选项处理匹配到的文件时, find命令将所有匹配到的文件一起传递给exec执行。但有些系统对能够传递给exec的命令长度有限制,这样在find命令运行几分钟之后,就会出现溢出错误。错误信息通常是“参数列太长”或“参数列溢出”。这就是xargs命令的用处所在,特别是与find命令一起使用。","text":"在使用 find命令的-exec选项处理匹配到的文件时, find命令将所有匹配到的文件一起传递给exec执行。但有些系统对能够传递给exec的命令长度有限制,这样在find命令运行几分钟之后,就会出现溢出错误。错误信息通常是“参数列太长”或“参数列溢出”。这就是xargs命令的用处所在,特别是与find命令一起使用。 find命令把匹配到的文件传递给xargs命令,而xargs命令每次只获取一部分文件而不是全部,不像-exec选项那样。这样它可以先处理最先获取的一部分文件,然后是下一批,并如此继续下去。 在有些系统中,使用-exec选项会为处理每一个匹配到的文件而发起一个相应的进程,并非将匹配到的文件全部作为参数一次执行;这样在有些情况下就会出现进程过多,系统性能下降的问题,因而效率不高; 而使用xargs命令则只有一个进程。另外,在使用xargs命令时,究竟是一次获取所有的参数,还是分批取得参数,以及每一次获取参数的数目都会根据该命令的选项及系统内核中相应的可调参数来确定。 使用实例例一:查找系统中的每一个普通文件,然后使用xargs命令来测试它们分别属于哪类文件 $ find . -type f -print | xargs file 例二:在整个系统中查找内存信息转储文件(core dump) ,然后把结果保存到/tmp/core.log 文件中 $ find / -name "core" -print | xargs echo "" >/tmp/core.log 例三:在当前目录下查找所有用户具有读、写和执行权限的文件,并收回相应的写权限 $ find . -perm -7 -print | xargs chmod o-w 例四:用grep命令在所有的普通文件中搜索hostname这个词 $ find . -type f -print | xargs grep "hostname" 例五:用grep命令在当前目录下的所有普通文件中搜索hostnames这个词 # \\用来取消find命令中的*在shell中的特殊含义 $ find . -name \\* -type f -print | xargs grep "hostnames" 例六:使用xargs执行mv $ find . -name "*.log" | xargs -i mv {} test4 例七:find后执行xargs提示xargs: argument line too long解决方法 # -l1是一次处理一个;-t是处理之前打印出命令 $ find . -type f -atime +0 -print0 | xargs -0 -l1 -t rm -f 例八:使用-i参数默认的前面输出用{}代替,-I参数可以指定其他代替字符,如例子中的[] $ find . -name "file" | xargs -I [] cp [] .. 例九:xargs的-p参数的使用 # -p参数会提示让你确认是否执行后面的命令,y执行,n不执行 $ find . -name "*.log" | xargs -p -i mv {} ..","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(20): find命令之exec","slug":"linux-command-20-find-exec","date":"2016-12-20T02:47:32.000Z","updated":"2024-07-05T11:10:22.887Z","comments":true,"path":"2016/12/20/linux-command-20-find命令之exec/","permalink":"http://yelog.org/2016/12/20/linux-command-20-find%E5%91%BD%E4%BB%A4%E4%B9%8Bexec/","excerpt":"find是我们很常用的一个Linux命令,但是我们一般查找出来的并不仅仅是看看而已,还会有进一步的操作,这个时候exec的作用就显现出来了","text":"find是我们很常用的一个Linux命令,但是我们一般查找出来的并不仅仅是看看而已,还会有进一步的操作,这个时候exec的作用就显现出来了 命令介绍 -exec 参数后面跟的是command命令,它的终止是以;为结束标志的,所以这句命令后面的分号是不可缺少的,考虑到各个系统中分号会有不同的意义,所以前面加反斜杠。 {} 花括号代表前面find查找出来的文件名。 使用find时,只要把想要的操作写在一个文件里,就可以用exec来配合find查找,很方便的。在有些操作系统中只允许-exec选项执行诸如ls或ls -l这样的命令。大多数用户使用这一选项是为了查找旧文件并删除它们。建议在真正执行rm命令删除文件之前,最好先用ls命令看一下,确认它们是所要删除的文件。 exec选项后面跟随着所要执行的命令或脚本,然后是一对儿{ },一个空格和一个\\,最后是一个分号。为了使用exec选项,必须要同时使用print选项。如果验证一下find命令,会发现该命令只输出从当前路径起的相对路径及文件名。 使用实例例一:ls -l命令放在find命令的-exec选项中 # find命令匹配到了当前目录下的所有普通文件,并在-exec选项中使用ls -l命令将它们列出 $ find . -type f -exec ls -l {} \\; 例二:在目录中查找更改时间在n日以前的文件并删除它们 $ find . -type f -mtime +14 -exec rm {} \\; 例三:在目录中查找更改时间在n日以前的文件并删除它们,在删除之前先给出提示 $ find . -name "*.log" -mtime +5 -ok rm {} \\; 例四:-exec中使用grep命令 $ find /etc -name "passwd*" -exec grep "root" {} \\; 例五:查找文件移动到指定目录 $ find . -name "*.log" -exec mv {} .. \\; 例六:用exec选项执行cp命令 $ find . -name "*.log" -exec cp {} test3 \\;","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(19): find命令概览","slug":"linux-command-19-find","date":"2016-12-19T07:19:10.000Z","updated":"2024-07-05T11:10:23.025Z","comments":true,"path":"2016/12/19/linux-command-19-find命令概览/","permalink":"http://yelog.org/2016/12/19/linux-command-19-find%E5%91%BD%E4%BB%A4%E6%A6%82%E8%A7%88/","excerpt":"Linux下find命令在目录结构中搜索文件,并执行指定的操作。Linux下find命令提供了相当多的查找条件,功能很强大。由于find具有强大的功能,所以它的选项也很多,其中大部分选项都值得我们花时间来了解一下。即使系统中含有网络文件系统( NFS),find命令在该文件系统中同样有效,只你具有相应的权限。 在运行一个非常消耗资源的find命令时,很多人都倾向于把它放在后台执行,因为遍历一个大的文件系统可能会花费很长的时间(这里是指30G字节以上的文件系统)。","text":"Linux下find命令在目录结构中搜索文件,并执行指定的操作。Linux下find命令提供了相当多的查找条件,功能很强大。由于find具有强大的功能,所以它的选项也很多,其中大部分选项都值得我们花时间来了解一下。即使系统中含有网络文件系统( NFS),find命令在该文件系统中同样有效,只你具有相应的权限。 在运行一个非常消耗资源的find命令时,很多人都倾向于把它放在后台执行,因为遍历一个大的文件系统可能会花费很长的时间(这里是指30G字节以上的文件系统)。 命令格式$ find pathname -options [-print -exec -ok ...] 命令功能 用于在文件树种查找文件,并作出相应的处理 命令参数 参数 描述 pathname find命令所查找的目录路径。例如用.来表示当前目录,用/来表示系统根目录 -print find命令将匹配的文件输出到标准输出 -exec find命令对匹配的文件执行该参数所给出的shell命令。相应命令的形式为’command’ { } ;,注意{ }和\\;之间的空格 -ok 和-exec的作用相同,只不过以一种更为安全的模式来执行该参数所给出的shell命令,在执行每一个命令之前,都会给出提示,让用户来确定是否执行 命令选项 选项 描述 -name 按照文件名查找文件 -perm 按照文件权限来查找文件 -prune 使用这一选项可以使find命令不在当前指定的目录中查找,如果同时使用-depth选项,那么-prune将被find命令忽略 -user 按照文件属主来查找文件 -group 按照文件所属的组来查找文件 -mtime -n +n 按照文件的更改时间来查找文件, - n表示文件更改时间距现在n天以内,+ n表示文件更改时间距现在n天以前。find命令还有-atime和-ctime 选项,但它们都和-m time选项 -nogroup 查找无有效所属组的文件,即该文件所属的组在/etc/groups中不存在 -nouser 查找无有效属主的文件,即该文件的属主在/etc/passwd中不存在 -newer file1 ! file2 查找更改时间比文件file1新但比文件file2旧的文件 -type 查找某一类型的文件,诸如:b - 块设备文件d - 目录c - 字符设备文件p - 管道文件l - 符号链接文件f - 普通文件 -size n:[c] 查找文件长度为n块的文件,带有c时表示文件长度以字节计。-depth:在查找文件时,首先查找当前目录中的文件,然后再在其子目录中查找 -fstype 查找位于某一类型文件系统中的文件,这些文件系统类型通常可以在配置文件/etc/fstab中找到,该配置文件中包含了本系统中有关文件系统的信息 -mount 在查找文件时不跨越文件系统mount点 -follow 如果find命令遇到符号链接文件,就跟踪至链接所指向的文件 -cpio 对匹配的文件使用cpio命令,将这些文件备份到磁带设备中 -amin n 查找系统中最后N分钟访问的文件 -atime n 查找系统中最后n*24小时访问的文件 -cmin n 查找系统中最后N分钟被改变文件状态的文件 -ctime n 查找系统中最后n*24小时被改变文件状态的文件 -mmin n 查找系统中最后N分钟被改变文件数据的文件 -mtime n 查找系统中最后n*24小时被改变文件数据的文件 使用实例例一:查找指定时间内修改过的文件 # 查找48小时内修改过的文件 $ find -atime -2 例二:根据关键字查找 # 在当前目录查找一.log结尾的文件。 ". "代表当前目录 $ find . -name "*.log" 例三:按照目录或文件的权限来查找文件 # 查找/opt/soft/test/目录下 权限为 777的文件 $ find /opt/soft/test/ -perm 777 例四:按类型查找 # 查找当目录,以.log结尾的普通文件 $ find . -type f -name "*.log" 例五:查找当前所有目录并排序 $ find . -type d | sort 例六:按大小查找文件 # 查找当前目录大于1K的文件 $ find . -size +1000c -print","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(18): locate","slug":"linux-command-18-locate","date":"2016-12-18T07:09:28.000Z","updated":"2024-07-05T11:10:22.828Z","comments":true,"path":"2016/12/18/linux-command-18-locate/","permalink":"http://yelog.org/2016/12/18/linux-command-18-locate/","excerpt":"locate 让使用者可以很快速的搜寻档案系统内是否有指定的档案。其方法是先建立一个包括系统内所有档案名称及路径的数据库,之后当寻找时就只需查询这个数据库,而不必实际深入档案系统之中了。在一般的 distribution 之中,数据库的建立都被放在 crontab 中自动执行。","text":"locate 让使用者可以很快速的搜寻档案系统内是否有指定的档案。其方法是先建立一个包括系统内所有档案名称及路径的数据库,之后当寻找时就只需查询这个数据库,而不必实际深入档案系统之中了。在一般的 distribution 之中,数据库的建立都被放在 crontab 中自动执行。 命令格式$ locate [选择参数] [样式] 命令功能 locate命令可以在搜寻数据库时快速找到档案,数据库由updatedb程序来更新,updatedb是由cron daemon周期性建立的,locate命令在搜寻数据库时比由整个由硬盘资料来搜寻资料来得快,但较差劲的是locate所找到的档案若是最近才建立或 刚更名的,可能会找不到,在内定值中,updatedb每天会跑一次,可以由修改crontab来更新设定值。(etc/crontab) locate指定用在搜寻符合条件的档案,它会去储存档案与目录名称的数据库内,寻找合乎范本样式条件的档案或目录录,可以使用特殊字元(如* 或 ?等)来指定范本样式,如指定范本为kcpa*ner, locate会找出所有起始字串为kcpa且结尾为ner的档案或目录,如名称为kcpartner若目录录名称为kcpa_ner则会列出该目录下包括 子目录在内的所有档案。 locate指令和find找寻档案的功能类似,但locate是透过update程序将硬盘中的所有档案和目录资料先建立一个索引数据库,在 执行loacte时直接找该索引,查询速度会较快,索引数据库一般是由操作系统管理,但也可以直接下达update强迫系统立即修改索引数据库。 命令参数 参数 描述 -e 将排除在寻找的范围之外 -1 如果 是 1.则启动安全模式。在安全模式下,使用者不会看到权限无法看到的档案。这会始速度减慢,因为 locate 必须至实际的档案系统中取得档案的权限资料 -f 将特定的档案系统排除在外,例如我们没有到理要把 proc 档案系统中的档案放在资料库中 -q 安静模式,不会显示任何错误讯息 -n 至多显示 n个输出 -r 使用正规运算式 做寻找的条件 -o 指定资料库存的名称 -d 指定资料库的路径 -h 显示辅助讯息 -V 显示程式的版本讯息 使用实例例一:查找和pwd相关的所有文件 $ locate pwd 例二:搜索etc目录下所有以sh开头的文件 $ locate /etc/sh","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(17): whereis","slug":"linux-command-17-whereis","date":"2016-12-17T02:42:58.000Z","updated":"2024-07-05T11:10:22.988Z","comments":true,"path":"2016/12/17/linux-command-17-whereis/","permalink":"http://yelog.org/2016/12/17/linux-command-17-whereis/","excerpt":"whereis命令只能用于程序名的搜索,而且只搜索二进制文件(参数-b)、man说明文件(参数-m)和源代码文件(参数-s)。如果省略参数,则返回所有信息。","text":"whereis命令只能用于程序名的搜索,而且只搜索二进制文件(参数-b)、man说明文件(参数-m)和源代码文件(参数-s)。如果省略参数,则返回所有信息。 和find相比,whereis查找的速度非常快,这是因为linux系统会将 系统内的所有文件都记录在一个数据库文件中,当使用whereis和下面即将介绍的locate时,会从数据库中查找数据,而不是像find命令那样,通 过遍历硬盘来查找,效率自然会很高。 但是该数据库文件并不是实时更新,默认情况下时一星期更新一次,因此,我们在用whereis和locate 查找文件时,有时会找到已经被删除的数据,或者刚刚建立文件,却无法查找到,原因就是因为数据库文件没有被更新。 命令格式$ whereis [-bmsu] [BMS 目录名 -f ] 文件名 命令功能 whereis命令是定位可执行文件、源代码文件、帮助文件在文件系统中的位置。这些文件的属性应属于原始代码,二进制文件,或是帮助文件。whereis 程序还具有搜索源代码、指定备用搜索路径和搜索不寻常项的能力。 命令参数 参数 描述 -b 定位可执行文件 -m 定位帮助文件 -s 定位源代码文件 -u 搜索默认路径下除可执行文件、源代码文件、帮助文件以外的其它文件 -B 指定搜索可执行文件的路径 -M 指定搜索帮助文件的路径 -S 指定搜索源代码文件的路径 使用实例例一:将和git文件相关的文件都查找出来 $ whereis git 例二:只将二进制文件 查找出来 $ whereis -b svn whereis -m svn 查出说明文档路径,whereis -s svn 找source源文件","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(16): which","slug":"linux-command-16-which","date":"2016-12-16T03:25:49.000Z","updated":"2024-07-05T11:10:22.863Z","comments":true,"path":"2016/12/16/linux-command-16-which/","permalink":"http://yelog.org/2016/12/16/linux-command-16-which/","excerpt":"我们经常在linux要查找某个文件,但不知道放在哪里了,可以使用下面的一些命令来搜索:which 查看可执行文件的位置。whereis 查看文件的位置。locate 配合数据库查看文件位置。find 实际搜寻硬盘查询文件名称。 which命令的作用是,在PATH变量指定的路径中,搜索某个系统命令的位置,并且返回第一个搜索结果。也就是说,使用which命令,就可以看到某个系统命令是否存在,以及执行的到底是哪一个位置的命令。","text":"我们经常在linux要查找某个文件,但不知道放在哪里了,可以使用下面的一些命令来搜索:which 查看可执行文件的位置。whereis 查看文件的位置。locate 配合数据库查看文件位置。find 实际搜寻硬盘查询文件名称。 which命令的作用是,在PATH变量指定的路径中,搜索某个系统命令的位置,并且返回第一个搜索结果。也就是说,使用which命令,就可以看到某个系统命令是否存在,以及执行的到底是哪一个位置的命令。 命令格式$ which 可执行文件名称 命令功能 which指令会在PATH变量指定的路径中,搜索某个系统命令的位置,并且返回第一个搜索结果。 命令参数 参数 描述 -n 指定文件名长度,指定的长度必须大于或等于所有文件中最长的文件名 -p 与-n参数相同,但此处的包括了文件的路径 -w 指定输出时栏位的宽度 -V 显示版本信息 使用实例例一:查找文件、显示命令路径 # which 是根据使用者所配置的 PATH 变量内的目录去搜寻可运行档的! # 所以,不同的 PATH 配置内容所找到的命令当然不一样的! $ which pwd 例二:用 which 去找出 which # 竟然会有两个 which ,其中一个是 alias 这就是所谓的『命令别名』,意思是输入 which 会等於后面接的那串命令! $ which which 例三:找出 cd 这个命令 # cd 这个常用的命令竟然找不到啊!为什么呢?这是因为 cd 是bash 内建的命令! # 但是 which 默认是找 PATH 内所规范的目录,所以当然一定找不到的! $ which cd","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(15): tail","slug":"linux-command-15-tail","date":"2016-12-15T07:00:26.000Z","updated":"2024-07-05T11:10:22.944Z","comments":true,"path":"2016/12/15/linux-command-15-tail/","permalink":"http://yelog.org/2016/12/15/linux-command-15-tail/","excerpt":"tail 命令从指定点开始将文件写到标准输出.使用tail命令的-f选项可以方便的查阅正在改变的日志文件,tail -f filename会把filename里最尾部的内容显示在屏幕上,并且不但刷新,使你看到最新的文件内容.","text":"tail 命令从指定点开始将文件写到标准输出.使用tail命令的-f选项可以方便的查阅正在改变的日志文件,tail -f filename会把filename里最尾部的内容显示在屏幕上,并且不但刷新,使你看到最新的文件内容. 命令格式tail[必要参数][选择参数][文件] 命令功能 用于显示指定文件末尾内容,不指定文件时,作为输入信息进行处理。常用查看日志文件。 命令参数 参数 描述 -f 循环读取 -q 不显示处理信息 -v 显示详细的处理信息 -c<数目> 显示的字节数 -n<行数> 显示行数 –pid=PID 与-f合用,表示在进程ID,PID死掉之后结束 -q, –quiet, –silent 从不输出给出文件名的首部 -s, –sleep-interval=S 与-f合用,表示在每次反复的间隔休眠S秒 使用实例例一:显示文件末尾内容 # 显示文件最后5行内容 $ tail -n 5 log2014.log 例二:循环查看文件内容 $ tail -f test.log 例三:从第5行开始显示文件 $ tail -n +5 log2014.log","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(14): head","slug":"linux-command-14-head","date":"2016-12-14T06:35:49.000Z","updated":"2024-07-05T11:10:22.814Z","comments":true,"path":"2016/12/14/linux-command-14-head/","permalink":"http://yelog.org/2016/12/14/linux-command-14-head/","excerpt":"head 与 tail 就像它的名字一样的浅显易懂,它是用来显示开头或结尾某个数量的文字区块,head 用来显示档案的开头至标准输出中,而 tail 想当然尔就是看档案的结尾。","text":"head 与 tail 就像它的名字一样的浅显易懂,它是用来显示开头或结尾某个数量的文字区块,head 用来显示档案的开头至标准输出中,而 tail 想当然尔就是看档案的结尾。 命令格式head [参数]... [文件]... 命令功能 head 用来显示档案的开头至标准输出中,默认head命令打印其相应文件的开头10行。 命令参数 参数 描述 -q 隐藏文件名 -v 显示文件名 -c<字节> 显示字节数 -n<行数> 显示的行数 使用实例例一:显示文件的前n行 $ head -n 5 log2014.log 例二:显示文件前n个字节 $ head -c 20 log2014.log 例三:文件的除了最后n个字节以外的内容 $ head -c -32 log2014.log 例四:输出文件除了最后n行的全部内容 $ head -n -6 log2014.log","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(13): less","slug":"linux-command-13-less","date":"2016-12-13T13:46:14.000Z","updated":"2024-07-05T11:10:23.007Z","comments":true,"path":"2016/12/13/linux-command-13-less/","permalink":"http://yelog.org/2016/12/13/linux-command-13-less/","excerpt":"less 工具也是对文件或其它输出进行分页显示的工具,应该说是linux正统查看文件内容的工具,功能极其强大。less 的用法比起 more 更加的有弹性。在 more 的时候,我们并没有办法向前面翻, 只能往后面看,但若使用了 less 时,就可以使用 [pageup] [pagedown] 等按键的功能来往前往后翻看文件,更容易用来查看一个文件的内容!除此之外,在 less 里头可以拥有更多的搜索功能,不止可以向下搜,也可以向上搜。","text":"less 工具也是对文件或其它输出进行分页显示的工具,应该说是linux正统查看文件内容的工具,功能极其强大。less 的用法比起 more 更加的有弹性。在 more 的时候,我们并没有办法向前面翻, 只能往后面看,但若使用了 less 时,就可以使用 [pageup] [pagedown] 等按键的功能来往前往后翻看文件,更容易用来查看一个文件的内容!除此之外,在 less 里头可以拥有更多的搜索功能,不止可以向下搜,也可以向上搜。 命令格式$ less [参数] 文件 命令功能 less 与 more 类似,但使用 less 可以随意浏览文件,而 more 仅能向前移动,却不能向后移动,而且 less 在查看之前不会加载整个文件。 命令参数 参数 描述 -b <缓冲区大小> 设置缓冲区的大小 -e 当文件显示结束后,自动离开 -f 强迫打开特殊文件,例如外围设备代号、目录和二进制文件 -g 只标志最后搜索的关键词 -i 忽略搜索时的大小写 -m 显示类似more命令的百分比 -N 显示每行的行号 -o <文件名> 将less 输出的内容在指定文件中保存起来 -Q 不使用警告音 -s 显示连续空行为一行 -S 行过长时间将超出部分舍弃 -x <数字> 将“tab”键显示为规定的数字空格 /字符串 向下搜索“字符串”的功能 ?字符串 向上搜索“字符串”的功能 n 重复前一个搜索(与 / 或 ? 有关) N 反向重复前一个搜索(与 / 或 ? 有关) b 向后翻一页 d 向后翻半页 h 显示帮助界面 Q 退出less 命令 u 向前滚动半页 y 向前滚动一行 空格键 滚动一行 回车键 滚动一页 [pagedown] 向下翻动一页 [pageup] 向上翻动一页 常用操作全屏导航 操作 描述 ctrl + F 向前移动一屏 ctrl + B 向后移动一屏 ctrl + D 向前移动半屏 ctrl + U 向后移动半屏 单行导航 操作 描述 j 向前移动一行 k 向后移动一行 其他导航 操作 描述 G 移动到最后一行 g 移动到第一行 q / ZZ 退出 less 命令 其它有用的命令 操作 描述 v 使用配置的编辑器编辑当前文件 h 显示 less 的帮助文档 &pattern 仅显示匹配模式的行,而不是整个文件 标记导航 当使用 less 查看大文件时,可以在任何一个位置作标记,可以通过命令导航到标有特定标记的文本位置 操作 描述 ma 使用 a 标记文本的当前位置 ‘a 导航到标记 a 处 使用实例例一:查看文件 $ less log2013.log 例二:ps查看进程信息并通过less分页显示 $ ps -ef |less 例三:查看命令历史使用记录并通过less分页显示 $ history | less 例三:浏览多个文件 $ Less log2013.log log2014.log 输入 :n后,切换到 log2014.log输入 :p 后,切换到log2013.log","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(12): more","slug":"linux-command-12-more","date":"2016-12-12T13:29:39.000Z","updated":"2024-07-05T11:10:22.959Z","comments":true,"path":"2016/12/12/linux-command-12-more/","permalink":"http://yelog.org/2016/12/12/linux-command-12-more/","excerpt":"more命令,功能类似 cat ,cat命令是整个文件的内容从上到下显示在屏幕上。 more会以一页一页的显示方便使用者逐页阅读,而最基本的指令就是按空白键(space)就往下一页显示,按 b 键就会往回(back)一页显示,而且还有搜寻字串的功能 。more命令从前向后读取文件,因此在启动时就加载整个文件。","text":"more命令,功能类似 cat ,cat命令是整个文件的内容从上到下显示在屏幕上。 more会以一页一页的显示方便使用者逐页阅读,而最基本的指令就是按空白键(space)就往下一页显示,按 b 键就会往回(back)一页显示,而且还有搜寻字串的功能 。more命令从前向后读取文件,因此在启动时就加载整个文件。 命令格式$ more [-dlfpcsu ] [-num ] [+/ pattern] [+ linenum] [file ... ] 命令功能 more命令和cat的功能一样都是查看文件里的内容,但有所不同的是more可以按页来查看文件的内容,还支持直接跳转行等功能。 命令参数 参数 描述 +n 从笫n行开始显示 -n 定义屏幕大小为n行 +/pattern 每个档案显示前搜寻该字串(pattern),然后从该字串前两行之后开始显示 -c 从顶部清屏,然后显示 -d 提示“Press space to continue,’q’ to quit(按空格键继续,按q键退出)”,禁用响铃功能 -l 忽略Ctrl+l(换页)字符 -p 通过清除窗口而不是滚屏来对文件进行换页,与-c选项相似 -s 把连续的多个空行显示为一行 -u 把文件内容中的下画线去掉 常用操作 操作 描述 Enter 向下n行,需要定义。默认为1行 Ctrl+F 向下滚动一屏 空格键 向下滚动一屏 Ctrl+B 返回上一屏 = 输出当前行的行号 :f 输出文件名和当前行的行号 V 调用vi编辑器 !命令 调用Shell,并执行命令 q 退出more 命令实例例一:显示文件中从第3行起的内容 $ more +3 log2012.log 例二:从文件中查找第一个出现”day3”字符串的行,并从该处前两行开始显示输出 $ more +/day3 log2012.log 例三:设定每屏显示行数 $ more -5 log2012.log 例四:列一个目录下的文件,由于内容太多,我们应该学会用more来分页显示。这得和管道 | 结合起来 $ ls -l | more -5 说明:每页显示5个文件信息,按 Ctrl+F 或者 空格键 将会显示下5条文件信息。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(11): nl","slug":"linux-command-11-nl","date":"2016-12-11T13:11:48.000Z","updated":"2024-07-05T11:10:23.043Z","comments":true,"path":"2016/12/11/linux-command-11-nl/","permalink":"http://yelog.org/2016/12/11/linux-command-11-nl/","excerpt":"nl命令在linux系统中用来计算文件中行号。nl 可以将输出的文件内容自动的加上行号!其默认的结果与 cat -n 有点不太一样, nl 可以将行号做比较多的显示设计,包括位数与是否自动补齐 0 等等的功能。","text":"nl命令在linux系统中用来计算文件中行号。nl 可以将输出的文件内容自动的加上行号!其默认的结果与 cat -n 有点不太一样, nl 可以将行号做比较多的显示设计,包括位数与是否自动补齐 0 等等的功能。 命令格式$ nl [选项]... [文件]... 命令功能 nl 命令读取 File 参数(缺省情况下标准输入),计算输入中的行号,将计算过的行号写入标准输出。 在输出中,nl 命令根据您在命令行中指定的标志来计算左边的行。 输入文本必须写在逻辑页中。每个逻辑页有头、主体和页脚节(可以有空节)。 除非使用 -p 标志,nl 命令在每个逻辑页开始的地方重新设置行号。 可以单独为头、主体和页脚节设置行计算标志(例如,头和页脚行可以被计算然而文本行不能)。 命令参数 种类 参数 描述 -b -b a 表示不论是否为空行,也同样列出行号(类似 cat -n) -b t 如果有空行,空的那一行不要列出行号(默认值) -n -n ln 行号在萤幕的最左方显示 -n rn 行号在自己栏位的最右方显示,且不加 0 -n rz 行号在自己栏位的最右方显示,且加 0 -w -w 行号栏位的占用的位数 -p -p 在逻辑定界符处不重新开始计算 命令实例例一:用 nl 列出 log2012.log 的内容 # 文件中的空白行,nl 不会加上行号 $ nl log2012.log 例二:用 nl 列出 log2012.log 的内容,空本行也加上行号 $ nl -b a log2012.log 例三:让行号前面自动补上0,统一输出格式 $ nl -b a -n rz log2014.log nl -b a -n rz 命令行号默认为六位,要调整位数可以加上参数 -w 3 调整为3位。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(10): cat","slug":"linux-command-10-cat","date":"2016-12-10T02:52:50.000Z","updated":"2024-07-05T11:10:22.915Z","comments":true,"path":"2016/12/10/linux-command-10-cat/","permalink":"http://yelog.org/2016/12/10/linux-command-10-cat/","excerpt":"cat命令的用途是连接文件或标准输入并打印。这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用。","text":"cat命令的用途是连接文件或标准输入并打印。这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用。 命令格式$ cat [选项] [文件]... 命令功能cat主要有三大功能 一次显示整个文件:cat filename 从键盘创建一个文件:cat > filename 只能创建新文件,不能编辑已有文件. 将几个文件合并为一个文件:cat file1 file2 > file 命令参数 参数 描述 -A, –show-all 等价于 -vET -b, –number-nonblank 对非空输出行编号 -e 等价于 -vE -E, –show-ends 在每行结束处显示 $ -n, –number 对输出的所有行编号,由1开始对所有输出的行数编号 -s, –squeeze-blank 有连续两行以上的空白行,就代换为一行的空白行 -t 与 -vT 等价 -T, –show-tabs 将跳格字符显示为 ^I -u (被忽略) -v, –show-nonprinting 使用 ^ 和 M- 引用,除了 LFD 和 TAB 之外 命令实例例一:把 log2012.log 的文件内容加上行号后输入 log2013.log 这个文件里 $ cat -n log2012.log log2013.log 例二:把 log2012.log 和 log2013.log 的文件内容加上行号(空白行不加)之后将内容附加到 log.log 里 $ cat -b log2012.log log2013.log log.log 例三:把 log2012.log 的文件内容加上行号后输入 log.log 这个文件里 $ cat -n log2012.log > log.log 例四:使用here doc来生成文件 $ cat >log.txt <<EOF > Hello > World > Linux > PWD=$(pwd) > EOF 例五:tac (反向列示) $ tac log.txt PWD=/opt/soft/test Linux World Hello tac 是将 cat 反写过来,所以他的功能就跟 cat 相反, cat 是由第一行到最后一行连续显示在萤幕上,而 tac 则是由最后一行到第一行反向在萤幕上显示出来!","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(9): touch","slug":"linux-command-9-touch","date":"2016-12-08T23:15:12.000Z","updated":"2024-07-05T11:10:23.030Z","comments":true,"path":"2016/12/09/linux-command-9-touch/","permalink":"http://yelog.org/2016/12/09/linux-command-9-touch/","excerpt":"linux的touch命令不常用,一般在使用make的时候可能会用到,用来修改文件时间戳,或者新建一个不存在的文件。","text":"linux的touch命令不常用,一般在使用make的时候可能会用到,用来修改文件时间戳,或者新建一个不存在的文件。 命令格式$ touch [选项]... 文件... 命令功能 touch命令参数可更改文档或目录的日期时间,包括存取时间和更改时间。 命令参数 参数 描述 -a 或–time=atime或–time=access或–time=use 只更改存取时间 -c 或–no-create 不建立任何文档 -d 使用指定的日期时间,而非现在的时间 -f 此参数将忽略不予处理,仅负责解决BSD版本touch指令的兼容性问题 -m 或–time=mtime或–time=modify 只更改变动时间 -r 把指定文档或目录的日期时间,统统设成和参考文档或目录的日期时间相同 -t 使用指定的日期时间,而非现在的时间 命令实例例一:创建不存在的文件 $ touch 1.txt 例二:更新1.txt的时间和2.txt时间戳相同 $ touch -r 1.txt 2.txt 例三:设定文件的时间戳 $ touch -t 201211142234.50 1.txt 例四:创建不存在的文件 $ touch 1.txt 说明: -t time 使用指定的时间值 time 作为指定文件相应时间戳记的新值.此处的 time规定为如下形式的十进制数: [[CC]YY]MMDDhhmm[.SS] 这里,CC为年数中的前两位,即”世纪数”;YY为年数的后两位,即某世纪中的年数.如果不给出CC的值,则touch 将把年数CCYY限定在1969–2068之内.MM为月数,DD为天将把年数CCYY限定在1969–2068之内.MM为月数,DD为天数,hh 为小时数(几点),mm为分钟数,SS为秒数.此处秒的设定范围是0–61,这样可以处理闰秒.这些数字组成的时间是环境变量TZ指定的时区中的一个时 间.由于系统的限制,早于1970年1月1日的时间是错误的。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(8): cp","slug":"linux-command-8-cp","date":"2016-12-08T08:31:43.000Z","updated":"2024-07-05T11:10:23.039Z","comments":true,"path":"2016/12/08/linux-command-8-cp/","permalink":"http://yelog.org/2016/12/08/linux-command-8-cp/","excerpt":"cp命令用来复制文件或者目录,是Linux系统中最常用的命令之一。一般情况下,shell会设置一个别名,在命令行下复制文件时,如果目标文件已经存在,就会询问是否覆盖,不管你是否使用-i参数。但是如果是在shell脚本中执行cp时,没有-i参数时不会询问是否覆盖。这说明命令行和shell脚本的执行方式有些不同。","text":"cp命令用来复制文件或者目录,是Linux系统中最常用的命令之一。一般情况下,shell会设置一个别名,在命令行下复制文件时,如果目标文件已经存在,就会询问是否覆盖,不管你是否使用-i参数。但是如果是在shell脚本中执行cp时,没有-i参数时不会询问是否覆盖。这说明命令行和shell脚本的执行方式有些不同。 命令格式$ cp [选项]... [-T] 源 目的 $ cp [选项]... -t 目录 源... 命令功能 将源文件复制至目标文件,或将多个源文件复制至目标目录。 命令参数 参数 描述 -a,–archive 为每个已存在的目标文件创建备份 -f, –force 如果目标文件无法打开则将其移除并重试(当 -n 选项存在时则不需再选此项) -i, –interactive 覆盖前询问(使前面的 -n 选项失效) -H 跟随源文件中的命令行符号链接 -l, –link 链接文件而不复制 -L, –dereference 总是跟随符号链接 -n, –no-clobber 不要覆盖已存在的文件(使前面的 -i 选项失效) -P, –no-dereference 不跟随源文件中的符号链接 -p 等于–preserve=模式,所有权,时间戳 -R, -r, –recursive 复制目录及目录内的所有项目 命令实例例一:复制单个文件到目标目录,文件在目标文件中存在,会询问覆盖 # 在没有带-a参数时,两个文件的时间是不一样的。在带了-a参数时,两个文件的时间是一致的。 $ cp 1.txt test5 例二:复制整个目录 # 注意目标目录存在与否结果是不一样的。目标目录存在时,整个源目录被复制到目标目录里面。 $ cp -a test3 test5 例三:复制的 log.log 建立一个连结档 log_link.log $ cp -s log.log log_link.log","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(7): mv","slug":"linux-command-7-mv","date":"2016-12-07T07:55:20.000Z","updated":"2024-07-05T11:10:22.867Z","comments":true,"path":"2016/12/07/linux-command-7-mv/","permalink":"http://yelog.org/2016/12/07/linux-command-7-mv/","excerpt":"mv命令是move的缩写,可以用来移动文件或者将文件改名(move (rename) files),是Linux系统下常用的命令,经常用来备份文件或者目录","text":"mv命令是move的缩写,可以用来移动文件或者将文件改名(move (rename) files),是Linux系统下常用的命令,经常用来备份文件或者目录 命令格式$ mv [选项] 源文件或目录 目标文件或目录 命令功能 视mv命令中第二个参数类型的不同(是目标文件还是目标目录),mv命令将文件重命名或将其移至一个新的目录中。当第二个参数类型是文件时,mv命令完成文件重命名,此时,源文件只能有一个(也可以是源目录名),它将所给的源文件或目录重命名为给定的目标文件名。当第二个参数是已存在的目录名称时,源文件或目录参数可以有多个,mv命令将各参数指定的源文件均移至目标目录中。在跨文件系统移动文件时,mv先拷贝,再将原有文件删除,而链至该文件的链接也将丢失。 命令参数 参数 描述 -b 若需覆盖文件,则覆盖前先行备份。 -f force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖 -i 若目标文件 (destination) 已经存在时,就会询问是否覆盖! -u 若目标文件已经存在,且 source 比较新,才会更新(update) -t –target-directory=DIRECTORY move all SOURCE arguments into DIRECTORY,即指定mv的目标目录,该选项适用于移动多个源文件到一个目录的情况,此时目标目录在前,源文件在后。 命令实例例一:文件改名 $ mv test.txt test1.txt 例二:移动文件 #将文件test.txt 移动到/usr/doc目录下 $ mv test.txt /usr/doc 例三:将文件log1.txt,log2.txt,log3.txt移动到目录/usr/doc中 $ mv log1.txt log2.txt log3.txt /usr/doc $ mv -t /usr/doc log1.txt log2.txt log3.txt 例四:将文件file1改名为file2,如果file2已经存在,则询问是否覆盖 $ mv -i log1.txt log2.txt 例五:将文件file1改名为file2,即使file2存在,也是直接覆盖掉 $ mv -f log3.txt log2.txt 例六:目录的移动 #将doc下的product目录移动到/usr/doc目录下 $ mv doc/product /usr/doc 例七:移动当前文件夹下的所有文件到上一级目录 $ mv * ../ 例八:文件被覆盖前做简单备份,前面加参数-b $ mv log1.txt -b log2.txt -b不接受参数,mv会去读取环境变量VERSION_CONTROL来作为备份策略。–backup该选项指定如果目标文件存在时的动作,共有四种备份策略: CONTROL=none或off : 不备份。 CONTROL=numbered或t:数字编号的备份 CONTROL=existing或nil:如果存在以数字编号的备份,则继续编号备份m+1…n:执行mv操作前已存在以数字编号的文件log2.txt.1,那么再次执行将产生log2.txt2,以次类推。如果之前没有以数字编号的文件,则使用下面讲到的简单备份。 CONTROL=simple或never:使用简单备份:在被覆盖前进行了简单备份,简单备份只能有一份,再次被覆盖时,简单备份也会被覆盖。","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(6): rmdir","slug":"linux-command-6-rmdir","date":"2016-12-06T07:24:32.000Z","updated":"2024-07-05T11:10:23.020Z","comments":true,"path":"2016/12/06/linux-command-6-rmdir/","permalink":"http://yelog.org/2016/12/06/linux-command-6-rmdir/","excerpt":"今天学习一下linux中命令: rmdir命令。rmdir是常用的命令,该命令的功能是删除空目录,一个目录被删除之前必须是空的。(注意,rm - r dir命令可代替rmdir,但是有很大危险性。)删除某目录时也必须具有对父目录的写权限。","text":"今天学习一下linux中命令: rmdir命令。rmdir是常用的命令,该命令的功能是删除空目录,一个目录被删除之前必须是空的。(注意,rm - r dir命令可代替rmdir,但是有很大危险性。)删除某目录时也必须具有对父目录的写权限。 命令格式$ rmdir [选项]... 目录... 命令功能 该命令从一个目录中删除一个或多个子目录项,删除某目录时也必须具有对父目录的写权限。 命令参数 参数 描述 - p 递归删除目录dirname,当子目录删除后其父目录为空时,也一同被删除。如果整个路径被删除或者由于某种原因保留部分路径,则系统在标准输出上显示相应的信息 -v, –verbose 显示指令执行过程 命令实例例一:rmdir 不能删除非空目录 $ rmdir doc rmdir: doc: 目录非空 例二:rmdir -p 当子目录被删除后使它也成为空目录的话,则顺便一并删除 $ rmdir -p log/product","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(5): rm","slug":"linux-command-5-rm","date":"2016-12-05T05:40:38.000Z","updated":"2024-07-05T11:10:22.859Z","comments":true,"path":"2016/12/05/linux-command-5-rm/","permalink":"http://yelog.org/2016/12/05/linux-command-5-rm/","excerpt":"rm是常用的命令,该命令的功能为删除一个目录中的一个或多个文件或目录,它也可以将某个目录及其下的所有文件及子目录均删除。对于链接文件,只是删除了链接,原有文件均保持不变。rm是一个危险的命令,使用的时候要特别当心,尤其对于新手,否则整个系统就会毁在这个命令(比如在/(根目录)下执行rm * -rf)。所以,我们在执行rm之前最好先确认一下在哪个目录,到底要删除什么东西,操作时保持高度清醒的头脑。","text":"rm是常用的命令,该命令的功能为删除一个目录中的一个或多个文件或目录,它也可以将某个目录及其下的所有文件及子目录均删除。对于链接文件,只是删除了链接,原有文件均保持不变。rm是一个危险的命令,使用的时候要特别当心,尤其对于新手,否则整个系统就会毁在这个命令(比如在/(根目录)下执行rm * -rf)。所以,我们在执行rm之前最好先确认一下在哪个目录,到底要删除什么东西,操作时保持高度清醒的头脑。 命令格式$ rm [选项] 文件... 命令功能 参数 描述 -f, –force 忽略不存在的文件,从不给出提示 -i, –interactive 进行交互式删除 -r, -R, –recursive 指示rm将参数中列出的全部目录和子目录均递归地删除 -v, –verbose 详细显示进行的步骤 –help 显示此帮助信息并退出 –version 输出版本信息并退出 命令实例例一:删除文件file,系统会先询问是否删除 $ rm file 例二:强行删除file,系统不再提示 $ rm -f file **例三:删除任何.log文件;删除前逐一询问确认 ** $ rm -i *.log 例四:对test文件夹进行递归删除 $ rm -r test 例五:递归删除,系统不用一一确认 $ rm -rf test 例六:删除以 -f 开头的文件 $ rm -- -f 例七:自定义回收站功能 #下面的操作过程模拟了回收站的效果,即删除文件的时候只是把文件放到一个临时目录中,这样在需要的时候还可以恢复过来。 $ myrm(){ D=/tmp/$(date +%Y%m%d%H%M%S); mkdir -p $D; mv "$@" $D && echo "moved to $D ok"; }","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(4): mkdir","slug":"linux-command-4-mkdir","date":"2016-12-04T01:27:32.000Z","updated":"2024-07-05T11:10:22.964Z","comments":true,"path":"2016/12/04/linux-command-4-mkdir/","permalink":"http://yelog.org/2016/12/04/linux-command-4-mkdir/","excerpt":"linux mkdir 命令用来创建指定的名称的目录,要求创建目录的用户在当前目录中具有写权限,并且指定的目录名不能是当前目录中已有的目录。","text":"linux mkdir 命令用来创建指定的名称的目录,要求创建目录的用户在当前目录中具有写权限,并且指定的目录名不能是当前目录中已有的目录。 命令格式$ mkdir [选项] 目录... 命令功能 通过 mkdir 命令可以实现在指定位置创建以 DirName(指定的文件名)命名的文件夹或目录。要创建文件夹或目录的用户必须对所创建的文件夹的父文件夹具有写权限。并且,所创建的文件夹(目录)不能与其父目录(即父文件夹)中的文件名重名,即同一个目录下不能有同名的(区分大小写)。 命令参数 参数 说明 -m, –mode=模式 设定权限<模式> (类似 chmod),而不是 rwxrwxrwx 减 umask -p, –parents 可以是一个路径名称。此时若路径中的某些目录尚不存在,加上此选项后,系统将自动建立好那些尚不存在的目录,即一次可以建立多个目录 -v, –verbose 每次创建新目录都显示信息 –help 显示此帮助信息并退出 –version 输出版本信息并退出 命令是实例例一:创建一个空目录 $ mkdir test 例二:递归创建多个目录 #在当前目录创建一个嵌套文件夹test1/test11 $ mkdir -p test1/test11 例三:创建权限为777的目录 $ mkdir -m 777 test 例四:创建新目录都显示信息 $ mkdir -v test 例五:一个命令创建项目的目录结构 $ mkdir -vp scf/{lib/,bin/,doc/{info,product},logs/{info,product},service/deploy/{info,product}} mkdir: 已创建目录 “scf” mkdir: 已创建目录 “scf/lib” mkdir: 已创建目录 “scf/bin” mkdir: 已创建目录 “scf/doc” mkdir: 已创建目录 “scf/doc/info” mkdir: 已创建目录 “scf/doc/product” mkdir: 已创建目录 “scf/logs” mkdir: 已创建目录 “scf/logs/info” mkdir: 已创建目录 “scf/logs/product” mkdir: 已创建目录 “scf/service” mkdir: 已创建目录 “scf/service/deploy” mkdir: 已创建目录 “scf/service/deploy/info” mkdir: 已创建目录 “scf/service/deploy/product” [root@localhost test]# tree scf/ scf/ |-- bin |-- doc | |-- info | `-- product |-- lib |-- logs | |-- info | `-- product `-- service `-- deploy |-- info `-- product 12 directories, 0 files","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(3): pwd","slug":"linux-command-3-pwd","date":"2016-12-03T01:15:52.000Z","updated":"2024-07-05T11:10:22.934Z","comments":true,"path":"2016/12/03/linux-command-3-pwd/","permalink":"http://yelog.org/2016/12/03/linux-command-3-pwd/","excerpt":"Linux中用 pwd 命令来查看”当前工作目录“的完整路径。 简单得说,每当你在终端进行操作时,你都会有一个当前工作目录。 在不太确定当前位置时,就会使用pwd来判定当前目录在文件系统内的确切位置。","text":"Linux中用 pwd 命令来查看”当前工作目录“的完整路径。 简单得说,每当你在终端进行操作时,你都会有一个当前工作目录。 在不太确定当前位置时,就会使用pwd来判定当前目录在文件系统内的确切位置。 命令格式$ pwd [选项] 命令功能 查看”当前工作目录“的完整路径 常用参数一般情况下不带任何参数如果目录是链接时:格式:pwd -P 显示出实际路径,而非使用链接(link) 的路径 实用实例例一:用 pwd 命令查看当前工作目录的完整路径 $ pwd /home/faker 例二:目录连接链接时,pwd -P 显示出实际路径,而非使用连接(link)路径;pwd显示的是连接路径 #目录为链接时,输出链接路径 $ pwd -L #目录为链接时,输出物理路径 $ pwd -P /home/faker 例三:当前目录被删除了,而pwd命令仍然显示那个目录 $ cd /opt/soft $ rm ../soft -rf $ pwd /opt/soft $ /bin/pwd /bin/pwd: couldnt find directory entry in “..” with matching i-node /home/faker","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(2): cd","slug":"linux-command-2-cd","date":"2016-12-02T03:04:55.000Z","updated":"2024-07-05T11:10:22.949Z","comments":true,"path":"2016/12/02/linux-command-2-cd/","permalink":"http://yelog.org/2016/12/02/linux-command-2-cd/","excerpt":"Linux cd 命令可以说是Linux中最基本的命令语句,其他的命令语句要进行操作,都是建立在使用 cd 命令上的。所以,学习Linux 常用命令,首先就要学好 cd 命令的使用方法技巧。","text":"Linux cd 命令可以说是Linux中最基本的命令语句,其他的命令语句要进行操作,都是建立在使用 cd 命令上的。所以,学习Linux 常用命令,首先就要学好 cd 命令的使用方法技巧。 命令格式$ cd [目录名] 命令功能 切换当前目录至目标目录 常用范例例一:进入系统根目录 $ cd / 例二:进入父级目录 $ cd .. $ cd ..// 例三:使用 cd 命令进入当前用户主目录 $ cd $ cd ~ 例四:跳转到指定目录 $ cd /usr/bin 例五:返回进入此目录之前所在的目录 $ cd - 例六:把上个命令的参数作为cd参数使用 $ cd !$","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"每天一个linux命令(1): ls","slug":"linux-command-1-ls","date":"2016-12-01T01:38:59.000Z","updated":"2024-07-05T11:10:22.877Z","comments":true,"path":"2016/12/01/linux-command-1-ls/","permalink":"http://yelog.org/2016/12/01/linux-command-1-ls/","excerpt":"ls命令是linux下最常用的命令。ls命令就是list的缩写,缺省下ls用来打印出当前目录的清单,如果ls指定其他目录,那么就会显示指定目录里的文件及文件夹清单。 通过ls命令不仅可以查看linux文件夹包含的文件,而且可以查看文件权限(包括目录、文件夹、文件权限)、查看目录信息等等。ls命令在日常的linux操作中用的很多!","text":"ls命令是linux下最常用的命令。ls命令就是list的缩写,缺省下ls用来打印出当前目录的清单,如果ls指定其他目录,那么就会显示指定目录里的文件及文件夹清单。 通过ls命令不仅可以查看linux文件夹包含的文件,而且可以查看文件权限(包括目录、文件夹、文件权限)、查看目录信息等等。ls命令在日常的linux操作中用的很多! 命令格式$ ls [选项] [目录名] 命令功能列出目标目录中所有的子目录和文件。 常用参数 参数 说明 -a,–all 列出目录下的所有文件,包括以 . 开头的隐含文件 -A 同-a,但不列出“.”(表示当前目录)和“..”(表示当前目录的父目录)。 -c 配合 -lt 根据 ctime 排序及显示 ctime (文件状态最后更改的时间)配合 -lt:显示 ctime 但根据名称排序否则:根据 ctime 排序 -C 每栏由上至下列出项目 -color[=WHEN] 控制是否使用色彩分辨文件。WHEN 可以是’never’、’always’或’auto’其中之一 -d,–directory 将目录象文件一样显示,而不是显示其下的文件。 -D,–dired 产生适合 Emacs 的 dired 模式使用的结果 -f 对输出的文件不进行排序,-aU 选项生效,-lst 选项失效 -g 类似 -l,但不列出所有者 -G, –no-group 不列出任何有关组的信息 -h,–human-readable 以容易理解的格式列出文件大小 (例如 1K 234M 2G) –si 类似 -h,但文件大小取 1000 的次方而不是 1024 -H, –dereference-command-line 使用命令列中的符号链接指示的真正目的地 –indicator-style=<方式> 指定在每个项目名称后加上指示符号<方式>:none (默认),classify (-F),file-type (-p) -i, –inode 印出每个文件的 inode 号 -I,–ignore=样式 不印出任何符合 shell 万用字符<样式>的项目 -k 即 –block-size=1K,以 k 字节的形式表示文件的大小 -l 除了文件名之外,还将文件的权限、所有者、文件大小等信息详细列出来。 -L, –dereference -m 所有项目以逗号分隔,并填满整行行宽 -o 类似 -l,显示文件的除组信息外的详细信息。 -r, –reverse 依相反次序排列 -R, –recursive 同时列出所有子目录层 -s,–size 以块大小为单位列出所有文件的大小 -S 根据文件大小排序 –sort=WORD 可选用的 WORD 和它们代表的相应选项: extension -X status -cnone -U time -tsize -S atime -utime -t access -uversion -v use -u -t 以文件修改时间排序 -u 配合 -lt:显示访问时间而且依访问时间排序配合 -l:显示访问时间但根据名称排序否则:根据访问时间排序 -U 不进行排序;依文件系统原有的次序列出项目 -v 根据版本进行排序 -w, –width=COLS 自行指定屏幕宽度而不使用目前的数值 -x 逐行列出项目而不是逐栏列出 -X 根据扩展名排序 -1 每行只列出一个文件 –help 显示此帮助信息并离开 –version 显示版本信息并离开 常用范例例一:列出/home/faker/ 文件夹下的所有文件和目录的详细资料 $ ls -l -R /home/faker $ ls -lR /home/faker 例二:列出当前目录中所有以“t”开头的目录的详细内容,可以使用如下命令 $ ls -l t* 例三:只列出文件下的子目录 $ ls -F /opt/soft |grep /$ 例四:列出文件下的子目录详细情况 $ ls -l /opt/soft | grep "^d" 例五:列出目前工作目录下所有名称是s 开头的文件,愈新的排愈后面,可以使用如下命令 $ ls -ltr s* 例六:列出目前工作目录下所有档案及目录;目录于名称后加”/“, 可执行档于名称后加* $ ls -AF 例七:计算当前目录下的文件数和目录数 $ ls -l * |grep "^-"|wc -l ---文件个数 $ ls -l * |grep "^d"|wc -l ---目录个数 例八:在ls中列出文件的绝对路径 $ ls | sed "s:^:`pwd`/:" 例九:列出当前目录下的所有文件(包括隐藏文件)的绝对路径, 对目录不做递归 $ find $PWD -maxdepth 1 | xargs ls -ld 例十:列出当前目录下的所有文件(包括隐藏文件)的绝对路径, 对目录不做递归 $ find $PWD -maxdepth 1 | xargs ls -ld 例十:递归列出当前目录下的所有文件(包括隐藏文件)的绝对路径 $ find $PWD | xargs ls -ld 例十:指定文件时间输出格式 $ ls -tl --time-style=full-iso $ ls -ctl --time-style=long-iso 2016-08-05 22:17:06.020535551 +0800 2016-10-29 12:03","categories":[{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"}],"tags":[{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"}]},{"title":"jQuery之checkbox|radio|select操作","slug":"jQuery之checkbox-radio-select操作","date":"2016-11-23T08:19:34.000Z","updated":"2024-07-05T11:10:22.398Z","comments":true,"path":"2016/11/23/jQuery-checkbox-radio-select/","permalink":"http://yelog.org/2016/11/23/jQuery-checkbox-radio-select/","excerpt":"jQuery1.6中添加了prop方法,看起来和用起来都和attr方法一样,但是在一些特殊情况下,attribute和properties的区别非常大,在jQuery1.6之前,.attr()方法在获取一些attributes的时候使用了property值,这样会导致一些不一致的行为。在jQuery1.6中,.prop()方法提供了一中明确的获取property值得方式,这样.attr()方法仅返回attributes。 –摘自jQuery API文档","text":"jQuery1.6中添加了prop方法,看起来和用起来都和attr方法一样,但是在一些特殊情况下,attribute和properties的区别非常大,在jQuery1.6之前,.attr()方法在获取一些attributes的时候使用了property值,这样会导致一些不一致的行为。在jQuery1.6中,.prop()方法提供了一中明确的获取property值得方式,这样.attr()方法仅返回attributes。 –摘自jQuery API文档 checkbox<input type='checkbox' value='1'/> <input type='checkbox' value='2'/> <input type='checkbox' value='3'/> $("input[type=checkbox]") //获取所有的checkbox $("input[type=checkbox]:checked") //获取所有的被选中的checkbox $("input[type=checkbox]:not(:checked)") //获取所有未被选中的checkbox $("input[type=checkbox]").not(":checked") //获取所有未被选中的checkbox $("input[type=checkbox]:first") //获取第一个checkbox的value值 $("input[type=checkbox]:checked").length //获取被选中checkbox的数量 $("input[type=checkbox]:first").prop("checked") //判断第一个checkbox是否被选中 $("input[type=checkbox]:first").prop("checkbox",true) //选中第一个checkbox $("input[type=checkbox]:not(:checked)").prop("checked",true) //全选 $("input[type=checkbox]:checkbox").prop("checked",false) //都不选中 //反选 $("input[type=checkbox]").each(function(){ if($(this).prop("checked")){ $(this).prop("checked",false); }else{ $(this).prop("checked",true); } }) radio<input type='radio' name='rank' value='1' /> <input type='radio' name='rank' value='2' /> <input type='radio' name='rank' value='3' /> $("input[type=radio]") //获取所有的radio $("input[type=radio]:checked") //获取被选中的radio $("input[type=radio]:not(:checkbox)") //获取所有没有被选中的radio $("input[type=radio]:checked").val() //获取被选中的radio的value值 $("input[type=radio]:first").prop("checked") //判断第一个radio是否被选中 $("input[type=radio]:first").prop("checked",true) //选中第一个radio select<select> <option value='1'>1</option> <option value='2'>2</option> <option value='4'>3</option> </select> $("select option:selected") //获取被选中的option $("select").val() //获取选中option的value值 $("select option:selected").text() //获取被选中的option的text值 $("select option:first").prop("selected") //判断第一个option是否被选中 $("select option:first").prop("selected",true) //选中第一个option $("select option:selected").prop("selected",false) //取消当前选中,然后默认选中第一个","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"jQuery","slug":"jQuery","permalink":"http://yelog.org/tags/jQuery/"}]},{"title":"jQuery选择器与节点操作","slug":"jQuery选择器与节点操作","date":"2016-11-22T09:11:48.000Z","updated":"2024-07-05T11:10:22.351Z","comments":true,"path":"2016/11/22/jQuery-selector/","permalink":"http://yelog.org/2016/11/22/jQuery-selector/","excerpt":"jQuery 是一个 JavaScript 函数库。jQuery的语法设计使得许多操作变得容易,如操作文档对象(document)、选择文档对象模型(DOM)元素、创建动画效果、处理事件、以及开发Ajax程序。jQuery也提供了给开发人员在其上创建插件的能力。这使开发人员可以对底层交互与动画、高级效果和高级主题化的组件进行抽象化。","text":"jQuery 是一个 JavaScript 函数库。jQuery的语法设计使得许多操作变得容易,如操作文档对象(document)、选择文档对象模型(DOM)元素、创建动画效果、处理事件、以及开发Ajax程序。jQuery也提供了给开发人员在其上创建插件的能力。这使开发人员可以对底层交互与动画、高级效果和高级主题化的组件进行抽象化。 jQuery获取元素元素选择器//元素选择器 <div > $("div") id选择器//id选择器 <div id='id'> $("#id") $("div#id") class选择器//class选择器 <div class='class'> $(".class") $("div.class") 属性过滤选择器<li class='check' type='li_01'></li> <li type='li_02'></li> <li type='li_03'></li> //通过属性获取 如果属性值为有特殊字符,一定要加引号 $("[type]") //获取有type属性的元素 $("[type='li_01']") //获取type值等于'li_01'的元素 $("[type!='li_01']") //获取type值不等于'li_01'的元素 $("[type*='li']") //模糊匹配 获取type值包含'li'的元素 $("[type^='li']") //模糊匹配 获取type值以'li'开始的元素 $("[type$='01']") //模糊匹配 获取type值以'01'结尾的元素 $("li[class='check'][type]") //获取多个条件同时满足的元素 * 选择器//遍历form下的所有元素,将其margin设置0 $('form *').css('margin','0px') 并列选择器$('p, div').css('color','red'); //将p元素和div元素的字体颜色设置为red 层叠选择器<div class='a'> <!-- 父级div --> <div class='a1'> <!-- 子级div1 --> <div class='a11'></div> <!-- 孙级div1 --> </div> <div class='a2'></div> <!-- 子级div2 --> <div class='a3'></div> <!-- 子级div3 --> <span></span> <!-- 子级span1 --> </div> $(".a div") //选择class=a的元素下所有的div 即选择到子级div1,2,3 孙级div1 $(".a > div") //选择class=a的元素的所有子div元素, 即选择到子级div1,2,3; $("div + span") //选择所有的div元素的下一个input元素节点,即选择到:子级div3 $(".a1 ~ div") //同胞选择器,返回class为a2的标签元素的所有属于同一个父元素的div标签,即div1,2,3 基本过滤选择器<ul> <li></li> <li></li> <li></li> <li></li> <li></li> </ul> $("li:first") //选择所有li元素的第一个 $("li:last") //选择所有li元素的最后一个 $("li:even") //选择所有li元素的第0,2,4... ...个元素(序号从0开始) $("li:odd") //选择所有li元素的第1,3,5... ...个元素 $("li:eq(2)") //选择所有li元素中的第三个(即序号为2) $("li:gt(3)") //选择所有li元素中序号大于3的li元素 $("li:ll(2)") //选择所有li元素中序号小于2的li元素 <input type="checkbox" /> <input type="checkbox" /> $("input[type='checkbox']:checked") //获取所有已被选中的type等于checkbox的input元素 $("input[type='checkbox']:not(:checked)") //获取所有未被选中的type等于checkbox的input元素 内容过滤器$("div:contains('Faker')") //选择所有div中含有Faker文本的元素 $("div:empty") //选择所有div中为空(不包含任何元素/文本)的元素 $("div:has('p')") //选择所有div中包含p元素的元素 $("div:parent") //选择所有的含有子元素或文本的div 可视化过滤器$("div:hidden") //选择所有被hidden的div元素 $("div:not(:hidden)") //选择所有没有被hidden的div元素 $("div:visible") //所有可视化的div元素 $("div:not(:visible)") //所有非可视化的div元素 子元素过滤器<body> <div class='d1'> <div class='d11'> <div class='d111'> </div> </div> </div> </body> $("body div:first-child") //返回所有的body元素下 所有div 为父元素的第一个元素 的元素. //:first 与 :first-child 的区别用法 //$("body div:first")只匹配到第一个合适的元素 即只匹配到 d1 //$("body div:first-child") 匹配所有合适的元素:d1是body的第一个元素,d11是d1的第一个元素.. //所以匹配到d1,d11,d111 $("div span:last-child") //返回所有的body元素下 所有div 为父元素的最后一个元素 的元素. //:last 与 :last-child 的区别参考first $("div button:only-child") //如果button是它父级元素的唯一子元素,此button将会被匹配 表单元素选择器$(":input") //选择所有的表单输入元素,包括input, textarea, select 和 button $(":text") //选择所有的text input元素 $(":password") //选择所有的password input元素 $(":radio") //选择所有的radio input元素 $(":checkbox") //选择所有的checkbox input元素 $(":submit") //选择所有的submit input元素 $(":image") //选择所有的image input元素 $(":reset") //选择所有的reset input元素 $(":button") //选择所有的button input元素 $(":file") //选择所有的file input元素 $(":hidden") //选择所有类型为hidden的input元素或表单的隐藏域 表单元素过滤器$(":enabled") //选择所有的可操作的表单元素 $(":disabled") //选择所有的不可操作的表单元素 $(":checked") //选择所有的被checked的表单元素 $("select option:selected") //选择所有的select 的子元素中被selected的元素 节点操作获取和操作节点属性<a href='index.html' data-type='a' style="color:red;">index</a> <input value='user' /> $("a").attr("href"); //获取href属性值 $("a").attr("href","about.html"); //设置href属性值 $("a").data("type"); //获取data-type属性值 $("a").css("color"); //通过key(color/display/....)获取css值 $("a").css("color","black"); //通过key/value 设置css属性 $("a").text(); //获取a的文本节点值 $("a").text("Index.html"); //设置a的文本节点值 $("input").val(); //获取input的value值 $("input").val("username"); //设置input的value值 插入节点的方法<div class="head"> <span>Faker<span> </div> $(".head").append("<span>hello</span>") //在.head中的最后插入一段html //结果: <div class="head"><span>Faker</span><span>hello</span></div> $("<span>hello</span>").appendTo(".head") //在.head中的最后插入一段html, //结果: <div class="head"><span>Faker</span><span>hello</span></div> $(".head").prepend("<span>hello</span>") //在.head中的开始插入一段html, //结果: <div class="head"><span>hello</span><span>Faker</span></div> $("<span>hello</span>").prependTo(".head") //在.head中的开始插入一段html, //结果: <div class="head"><span>hello</span><span>Faker</span></div> $(".head *:first").after("<span>hello</span>") //在.head中的第一个元素后插入一段html, //结果: <div class="head"><span>Faker</span><span>hello</span></div> $("<span>hello</span>").insertAfter(".head *:first") //在.head中的第一个元素后插入一段html, //结果: <div class="head"><span>Faker</span><span>hello</span></div> $(".head *:first").before("<span>hello</span>") //在.head中的第一个元素前插入一段html, //结果: <div class="head"><span>hello</span><span>Faker</span></div> $("<span>hello</span>").insertBefore(".head *:first") //在.head中的第一个元素后插入一段html, //结果: <div class="head"><span>hello</span><span>Faker</span></div> $.load()方法 在指定位置加载请求回来的html页面 <div class="head"> </div> $(".head").load(url[,data][,callback]) url: 请求HTML页面的URL地址 data(可选): 请求的key/value参数 callback(可选) 请求完成的回调函数","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"jQuery","slug":"jQuery","permalink":"http://yelog.org/tags/jQuery/"}]},{"title":"idea常用快捷键","slug":"idea-shortcuts","date":"2016-11-05T07:22:48.000Z","updated":"2024-07-05T11:10:22.479Z","comments":true,"path":"2016/11/05/idea-shortcuts/","permalink":"http://yelog.org/2016/11/05/idea-shortcuts/","excerpt":"工欲善其事 , 必先利其器","text":"工欲善其事 , 必先利其器 Idea作为IDE是相当niubility,但是要运用自如还得掌握一些常用快捷键,才能在开发过程中运用自如以下是idea的默认快捷键,如果英语能力没有问题,可以在Help->Keymap Reference 查看官方文档当然作为一款优秀的IDE,怎么会少了自定义快捷键(File->Setting->Keymap,可通过动作名/快捷键组合双向查找)好了,下面是博主在Java相关开发过程中常用到的一些快捷键 常用快捷键组合编辑 序号 快捷键组合 作用 Ctrl+D 重复光标所在行/或选中部分 Ctrl+C 复制光标所在行/或选中部分 Ctrl+V 粘贴 Ctrl+Shift+V 选择粘贴最近5次复制的内容 Ctrl+X 删除光标所在行/或选中部分 Ctrl+Y 删除光标所在行/或选中行 Shift+Enter 向下插入新行 Alt+Shift+↑/↓ 移动当前行到上/下一行 Ctrl+Alt+←/→ 定位到上/下一次光标位置 Ctrl+I 实现接口方法 Ctrl+Shift+o 删除没用的import Ctrl+O 重写父类方法 Ctrl+W 选中当前单词 Ctrl+P 提示参数 Ctrl+Q 查看方法/类的注释文档 Ctrl+Alt+L 格式化当前模板 Ctrl+/ 注释当前行,或选中行 Ctrl+Shift+/ 注释选中部分 /**+回车(类/方法/属性前) 添加注释 搜索/替换 序号 快捷键组合 作用 Ctrl+N 通过类名(文件名)的关键字快速打开文件(仅限.java文件) Ctrl+Shift+N 通过文件名关键字快速打开文件 Ctrl+Shift+N(两次) 通过文件名关键字快速打开文件(包括非本项目内文件,如Maven引入的) Ctrl+Shift+Alt+N 通过关键字(包括类名/方法名/url映射)快速打开文件,定位到类名/方法名/url映射的方法 Ctrl+F 搜索关键字(支持正则)在当前文件 F3 找下一个 Shift+F3 找上一个 Ctrl+R 替换关键字(支持正则)在当前文件 Ctrl+Shift+F 在所有文件(可以指定过滤文件)中查找关键字(支持正则) Ctrl+Shift+R 在所有文件(可以指定过滤文件)中替换关键字(支持正则) debugging 序号 快捷键组合 作用 F8 Step over(跳过下一行) F7 Step into(跳入当前行调用的方法体内) Shift+F7 Smart Step into(跳过) 未完待续","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"软件记录","slug":"工具/软件记录","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95/"}],"tags":[{"name":"IntellijIDEA","slug":"IntellijIDEA","permalink":"http://yelog.org/tags/IntellijIDEA/"}]},{"title":"web.xml详解","slug":"web-xml详解","date":"2016-10-24T02:10:45.000Z","updated":"2024-07-05T11:10:22.649Z","comments":true,"path":"2016/10/24/web-xml/","permalink":"http://yelog.org/2016/10/24/web-xml/","excerpt":"web.xml文件是用来配置:欢迎页、servlet、filter、listener等的. 当你的web项目工程没用到这些时,你可以不用web.xml文件来配置你的web工程。如果项目中有多项标签,其加载顺序依次是:context-param >> listener >> filter >> servlet(同类多个节点出现顺序依次加载)","text":"web.xml文件是用来配置:欢迎页、servlet、filter、listener等的. 当你的web项目工程没用到这些时,你可以不用web.xml文件来配置你的web工程。如果项目中有多项标签,其加载顺序依次是:context-param >> listener >> filter >> servlet(同类多个节点出现顺序依次加载) web.xml先读取context-param和listener这两种节点; 然后容器创建一个ServletContext(上下文),应用于整个项目; 容器会将读取到的context-param转化为键值对并存入servletContext; 根据listener创建监听; 容器会读取,根据指定的类路径来实例化过滤器; 此时项目初始化完成; 在发起第一次请求是,servlet节点才会被加载实例化。 参数设置context-paramcontext-param节点是web.xml中用于配置应用于整个web项目的​上下文。包括两个子节点,其中param-name 设定上下文的参数名称。必须是唯一名称;param-value 设定的参数名称的值。 读取节点的方法如下: ${initParam.参数名} Servlet中String paramValue=getServletContext().getInitParameter(“参数名”)​ web.xml中配置spring必须使用listener节点,但context-param节点可有可无,如果缺省则默认contextConfigLocation路径为“/WEB-INF/applicationContext.xml”;如果有多个xml文件,使用”,“分隔 listener<listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener> 为web应用程序定义监听器,监听器用来监听各种事件,比如:application和session事件,所有的监听器按照相同的方式定义,功能取决去它们各自实现的接口,常用的Web事件接口有如下几个:ServletContextListener:用于监听Web应用的启动和关闭;ServletContextAttributeListener:用于监听ServletContext范围(application)内属性的改变;ServletRequestListener:用于监听用户的请求;ServletRequestAttributeListener:用于监听ServletRequest范围(request)内属性的改变;HttpSessionListener:用于监听用户session的开始和结束;HttpSessionAttributeListener:用于监听HttpSession范围(session)内属性的改变。 配置Listener只要向Web应用注册Listener实现类即可,无序配置参数之类的东西,因为Listener获取的是Web应用ServletContext(application)的配置参数。为Web应用配置Listener的两种方式: 使用@WebListener修饰Listener实现类即可。 在web.xml文档中使用进行配置。 servletservlet即配置所需用的servlet,用于处理及响应客户的请求。容器的Context对象对请求路径(URL)做出处理,去掉请求URL的上下文路径后,按路径映射规则和Servlet映射路径()做匹配,如果匹配成功,则调用这个Servlet处理请求。 为Servlet命名:<servlet> <servlet-name>servlet</servlet-name> <servlet-class>org.whatisjava.TestServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> 为Servlet定制URL<servlet-mapping> <servlet-name>servlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> Load-on-startupLoad-on-startup 元素在web应用启动的时候指定了servlet被加载的顺序,它的值必须是一个整数。如果它的值是一个负整数或是这个元素不存在,那么容器会在该servlet被调用的时候,加载这个servlet 。如果值是正整数或零,容器在配置的时候就加载并初始化这个servlet,容器必须保证值小的先被加载。如果值相等,容器可以自动选择先加载谁。当值为0或者大于0时,表示容器在应用启动时就加载这个servlet;当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。正数的值越小,启动该servlet的优先级越高。 filter设置过滤器:如编码过滤器,过滤所有资源 <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> session设置会话(Session)过期时间,其中时间以分钟为单位,加入设置60分超时: <session-config> <session-timeout>60</session-timeout> </session-config> welcom-file-list<welcome-file-list> <welcome-file>index.jsp</welcome-file> <welcome-file>index.html</welcome-file> </welcome-file-list> PS:指定了两个欢迎页面,显示时按顺序从第一个找起,如果第一个存在,就显示第一个,后面的不起作用。如果第一个不存在,就找第二个,以此类推。如果都没有就404. 关于欢迎页面:访问一个网站时,默认看到的第一个页面就叫欢迎页,一般情况下是由首页来充当欢迎页的。一般情况下,我们会在web.xml中指定欢迎页。但 web.xml并不是一个Web的必要文件,没有web.xml,网站仍然是可以正常工作的。只不过网站的功能复杂起来后,web.xml的确有非常大用处,所以,默认创建的动态web工程在WEB-INF文件夹下面都有一个web.xml文件。 error-page<!-- 错误码 --> <error-page> <error-code>404</error-code> <location>/error404.jsp</location> </error-page> <!-- 错误类型 --> <error-page> <exception-type>java.lang.Exception<exception-type> <location>/exception.jsp<location> </error-page>","categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"javaee","slug":"javaee","permalink":"http://yelog.org/tags/javaee/"}]},{"title":"Hexo+Git服务器搭建blog","slug":"hexo-git-server-blog","date":"2016-10-23T01:24:03.000Z","updated":"2024-07-05T11:10:22.490Z","comments":true,"path":"2016/10/23/hexo-git-server-blog/","permalink":"http://yelog.org/2016/10/23/hexo-git-server-blog/","excerpt":"博主最近在服务器上搭建Hexo发布平台,感觉整个搭建过程和搭建思想蛮有意思,在此记录一下,供猿友参考Hexo 是一个快速,简单,功能强大,主题社区特别庞大的开源blog框架-》官网本次搭建是通过在服务器上搭建Git服务器来实现一键发布blog","text":"博主最近在服务器上搭建Hexo发布平台,感觉整个搭建过程和搭建思想蛮有意思,在此记录一下,供猿友参考Hexo 是一个快速,简单,功能强大,主题社区特别庞大的开源blog框架-》官网本次搭建是通过在服务器上搭建Git服务器来实现一键发布blog 搭建思路 客户端就是自己的电脑,可以把hexo的静态资源目录当成一个git仓库. 首先配置好远程git仓库,通过 hexo d 将静态网站资源push到远程git仓库 git仓库接收到push处理完成后,自动触发post-receive这个钩子. 执行钩子内容,进入到 /var/www/blog 目录(也是一个git仓库),拉取刚才hexo推送到git服务端的静态网站资源. 配置nginx,将80端口映射到 /var/www/blog 目录. 就可以直接通过ip访问到静态blog了 搭建过程环境准备在服务器上安装git并创建git远程仓库 如 blog.git搭建过程移步 搭建Git服务器 在 _config.yml 中配置git服务器deploy: type: git repo: git@server:/home/git/blog.git branch: master 如果ssh端口不是默认的22的话,如下配置,8080改为自己服务器上ssh端口 deploy: type: git repo: ssh://git@server:8080/home/git/blog.git branch: master 配置nginx现在已经可以使用 hexo d 将hexo中的生成的静态资源发送到远程服务器中,接下来我们要配置nginx来配置静态web。安装过程可以自行Google,在此只说明nginx如何配置静态web首先创建一个目录作为存放web资源(hexo生成的)的目录,如: /var/www/blog cd /var/www #创建blog目录,并克隆blog.git仓库的内容 git clone /home/git/blog.git blog 找到 nginx.conf 添加以下信息 server { listen 80; charset utf-8; root /var/www/blog; index index.htm index.html index.jsp; } #重启并加载配置文件 $ nginx -s reload 配置git服务器hooks这个钩子的作用是,当git服务器接受客户端push完成更新,执行此文件内容 #创建并编辑post-receive $ vim blog.git/hooks/post-receive 内容如下 #!/bin/sh unset GIT_DIR #还原环境变量,否则会拉不到代码 cd /var/www/blog git pull origin master #拉取最新代码 测试效果在本地的hexo下执行 hexo d查看 /var/www/blog文件夹内的内容也发生变化","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"},{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"}]},{"title":"Hexo+GitHub Pages搭建属于自己的blog","slug":"hexo-gitHub-pages-create-own-blog","date":"2016-10-22T13:24:05.000Z","updated":"2024-07-05T11:10:22.571Z","comments":true,"path":"2016/10/22/hexo-gitHub-pages-create-own-blog/","permalink":"http://yelog.org/2016/10/22/hexo-gitHub-pages-create-own-blog/","excerpt":"Hexo是一个快速,简单,功能强大的开源博客框架-》官网GitHub Pages 是一个不受限的网站空间。两者相得益彰。给那些喜欢自己折腾的人提供一些借鉴。","text":"Hexo是一个快速,简单,功能强大的开源博客框架-》官网GitHub Pages 是一个不受限的网站空间。两者相得益彰。给那些喜欢自己折腾的人提供一些借鉴。 搭建过程环境介绍博主使用系统:Deepin Linux 15.3桌面版安装 node与npm 安装Hexonpm install hexo-cli -g 初始化bloghexo init blog 至此,本地blog已经创建完成,是不是很简单,简单到没朋友 选择主题可以在hexo官网查看自己喜欢的主题通过git clone [url] themes/xxx 将主题克隆到本地,修改 _config.yml 中的theme:xxx 常用命令#创建一个新的文章 $ hexo new "文章名" #生成静态文件 $ hexo generate #将一个草稿发布出去 $ hexo publish [layout] <filename> #启动一个本地服务器 $ hexo server 更多命令移步官方文档 搭建github pages本地blog已经搭建完成,现在可以发布到github pages上 注册github账户到github官网注册一个github账户 配置登录免密码移步 Git之SSH与HTTPS免密码配置 创建github远程仓库在github上创建一个仓库 xxx.github.io xxx为自己的github用户名 安装插件$ npm install hexo-deployer-git --save 配置Hexo修改 _comfig.yml,xxx为你的用户名 deploy: type: git repo: git@github.com:xxx/xxx.github.io.git branch: master 推送服务器$ hexo deploy 若出现ERROR Deployer not found: git报错,请执行上面安装插件步骤 测试打开 xxx.github.io ,就能看到你的blog了","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"},{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"},{"name":"GitHub","slug":"GitHub","permalink":"http://yelog.org/tags/GitHub/"}]},{"title":"搭建Git服务器","slug":"set-up-git-server-on-vps","date":"2016-10-22T12:08:04.000Z","updated":"2024-07-05T11:10:22.607Z","comments":true,"path":"2016/10/22/set-up-git-server-on-vps/","permalink":"http://yelog.org/2016/10/22/set-up-git-server-on-vps/","excerpt":"最近由于准备在公司的服务器上面搭建静态博客(Hexo),然后需要先搭建一个git服务器作为转接,整个过程看似顺利,十几分钟就搭建完成,不过最后在验证这块卡了两个小时,在此记录下来,供准备搭建git服务器的新手小伙伴们借鉴。","text":"最近由于准备在公司的服务器上面搭建静态博客(Hexo),然后需要先搭建一个git服务器作为转接,整个过程看似顺利,十几分钟就搭建完成,不过最后在验证这块卡了两个小时,在此记录下来,供准备搭建git服务器的新手小伙伴们借鉴。 搭建git服务器通过ssh链接到服务器,开始进行操作 第一步在服务器上安装 git $ sudo apt-get install git 第二步创建 git 用户,用来运行git服务 $ sudo adduser git 第三步创建证书,免密码登录:收集所有需要登录的用户的公钥(id_rsa.pub)文件,把所有公钥导入到 /home/git/.ssh/authorized_keys 文件内,一行一个。如果个人的git中的公钥已经连接了其他服务器如:github,可以参考 一个客户端设置多个github账号 注意:一定要通过下面的命令将该文件其他用户的所有权限移除,否则会出现文章尾部问题 $ chmod 600 authorized_keys 第四步初始化git仓库 $ git init --bare test.git git创建一个裸仓库,裸仓库没有工作区,因为服务器上的git仓库纯粹为了共享,所有不能让用户直接登录到服务器上去改工作区,并且服务器的git仓库通常以 .git 结尾。然后,修改owner改为git: $ sudo chown -R git:git test.git 第五步禁用shell登录:处于安全的考虑,第二步创建的git用户不允许登录shell,这可以通过编辑 /etc/passwd 文件完成。 git:x:1003:1003::/home/git:/bin/bash 改为 git:x:1003:1003::/home/git:/usr/bin/git-shell 这样,git用户可以正常通过ssh使用git,但无法登录shell,因为我们为git用户指定的git-shell每次一登录就自动退出。 第六步克隆远程仓库:现在,可以通过git clone命令克隆远程仓库了,在各自的电脑上运行: $ git clone git@server:/home/git/test.git 如果服务器的ssh端口不是默认的22的话,比如说6789,可以这样写: $ git clone ssh://git@server:6789/home/git/test.git 问题来了本来根据文档,根据广大猿友的经验,我的搭建之路已经完成了,然后最后一步出现了问题。每次跟服务器进行交互(clone,pull,push),都让我输入git的密码,也就是说,我配置的ssh没有生效。然后就开始到处找原因,重新生成rsa,提升authorized_keys权限,重新创建服务器git账户,重新。。。。。 翻遍了 Stack Overflow 和 segmentfault ,两个小时过去了,问题仍然没有进展,这么简单的东西,问题到底出在哪里。 就在心灰意冷,准备放弃的时候,不知道是哪里来的灵感,准备把 authorized_keys 文件的其他用户的权限删掉,然后就能用了,后就能用了,就能用了,能用了,用了,了~~~~,命令如下,不想多说话,我想静静。 $ chmod 600 authorized_keys","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"}]},{"title":"如何给GitHub上的项目贡献代码","slug":"contributing-to-open-source-on-github","date":"2016-10-13T07:44:07.000Z","updated":"2024-07-05T11:10:22.622Z","comments":true,"path":"2016/10/13/contributing-to-open-source-on-github/","permalink":"http://yelog.org/2016/10/13/contributing-to-open-source-on-github/","excerpt":"最近一直在使用 hexo 的一款主题 yelee ,但是发现它的代码块由于空行不占位导致的显示错位,所以就去GitHub上翻issue,果然有好多人都在反映这个问题,并且作者已经打上bug标签,事情应该就马上结束了,就去忙别的了。这两天又去逛了一下issue,发现这个bug仍然屹立在那里,强迫症又犯了,趁着今天工作不怎么忙,就把这个bug解决了。然后问题来了,怎么才能给作者贡献代码呢。","text":"最近一直在使用 hexo 的一款主题 yelee ,但是发现它的代码块由于空行不占位导致的显示错位,所以就去GitHub上翻issue,果然有好多人都在反映这个问题,并且作者已经打上bug标签,事情应该就马上结束了,就去忙别的了。这两天又去逛了一下issue,发现这个bug仍然屹立在那里,强迫症又犯了,趁着今天工作不怎么忙,就把这个bug解决了。然后问题来了,怎么才能给作者贡献代码呢。 准备工作 首先通过 git clone 将项目克隆到本地(我早已拉下来,跳过此步骤) git pull 拉取最新代码(将所有的change都同步到本地) 将 原项目 fork 到 自己的github上,并复制代码url 在本地添加第二个仓库地址:git remote add [nickname] [your url] 修改 修改bug 或 新增功能 git commit [file1] [file2] ... -m [message] 本地提交代码 同步到github中并发到原项目 git push [nickname] 将代码 push 到自己的项目里,nickname就是添加的第二个仓库的名字 自己项目内,点击 pull requests -》 new pull request 将本次修改提交到原项目进行同步。","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"},{"name":"GitHub","slug":"GitHub","permalink":"http://yelog.org/tags/GitHub/"}]},{"title":"PostgreSQL常用操作","slug":"PostgreSQL常用操作","date":"2016-10-12T12:27:07.000Z","updated":"2024-07-05T11:10:22.304Z","comments":true,"path":"2016/10/12/PostgreSQL-2/","permalink":"http://yelog.org/2016/10/12/PostgreSQL-2/","excerpt":"控制台命令\\h: #查看SQL命令的解释,比如\\h select。 \\?: #查看psql命令列表。 \\l: #列出所有数据库。 \\c [database_name]: #连接其他数据库。 \\d: #列出当前数据库的所有表格。 \\d [table_name]: #列出某一张表格的结构。 \\du: #列出所有用户。 \\e: #打开文本编辑器。 \\conninfo: #列出当前数据库和连接的信息。","text":"控制台命令\\h: #查看SQL命令的解释,比如\\h select。 \\?: #查看psql命令列表。 \\l: #列出所有数据库。 \\c [database_name]: #连接其他数据库。 \\d: #列出当前数据库的所有表格。 \\d [table_name]: #列出某一张表格的结构。 \\du: #列出所有用户。 \\e: #打开文本编辑器。 \\conninfo: #列出当前数据库和连接的信息。 数据库操作基本的数据库操作,就是使用一般的SQL语言 # 创建新表 CREATE TABLE user_tbl(name VARCHAR(20), signup_date DATE); # 插入数据 INSERT INTO user_tbl(name, signup_date) VALUES('张三', '2013-12-22'); # 选择记录 SELECT * FROM user_tbl; # 更新数据 UPDATE user_tbl set name = '李四' WHERE name = '张三'; # 删除记录 DELETE FROM user_tbl WHERE name = '李四' ; # 添加栏位 ALTER TABLE user_tbl ADD email VARCHAR(40); # 更新结构 ALTER TABLE user_tbl ALTER COLUMN signup_date SET NOT NULL; # 更名栏位 ALTER TABLE user_tbl RENAME COLUMN signup_date TO signup; # 删除栏位 ALTER TABLE user_tbl DROP COLUMN email; # 表格更名 ALTER TABLE user_tbl RENAME TO backup_tbl; # 删除表格 DROP TABLE IF EXISTS backup_tbl;","categories":[{"name":"数据库","slug":"数据库","permalink":"http://yelog.org/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"}],"tags":[{"name":"PostgreSQL","slug":"PostgreSQL","permalink":"http://yelog.org/tags/PostgreSQL/"}]},{"title":"PostgreSQL初体验","slug":"PostgreSQL初体验","date":"2016-10-12T11:49:40.000Z","updated":"2024-07-05T11:10:22.318Z","comments":true,"path":"2016/10/12/PostgreSQL-1/","permalink":"http://yelog.org/2016/10/12/PostgreSQL-1/","excerpt":"创建操作系统用户创建一个新的Linux用户:dbuser $sudo adduser dbuser #创建一个新的Linux用户:dbuser","text":"创建操作系统用户创建一个新的Linux用户:dbuser $sudo adduser dbuser #创建一个新的Linux用户:dbuser 登录PostgreSQL控制台切换到postgres用户 $sudo su - postgres #切换到postgres用户 系统用户postgres以同名数据库用户的身份,登录数据库 $psql #系统用户postgres以同名数据库用户的身份,登录数据库 成功登录到控制台后,显示 postgres=# 注意:后面分号不能省略 \\password postgres #给postgres用户设置密码 创建数据库用户dbuser CREATE USER dbuser WITH PASSWORD 'dbuser'; #创建数据库用户dbuser 创建用户数据库,这里为exampledb,并指定所有者为dbuser。 CREATE DATABASE exampledb OWNER dbuser; #创建用户数据库,这里为exampledb,并指定所有者为dbuser。 将exampledb数据库的所有权限都赋予dbuser GRANT ALL PRIVILEGES ON DATABASE exampledb to dbuser; #将exampledb数据库的所有权限都赋予dbuser 推出控制台(也可以直接按ctrl+D) \\q #退出控制台","categories":[{"name":"数据库","slug":"数据库","permalink":"http://yelog.org/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"}],"tags":[{"name":"PostgreSQL","slug":"PostgreSQL","permalink":"http://yelog.org/tags/PostgreSQL/"}]},{"title":"PostgreSQL的介绍与安装","slug":"PostgreSQL的安装","date":"2016-10-12T07:44:16.000Z","updated":"2024-07-05T11:10:22.309Z","comments":true,"path":"2016/10/12/PostgreSQL-3/","permalink":"http://yelog.org/2016/10/12/PostgreSQL-3/","excerpt":"由于工作认识了PostgreSQL,在此系统学习一下这个数据库,本文除博主实践所得以外,大量译于 官方文档 PostgreSQL是什么 PostgreSQL 是一个基于 POSTGRES, Version 4.2 的对象关系数据库系统(ORDBMS),由加州大学伯克利分校计算机科学系开发。PostgreSQL 是一个开源的数据库,因为自由许可,任何人都可以免费的使用、修改、分发 PostgreSQL 数据库用于任何目的。","text":"由于工作认识了PostgreSQL,在此系统学习一下这个数据库,本文除博主实践所得以外,大量译于 官方文档 PostgreSQL是什么 PostgreSQL 是一个基于 POSTGRES, Version 4.2 的对象关系数据库系统(ORDBMS),由加州大学伯克利分校计算机科学系开发。PostgreSQL 是一个开源的数据库,因为自由许可,任何人都可以免费的使用、修改、分发 PostgreSQL 数据库用于任何目的。 它支持大部分的SQL标准并提供了许多流行的功能: 复杂查询(complex queries) 外键(foreign keys) 触发器(triggers) 可更新的视图(updatable views) 事务完整性(transactional integrity) 多版本并发控制(multiversion concurrency control) 用户也可以给PostgreSQL扩展很多东西,比如: 数据类型(data types) 函数(functions) 运算符(operators) 聚合函数(aggregate functions) 索引方法(index methods) 安装博主开发环境: 系统 :深度Linux 15.3 桌面版 PostgreSQL :9.4 通过apt-get安装$ apt-get install postgresql-9.4 仓库有许多不同的包(包括第三方插件),最常见、最重要的包(根据需要替换版本号): postgresql-client-9.4 - 客户端库和二进制文件 postgresql-9.4 - 核心数据库服务器 postgresql-contrib-9.4 - 提供额外的模块 libpq-dev - C语言前端开发库和头文件 postgresql-server-dev-9.4 - C语言后端开发库和头文件 pgadmin3 - pgAdmin III 图形化管理工具","categories":[{"name":"数据库","slug":"数据库","permalink":"http://yelog.org/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"}],"tags":[{"name":"PostgreSQL","slug":"PostgreSQL","permalink":"http://yelog.org/tags/PostgreSQL/"}]},{"title":"FreeMarker语法详解","slug":"freemarker语法详解","date":"2016-10-05T03:58:50.000Z","updated":"2024-07-05T11:10:22.416Z","comments":true,"path":"2016/10/05/FreeMarker/","permalink":"http://yelog.org/2016/10/05/FreeMarker/","excerpt":"FreeMarker是一款 模板引擎 :即一种基于模板和要改变的数据,并用来生成输出文本(HTML网页、电子邮件、配置文件、源代码等)的通用工具。FreeMarker模板文件主要有4部分组成 文本,直接输出的部分 注释,即<#–…–>格式不会输出 插值(Interpolation):即${..}或者#{..}格式的部分,将使用数据模型中的部分替代输出 FTL指令:FreeMarker指令,和HTML标记类似,名字前加#予以区分,不会输出。","text":"FreeMarker是一款 模板引擎 :即一种基于模板和要改变的数据,并用来生成输出文本(HTML网页、电子邮件、配置文件、源代码等)的通用工具。FreeMarker模板文件主要有4部分组成 文本,直接输出的部分 注释,即<#–…–>格式不会输出 插值(Interpolation):即${..}或者#{..}格式的部分,将使用数据模型中的部分替代输出 FTL指令:FreeMarker指令,和HTML标记类似,名字前加#予以区分,不会输出。 一些规则FTL指令规则FreeMarker有三种FTL标签,这和HTML的标签是完全类似的 开始标签:<#directivename parameters> 结束标签:</#directivename> 空标签: <#directivename parameters /> 实际上,使用标签时前面的#符号也可能变成@,如果该指令是一个用户指令而不是系统内建指令时,应将#符号改为@符号 插值规则FreeMarker的插值有如下两种类型 1、通用插值:${expr} 2、数字格式化插值:#{expr}或者#{expr;format}通用插值,有可以分为四种情况 a、插值结果为字符串值:直接输出表达式结果 b、插值结果为数字值:根据默认格式(#setting 指令设置)将表达式结果转换成文本输出。可以使用内建的字符串函数格式单个插值,例如 <#setting number_format = "currency" /> <#assign str = 42 /> ${str} ${str?string} ${str?string.number} ${str?string.currency} ${str?string.percent} ${str?string.computer} 日期处理 ${openingTime?string.short} ${openingTime?string.medium} ${openingTime?string.long} ${openingTime?string.full} ${nextDiscountDay?string.short} ${nextDiscountDay?string.medium} ${nextDiscountDay?string.long} ${nextDiscountDay?string.full} ${lastUpdated?string.short} ${lastUpdated?string.medium} ${lastUpdated?string.long} ${lastUpdated?string.full} ${lastUpdated?string("yyyy-MM-dd HH:mm:ss zzzz")} ${lastUpdated?string("EEE, MMM d, ''yy")} ${lastUpdated?string("EEEE, MMMM dd, yyyy, hh:mm:ss a '('zzz')'")} if,elseif,elseif<#if condition> …… <#elseif condition2> …… <#else> …… </#if> switch,case<#switch value> <#case refValue1> ... <#break> <#case refValue2> ... <#break> ... <#case refValueN> ... <#break> <#default> ... </#switch> <#t> 去掉左右空白和回车换行 <#lt>去掉左边空白和回车换行 <#rt>去掉右边空白和回车换行 <#nt>取消上面的效果 list<#list sequence as item> ... <#if item = "spring"> <#break> </#if> ... </#list> iterm_index:当前值得下标,从0开始item_has_next:判断list是否还有值 include<#include filename [options]> options 包含两个属性encoding=”GBK”parse=”true” 是否作为ftl语法解析,默认是true示例:<#include “/common/copyright.ftl” encoding=”GBK” parse=”true”> import<#import path as hash> 类似于java里的import,它导入文件,然后就可以在当前文件里使用被导入文件里的宏组件 compress<#compress> ... </#compress> escape, noescape<#escape identifier as expression> ... <#noescape>...</#noescape> ... </#escape> 主要使用在相似的字符串变量输出,比如某一个模块的所有字符串输出都必须是html安全的,这个时候就可以使用该表达式示例: <#escape x as x?html> First name: ${firstName} <#noescape>Last name: ${lastName}</#noescape> Maiden name: ${maidenName} </#escape> 相同表达式 First name: ${firstName?html} Last name: ${lastName } Maiden name: ${maidenName?html} assign<#assign name=value> <#-- 或则 --> <#assign name1=value1 name2=value2 ... nameN=valueN> <#-- 或则 --> <#assign same as above... in namespacehash> <#-- 或则 --> <#assign name> capture this </#assign> <#-- 或则 --> <#assign name in namespacehash> capture this </#assign> 生成变量,并且给变量赋值 global<#global name=value> <#--或则--> <#global name1=value1 name2=value2 ... nameN=valueN> <#--或则--> <#global name> capture this </#global> 全局赋值语法,利用这个语法给变量赋值,那么这个变量在所有的namespace [A1] 中是可见的, 如果这个变量被当前的assign 语法覆盖 如<#global x=2> <#assign x=1> 在当前页面里x=2 将被隐藏,或者通过${.global.x} 来访问 setting<#setting name=value> 用来设置整个系统的一个环境localenumber_formatboolean_formatdate_format , time_format , datetime_formattime_zoneclassic_compatible macro, nested, return<#macro name param1 param2 ... paramN> ... <#nested loopvar1, loopvar2, ..., loopvarN> ... <#return> ... </#macro> t, lt, rt<#t> 去掉左右空白和回车换行 <#lt>去掉左边空白和回车换行 <#rt>去掉右边空白和回车换行 <#nt>取消上面的效果","categories":[{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"freemarker","slug":"freemarker","permalink":"http://yelog.org/tags/freemarker/"}]},{"title":"正则表达式详解","slug":"regular-expression","date":"2016-10-04T01:41:40.000Z","updated":"2024-07-05T11:10:22.450Z","comments":true,"path":"2016/10/04/regular-expression/","permalink":"http://yelog.org/2016/10/04/regular-expression/","excerpt":"本文目标 本文旨在更加简洁清晰的展现正则表达式, 第一部分 是对正则表达式语法的简洁介绍, 第二部分 则是常用正则表达式的示例。","text":"本文目标 本文旨在更加简洁清晰的展现正则表达式, 第一部分 是对正则表达式语法的简洁介绍, 第二部分 则是常用正则表达式的示例。 简介及语法正则表达式是什么 在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。 正则表达式语言由两种基本字符类型组成:原义(正常)文本字符和元字符。元字符使正则表达式具有处理能力。 常用元字符 元字符 做什么用 . 匹配除换行符以外的任意字符 \\w 匹配字母或数字或下划线或汉字 \\s 匹配任意的空白符,包括空格,制表符(Tab),换行符,中文全角空格等) \\d 匹配数字 \\b 匹配单词的开始或结束 ^ 匹配字符串的开始 $ 匹配字符串的结束 字符转义 如果你想查找元字符本身的话,比如你查找 . ,或者 * ,就出现了问题:你没办法指定它们,因为它们会被解释成别的意思。这时你就得使用 \\ 来取消这些字符的特殊意义。因此,你应该使用 \\. 和 \\* 。当然,要查找 \\ 本身,你也得用 \\\\ .例如: deerchao\\.net 匹配 deerchao.net,C:\\\\Windows 匹配 C:\\Windows 。 重复 你已经看过了前面的 * , + , {2} , {5,12} 这几个匹配重复的方式了。下面是正则表达式中所有的限定符(指定数量的代码,例如*,{5,12}等) 元字符 做什么用 * 重复零次或更多次 + 重复一次或更多次 ? 重复零次或一次 {n} 重复n次 {n,} 重复n次或更多次 {n,m} 重复n到m次 下面是一些重复的示例:Windows\\d+ 匹配Windows后面跟1个或更多数字^\\w+ 匹配一行的第一个单词(或整个字符串的第一个单词,具体匹配哪个意思得看选项设置) 字符类 要想查找数字,字母或数字,空白是很简单的,因为已经有了对应这些字符集合的元字符,但是如果你想匹配没有预定义元字符的字符集合(比如元音字母a,e,i,o,u),应该怎么办?很简单,你只需要在方括号里列出它们就行了,像 [aeiou] 就匹配任何一个英文元音字母, [.?!] 匹配标点符号(.或?或!)。我们也可以轻松地指定一个字符范围,像 [0-9] 代表的含意与 \\d 就是完全一致的:一位数字;同理[a-z0-9A-Z_]也完全等同于 \\w (如果只考虑英文的话)。下面是一个更复杂的表达式: \\(?0\\d{2}[) -]?\\d{8} 。这个表达式可以匹配几种格式的电话号码,像(010)88886666,或022-22334455,或02912345678等。我们对它进行一些分析吧:首先是一个转义字符(,它能出现0次或1次(?),然后是一个0,后面跟着2个数字(\\d{2}),然后是)或-或空格中的一个,它出现1次或不出现(?),最后是8个数字(\\d{8})。 分支条件 正则表达式里的分枝条件指的是有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用|把不同的规则分隔开。示例: 0\\d{2}-\\d{8}|0\\d{3}-\\d{7}这个表达式匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔。你可以试试用分枝条件把这个表达式扩展成也支持4位区号的。示例: \\d{5}-\\d{4}|\\d{5}这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。之所以要给出这个示例是因为它能说明一个问题:使用分枝条件时,要注意各个条件的顺序。如果你把它改成 \\d{5}|\\d{5}-\\d{4} 的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。原因是匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。 分组 重复单个字符,直接在字符后面加上限定符就行了。但如果想要重复多个字符,我们可以用小括号来指定 子表达式(也叫作分组)。(\\d{1,3}\\.){3}\\d{1,3} 是一个简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它: \\d{1,3} 匹配1到3位的数字, (\\d{1,3}\\.){3} 匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,最后再加上一个一到三位的数字 (\\d{1,3}) 。不幸的是,它也将匹配 256.300.888.999 这种不可能存在的IP地址。我们只能使用冗长的分组,选择,字符串来描述一个 正确的IP地址: ((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?) 反义 有时需要查找不属于某个能简单定义的字符类的字符。比如想查找除了数字以外,其它任意字符都行的情况,这时需要用到反义: 元字符 做什么用 \\W 匹配任意不是字母,数字,下划线,汉字的字符 \\S 匹配任意不是空白符的字符 \\D 匹配任意非数字的字符 \\B 匹配不是单词开头或结束的位置 [^x] 匹配除了x以外的任意字符 [^aeiou] 匹配除了aeiou这几个字母以外的任意字符 示例: \\S+ 匹配不包含空白符的字符串。 <a[^>]+> 匹配用尖括号括起来的以a开头的字符串。 后向引用 使用小括号指定一个子表达式后,匹配这个子表达式 的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个 组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。 后向引用 用于重复搜索前面某个分组匹配的文本。示例: \\b(\\w+)\\b\\s+\\1\\b 可以用来匹配重复的单词,像go go, 或者kitty kitty。这个表达式首先是一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字 \\b(\\w+)\\b ,这个单词会被捕获到编号为1的分组中,然后是1个或几个空白符\\s+,最后是分组1中捕获的内容(也就是前面匹配的那个单词) \\1 。你也可以自己指定子表达式的 组名.要指定一个子表达式的组名,请使用这样的语法: (?<Word>\\w+) (或者把尖括号换成 ' 也行: (?'Word'\\w+)),这样就把\\w+的组名指定为 Word 了。要反向引用这个分组捕获的内容,你可以使用 \\k<Word> ,所以上一个示例也可以写成这样: \\b(?<Word>\\w+)\\b\\s+\\k<Word>\\b 。 零宽断言 接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\\b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为 零宽断言。(?=exp) 也叫 零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp。比如\\b\\w+(?=ing\\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找 I’m singing while you’re dancing. 时,它会匹配 sing 和 danc 。(?<=exp) 也叫 零宽度正回顾后发断言 ,它断言自身出现的位置的前面能匹配表达式exp。比如 (?<=\\bre)\\w+\\b 会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找 reading a book 时,它匹配ading。假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分: ((?<=\\d)\\d{3})+\\b ,用它对1234567890进行查找时结果是234567890。下面这个示例同时使用了这两种断言: (?<=\\s)\\d+(?=\\s) 匹配以空白符间隔的数字( 再次强调,不包括这些空白符 )。 负向零宽断言 前面我们提到过怎么查找不是某个字符或不在某个字符类里的字符的方法(反义)。但是如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?例如,如果我们想查找这样的单词–它里面出现了字母q,但是q后面跟的不是字母u,我们可以尝试这样:\\b\\w*q[^u]\\w*\\b 匹配 包含后面不是字母u的字母q的单词 。但是如果多做测试(或者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像Iraq,Benq,这个表达式就会出错。这是因为[^u]总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的[^u]将会匹配q后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的 \\w*\\b 将会匹配下一个单词,于是 \\b\\w*q[^u]\\w*\\b 就能匹配整个Iraq fighting。负向零宽断言 能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,我们可以这样来解决这个问题: \\b\\w*q(?!u)\\w*\\b 。零宽度负预测先行断言(?!exp),断言此位置的后面不能匹配表达式exp。例如: \\d{3}(?!\\d) 匹配三位数字,而且这三位数字的后面不能是数字; \\b((?!abc)\\w)+\\b 匹配不包含连续字符串abc的单词。同理,我们可以用(?<!exp),*零宽度负回顾后发断言来断言** 此位置的前面不能匹配表达式exp:(?<![a-z])\\d{7}匹配前面不是小写字母的七位数字。一个更复杂的示例:`(?<=<(\\w+)>).(?=</\\1>) 匹配不包含属性的简单HTML标签内里的内容。(?<=<(\\w+)>)指定了这样的 **前缀**:被尖括号括起来的单词(比如可能是<b>),然后是.*(任意的字符串),最后是一个 **后缀**(?=</\\1>)。注意后缀里的 \\/ ,它用到了前面提过的字符转义;\\1则是一个反向引用,引用的正是捕获的第一组,前面的 (\\w+)` 匹配的内容,这样如果前缀实际上是的话,后缀就是了。整个表达式匹配的是和之间的内容(再次提醒,不包括前缀和后缀本身)。 注释 小括号的另一种用途是通过语法(?#comment)来包含注释。例如:2[0-4]\\d(?#200-249)|25[0-5](?#250-255)|[01]?\\d\\d?(?#0-199)。要包含注释的话,最好是启用“忽略模式里的空白符”选项,这样在编写表达式时能任意的添加空格,Tab,换行,而实际使用时这些都将被忽略。启用这个选项后,在#后面到这一行结束的所有文本都将被当成注释忽略掉。例如,我们可以前面的一个表达式写成这样: (?<= # 断言要匹配的文本的前缀 <(\\w+)> # 查找尖括号括起来的字母或数字(即HTML/XML标签) ) # 前缀结束 .* # 匹配任意文本 (?= # 断言要匹配的文本的后缀 <\\/\\1> # 查找尖括号括起来的内容:前面是一个”/“,后面是先前捕获的标签 ) # 后缀结束 贪婪与懒惰 当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配 尽可能多 的字符。以这个表达式为例: a.*b ,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为 贪婪匹配。有时,我们更需要 懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号 ? 。这样 .*? 就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。示例: a.*?b 匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)和ab(第四到第五个字符)。 语法 做什么用 *? 重复任意次,但尽可能少重复 +? 重复1次或更多次,但尽可能少重复 ?? 重复0次或1次,但尽可能少重复 {n,m}? 重复n到m次,但尽可能少重复 {n,}? 重复n次以上,但尽可能少重复 其他元字符 元字符 做什么用 \\a 报警字符(打印它的效果是电脑嘀一声) \\b 通常是单词分界位置,但如果在字符类里使用代表退格 \\t 制表符,Tab \\r 回车 \\v 竖向制表符 \\f 换页符 \\n 换行符 \\e Escape \\0nn ASCII代码中八进制代码为nn的字符 \\xnn ASCII代码中十六进制代码为nn的字符 \\unnnn Unicode代码中十六进制代码为nnnn的字符 \\cN ASCII控制字符。比如\\cC代表Ctrl+C \\A 字符串开头(类似^,但不受处理多行选项的影响) \\Z 字符串结尾或行尾(不受处理多行选项的影响) \\z 字符串结尾(类似$,但不受处理多行选项的影响) \\G 当前搜索的开头 \\p{name} Unicode中命名为name的字符类,例如\\p{IsGreek} (?>exp) 贪婪子表达式 (?<x>-<y>exp) 平衡组 (?im-nsx:exp) 在子表达式exp中改变处理选项 (?im-nsx) 为表达式后面的部分改变处理选项 1 把exp当作零宽正向先行断言,如果在这个位置能匹配,使用yes作为此组的表达式;否则使用no (?(exp)yes) 同上,只是使用空表达式作为no 2 如果命名为name的组捕获到了内容,使用yes作为表达式;否则使用no (?(name)yes) 同上,只是使用空表达式作为no 正则表达式常用实例账号/密码帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线) = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$" 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线) = "^[a-zA-Z]\\w{5,17}$" 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间) = "^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$" 字符串校验汉字 = "^[\\u4e00-\\u9fa5]{0,}$"; 英文和数字 = "^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$"; 长度为3-20的所有字符 = "^.{3,20}$"; 由26个英文字母组成的字符串 = "^[A-Za-z]+$"; 由26个大写英文字母组成的字符串 = "^[A-Z]+$"; 由26个小写英文字母组成的字符串 = "^[a-z]+$"; 由数字和26个英文字母组成的字符串 = "^[A-Za-z0-9]+$"; 由数字、26个英文字母或者下划线组成的字符串 = "^\\w+$ 或 ^\\w{3,20}$"; 中文、英文、数字包括下划线 = "^[\\u4E00-\\u9FA5A-Za-z0-9_]+$"; 中文、英文、数字但不包括下划线等符号 = "^[\\u4E00-\\u9FA5A-Za-z0-9]+$ 或 ^[\\u4E00-\\u9FA5A-Za-z0-9]{2,20}$"; 禁止输入含有~的字符 = "[^~\\x22]+"; 手机号/** * 手机号码 * 移动:134,135,136,137,138,139,147,150,151,152,157,158,159,170,178,182,183,184,187,188 * 联通:130,131,132,145,152,155,156,1709,171,176,185,186 * 电信:133,134,153,1700,177,180,181,189 */ String MOBILE = "^1(3[0-9]|4[57]|5[0-35-9]|7[01678]|8[0-9])\\\\d{8}$"; /** * 中国移动:China Mobile * 134,135,136,137,138,139,147,150,151,152,157,158,159,170,178,182,183,184,187,188 */ String CM = "^1(3[4-9]|4[7]|5[0-27-9]|7[0]|7[8]|8[2-478])\\\\d{8}$"; /** * 中国联通:China Unicom * 130,131,132,145,152,155,156,1709,171,176,185,186 */ String CU = "^1(3[0-2]|4[5]|5[56]|709|7[1]|7[6]|8[56])\\\\d{8}$"; /** * 中国电信:China Telecom * 133,134,153,1700,177,180,181,189 */ String CT = "^1(3[34]|53|77|700|8[019])\\\\d{8}$"; IP地址String IPREGEXP = "((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)"; EMAIL地址Email地址 = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"; 域名域名 = "[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?"; InternetURL = "[a-zA-z]+://[^\\s]* 或 ^http://([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?$" 身份证身份证号(15位、18位数字) = "^\\d{15}|\\d{18}$" 短身份证号码(数字、字母x结尾) = "^([0-9]){7,18}(x|X)?$ 或 ^\\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$" 数字类校验数字 = "^[0-9]*$"; n位的数字 = "^\\d{n}$"; 至少n位的数字 = "^\\d{n,}$"; m-n位的数字 = "^\\d{m,n}$"; 零和非零开头的数字 = "^(0|[1-9][0-9]*)$"; 非零开头的最多带两位小数的数字 = "^([1-9][0-9]*)+(.[0-9]{1,2})?$"; 带1-2位小数的正数或负数 = "^(\\-)?\\d+(\\.\\d{1,2})?$"; 正数、负数、和小数 = "^(\\-|\\+)?\\d+(\\.\\d+)?$"; 有两位小数的正实数 = "^[0-9]+(.[0-9]{2})?$"; 有1~3位小数的正实数 = "^[0-9]+(.[0-9]{1,3})?$"; 非零的正整数 = "^[1-9]\\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\\+?[1-9][0-9]*$"; 非零的负整数 = "^\\-[1-9][]0-9"*$" 或 "^-[1-9]\\d*$";","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"软件记录","slug":"工具/软件记录","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95/"}],"tags":[{"name":"regex","slug":"regex","permalink":"http://yelog.org/tags/regex/"}]},{"title":"Git常用命令","slug":"git-command","date":"2016-10-02T09:15:39.000Z","updated":"2024-07-05T11:10:22.593Z","comments":true,"path":"2016/10/02/git-command/","permalink":"http://yelog.org/2016/10/02/git-command/","excerpt":"经常用到Git,但是很多命令记不住,将其整理于此。(大量摘自网络) 一般来说,日常使用只要记住下图6个命令,就可以了。但是熟练使用,恐怕要要记住60~100个命令。","text":"经常用到Git,但是很多命令记不住,将其整理于此。(大量摘自网络) 一般来说,日常使用只要记住下图6个命令,就可以了。但是熟练使用,恐怕要要记住60~100个命令。 下面整理的 Git 命令清单。几个专业名词的译名如下。 Workspace:工作区 Index / Stage:暂存区 Repository:仓库区(本地仓库) Remote:远程仓库 新建版本仓库# 在当前目录新建一个Git代码库 $ git init # 新建一个目录,将其初始化为Git代码库 $ git init [project-name] # 下载一个项目和它的整个代码历史, -o 给远程仓库起名:faker,默认origin $ git clone [-o faker] [url] 配置Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)。 # 显示当前的Git配置 $ git config --list # 编辑Git配置文件 $ git config -e [--global] # 设置提交代码时的用户信息 $ git config [--global] user.name "[name]" $ git config [--global] user.email "[email address]" # 设置大小写敏感(windows不区分大小写的解决办法) $ git config core.ignorecase false 增加/删除文件# 添加指定文件到暂存区 $ git add [file1] [file2] ... # 添加指定目录到暂存区,包括子目录 $ git add [dir] # 添加当前目录的所有文件到暂存区 $ git add . # 添加每个变化前,都会要求确认 # 对于同一个文件的多处变化,可以实现分次提交 $ git add -p # 删除工作区文件,并且将这次删除放入暂存区 $ git rm [file1] [file2] ... # 停止追踪指定文件,但该文件会保留在工作区 $ git rm --cached [file] # 改名文件,并且将这个改名放入暂存区 $ git mv [file-original] [file-renamed] 代码提交# 提交暂存区到仓库区 $ git commit -m [message] # 提交暂存区的指定文件到仓库区 $ git commit [file1] [file2] ... -m [message] # 提交工作区自上次commit之后的变化,直接到仓库区 $ git commit -a # 提交时显示所有diff信息 $ git commit -v # 使用一次新的commit,替代上一次提交 # 如果代码没有任何新变化,则用来改写上一次commit的提交信息 $ git commit --amend -m [message] # 重做上一次commit,并包括指定文件的新变化 $ git commit --amend [file1] [file2] ... 分支# 列出所有本地分支 $ git branch # 列出所有远程分支 $ git branch -r # 列出所有本地分支和远程分支 $ git branch -a # 列出所有本地分支,并展示没有分支最后一次提交的信息 $ git branch -v # 列出所有本地分支,并展示没有分支最后一次提交的信息和远程分支的追踪情况 $ git branch -vv # 列出所有已经合并到当前分支的分支 $ git branch --merged # 列出所有还没有合并到当前分支的分支 $ git branch --no-merged # 新建一个分支,但依然停留在当前分支 $ git branch [branch-name] # 新建一个分支,并切换到该分支 $ git checkout -b [branch] # 新建一个与远程分支同名的分支,并切换到该分支 $ git checkout --track [branch-name] # 新建一个分支,指向指定commit $ git branch [branch] [commit] # 新建一个分支,与指定的远程分支建立追踪关系 $ git branch --track [branch] [remote-branch] # 切换到指定分支,并更新工作区 $ git checkout [branch-name] # 切换到上一个分支 $ git checkout - # 建立追踪关系,在现有分支与指定的远程分支之间 $ git branch --set-upstream-to=[remote-branch] $ git branch --set-upstream [branch] [remote-branch] # 已被弃用 # 合并指定分支到当前分支 $ git merge [branch] # 中断此次合并(你可能不想处理冲突) $ git merge --abort # 选择一个commit,合并进当前分支 $ git cherry-pick [commit] # 删除分支 $ git branch -d [branch-name] #新增远程分支 远程分支需先在本地创建,再进行推送 $ git push origin [branch-name] # 删除远程分支 $ git push origin --delete [branch-name] $ git branch -dr [remote/branch] 标签# 列出所有tag $ git tag # 新建一个tag在当前commit $ git tag [tag] # 新建一个tag在指定commit $ git tag [tag] [commit] # 删除本地tag $ git tag -d [tag] # 删除远程tag $ git push origin :refs/tags/[tagName] # 查看tag信息 $ git show [tag] # 提交指定tag $ git push [remote] [tag] # 提交所有tag $ git push [remote] --tags # 新建一个分支,指向某个tag $ git checkout -b [branch] [tag] 查看信息/搜索# 显示有变更的文件 $ git status [-sb] #s:short,给一个短格式的展示,b:展示当前分支 # 显示当前分支的版本历史 $ git log # 显示commit历史,以及每次commit发生变更的文件 $ git log --stat # 搜索提交历史,根据关键词 $ git log -S [keyword] # 显示某个commit之后的所有变动,每个commit占据一行 $ git log [tag] HEAD --pretty=format:%s # 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件 $ git log [tag] HEAD --grep feature # 显示某个文件的版本历史,包括文件改名 $ git log --follow [file] $ git whatchanged [file] # 显示指定文件相关的每一次diff $ git log -p [file] # 显示过去5次提交 $ git log -5 --pretty --oneline # 图形化显示所有分支 $ git log --oneline --graph --all # 显示在分支2而不在分支1中的提交 $ git log [分支1]..[分支2] $ git log ^[分支1] [分支2] $ git log [分支2] --not [分支1] # 显示两个分支不同时包含的提交 $ git log [分支1]...[分支2] # 显示所有提交过的用户,按提交次数排序 $ git shortlog -sn # 显示指定文件是什么人在什么时间修改过 $ git blame [file] # 显示暂存区和工作区的差异 $ git diff # 显示暂存区和上一个commit的差异 $ git diff --cached [file] # 显示工作区与当前分支最新commit之间的差异 $ git diff HEAD # 显示两次提交之间的差异 $ git diff [first-branch]...[second-branch] # 显示今天你写了多少行代码 $ git diff --shortstat "@{0 day ago}" # 显示某次提交的元数据和内容变化 $ git show [commit] # 显示某次提交发生变化的文件 $ git show --name-only [commit] # 显示某次提交时,某个文件的内容 $ git show [commit]:[filename] # 显示当前分支的最近几次提交 $ git reflog # 搜索你工作目录的文件,输出匹配行号 $ git grep -n [关键字] # 搜索你工作目录的文件,输出每个文件包含多少个匹配 $ git grep --count [关键字] # 优化阅读 $ git grep --break --heading [关键字] # 查询iCheck这个字符串那次提交的 $ git log -SiCheck --oneline # 查询git_deflate_bound函数每一次的变更 $ git log -L :git_deflate_bound:zlib.c 远程同步# 下载远程仓库的所有变动 [shortname] 为远程仓库的shortname, 如origin,为空时:默认origin $ git fetch [shortname] # 显示所有远程仓库 $ git remote -v #显式地获得远程引用的完整列表 [shortname] 为远程仓库的shortname, 如origin,为空时:默认origin $ git ls-remote [shortname] # 显示某个远程仓库的信息 [remote] 为远程仓库的shortname, 如origin $ git remote show [shortname] # 增加一个新的远程仓库,并命名 $ git remote add [shortname] [url] # 重命名一个远程仓库(shortname) $ git remote rename [旧仓库名] [新仓库名] # 删除一个远程链接 $ git remote rm [shortname] [url] $ git remote remove [shortname] [url] # 修改远程仓库地址 $ git remote set-url [shortname] [url] # 取回远程仓库的变化,并与本地分支合并 $ git pull [remote] [branch] # 上传本地当前分支到远程仓库 git push [remote] # 上传本地指定分支到远程仓库 $ git push [remote] [branch] # 推送所有分支到远程仓库 $ git push [remote] --all # 强行推送当前分支到远程仓库,即使有冲突 $ git push [remote] --force 撤销# 恢复暂存区的指定文件到工作区 $ git checkout [file] # 恢复某个commit的指定文件到暂存区和工作区 $ git checkout [commit] [file] # 恢复暂存区的所有文件到工作区 $ git checkout . #只会保留源码(工作区),回退commit(本地仓库)与index(暂存区)到某个版本 $ git reset <commit_id> #默认为 --mixed模式 $ git reset --mixed <commit_id> #保留源码(工作区)和index(暂存区),只回退commit(本地仓库)到某个版本 $ git reset --soft <commit_id> #源码(工作区)、commit(本地仓库)与index(暂存区)都回退到某个版本 $ git reset --hard <commit_id> # 恢复到最后一次提交的状态 $ git reset --hard HEAD # 新建一个commit,用来撤销指定commit # 后者的所有变化都将被前者抵消,并且应用到当前分支 $ git revert [commit] # 将工作区和暂存区的代码全都存储起来了 $ git stash [save] # 只保存工作区,不存储暂存区 $ git stash --keep-index # 存储工作区、暂存区和未跟踪文件 $ git stash -u $ git stash --include-untracked # 不存储所有改动的东西,但会交互式的提示那些改动想要被储藏、哪些改动需要保存在工作目录中 $ git stash --patch # 不指定名字,Git认为指定最近的储藏,将存储的代码(工作区和暂存区)都应用到工作区 $ git stash apply [stash@{2}] # 存储的工作区和暂存区的代码应用到工作区和暂存区 $ git stash apply [stash@{2}] --index # 将存储的代码(工作区和暂存区)都应用到工作区,并从栈上扔掉他 $ git stash pop # 删除stash@{2}的存储 $ git stash drop [stash@{2}] # 获取储藏的列表 $ git stash list # 移除工作目录中所有未跟踪的文件及口口那个的子目录,不会移除.gitiignore忽略的文件 $ git clean -f -d 其他# 生成一个可供发布的压缩包 $ git archive","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"}]},{"title":"一个客户端设置多个github账号","slug":"computer-mutiple-github-account","date":"2016-09-30T08:42:48.000Z","updated":"2024-07-05T11:10:22.627Z","comments":true,"path":"2016/09/30/computer-mutiple-github-account/","permalink":"http://yelog.org/2016/09/30/computer-mutiple-github-account/","excerpt":"最近想要使用自己的GitHub搭建Hexo博客,同时还要使用工作的GitHub开发项目,所以在网上找寻了一些文章,在此将自己的搭建过程记录一下。","text":"最近想要使用自己的GitHub搭建Hexo博客,同时还要使用工作的GitHub开发项目,所以在网上找寻了一些文章,在此将自己的搭建过程记录一下。 前期工作两个GitHub账号(假设两个账号为one,two)取消Git全局设置 $ git config --global --unset user.name $ git config --global --unset user.email SSH配置生成id_rsa私钥,id_rsa.pub公钥。one可以直接回车,默认生成 id_rsa 和 id_rsa.pub 。 $ ssh-keygen -t rsa -C "one@xx.com" 添加two会出现提示输入文件名,输入与默认配置不一样的文件名,如:id_rsa_two。 $ cd ~/.ssh $ ssh-keygen -t rsa -C "two@126.com" # 之后会提示输入文件名 GitHub添加公钥 id_rsa.pub 、 id_rsa_two.pub,分别登陆one,two的账号,在 Account Settings 的 SSH Keys 里,点 Add SSH Keys ,将公钥(.pub文件)中的内容粘贴到 Key 中,并输入 Title。添加 ssh Key $ ssh-add ~/.ssh/id_rsa $ ssh-add ~/.ssh/id_rsa_two 可以在添加前使用下面命令删除所有的 key $ ssh-add -D 最后可以通过下面命令,查看 key 的设置 $ ssh-add -l 修改ssh config文件$ cd ~/.ssh/ $ touch config 打开 .ssh 文件夹下的 config 文件,进行配置 # default Host github.com HostName github.com User git IdentityFile ~/.ssh/id_rsa # two Host two.github.com # 前缀名可以任意设置 HostName github.com User git IdentityFile ~/.ssh/id_rsa_two 这里必须采用这样的方式设置,否则 push 时会出现以下错误: ERROR: Permission to two/two.github.com.git denied to one. 简单分析下原因,我们可以发现 ssh 客户端是通过类似: git@github.com:one/one.github.com.git 这样的 Git 地址中的 User 和 Host 来识别使用哪个本地私钥的。很明显,如果 User 和 Host 始终为 git 和 github.com,那么就只能使用一个私钥。所以需要上面的方式配置,每个账号使用了自己的 Host,每个 Host 的域名做 CNAME 解析到 github.com,这样 ssh 在连接时就可以区别不同的账号了。 $ ssh -T git@github.com # 测试one ssh连接 # Hi ***! You've successfully authenticated, but GitHub does not provide shell access. $ ssh -T git@two.github.com # 测试two ssh连接 # Hi ***! You've successfully authenticated, but GitHub does not provide shell access. 但是这样还没有完,下面还有关联的设置。 在Git项目中配置账号关联可以用 git init 或者 git clone 创建本地项目分别在one和two的git项目目录下,使用下面的命令设置名字和邮箱 $ git config user.name "__name__" # __name__ 例如 one $ git config user.email "__email__" # __email__ 例如 one@126.com 注意:由于我不知道Hexo怎样配置 局部的config,所以,我将two的config使用全局,而工作目录配置局部。 $ git config --global user.name "__name__" # __name__ 例如 two $ git config --global user.email "__email__" # __email__ 例如 two@126.com 查看git项目的配置 $ git config --list 查看 one 的 remote.origin.url=git@github.com:one/one.github.com.git查看 two 的 remote.origin.url=git@github.com:two/two.github.com.git由于 one 使用的是默认的 Host ,所以不需要修改,但是 two 使用的是 two.github.com ,则需要进行修改 $ git remote rm origin $ git remote add origin git@two.github.com:two/two.github.com.git 我在Hexo中的配置(使用two账号) deploy: type: git repo: git@two.github.com:two/two.github.io.git branch: master 上传更改上面所有的设置无误后,可以修改代码,然后上传了。 $ git add -A $ git commit -m "your comments" $ git push 如果遇到warning warning: push.default is unset; its implicit value is changing in Git 2.0 from ‘matching’ to ‘simple’. To squelch this messageand maintain the current behavior after the default changes, use… 推荐使用 $ git config --global push.default simple","categories":[{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"},{"name":"GitHub","slug":"GitHub","permalink":"http://yelog.org/tags/GitHub/"}]}],"categories":[{"name":"后端","slug":"后端","permalink":"http://yelog.org/categories/%E5%90%8E%E7%AB%AF/"},{"name":"工具","slug":"工具","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/"},{"name":"软件记录","slug":"工具/软件记录","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/%E8%BD%AF%E4%BB%B6%E8%AE%B0%E5%BD%95/"},{"name":"IOT","slug":"工具/IOT","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/IOT/"},{"name":"运维","slug":"运维","permalink":"http://yelog.org/categories/%E8%BF%90%E7%BB%B4/"},{"name":"开发","slug":"开发","permalink":"http://yelog.org/categories/%E5%BC%80%E5%8F%91/"},{"name":"swift","slug":"开发/swift","permalink":"http://yelog.org/categories/%E5%BC%80%E5%8F%91/swift/"},{"name":"IDEA","slug":"工具/IDEA","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/IDEA/"},{"name":"大前端","slug":"大前端","permalink":"http://yelog.org/categories/%E5%A4%A7%E5%89%8D%E7%AB%AF/"},{"name":"hexo","slug":"工具/hexo","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/hexo/"},{"name":"读书","slug":"读书","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/"},{"name":"阅读笔记","slug":"读书/阅读笔记","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/"},{"name":"数据库","slug":"数据库","permalink":"http://yelog.org/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"},{"name":"git","slug":"工具/git","permalink":"http://yelog.org/categories/%E5%B7%A5%E5%85%B7/git/"},{"name":"书单","slug":"读书/书单","permalink":"http://yelog.org/categories/%E8%AF%BB%E4%B9%A6/%E4%B9%A6%E5%8D%95/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yelog.org/tags/java/"},{"name":"translation","slug":"translation","permalink":"http://yelog.org/tags/translation/"},{"name":"mac","slug":"mac","permalink":"http://yelog.org/tags/mac/"},{"name":"efficiency","slug":"efficiency","permalink":"http://yelog.org/tags/efficiency/"},{"name":"terminal","slug":"terminal","permalink":"http://yelog.org/tags/terminal/"},{"name":"home-assistant","slug":"home-assistant","permalink":"http://yelog.org/tags/home-assistant/"},{"name":"iot","slug":"iot","permalink":"http://yelog.org/tags/iot/"},{"name":"docker","slug":"docker","permalink":"http://yelog.org/tags/docker/"},{"name":"k8s","slug":"k8s","permalink":"http://yelog.org/tags/k8s/"},{"name":"swift","slug":"swift","permalink":"http://yelog.org/tags/swift/"},{"name":"macos","slug":"macos","permalink":"http://yelog.org/tags/macos/"},{"name":"ocr","slug":"ocr","permalink":"http://yelog.org/tags/ocr/"},{"name":"spring-boot","slug":"spring-boot","permalink":"http://yelog.org/tags/spring-boot/"},{"name":"vim","slug":"vim","permalink":"http://yelog.org/tags/vim/"},{"name":"IntellijIDEA","slug":"IntellijIDEA","permalink":"http://yelog.org/tags/IntellijIDEA/"},{"name":"concurrent","slug":"concurrent","permalink":"http://yelog.org/tags/concurrent/"},{"name":"nacos","slug":"nacos","permalink":"http://yelog.org/tags/nacos/"},{"name":"springcloud","slug":"springcloud","permalink":"http://yelog.org/tags/springcloud/"},{"name":"gray-release","slug":"gray-release","permalink":"http://yelog.org/tags/gray-release/"},{"name":"ElementUI","slug":"ElementUI","permalink":"http://yelog.org/tags/ElementUI/"},{"name":"Vue","slug":"Vue","permalink":"http://yelog.org/tags/Vue/"},{"name":"SpringCloud","slug":"SpringCloud","permalink":"http://yelog.org/tags/SpringCloud/"},{"name":"SkyWalking","slug":"SkyWalking","permalink":"http://yelog.org/tags/SkyWalking/"},{"name":"3-hexo","slug":"3-hexo","permalink":"http://yelog.org/tags/3-hexo/"},{"name":"hexo","slug":"hexo","permalink":"http://yelog.org/tags/hexo/"},{"name":"javascript","slug":"javascript","permalink":"http://yelog.org/tags/javascript/"},{"name":"linux","slug":"linux","permalink":"http://yelog.org/tags/linux/"},{"name":"reading","slug":"reading","permalink":"http://yelog.org/tags/reading/"},{"name":"活着","slug":"活着","permalink":"http://yelog.org/tags/%E6%B4%BB%E7%9D%80/"},{"name":"shell","slug":"shell","permalink":"http://yelog.org/tags/shell/"},{"name":"nginx","slug":"nginx","permalink":"http://yelog.org/tags/nginx/"},{"name":"PostgreSQL","slug":"PostgreSQL","permalink":"http://yelog.org/tags/PostgreSQL/"},{"name":"keybord","slug":"keybord","permalink":"http://yelog.org/tags/keybord/"},{"name":"emacs","slug":"emacs","permalink":"http://yelog.org/tags/emacs/"},{"name":"encoding","slug":"encoding","permalink":"http://yelog.org/tags/encoding/"},{"name":"dubbo","slug":"dubbo","permalink":"http://yelog.org/tags/dubbo/"},{"name":"zookeeper","slug":"zookeeper","permalink":"http://yelog.org/tags/zookeeper/"},{"name":"fragment_cache","slug":"fragment-cache","permalink":"http://yelog.org/tags/fragment-cache/"},{"name":"浏览器","slug":"浏览器","permalink":"http://yelog.org/tags/%E6%B5%8F%E8%A7%88%E5%99%A8/"},{"name":"js","slug":"js","permalink":"http://yelog.org/tags/js/"},{"name":"firewall","slug":"firewall","permalink":"http://yelog.org/tags/firewall/"},{"name":"maven","slug":"maven","permalink":"http://yelog.org/tags/maven/"},{"name":"nexus","slug":"nexus","permalink":"http://yelog.org/tags/nexus/"},{"name":"mybatis","slug":"mybatis","permalink":"http://yelog.org/tags/mybatis/"},{"name":"mathjax","slug":"mathjax","permalink":"http://yelog.org/tags/mathjax/"},{"name":"pjax","slug":"pjax","permalink":"http://yelog.org/tags/pjax/"},{"name":"samba","slug":"samba","permalink":"http://yelog.org/tags/samba/"},{"name":"tale","slug":"tale","permalink":"http://yelog.org/tags/tale/"},{"name":"docker仓库","slug":"docker仓库","permalink":"http://yelog.org/tags/docker%E4%BB%93%E5%BA%93/"},{"name":"linux命令","slug":"linux命令","permalink":"http://yelog.org/tags/linux%E5%91%BD%E4%BB%A4/"},{"name":"运维","slug":"运维","permalink":"http://yelog.org/tags/%E8%BF%90%E7%BB%B4/"},{"name":"centos","slug":"centos","permalink":"http://yelog.org/tags/centos/"},{"name":"磁盘分区","slug":"磁盘分区","permalink":"http://yelog.org/tags/%E7%A3%81%E7%9B%98%E5%88%86%E5%8C%BA/"},{"name":"git","slug":"git","permalink":"http://yelog.org/tags/git/"},{"name":"encryption","slug":"encryption","permalink":"http://yelog.org/tags/encryption/"},{"name":"jsp","slug":"jsp","permalink":"http://yelog.org/tags/jsp/"},{"name":"jstl","slug":"jstl","permalink":"http://yelog.org/tags/jstl/"},{"name":"spring","slug":"spring","permalink":"http://yelog.org/tags/spring/"},{"name":"springmvc","slug":"springmvc","permalink":"http://yelog.org/tags/springmvc/"},{"name":"postgres","slug":"postgres","permalink":"http://yelog.org/tags/postgres/"},{"name":"sql","slug":"sql","permalink":"http://yelog.org/tags/sql/"},{"name":"deepin","slug":"deepin","permalink":"http://yelog.org/tags/deepin/"},{"name":"node","slug":"node","permalink":"http://yelog.org/tags/node/"},{"name":"jQuery","slug":"jQuery","permalink":"http://yelog.org/tags/jQuery/"},{"name":"ftp","slug":"ftp","permalink":"http://yelog.org/tags/ftp/"},{"name":"AngularJs","slug":"AngularJs","permalink":"http://yelog.org/tags/AngularJs/"},{"name":"Git","slug":"Git","permalink":"http://yelog.org/tags/Git/"},{"name":"javaee","slug":"javaee","permalink":"http://yelog.org/tags/javaee/"},{"name":"GitHub","slug":"GitHub","permalink":"http://yelog.org/tags/GitHub/"},{"name":"freemarker","slug":"freemarker","permalink":"http://yelog.org/tags/freemarker/"},{"name":"regex","slug":"regex","permalink":"http://yelog.org/tags/regex/"}]} \ No newline at end of file diff --git a/index.html b/index.html index bc247a939..9e1dbfdcf 100644 --- a/index.html +++ b/index.html @@ -248,7 +248,7 @@
  • 全部文章 - (163) + (164)
  • @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + + + Jackson 时间序列化/反序列化详解 + + /2024/07/05/Jackson%20Time%20Serializer/Deserializer/ + + 前言

    最近在项目中遇到了时间序列化的问题,所以研究了一下 Jackson 的时间序列化/反序列化,这里做一个详细的总结。

    0. 准备工作

    准备实体类 User.java

    package com.example.testjava.entity;import lombok.Builder;import lombok.Data;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.util.Date;@Builder@Datapublic class User {    private String name;    private Date date;    private LocalDate localDate;    private LocalDateTime localDateTime;    private LocalTime localTime;    private java.sql.Date sqlDate;    private java.sql.Time sqlTime;    private java.sql.Timestamp timestamp;}

    简单查询

    package com.example.testjava.controller;import com.example.testjava.entity.User;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.Date;@RestController@RequestMapping("/jackson")public class JacksonTestController {    @GetMapping("/query")    public User testJavaDate() {        return User.builder()                .name("test")                .date(new Date())                .localDate(java.time.LocalDate.now())                .localDateTime(java.time.LocalDateTime.now())                .localTime(java.time.LocalTime.now())                .sqlDate(new java.sql.Date(System.currentTimeMillis()))                .sqlTime(new java.sql.Time(System.currentTimeMillis()))                .timestamp(new java.sql.Timestamp(System.currentTimeMillis()))                .build();    }}

    1. 序列化

    1.1. 默认返回

    {    "name": "test",    "date": "2024-07-05T08:09:47.100+00:00",    "localDate": "2024-07-05",    "localDateTime": "2024-07-05T16:09:47.100462",    "localTime": "16:09:47.100514",    "sqlDate": "2024-07-05",    "sqlTime": "16:09:47",    "timestamp": "2024-07-05T08:09:47.100+00:00"}

    1.2. 添加配置

    配置如下

    spring:  jackson:    date-format: yyyy-MM-dd HH:mm:ss

    返回效果

    {    "name": "test",    "date": "2024-07-05 08:16:07",    "localDate": "2024-07-05",    "localDateTime": "2024-07-05T16:16:07.097035",    "localTime": "16:16:07.09705",    "sqlDate": "2024-07-05",    "sqlTime": "16:16:07",    "timestamp": "2024-07-05 08:16:07"}

    可以发现, 日期时间类型中, 只有 java.time.LocalDateTime 没有按照配置序列化, java.util.Datejava.sql.Timestamp 按照配置序列化了。

    1.3. 添加注解

    package com.example.testjava.entity;import com.fasterxml.jackson.annotation.JsonFormat;import lombok.Builder;import lombok.Data;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.util.Date;@Builder@Datapublic class User {    private String name;    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")    private Date date;    private LocalDate localDate;    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")    private LocalDateTime localDateTime;    @JsonFormat(pattern = "HH:mm:ss")    private LocalTime localTime;    private java.sql.Date sqlDate;    private java.sql.Time sqlTime;    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")    private java.sql.Timestamp timestamp;}

    返回效果

    {    "name": "test",    "date": "2024-07-05 08:24:36",    "localDate": "2024-07-05",    "localDateTime": "2024-07-05 16:24:36",    "localTime": "16:24:36",    "sqlDate": "2024-07-05",    "sqlTime": "16:24:36",    "timestamp": "2024-07-05 08:24:36"}

    注解是可以都有效的

    2. 反序列化

    准备请求

        @PostMapping("/save")    public User save(@RequestBody User user) {        return user;    }

    请求参数

    {    "name": "test",    "date": "2024-07-05 08:24:36",    "localDate": "2024-07-05",    "localDateTime": "2024-07-05 16:24:36",    "localTime": "16:24:36",    "sqlDate": "2024-07-05",    "sqlTime": "16:24:36",    "timestamp": "2024-07-05 08:24:36"}

    2.1 默认效果

    默认报错

    JSON parse error: Cannot deserialize value of type `java.util.Date` from String \"2024-07-05 08:24:36\"

    2.2 添加配置

    有两种方法可以解决, 一个是自定义时间序列化, 一个是自定义 objectMapper

    2.2.1 自定义时间序列化

    /** * 此转换方法试用于 json 请求 * LocalDateTime 时间格式转换 支持 */@JsonComponent@Configurationpublic class LocalDateTimeFormatConfiguration extends JsonDeserializer<LocalDateTime> {    @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")    private String pattern;    /**     * LocalDate 类型全局时间格式化     * @return     */    @Bean    public LocalDateTimeSerializer localDateTimeDeserializer() {        return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern));    }    @Bean    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {        return builder -> builder.serializerByType(LocalDateTime.class, localDateTimeDeserializer());    }    @Override    public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {        return StrUtil.isEmpty(jsonParser.getText()) ? null : LocalDateTimeUtil.of(new DateTime(jsonParser.getText()));    }}
    @JsonComponent@Configurationpublic class DateFormatConfiguration extends JsonDeserializer<Date> {    @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")    private String pattern;    /**     * date 类型全局时间格式化     *     * @return     */    @Bean    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilder() {        return builder -> {            TimeZone tz = TimeZone.getTimeZone("UTC");            DateFormat df = new SimpleDateFormat(pattern);            df.setTimeZone(tz);            builder.failOnEmptyBeans(false)                    .failOnUnknownProperties(false)                    .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)                    .dateFormat(df);        };    }    @Override    public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {        return StrUtil.isEmpty(jsonParser.getText()) ? null : new DateTime(jsonParser.getText());    }}

    2.2.2 自定义 objectMapper

    package com.example.testjava.config;import cn.hutool.core.date.DatePattern;import cn.hutool.core.date.DateTime;import cn.hutool.core.date.LocalDateTimeUtil;import cn.hutool.core.util.StrUtil;import com.fasterxml.jackson.core.JacksonException;import com.fasterxml.jackson.core.JsonGenerator;import com.fasterxml.jackson.core.JsonParser;import com.fasterxml.jackson.databind.Module;import com.fasterxml.jackson.databind.*;import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.autoconfigure.AutoConfigureBefore;import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.io.IOException;import java.sql.Time;import java.sql.Timestamp;import java.text.SimpleDateFormat;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.time.format.DateTimeFormatter;import java.util.Date;@Configuration@AutoConfigureBefore(JacksonAutoConfiguration.class)public class JacksonConfig {    @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")    private String pattern;    @Bean    public ObjectMapper objectMapper() {        ObjectMapper objectMapper = new ObjectMapper();        // 在反序列化时, 如果对象没有对应的字段, 不抛出异常        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);        objectMapper.registerModule(javaTimeModule());        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);        return objectMapper;    }    private Module javaTimeModule() {        JavaTimeModule module = new JavaTimeModule();        // 序列化        module.addSerializer(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern)));        module.addSerializer(new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));        module.addSerializer(new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));        module.addSerializer(Date.class, new JsonSerializer<>() {            @Override            public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {                SimpleDateFormat sdf = new SimpleDateFormat(pattern);                jsonGenerator.writeString(sdf.format(date));            }        });        module.addSerializer(java.sql.Date.class, new JsonSerializer<>() {            @Override            public void serialize(java.sql.Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {                SimpleDateFormat sdf = new SimpleDateFormat(pattern);                jsonGenerator.writeString(sdf.format(date));            }        });        module.addSerializer(Timestamp.class, new JsonSerializer<>() {            @Override            public void serialize(Timestamp timestamp, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {                SimpleDateFormat sdf = new SimpleDateFormat(pattern);                jsonGenerator.writeString(sdf.format(timestamp));            }        });        module.addSerializer(Time.class, new JsonSerializer<>() {            @Override            public void serialize(Time time, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {                SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.NORM_TIME_PATTERN);                jsonGenerator.writeString(sdf.format(time));            }        });        // 反序列化        module.addDeserializer(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() {            @Override            public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {                return StrUtil.isEmpty(jsonParser.getText()) ? null : LocalDateTimeUtil.of(new DateTime(jsonParser.getText()));            }        });        module.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));        module.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));        module.addDeserializer(Date.class, new JsonDeserializer<Date>() {            @Override            public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {                return StrUtil.isEmpty(jsonParser.getText()) ? null : new DateTime(jsonParser.getText());            }        });        module.addDeserializer(java.sql.Date.class, new JsonDeserializer<java.sql.Date>() {            @Override            public java.sql.Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {                return StrUtil.isEmpty(jsonParser.getText()) ? null : new java.sql.Date(new DateTime(jsonParser.getText()).getTime());            }        });        module.addDeserializer(Timestamp.class, new JsonDeserializer<Timestamp>() {            @Override            public Timestamp deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {                return StrUtil.isEmpty(jsonParser.getText()) ? null : new Timestamp(new DateTime(jsonParser.getText()).getTime());            }        });        module.addDeserializer(Time.class, new JsonDeserializer<Time>() {            @Override            public Time deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {                return StrUtil.isEmpty(jsonParser.getText()) ? null : Time.valueOf(jsonParser.getText());            }        });        // 添加默认处理        return module;    }}

    效果可以返回正确的数据

    {    "name": "test",    "date": "2024-07-05 08:24:36",    "localDate": "2024-07-05",    "localDateTime": "2024-07-05 16:24:36",    "localTime": "16:24:36",    "sqlDate": "2024-07-05 00:00:00",    "sqlTime": "16:24:36",    "timestamp": "2024-07-05 08:24:36"}
    ]]> + + + + + 后端 + + + + + + + java + + translation + + + + + + + 2024年MacOS终端大比拼 @@ -443,7 +470,7 @@ /2020/09/01/Docker-summary/ - 一、概述

    1.1 什么是docker

    Docker 诞生于 2013 年初,由 dotCloud 公司(后改名为 Docker Inc)基于 Go 语言实现并开源的项目。此项目后来加入 Linux基金会,遵从了 Apache 2.0 协议

    Docker 项目的目标是实现轻量级的操作系统虚拟化解决方案。Docker 是在 Linux 容器技术(LXC)的基础上进行了封装,让用户可以快速并可靠的将应用程序从一台运行到另一台上。

    使用容器部署应用被称为容器化,容器化技术的几大优势:

    1. 灵活:甚至复杂的应用也可以被容器化
    2. 轻量:容器利用和共享宿主机内核,从而在利用系统资源比虚拟机更加的有效
    3. 可移植:你可以在本地构建,在云端部署并在任何地方运行
    4. 松耦合:容器是高度封装和自给自足的,允许你在不破环其他容器的情况下替换或升级任何一个
    5. 可扩展:你可以通过数据中心来新增和自动分发容器
    6. 安全:容器依赖强约束和独立的进程

    1.2 和传统虚拟机的区别

    容器在Linux上本地运行,并与其他容器共享主机的内核。它运行一个离散进程,不占用任何其他可执行文件更多的内存,从而使其轻巧。

    image.png

    1.3 相关链接

    官网:https://www.docker.com/

    文档:https://docs.docker.com/

    二、Image镜像

    2.1 介绍

    Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变

    1. 父镜像:每个镜像都可能依赖于有一个或多个下层组成的另一个镜像。下层那个镜像就是上层镜像的父镜像
    2. 基础镜像:一个没有任何父镜像的镜像,被称为基础镜像
    3. 镜像ID:所有镜像都是通过一个 64 位十六进制字符串(256 bit 的值)来标识的。为了简化使用,前 12 个自负可以组成一个短ID,可以在命令行中使用。短ID还是有一定的碰撞几率,所以服务器总是返回长ID

    2.2 从仓库下载镜像

    可以通过 docker pull 命令从仓库获取所需要的镜像

    docker pull [选项] [Docker Registry 地址]<镜像名>:<标签>

    选项:

    1. –all-tags,-a : 拉去所有 tagged 镜像
    2. –disable-content-trust:忽略镜像的校验,默认
    3. –platform:如果服务器是开启多平台支持的,则需要设置平台
    4. –quiet,-q:静默执行,不打印详细信息

    标签: 下载指定标签的镜像,默认 latest

    示例

    # 从 Docker Hub 下载最新的 debian 镜像docker pull debian# 从 Docker Hub 下载 jessie 版 debian 镜像docker pull debian:jessie# 下载指定摘要(sha256)的镜像docker pull ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2

    2.3 列出本地镜像

    # 列出已下载的镜像 image_name: 指定列出某个镜像docker images [选项] [image_name]

    选项

    参数描述
    –all, -a展示所有镜像(包括 intermediate 镜像)
    –digests展示摘要
    –filter, -f添加过滤条件
    –format使用 Go 模版更好的展示
    –no-trunc不删减输出
    –quiet, -q静默输出,仅仅展示 IDs

    示例

    # 展示本地所有下载的镜像docker images# 在本地查找镜像名是 "java" 标签是 "8" 的 奖项docker images: java:8# 查找悬挂镜像docker images --filter "dangling=true"# 过滤 lable 为 "com.example.version" 的值为 0.1 的镜像docker images --filter "label=com.example.version=0.1"

    2.4 Dockerfile创建镜像

    为了方便分享和快速部署,我们可以使用 docker build 来创建一个新的镜像,首先创建一个文件 Dockerfile,如下

    # This is a commentFROM ubuntu:14.04MAINTAINER Chris <jaytp@qq.com>RUN apt-get -qq updateRUN apt-get -qqy install ruby ruby-devRUN gem install sinatra

    然后在此 Dockerfile 所在目录执行 docker build -t yelog/ubuntu:v1 . 来生成镜像,所属组织/镜像名:标签

    2.5 上传镜像

    用户可以通过 docker push 命令,把自己创建的镜像上传到仓库中来共享。例如,用户在 Docker Hub 上完成注册后,可以推送自己的镜像到仓库中。

    docker push yelog/ubuntu

    2.6 导出和载入镜像

    docker 支持将镜像导出为文件,然后可以再从文件导入到本地镜像仓库

    # 导出docker load --input yelog_ubuntu_v1.tar# 载入docker load < yelog_ubuntu_v1.tar

    2.7 移除本地镜像

    # -f 强制删除docker rmi [-f] yelog/ubuntu:v1# 删除悬挂镜像docker rmi $(docker images -f "dangling=true" -q)# 删除所有未被容器使用的镜像docker image prune -a

    三、容器

    3.1 介绍

    容器和镜像,就像面向对象中的 示例 一样,镜像是静态的定义,容器是镜像运行的实体,容器可以被创建、启动、停止、删除和暂停等

    容器的实质是进城,耽于直接的宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的 root 文件系统、网络配置和进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。

    3.2 创建容器

    我们可以通过命令 docker run 命令创建容器

    如下,启动一个容器,执行命令输出 “Hello word”,之后终止容器

    docker run ubuntu:14.04 /bin/echo 'Hello world'

    下面的命令则是启动一个 bash 终端,允许用户进行交互

    docker run -t -i ubuntu:14.04 /bin/bash

    -t 让 Dcoker 分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上

    -i 责让容器的标准输入保持打开

    更多参数可选

    -a stdin指定标准输入输出内容类型
    -d后台运行容器,并返回容器ID
    -i以交互模式运行容器,通常与 -t 同时使用
    -P随机端口映射,容器端口内部随即映射到宿主机的端口上
    -p指定端口映射, -p 宿主机端口:容器端口
    -t为容器重新分配一个伪输入终,通常与 -i 同时使用
    –name=”gate”为容器指定一个名称
    –dns 8.8.8.8指定容器的 DNS 服务器,默认与宿主机一致
    –dns-search example.com指定容器 DNS 搜索域名,默认与宿主机一致
    -h “gate”指定容器的 hostname
    -e username=’gate’设置环境变量
    –env-file=[]从指定文件读入环境变量
    –cpuset=”0-2” or –cpuset=”0,1,2”绑定容器到指定 CPU 运行
    -m设置容器使用内存最大值
    –net=”bridge”指定容器的网络连接类型支持 bridge/host/none/container
    –link=[]添加链接到另一个容器
    –expose=[]开放一个端口或一组端口
    –volume,-v绑定一个卷

    当利用 docker run 来创建容器时,Dcoker 在后台运行的标准操作包括:

    • 检查本地是否存在指定的镜像,不存在就从公有仓库下载
    • 利用镜像创建并启动一个容器
    • 分配一个文件系统,并在只读的镜像外面挂在一层可读写层
    • 从宿主主机配置的网桥接口中桥接一个虚拟借口到容器中去
    • 从地址池配置一个 ip 地址给容器
    • 执行用户指定的应用程序
    • 执行用户指定的应用程序
    • 执行完毕后容器被终止

    3.3 启动容器

    # 创建一个名为 test 的容器,容器任务是:打印一行 Hello worddocker run --name='test' ubuntu:14.04 /bin/echo 'Hello world'# 查看所有可用容器 [-a]包括终止在内的所有容器docker ps -a# 启动指定 name 的容器docker start test# 重启指定 name 的容器docker restart test# 查看日志运行日志(每次启动的日志均被查询出来)$ docker logs testHello worldHello world

    3.4 守护态运行

    前面创建的容器都是执行任务(打印Hello world)后,容器就终止了。更多的时候,我们需要让 Docker 容器在后台以守护态(Daemonized)形式运行。此时,可以通过添加 -d 参数来实现

    注意:docker是否会长久运行,和 docker run 指定的命令有关

    # 创建 docker 后台守护进程的容器docker run --name='test2' -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"# 查看容器$ docker psCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES237e555d4457        ubuntu:14.04        "/bin/sh -c 'while t…"   52 seconds ago      Up 51 seconds                                           test2# 获取容器的输出信息$ docker logs test2hello worldhello worldhello world

    3.5 进入容器

    上一步我们已经实现了容器守护态长久运行,某些时候需要进入容器进行操作,可以使用 attachexec 进入容器。

    # 不安全的,ctrl+d 退出时容器也会终止docker attach [容器Name]# 以交互式命令行进入,安全的,推荐使用docker exec -it [容器Name] /bin/bash

    命令优化

    1. 使用 docker exec 命令时,好用,但是命令过长,我们可以通过自定义命令来简化使用
    2. 创建文件 /user/bin/ctn 命令文件,内容如下
    docker exec -it $1 /bin/bash
    1. 检查环境变量有没有配置目录 /usr/bin (一般是有配置在环境变量里面的,不过最好再确认一下)
    $PATHbash: /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games: No such file or directory
    1. 完成上面步骤后,就可以直接通过命令 ctn 来进入容器

    注意:如果是使用非 root 账号创建的命令,而 docker 命令是 root 权限,可能存在权限问题,可以通过设置 chmod 777 /usr/bin/ctn 设置权限,使用 sudo ctn [容器Name] 即可进入容器

    $ ctn [容器Name]
    1. 使用上面命令时,容器Name 需要手动输入,容器出错。我们可以借助 complete 命令来补全 容器Name,在 ~/.bashrc (作用于当前用户,如果想要所要用户上校,可以修改 /etc/bashrc)文件中添加一行,内容如下。保存后执行 source ~/.bashrc 使之生效,之后我们输入 ctn 后,按 tab 就会提示或自动补全容器名了了
    # ctn auto completecomplete -W "$(docker ps --format"{{.Names}}")" ctn

    注意: 由于提示的 容器Name 是 ~/.bashrc 生效时的列表,所有如果之后 docker 容器列表有变动,需要重新执行 source ~/.bashrc 使之更新提示列表

    3.6 终止容器

    通过 docker stop [容器Name] 来终止一个运行中的容器

    # 终止容器名为 test2 的容器docker stop test2# 查看正在运行中的容器docker ps# 查看所有容器(包括终止的)docker ps -a

    3.7 将容器保存为镜像

    我们修改一个容器后,可以经当前容器状态打包成镜像,方便下次直接通过镜像仓库生成当前状态的容器。

    # 创建容器docker run -t -i training/sinatra /bin/bash# 添加两个应用gem install json# 将修改后的容器打包成新的镜像docker commit -m "Added json gem" -a "Docker Newbee" 0b2616b0e5a8 ouruser/sinatra:v2

    3.8 导出/导入容器

    容器 ->导出> 容器快照文件 ->导入> 本地镜像仓库 ->新建> 容器

    $ docker ps -aCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES2a8bffa405c8        ubuntu:14.04        "/bin/sh -c 'while t…"   About an hour ago   Up 3 seconds                                            test2# 导出$ docker export 2a8bffa405c8 > ubuntu.tar# 导入为镜像$ docker ubuntu.tar | docker import - test/ubuntu:v1.0# 从指定 URL 或者某个目录导入$ docker import http://example.com/exampleimage.tgz example/imagerepo

    注意:用户既可以通过 docker load 来导入镜像存储文件到本地镜像仓库,也可以使用 docker import 来导入一个容器快找到本地镜像仓库,两者的区别在于容器快照将丢失所有的历史记录和元数据信息,仅保存容器当时的状态,而镜像存储文件将保存完成的记录,体积要更大。所有容器快照文件导入时需要重新指定标签等元数据信息。

    3.9 删除容器

    可以使用 docker rm [容器Name] 来删除一个终止状态的容器,如果容器还未终止,可以先使用 docker stop [容器Name] 来终止容器,再进行删除操作

    docker rm test2# 删除容器 -f: 强制删除,无视是否运行$ docker [-f] rm myubuntu# 删除所有已关闭的容器$ docker rm $(docker ps -a -q)

    3.10 查看容器状态

    docker stats $(docker ps --format={{.Names}})

    四、数据卷

    4.1 介绍

    数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多特性:

    • 数据卷可以在容器之间共享和重用
    • 对数据卷的修改会立马生效
    • 对数据卷的更新,不会影响镜像
    • 卷会一直存在,直到没有容器使用

    数据卷类似于 Linux 下对目录或文件进行 mount

    4.2 创建数据卷

    在用 docker run 命令的时候,使用 -v 标记来创建一个数据卷并挂在在容器里,可同时挂在多个。

    # 创建一个 web 容器,并加载一个数据卷到容器的 /webapp 目录docker run -d -P --name web -v /webapp training/webapp python app.py# 挂载一个宿主机目录 /data/webapp 到容器中的 /opt/webappdocker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py# 默认是读写权限,也可以指定为只读docker run -d -P --name web -v /src/webapp:/opt/webapp:ro# 挂载单个文件docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash

    4.3 数据卷容器

    如果需要多个容器共享数据,最好创建数据卷容器,就是一个正常的容器,撰文用来提供数据卷供其他容器挂载的

    # 创建一个数据卷容器 dbdatadocker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres# 其他容器挂载 dbdata 容器的数据卷docker run -d --volumes-from dbdata --name db1 training/postgresdocker run -d --volumes-from dbdata --name db2 training/postgres

    五、网络

    5.1 外部访问容器

    在容器内运行一些服务,需要外部可以访问到这些服务,可以通过 -P-p 参数来指定端口映射。

    当使用 -P 标记时,Docker 会随即映射一个 49000~49900 的端口到内部容器开放的网络端口。

    使用 docker ps 可以查看端口映射情况

    $ docker psCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES7f43807dc042        training/webapp     "python app.py"          3 seconds ago       Up 2 seconds        0.0.0.0:32770->5000/tcp             amazing_liskov

    -p 指定端口映射,支持格式 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort

    # 不限制ip访问docker run -d -p 5000:5000 training/webapp python app.py# 只允许宿主机回环地址访问docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py# 宿主机自动分配绑定端口docker run -d -p 127.0.0.1::5000 training/webapp python app.py# 指定 udp 端口docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py# 指定多个端口映射docker run -d -p 5000:5000  -p 3000:80 training/webapp python app.py# 查看映射端口配置$ docker port amazing_liskov5000/tcp -> 0.0.0.0:32770

    5.2 容器互联

    容器除了跟宿主机端口映射外,还有一种容器间交互的方式,可以在源/目标容器之间建立一个隧道,目标容器可以看到源容器指定的信息。

    可以通过 --link name:alias 来连接容器,下面就是 “web容器连接db容器” 的例子

    # 创建 容器dbdocker run -d --name db training/postgres# 创建 容器web 并连接到 容器dbdocker run -d -P --name web --link db:db training/webapp python app.py# 进入 容器web,测试连通性$ ctn web$ ping dbPING db (172.17.0.3) 56(84) bytes of data.64 bytes from db (172.17.0.3): icmp_seq=1 ttl=64 time=0.254 ms64 bytes from db (172.17.0.3): icmp_seq=2 ttl=64 time=0.190 ms64 bytes from db (172.17.0.3): icmp_seq=3 ttl=64 time=0.389 ms

    5.3 访问控制

    容器想要访问外部网络,需要宿主机的转发支持。在 Linux 系统中,通过以下命令检查是否打开

    $ sysctl net.ipv4.ip_forwardnet.ipv4.ip_forward = 1

    如果是 0,说明没有开启转发,则需要手动打开。

    $ sysctl -w net.ipv4.ip_forward=1

    5.4 配置 docker0 桥接

    Docker 服务默认会创建一个 docker0 网桥,他在内核层连通了其他物理或虚拟网卡,这就将容器和主机都放在同一个物理网络。

    Docker 默认制定了 docker0 接口的IP地址和子网掩码,让主机和容器间可以通过网桥相互通信,他还给了 MTU(接口允许接收的最大单元),通常是 1500 Bytes,或宿主机网络路由上支持的默认值。这些都可以在服务启动的时候进行配置。

    • --bip=CIDR ip地址加子网掩码格式,如 192.168.1.5/24
    • --mtu=BYTES 覆盖默认的 Docker MTU 配置

    可以通过 brctl show 来查看网桥和端口连接信息

    5.5 网络配置文件

    Docker 1.2.0 开始支持在运行中的容器里编辑 /etc/hosts/etc/hostsname/etc/resolve.conf 文件,修改都是临时的,重新容器将会丢失修改,通过 docker commit 也不会被提交。

    六、Dockerfile

    6.1 介绍

    Dockerfile 是由一行行命令组成的命令集合,分为四个部分:

    1. 基础镜像信息
    2. 维护着信息
    3. 镜像操作指令
    4. 容器启动时执行指令

    如下:

    # 最前面一般放这个 Dockerfile 的介绍、版本、作者及使用说明等# This dockerfile uses the ubuntu image# VERSION 2 - EDITION 1# Author: docker_user# Command format: Instruction [arguments / command] ..# 使用的基础镜像,必须放在非注释第一行FROM ubuntu# 维护着信息信息: 名字 联系方式MAINTAINER docker_user docker_user@email.com# 构建镜像的命令:对镜像做的调整都在这里RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.listRUN apt-get update && apt-get install -y nginxRUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf# 创建/运行 容器时的操作指令 # 可以理解为 docker run 后跟的运行指令CMD /usr/sbin/nginx

    6.2 指令

    指令一般格式为 INSTRUCTION args,包括 FORMMAINTAINERRUN

    FORM第一条指令必须是 FORM 指令,并且如果在同一个Dockerfile 中创建多个镜像,可以使用多个 FROM 指令(每个镜像一次)FORM ubuntuFORM ubuntu:14.04
    MAINTAINER维护者信息MAINTAINER Chris xx@gmail.com
    RUN每条 RUN 指令在当前镜像基础上执行命令,并提交为新的镜像。当命令过长时可以使用 \ 来换行在 shell 终端中运行命令RUN apt-get update && apt-get install -y nginxexec 中执行:RUN ["/bin/bash", "-c", "echo hello"]
    CMD指定启动容器时执行的命令,每个 Dockerfile 只能有一条 CMD 命令。如果指定了多条命令,只有最后一条会被执行。CMD ["executable","param1","param2"] 使用 exec 执行,推荐方式;CMD command param1 param2/bin/sh 中执行,提供给需要交互的应用;CMD ["param1","param2"] 提供给 ENTRYPOINT 的默认参数;
    EXPOSE告诉服务端容器暴露的端口号,EXPOSE
    ENV指定环境变量ENV PG_MAJOR 9.3ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
    ADDADD 该命令将复制指定的 到容器中的 。其中 可以是 Dockerfile 所在目录的一个相对路径,也可以是一个URL;还可以是一个 tar文件(自动解压为目录)
    COPY格式为 COPY 复制本地主机的 (为 Dockerfile 所在目录的相对路径)到容器中的 。当使用本地目录为源目录时,推荐使用 COPY
    ENTRYPOINT配置容器启动执行的命令,并且不可被 docker run 提供的参数覆盖每个Docekrfile 中只能有一个 ENTRYPOINT ,当指定多个时,只有最后一个起效两种格式ENTRYPOINT ["executable", "param1", "param2"]``ENTRYPOINT command param1 param2(shell中执行)
    VOLUME创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。VOLUME [“/data”]
    USER指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户USER daemon
    WORKDIR为后续的 RUNCMDENTRYPOINT 指令配置工作目录。可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。格式为 WORKDIR /path/to/workdir。 WORKDIR /aWORKDIR bWORKDIR cRUN pwd最后的路径为 /a/b/c
    ONBUILD配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。格式为 ONBUILD [INSTRUCTION]

    6.3 创建镜像

    编写完成 Dockerfile 之后,可以通过 docker build 命令来创建镜像

    docker build [选项] 路径 该命令江都区指定路径下(包括子目录)的Dockerfile,并将该路径下所有内容发送给 Docker 服务端,有服务端来创建镜像。可以通过 .dockerignore 文件来让 Docker 忽略路径下的目录与文件

    # 使用 -t 指定镜像的标签信息docker build -t myrepo/myimage .

    七、Docker Compose

    7.1 介绍

    Docker Compose 是 Docker 官方编排项目之一,负责快速在集群中部署分布式应用。维护地址:https://github.com/docker/compose,由 Python 编写,实际调用 Docker提供的API实现。

    Dockerfile 可以让用户管理一个单独的应用容器,而 Compose 则允许用户在一个模版(YAML格式)中定义一组相关联的应用容器(被称为一个project/项目),例如一个 web容器再加上数据库、redis等。

    7.2 安装

    # 使用 pip 进行安装pip install -U docker-compose# 查看用法docker-ompose -h# 添加 bash 补全命令curl -L https://raw.githubusercontent.com/docker/compose/1.2.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose

    7.3 使用

    术语

    • 服务/service: 一个应用容器,实际上可以运行多个相同镜像的实例
    • 项目/project: 有一组关联的应用容器组成的完成业务单元

    示例:创建一个 Haproxy 挂载三个 Web 容器

    创建一个 compose-haproxy-web 目录,作为项目工作目录,并在其中分别创建两个子目录: haproxyweb

    compose-haproxy-webcompose-haproxy-webgit clone https://github.com/yelog/compose-haproxy-web.git

    目录长这样:

    compose-haproxy-web├── docker-compose.yml├── haproxy│   └── haproxy.cfg└── web    ├── Dockerfile    ├── index.html    └── index.py

    在该目录执行 docker-compose up 命令,会整合输出所有容器的输出

    $ docker-compose upStarting compose-haproxy-web_webb_1 ... doneStarting compose-haproxy-web_webc_1 ... doneStarting compose-haproxy-web_weba_1 ... doneRecreating compose-haproxy-web_haproxy_1 ... doneAttaching to compose-haproxy-web_webb_1, compose-haproxy-web_weba_1, compose-haproxy-web_webc_1, compose-haproxy-web_haproxy_1haproxy_1  | [NOTICE] 244/131022 (1) : haproxy version is 2.2.2haproxy_1  | [NOTICE] 244/131022 (1) : path to executable is /usr/local/sbin/haproxyhaproxy_1  | [ALERT] 244/131022 (1) : parsing [/usr/local/etc/haproxy/haproxy.cfg:14] : 'listen' cannot handle unexpected argument ':70'.haproxy_1  | [ALERT] 244/131022 (1) : parsing [/usr/local/etc/haproxy/haproxy.cfg:14] : please use the 'bind' keyword for listening addresses.haproxy_1  | [ALERT] 244/131022 (1) : Error(s) found in configuration file : /usr/local/etc/haproxy/haproxy.cfghaproxy_1  | [ALERT] 244/131022 (1) : Fatal errors found in configuration.compose-haproxy-web_haproxy_1 exited with code 1

    此时访问本地的 80 端口,会经过 haproxy 自动转发到后端的某个 web 容器上,刷新页面,可以观察到访问的容器地址的变化。

    7.4 命令说明

    大部分命令都可以运行在一个或多个服务上。如果没有特别的说明,命令则应用在项目所有的服务上。

    执行 docker-compose [COMMAND] --help 查看具体某个命令的使用说明

    使用格式

    docker-compose [options] [COMMAND] [ARGS...]
    build构建/重建服务服务一旦构建后,将会带上一个标记名,例如 web_db可以随时在项目目录运行 docker-compose build 来重新构建服务
    help获得一个命令的信息
    kill通过发送 SIGKILL 信号来强制停止服务容器,支持通过参数来指定发送信号,例如docker-compose kill -s SIGINT
    logs查看服务的输出
    port打印绑定的公共端口
    ps列出所有容器
    pull拉去服务镜像
    rm删除停止的服务容器
    run在一个服务上执行一个命令docker-compose run ubuntu ping docker.com
    scale设置同一个服务运行的容器个数通过 service=num 的参数来设置数量docker-compose scale web=2 worker=3
    start启动一个已经存在的服务容器
    stop停止一个已经运行的容器,但不删除。可以通过 docker-compose start 再次启动
    up构建、创建、启动、链接一个服务相关的容器链接服务都将被启动,除非他们已经运行docker-compose up -d 将后台运行并启动docker-compose up 已存在容器将会重新创建docker-compose up --no-recreate 将不会重新创建容器

    7.5 环境变量

    环境变量可以用来配置 Compose 的行为

    Docker_ 开头的变量用来配置 Docker 命令行客户端使用的一样

    COMPOSE_PROJECT_NAME设置通过 Compose 启动的每一个容器前添加的项目名称,默认是当前工作目录的名字。
    COMPOSE_FILE设置要使用的 docker-compose.yml 的路径。默认路径是当前工作目录。
    DOCKER_HOST设置 Docker daemon 的地址。默认使用 unix:///var/run/docker.sock,与 Docker 客户端采用的默认值一致。
    DOCKER_TLS_VERIFY如果设置不为空,则与 Docker daemon 交互通过 TLS 进行。
    DOCKER_CERT_PATH配置 TLS 通信所需要的验证(ca.pemcert.pemkey.pem)文件的路径,默认是 ~/.docker

    7.6 docker-compose.yml

    默认模版文件是 docker-compose.yml ,启动定义了每个服务都必须经过 image 指令指定镜像或 build 指令(需要 Dockerfile) 来自动构建。

    其他大部分指令跟 docker run 类似

    如果使用 build 指令,在 Dockerfile 中设置的选项(如 CMDEXPOSE 等)将会被自动获取,无需在 docker-compose.yml 中再次设置。

    **image**

    指定镜像名称或镜像ID,如果本地仓库不存在,将尝试从远程仓库拉去此镜像

    image: ubuntuimage: orchardup/postgresqlimage: a4bc65fd**build**

    指定 Dockerfile 所在文件的路径。Compose 将利用它自动构建这个镜像,然后使用这个镜像。

    build: /path/to/build/dir**command**

    覆盖容器启动默认执行命令

    command: bundle exec thin -p 3000**links**

    链接到其他服务中的容器,使用服务名称或别名

    links:    - db  - db:database  - redis

    别名会自动在服务器中的 /etc/hosts 里创建。例如:

    172.17.2.186  db172.17.2.186  database172.17.2.187  redis**external_links**

    连接到 docker-compose.yml 外部的容器,甚至并非 Compose 管理的容器。

    external_links: - redis_1 - project_db_1:mysql - project_db_1:postgresql

    ports

    暴露端口信息 HOST:CONTAINER

    格式或者仅仅指定容器的端口(宿主机会随机分配端口)

    ports: - "3000" - "8000:8000" - "49100:22" - "127.0.0.1:8001:8001"

    注:当使用 HOST:CONTAINER 格式来映射端口时,如果你使用的容器端口小于 60 你可能会得到错误得结果,因为 YAML 将会解析 xx:yy 这种数字格式为 60 进制。所以建议采用字符串格式。

    **expose**

    暴露端口,但不映射到宿主机,只被连接的服务访问

    expose: - "3000" - "8000"

    volumes

    卷挂载路径设置。可以设置宿主机路径 (HOST:CONTAINER) 或加上访问模式 (HOST:CONTAINER:ro)。

    volumes: - /var/lib/mysql - cache/:/tmp/cache - ~/configs:/etc/configs/:ro

    **
    **

    volumes_from

    从另一个服务或容器挂载它的所有卷。

    volumes_from: - service_name - container_name
    environment

    设置环境变量。你可以使用数组或字典两种格式。

    只给定名称的变量会自动获取它在 Compose 主机上的值,可以用来防止泄露不必要的数据。

    environment:  RACK_ENV: development  SESSION_SECRET:environment:  - RACK_ENV=development  - SESSION_SECRET

    env_file

    从文件中获取环境变量,可以为单独的文件路径或列表。

    如果通过 docker-compose -f FILE 指定了模板文件,则 env_file 中路径会基于模板文件路径。

    如果有变量名称与 environment 指令冲突,则以后者为准。

    env_file: .envenv_file:  - ./common.env  - ./apps/web.env  - /opt/secrets.env

    环境变量文件中每一行必须符合格式,支持 # 开头的注释行。

    # common.env: Set Rails/Rack environmentRACK_ENV=development

    extends

    基于已有的服务进行扩展。例如我们已经有了一个 webapp 服务,模板文件为 common.yml

    # common.ymlwebapp:  build: ./webapp  environment:    - DEBUG=false    - SEND_EMAILS=false

    编写一个新的 development.yml 文件,使用 common.yml 中的 webapp 服务进行扩展。

    # development.ymlweb:  extends:    file: common.yml    service: webapp  ports:    - "8000:8000"  links:    - db  environment:    - DEBUG=truedb:  image: postgres

    后者会自动继承 common.yml 中的 webapp 服务及相关环节变量。

    **
    **

    net

    设置网络模式。使用和 docker client--net 参数一样的值。

    net: "bridge"net: "none"net: "container:[name or id]"net: "host"

    **
    **

    pid

    跟主机系统共享进程命名空间。打开该选项的容器可以相互通过进程 ID 来访问和操作。

    pid: "host"

    dns

    配置 DNS 服务器。可以是一个值,也可以是一个列表。

    dns: 8.8.8.8dns:  - 8.8.8.8  - 9.9.9.9

    cap_add, cap_drop

    添加或放弃容器的 Linux 能力(Capabiliity)。

    cap_add:  - ALLcap_drop:  - NET_ADMIN  - SYS_ADMIN

    **
    **

    dns_search

    配置 DNS 搜索域。可以是一个值,也可以是一个列表。

    dns_search: example.comdns_search:  - domain1.example.com  - domain2.example.com

    **
    **

    working_dir, entrypoint, user, hostname, domainname, mem_limit, privileged, restart, stdin_open, tty, cpu_shares

    这些都是和 docker run 支持的选项类似。

    八、安全

    8.1 内核命名空间

    当使用 docker run 启动一个容器时,在后台 Docker 为容器创建一个独立的命名空间和控制集合。命名空间踢空了最基础的也是最直接的隔离,在容器中运行的进程不会被运行在主机上的进程和其他容器发现和作用。

    8.2 控制组

    控制组是 Linux 容器机制的另一个关键组件,负责实现资源的审计和限制。

    它提供了很多特性,确保哥哥容器可以公平地分享主机的内存、CPU、磁盘IO等资源;当然,更重要的是,控制组确保了当容器内的资源使用产生压力时不会连累主机系统。

    8.3 内核能力机制

    能力机制是 Linux 内核的一个强大特性,可以提供细粒度的权限访问控制。 可以作用在进程上,也可以作用在文件上。

    例如一个服务需要绑定低于 1024 的端口权限,并不需要 root 权限,那么它只需要被授权 net_bind_service 能力即可。

    默认情况下, Docker 启动的容器被严格限制只允许使用内核的一部分能力。

    使用能力机制加强 Docker 容器的安全有很多好处,可以按需分配给容器权限,这样,即便攻击者在容器中取得了 root 权限,也不能获取宿主机较高权限,能进行的破坏也是有限的。

    参考资料

    https://docs.docker.com/engine/reference/commandline/images/

    http://www.dockerinfo.net/

    ]]>
    + 一、概述

    1.1 什么是docker

    Docker 诞生于 2013 年初,由 dotCloud 公司(后改名为 Docker Inc)基于 Go 语言实现并开源的项目。此项目后来加入 Linux基金会,遵从了 Apache 2.0 协议

    Docker 项目的目标是实现轻量级的操作系统虚拟化解决方案。Docker 是在 Linux 容器技术(LXC)的基础上进行了封装,让用户可以快速并可靠的将应用程序从一台运行到另一台上。

    使用容器部署应用被称为容器化,容器化技术的几大优势:

    1. 灵活:甚至复杂的应用也可以被容器化
    2. 轻量:容器利用和共享宿主机内核,从而在利用系统资源比虚拟机更加的有效
    3. 可移植:你可以在本地构建,在云端部署并在任何地方运行
    4. 松耦合:容器是高度封装和自给自足的,允许你在不破环其他容器的情况下替换或升级任何一个
    5. 可扩展:你可以通过数据中心来新增和自动分发容器
    6. 安全:容器依赖强约束和独立的进程

    1.2 和传统虚拟机的区别

    容器在Linux上本地运行,并与其他容器共享主机的内核。它运行一个离散进程,不占用任何其他可执行文件更多的内存,从而使其轻巧。

    image.png

    1.3 相关链接

    官网:https://www.docker.com/

    文档:https://docs.docker.com/

    二、Image镜像

    2.1 介绍

    Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变

    1. 父镜像:每个镜像都可能依赖于有一个或多个下层组成的另一个镜像。下层那个镜像就是上层镜像的父镜像
    2. 基础镜像:一个没有任何父镜像的镜像,被称为基础镜像
    3. 镜像ID:所有镜像都是通过一个 64 位十六进制字符串(256 bit 的值)来标识的。为了简化使用,前 12 个自负可以组成一个短ID,可以在命令行中使用。短ID还是有一定的碰撞几率,所以服务器总是返回长ID

    2.2 从仓库下载镜像

    可以通过 docker pull 命令从仓库获取所需要的镜像

    docker pull [选项] [Docker Registry 地址]<镜像名>:<标签>

    选项:

    1. –all-tags,-a : 拉去所有 tagged 镜像
    2. –disable-content-trust:忽略镜像的校验,默认
    3. –platform:如果服务器是开启多平台支持的,则需要设置平台
    4. –quiet,-q:静默执行,不打印详细信息

    标签: 下载指定标签的镜像,默认 latest

    示例

    # 从 Docker Hub 下载最新的 debian 镜像docker pull debian# 从 Docker Hub 下载 jessie 版 debian 镜像docker pull debian:jessie# 下载指定摘要(sha256)的镜像docker pull ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2

    2.3 列出本地镜像

    # 列出已下载的镜像 image_name: 指定列出某个镜像docker images [选项] [image_name]

    选项

    参数描述
    –all, -a展示所有镜像(包括 intermediate 镜像)
    –digests展示摘要
    –filter, -f添加过滤条件
    –format使用 Go 模版更好的展示
    –no-trunc不删减输出
    –quiet, -q静默输出,仅仅展示 IDs

    示例

    # 展示本地所有下载的镜像docker images# 在本地查找镜像名是 "java" 标签是 "8" 的 奖项docker images: java:8# 查找悬挂镜像docker images --filter "dangling=true"# 过滤 lable 为 "com.example.version" 的值为 0.1 的镜像docker images --filter "label=com.example.version=0.1"

    2.4 Dockerfile创建镜像

    为了方便分享和快速部署,我们可以使用 docker build 来创建一个新的镜像,首先创建一个文件 Dockerfile,如下

    # This is a commentFROM ubuntu:14.04MAINTAINER Chris <jaytp@qq.com>RUN apt-get -qq updateRUN apt-get -qqy install ruby ruby-devRUN gem install sinatra

    然后在此 Dockerfile 所在目录执行 docker build -t yelog/ubuntu:v1 . 来生成镜像,所属组织/镜像名:标签

    2.5 上传镜像

    用户可以通过 docker push 命令,把自己创建的镜像上传到仓库中来共享。例如,用户在 Docker Hub 上完成注册后,可以推送自己的镜像到仓库中。

    docker push yelog/ubuntu

    2.6 导出和载入镜像

    docker 支持将镜像导出为文件,然后可以再从文件导入到本地镜像仓库

    # 导出docker load --input yelog_ubuntu_v1.tar# 载入docker load < yelog_ubuntu_v1.tar

    2.7 移除本地镜像

    # -f 强制删除docker rmi [-f] yelog/ubuntu:v1# 删除悬挂镜像docker rmi $(docker images -f "dangling=true" -q)# 删除所有未被容器使用的镜像docker image prune -a

    三、容器

    3.1 介绍

    容器和镜像,就像面向对象中的 示例 一样,镜像是静态的定义,容器是镜像运行的实体,容器可以被创建、启动、停止、删除和暂停等

    容器的实质是进城,耽于直接的宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的 root 文件系统、网络配置和进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。

    3.2 创建容器

    我们可以通过命令 docker run 命令创建容器

    如下,启动一个容器,执行命令输出 “Hello word”,之后终止容器

    docker run ubuntu:14.04 /bin/echo 'Hello world'

    下面的命令则是启动一个 bash 终端,允许用户进行交互

    docker run -t -i ubuntu:14.04 /bin/bash

    -t 让 Dcoker 分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上

    -i 责让容器的标准输入保持打开

    更多参数可选

    -a stdin指定标准输入输出内容类型
    -d后台运行容器,并返回容器ID
    -i以交互模式运行容器,通常与 -t 同时使用
    -P随机端口映射,容器端口内部随即映射到宿主机的端口上
    -p指定端口映射, -p 宿主机端口:容器端口
    -t为容器重新分配一个伪输入终,通常与 -i 同时使用
    –name=”gate”为容器指定一个名称
    –dns 8.8.8.8指定容器的 DNS 服务器,默认与宿主机一致
    –dns-search example.com指定容器 DNS 搜索域名,默认与宿主机一致
    -h “gate”指定容器的 hostname
    -e username=’gate’设置环境变量
    –env-file=[]从指定文件读入环境变量
    –cpuset=”0-2” or –cpuset=”0,1,2”绑定容器到指定 CPU 运行
    -m设置容器使用内存最大值
    –net=”bridge”指定容器的网络连接类型支持 bridge/host/none/container
    –link=[]添加链接到另一个容器
    –expose=[]开放一个端口或一组端口
    –volume,-v绑定一个卷

    当利用 docker run 来创建容器时,Dcoker 在后台运行的标准操作包括:

    • 检查本地是否存在指定的镜像,不存在就从公有仓库下载
    • 利用镜像创建并启动一个容器
    • 分配一个文件系统,并在只读的镜像外面挂在一层可读写层
    • 从宿主主机配置的网桥接口中桥接一个虚拟借口到容器中去
    • 从地址池配置一个 ip 地址给容器
    • 执行用户指定的应用程序
    • 执行用户指定的应用程序
    • 执行完毕后容器被终止

    3.3 启动容器

    # 创建一个名为 test 的容器,容器任务是:打印一行 Hello worddocker run --name='test' ubuntu:14.04 /bin/echo 'Hello world'# 查看所有可用容器 [-a]包括终止在内的所有容器docker ps -a# 启动指定 name 的容器docker start test# 重启指定 name 的容器docker restart test# 查看日志运行日志(每次启动的日志均被查询出来)$ docker logs testHello worldHello world

    3.4 守护态运行

    前面创建的容器都是执行任务(打印Hello world)后,容器就终止了。更多的时候,我们需要让 Docker 容器在后台以守护态(Daemonized)形式运行。此时,可以通过添加 -d 参数来实现

    注意:docker是否会长久运行,和 docker run 指定的命令有关

    # 创建 docker 后台守护进程的容器docker run --name='test2' -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"# 查看容器$ docker psCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES237e555d4457        ubuntu:14.04        "/bin/sh -c 'while t…"   52 seconds ago      Up 51 seconds                                           test2# 获取容器的输出信息$ docker logs test2hello worldhello worldhello world

    3.5 进入容器

    上一步我们已经实现了容器守护态长久运行,某些时候需要进入容器进行操作,可以使用 attachexec 进入容器。

    # 不安全的,ctrl+d 退出时容器也会终止docker attach [容器Name]# 以交互式命令行进入,安全的,推荐使用docker exec -it [容器Name] /bin/bash

    命令优化

    1. 使用 docker exec 命令时,好用,但是命令过长,我们可以通过自定义命令来简化使用
    2. 创建文件 /user/bin/ctn 命令文件,内容如下
    docker exec -it $1 /bin/bash
    1. 检查环境变量有没有配置目录 /usr/bin (一般是有配置在环境变量里面的,不过最好再确认一下)
    $PATHbash: /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games: No such file or directory
    1. 完成上面步骤后,就可以直接通过命令 ctn 来进入容器

    注意:如果是使用非 root 账号创建的命令,而 docker 命令是 root 权限,可能存在权限问题,可以通过设置 chmod 777 /usr/bin/ctn 设置权限,使用 sudo ctn [容器Name] 即可进入容器

    $ ctn [容器Name]
    1. 使用上面命令时,容器Name 需要手动输入,容器出错。我们可以借助 complete 命令来补全 容器Name,在 ~/.bashrc (作用于当前用户,如果想要所要用户上校,可以修改 /etc/bashrc)文件中添加一行,内容如下。保存后执行 source ~/.bashrc 使之生效,之后我们输入 ctn 后,按 tab 就会提示或自动补全容器名了了
    # ctn auto completecomplete -W "$(docker ps --format"{{.Names}}")" ctn

    注意: 由于提示的 容器Name 是 ~/.bashrc 生效时的列表,所有如果之后 docker 容器列表有变动,需要重新执行 source ~/.bashrc 使之更新提示列表

    3.6 终止容器

    通过 docker stop [容器Name] 来终止一个运行中的容器

    # 终止容器名为 test2 的容器docker stop test2# 查看正在运行中的容器docker ps# 查看所有容器(包括终止的)docker ps -a

    3.7 将容器保存为镜像

    我们修改一个容器后,可以经当前容器状态打包成镜像,方便下次直接通过镜像仓库生成当前状态的容器。

    # 创建容器docker run -t -i training/sinatra /bin/bash# 添加两个应用gem install json# 将修改后的容器打包成新的镜像docker commit -m "Added json gem" -a "Docker Newbee" 0b2616b0e5a8 ouruser/sinatra:v2

    3.8 导出/导入容器

    容器 ->导出> 容器快照文件 ->导入> 本地镜像仓库 ->新建> 容器

    $ docker ps -aCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES2a8bffa405c8        ubuntu:14.04        "/bin/sh -c 'while t…"   About an hour ago   Up 3 seconds                                            test2# 导出$ docker export 2a8bffa405c8 > ubuntu.tar# 导入为镜像$ docker ubuntu.tar | docker import - test/ubuntu:v1.0# 从指定 URL 或者某个目录导入$ docker import http://example.com/exampleimage.tgz example/imagerepo

    注意:用户既可以通过 docker load 来导入镜像存储文件到本地镜像仓库,也可以使用 docker import 来导入一个容器快找到本地镜像仓库,两者的区别在于容器快照将丢失所有的历史记录和元数据信息,仅保存容器当时的状态,而镜像存储文件将保存完成的记录,体积要更大。所有容器快照文件导入时需要重新指定标签等元数据信息。

    3.9 删除容器

    可以使用 docker rm [容器Name] 来删除一个终止状态的容器,如果容器还未终止,可以先使用 docker stop [容器Name] 来终止容器,再进行删除操作

    docker rm test2# 删除容器 -f: 强制删除,无视是否运行$ docker [-f] rm myubuntu# 删除所有已关闭的容器$ docker rm $(docker ps -a -q)

    3.10 查看容器状态

    docker stats $(docker ps --format={{.Names}})

    四、数据卷

    4.1 介绍

    数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多特性:

    • 数据卷可以在容器之间共享和重用
    • 对数据卷的修改会立马生效
    • 对数据卷的更新,不会影响镜像
    • 卷会一直存在,直到没有容器使用

    数据卷类似于 Linux 下对目录或文件进行 mount

    4.2 创建数据卷

    在用 docker run 命令的时候,使用 -v 标记来创建一个数据卷并挂在在容器里,可同时挂在多个。

    # 创建一个 web 容器,并加载一个数据卷到容器的 /webapp 目录docker run -d -P --name web -v /webapp training/webapp python app.py# 挂载一个宿主机目录 /data/webapp 到容器中的 /opt/webappdocker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py# 默认是读写权限,也可以指定为只读docker run -d -P --name web -v /src/webapp:/opt/webapp:ro# 挂载单个文件docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash

    4.3 数据卷容器

    如果需要多个容器共享数据,最好创建数据卷容器,就是一个正常的容器,撰文用来提供数据卷供其他容器挂载的

    # 创建一个数据卷容器 dbdatadocker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres# 其他容器挂载 dbdata 容器的数据卷docker run -d --volumes-from dbdata --name db1 training/postgresdocker run -d --volumes-from dbdata --name db2 training/postgres

    五、网络

    5.1 外部访问容器

    在容器内运行一些服务,需要外部可以访问到这些服务,可以通过 -P-p 参数来指定端口映射。

    当使用 -P 标记时,Docker 会随即映射一个 49000~49900 的端口到内部容器开放的网络端口。

    使用 docker ps 可以查看端口映射情况

    $ docker psCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES7f43807dc042        training/webapp     "python app.py"          3 seconds ago       Up 2 seconds        0.0.0.0:32770->5000/tcp             amazing_liskov

    -p 指定端口映射,支持格式 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort

    # 不限制ip访问docker run -d -p 5000:5000 training/webapp python app.py# 只允许宿主机回环地址访问docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py# 宿主机自动分配绑定端口docker run -d -p 127.0.0.1::5000 training/webapp python app.py# 指定 udp 端口docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py# 指定多个端口映射docker run -d -p 5000:5000  -p 3000:80 training/webapp python app.py# 查看映射端口配置$ docker port amazing_liskov5000/tcp -> 0.0.0.0:32770

    5.2 容器互联

    容器除了跟宿主机端口映射外,还有一种容器间交互的方式,可以在源/目标容器之间建立一个隧道,目标容器可以看到源容器指定的信息。

    可以通过 --link name:alias 来连接容器,下面就是 “web容器连接db容器” 的例子

    # 创建 容器dbdocker run -d --name db training/postgres# 创建 容器web 并连接到 容器dbdocker run -d -P --name web --link db:db training/webapp python app.py# 进入 容器web,测试连通性$ ctn web$ ping dbPING db (172.17.0.3) 56(84) bytes of data.64 bytes from db (172.17.0.3): icmp_seq=1 ttl=64 time=0.254 ms64 bytes from db (172.17.0.3): icmp_seq=2 ttl=64 time=0.190 ms64 bytes from db (172.17.0.3): icmp_seq=3 ttl=64 time=0.389 ms

    5.3 访问控制

    容器想要访问外部网络,需要宿主机的转发支持。在 Linux 系统中,通过以下命令检查是否打开

    $ sysctl net.ipv4.ip_forwardnet.ipv4.ip_forward = 1

    如果是 0,说明没有开启转发,则需要手动打开。

    $ sysctl -w net.ipv4.ip_forward=1

    5.4 配置 docker0 桥接

    Docker 服务默认会创建一个 docker0 网桥,他在内核层连通了其他物理或虚拟网卡,这就将容器和主机都放在同一个物理网络。

    Docker 默认制定了 docker0 接口的IP地址和子网掩码,让主机和容器间可以通过网桥相互通信,他还给了 MTU(接口允许接收的最大单元),通常是 1500 Bytes,或宿主机网络路由上支持的默认值。这些都可以在服务启动的时候进行配置。

    • --bip=CIDR ip地址加子网掩码格式,如 192.168.1.5/24
    • --mtu=BYTES 覆盖默认的 Docker MTU 配置

    可以通过 brctl show 来查看网桥和端口连接信息

    5.5 网络配置文件

    Docker 1.2.0 开始支持在运行中的容器里编辑 /etc/hosts/etc/hostsname/etc/resolve.conf 文件,修改都是临时的,重新容器将会丢失修改,通过 docker commit 也不会被提交。

    六、Dockerfile

    6.1 介绍

    Dockerfile 是由一行行命令组成的命令集合,分为四个部分:

    1. 基础镜像信息
    2. 维护着信息
    3. 镜像操作指令
    4. 容器启动时执行指令

    如下:

    # 最前面一般放这个 Dockerfile 的介绍、版本、作者及使用说明等# This dockerfile uses the ubuntu image# VERSION 2 - EDITION 1# Author: docker_user# Command format: Instruction [arguments / command] ..# 使用的基础镜像,必须放在非注释第一行FROM ubuntu# 维护着信息信息: 名字 联系方式MAINTAINER docker_user docker_user@email.com# 构建镜像的命令:对镜像做的调整都在这里RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.listRUN apt-get update && apt-get install -y nginxRUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf# 创建/运行 容器时的操作指令 # 可以理解为 docker run 后跟的运行指令CMD /usr/sbin/nginx

    6.2 指令

    指令一般格式为 INSTRUCTION args,包括 FORMMAINTAINERRUN

    FORM第一条指令必须是 FORM 指令,并且如果在同一个Dockerfile 中创建多个镜像,可以使用多个 FROM 指令(每个镜像一次)FORM ubuntuFORM ubuntu:14.04
    MAINTAINER维护者信息MAINTAINER Chris xx@gmail.com
    RUN每条 RUN 指令在当前镜像基础上执行命令,并提交为新的镜像。当命令过长时可以使用 \ 来换行在 shell 终端中运行命令RUN apt-get update && apt-get install -y nginxexec 中执行:RUN ["/bin/bash", "-c", "echo hello"]
    CMD指定启动容器时执行的命令,每个 Dockerfile 只能有一条 CMD 命令。如果指定了多条命令,只有最后一条会被执行。CMD ["executable","param1","param2"] 使用 exec 执行,推荐方式;CMD command param1 param2/bin/sh 中执行,提供给需要交互的应用;CMD ["param1","param2"] 提供给 ENTRYPOINT 的默认参数;
    EXPOSE告诉服务端容器暴露的端口号,EXPOSE
    ENV指定环境变量ENV PG_MAJOR 9.3ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
    ADDADD 该命令将复制指定的 到容器中的 。其中 可以是 Dockerfile 所在目录的一个相对路径,也可以是一个URL;还可以是一个 tar文件(自动解压为目录)
    COPY格式为 COPY 复制本地主机的 (为 Dockerfile 所在目录的相对路径)到容器中的 。当使用本地目录为源目录时,推荐使用 COPY
    ENTRYPOINT配置容器启动执行的命令,并且不可被 docker run 提供的参数覆盖每个Docekrfile 中只能有一个 ENTRYPOINT ,当指定多个时,只有最后一个起效两种格式ENTRYPOINT ["executable", "param1", "param2"]``ENTRYPOINT command param1 param2(shell中执行)
    VOLUME创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。VOLUME [“/data”]
    USER指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户USER daemon
    WORKDIR为后续的 RUNCMDENTRYPOINT 指令配置工作目录。可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。格式为 WORKDIR /path/to/workdir。 WORKDIR /aWORKDIR bWORKDIR cRUN pwd最后的路径为 /a/b/c
    ONBUILD配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。格式为 ONBUILD [INSTRUCTION]

    6.3 创建镜像

    编写完成 Dockerfile 之后,可以通过 docker build 命令来创建镜像

    docker build [选项] 路径 该命令江都区指定路径下(包括子目录)的Dockerfile,并将该路径下所有内容发送给 Docker 服务端,有服务端来创建镜像。可以通过 .dockerignore 文件来让 Docker 忽略路径下的目录与文件

    # 使用 -t 指定镜像的标签信息docker build -t myrepo/myimage .

    七、Docker Compose

    7.1 介绍

    Docker Compose 是 Docker 官方编排项目之一,负责快速在集群中部署分布式应用。维护地址:https://github.com/docker/compose,由 Python 编写,实际调用 Docker提供的API实现。

    Dockerfile 可以让用户管理一个单独的应用容器,而 Compose 则允许用户在一个模版(YAML格式)中定义一组相关联的应用容器(被称为一个project/项目),例如一个 web容器再加上数据库、redis等。

    7.2 安装

    # 使用 pip 进行安装pip install -U docker-compose# 查看用法docker-ompose -h# 添加 bash 补全命令curl -L https://raw.githubusercontent.com/docker/compose/1.2.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose

    7.3 使用

    术语

    • 服务/service: 一个应用容器,实际上可以运行多个相同镜像的实例
    • 项目/project: 有一组关联的应用容器组成的完成业务单元

    示例:创建一个 Haproxy 挂载三个 Web 容器

    创建一个 compose-haproxy-web 目录,作为项目工作目录,并在其中分别创建两个子目录: haproxyweb

    compose-haproxy-webcompose-haproxy-webgit clone https://github.com/yelog/compose-haproxy-web.git

    目录长这样:

    compose-haproxy-web├── docker-compose.yml├── haproxy│   └── haproxy.cfg└── web    ├── Dockerfile    ├── index.html    └── index.py

    在该目录执行 docker-compose up 命令,会整合输出所有容器的输出

    $ docker-compose upStarting compose-haproxy-web_webb_1 ... doneStarting compose-haproxy-web_webc_1 ... doneStarting compose-haproxy-web_weba_1 ... doneRecreating compose-haproxy-web_haproxy_1 ... doneAttaching to compose-haproxy-web_webb_1, compose-haproxy-web_weba_1, compose-haproxy-web_webc_1, compose-haproxy-web_haproxy_1haproxy_1  | [NOTICE] 244/131022 (1) : haproxy version is 2.2.2haproxy_1  | [NOTICE] 244/131022 (1) : path to executable is /usr/local/sbin/haproxyhaproxy_1  | [ALERT] 244/131022 (1) : parsing [/usr/local/etc/haproxy/haproxy.cfg:14] : 'listen' cannot handle unexpected argument ':70'.haproxy_1  | [ALERT] 244/131022 (1) : parsing [/usr/local/etc/haproxy/haproxy.cfg:14] : please use the 'bind' keyword for listening addresses.haproxy_1  | [ALERT] 244/131022 (1) : Error(s) found in configuration file : /usr/local/etc/haproxy/haproxy.cfghaproxy_1  | [ALERT] 244/131022 (1) : Fatal errors found in configuration.compose-haproxy-web_haproxy_1 exited with code 1

    此时访问本地的 80 端口,会经过 haproxy 自动转发到后端的某个 web 容器上,刷新页面,可以观察到访问的容器地址的变化。

    7.4 命令说明

    大部分命令都可以运行在一个或多个服务上。如果没有特别的说明,命令则应用在项目所有的服务上。

    执行 docker-compose [COMMAND] --help 查看具体某个命令的使用说明

    使用格式

    docker-compose [options] [COMMAND] [ARGS...]
    build构建/重建服务服务一旦构建后,将会带上一个标记名,例如 web_db可以随时在项目目录运行 docker-compose build 来重新构建服务
    help获得一个命令的信息
    kill通过发送 SIGKILL 信号来强制停止服务容器,支持通过参数来指定发送信号,例如docker-compose kill -s SIGINT
    logs查看服务的输出
    port打印绑定的公共端口
    ps列出所有容器
    pull拉去服务镜像
    rm删除停止的服务容器
    run在一个服务上执行一个命令docker-compose run ubuntu ping docker.com
    scale设置同一个服务运行的容器个数通过 service=num 的参数来设置数量docker-compose scale web=2 worker=3
    start启动一个已经存在的服务容器
    stop停止一个已经运行的容器,但不删除。可以通过 docker-compose start 再次启动
    up构建、创建、启动、链接一个服务相关的容器链接服务都将被启动,除非他们已经运行docker-compose up -d 将后台运行并启动docker-compose up 已存在容器将会重新创建docker-compose up --no-recreate 将不会重新创建容器

    7.5 环境变量

    环境变量可以用来配置 Compose 的行为

    Docker_ 开头的变量用来配置 Docker 命令行客户端使用的一样

    COMPOSE_PROJECT_NAME设置通过 Compose 启动的每一个容器前添加的项目名称,默认是当前工作目录的名字。
    COMPOSE_FILE设置要使用的 docker-compose.yml 的路径。默认路径是当前工作目录。
    DOCKER_HOST设置 Docker daemon 的地址。默认使用 unix:///var/run/docker.sock,与 Docker 客户端采用的默认值一致。
    DOCKER_TLS_VERIFY如果设置不为空,则与 Docker daemon 交互通过 TLS 进行。
    DOCKER_CERT_PATH配置 TLS 通信所需要的验证(ca.pemcert.pemkey.pem)文件的路径,默认是 ~/.docker

    7.6 docker-compose.yml

    默认模版文件是 docker-compose.yml ,启动定义了每个服务都必须经过 image 指令指定镜像或 build 指令(需要 Dockerfile) 来自动构建。

    其他大部分指令跟 docker run 类似

    如果使用 build 指令,在 Dockerfile 中设置的选项(如 CMDEXPOSE 等)将会被自动获取,无需在 docker-compose.yml 中再次设置。

    **image**

    指定镜像名称或镜像ID,如果本地仓库不存在,将尝试从远程仓库拉去此镜像

    image: ubuntuimage: orchardup/postgresqlimage: a4bc65fd**build**

    指定 Dockerfile 所在文件的路径。Compose 将利用它自动构建这个镜像,然后使用这个镜像。

    build: /path/to/build/dir**command**

    覆盖容器启动默认执行命令

    command: bundle exec thin -p 3000**links**

    链接到其他服务中的容器,使用服务名称或别名

    links:    - db  - db:database  - redis

    别名会自动在服务器中的 /etc/hosts 里创建。例如:

    172.17.2.186  db172.17.2.186  database172.17.2.187  redis**external_links**

    连接到 docker-compose.yml 外部的容器,甚至并非 Compose 管理的容器。

    external_links: - redis_1 - project_db_1:mysql - project_db_1:postgresql

    ports

    暴露端口信息 HOST:CONTAINER

    格式或者仅仅指定容器的端口(宿主机会随机分配端口)

    ports: - "3000" - "8000:8000" - "49100:22" - "127.0.0.1:8001:8001"

    注:当使用 HOST:CONTAINER 格式来映射端口时,如果你使用的容器端口小于 60 你可能会得到错误得结果,因为 YAML 将会解析 xx:yy 这种数字格式为 60 进制。所以建议采用字符串格式。

    **expose**

    暴露端口,但不映射到宿主机,只被连接的服务访问

    expose: - "3000" - "8000"

    volumes

    卷挂载路径设置。可以设置宿主机路径 (HOST:CONTAINER) 或加上访问模式 (HOST:CONTAINER:ro)。

    volumes: - /var/lib/mysql - cache/:/tmp/cache - ~/configs:/etc/configs/:ro

    **
    **

    volumes_from

    从另一个服务或容器挂载它的所有卷。

    volumes_from: - service_name - container_name
    environment

    设置环境变量。你可以使用数组或字典两种格式。

    只给定名称的变量会自动获取它在 Compose 主机上的值,可以用来防止泄露不必要的数据。

    environment:  RACK_ENV: development  SESSION_SECRET:environment:  - RACK_ENV=development  - SESSION_SECRET

    env_file

    从文件中获取环境变量,可以为单独的文件路径或列表。

    如果通过 docker-compose -f FILE 指定了模板文件,则 env_file 中路径会基于模板文件路径。

    如果有变量名称与 environment 指令冲突,则以后者为准。

    env_file: .envenv_file:  - ./common.env  - ./apps/web.env  - /opt/secrets.env

    环境变量文件中每一行必须符合格式,支持 # 开头的注释行。

    # common.env: Set Rails/Rack environmentRACK_ENV=development

    extends

    基于已有的服务进行扩展。例如我们已经有了一个 webapp 服务,模板文件为 common.yml

    # common.ymlwebapp:  build: ./webapp  environment:    - DEBUG=false    - SEND_EMAILS=false

    编写一个新的 development.yml 文件,使用 common.yml 中的 webapp 服务进行扩展。

    # development.ymlweb:  extends:    file: common.yml    service: webapp  ports:    - "8000:8000"  links:    - db  environment:    - DEBUG=truedb:  image: postgres

    后者会自动继承 common.yml 中的 webapp 服务及相关环节变量。

    **
    **

    net

    设置网络模式。使用和 docker client--net 参数一样的值。

    net: "bridge"net: "none"net: "container:[name or id]"net: "host"

    **
    **

    pid

    跟主机系统共享进程命名空间。打开该选项的容器可以相互通过进程 ID 来访问和操作。

    pid: "host"

    dns

    配置 DNS 服务器。可以是一个值,也可以是一个列表。

    dns: 8.8.8.8dns:  - 8.8.8.8  - 9.9.9.9

    cap_add, cap_drop

    添加或放弃容器的 Linux 能力(Capabiliity)。

    cap_add:  - ALLcap_drop:  - NET_ADMIN  - SYS_ADMIN

    **
    **

    dns_search

    配置 DNS 搜索域。可以是一个值,也可以是一个列表。

    dns_search: example.comdns_search:  - domain1.example.com  - domain2.example.com

    **
    **

    working_dir, entrypoint, user, hostname, domainname, mem_limit, privileged, restart, stdin_open, tty, cpu_shares

    这些都是和 docker run 支持的选项类似。

    八、安全

    8.1 内核命名空间

    当使用 docker run 启动一个容器时,在后台 Docker 为容器创建一个独立的命名空间和控制集合。命名空间踢空了最基础的也是最直接的隔离,在容器中运行的进程不会被运行在主机上的进程和其他容器发现和作用。

    8.2 控制组

    控制组是 Linux 容器机制的另一个关键组件,负责实现资源的审计和限制。

    它提供了很多特性,确保哥哥容器可以公平地分享主机的内存、CPU、磁盘IO等资源;当然,更重要的是,控制组确保了当容器内的资源使用产生压力时不会连累主机系统。

    8.3 内核能力机制

    能力机制是 Linux 内核的一个强大特性,可以提供细粒度的权限访问控制。 可以作用在进程上,也可以作用在文件上。

    例如一个服务需要绑定低于 1024 的端口权限,并不需要 root 权限,那么它只需要被授权 net_bind_service 能力即可。

    默认情况下, Docker 启动的容器被严格限制只允许使用内核的一部分能力。

    使用能力机制加强 Docker 容器的安全有很多好处,可以按需分配给容器权限,这样,即便攻击者在容器中取得了 root 权限,也不能获取宿主机较高权限,能进行的破坏也是有限的。

    参考资料

    https://docs.docker.com/engine/reference/commandline/images/

    http://www.dockerinfo.net/

    ]]>
    @@ -2051,7 +2078,7 @@ /2017/03/08/font-develop-rule/ - 一、前言

    随着开发人员的不断增加,在没有规范的情况下,就会导致开发的页面不统一,不像是一个系统。为了解决这个问题,就有了此规范的出现,当然为了不影响各个功能的灵活性,此规范要求不高, 请耐心阅读,并应用到日常开发中。

    当然,如果你有更好的建议,可以通过邮件联系 yangyj13@lenovo.com,进行沟通来完善此篇规范。

    二、编程规范

    2.1 命名规范

    2.1.1 文件命名

    全部采用小写方式,以横杠分割。

    正例: resource.vueuser-info.vue

    反例: basic_data.vueEventLog.vue

    2.1.2 目录命名

    全部采用小写方式,以横杠分割。

    正例: systemship-support

    反例: errorPageComponents

    2.1.3 JS、CSS、SCSS、HTML、PNG文件命名

    全部采用小写方式,以横杠分割。

    正例: btn.scsselement-ui.scsslenovo-logo.png

    反例: leftSearch.scssLeGrid.js

    2.1.4 命名规范性

    代码中命名严禁使用拼音和英文混合的方式,更不允许直接使用中文的方式。说明: 正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音的命名方式也要避免采用。

    正例: loadingsearchFormtableHeightdmsLoadingrmb 专有名词缩写,视同英文
    反例: getLiaoPanNameDMSLoading

    2.2 插件使用

    2.2.1 eslint 代码规范

    注意:前端的代码格式化已经在 eslint 中声明了,所以确保自己已经启用了 eslint,并使 eslint 进行代码格式化。

    2.2.2 i18n 国际化

    所有展示的内容都要支持国际化。国际化内容写到 /src/lang/ 下的对应模块,通过 this.$t('xx.xx.xx') 来使用。

    英文国际化的列或标签,请使用开头字母大写的方式,如: UserIdStatusUserName

    image-20211227175812563

    2.3 组件使用

    2.3.1 table 表格

    表格组件推荐使用 vxe-table,功能更加全面,之后也会主力优化此表格。比如可编辑表格的样式经过优化:可编辑表格

    2.3.2 dialog 弹窗

    弹窗组件推荐使用 vxe-modal,代码设计更加合理,功能也更加全面。

    2.3.2 element-ui

    tablemodal 外,其他组件比如 formbuttonDateTimePicker 优先使用 element-ui

    2.3.2.1 icon 图标

    图标优先使用 element-ui 的图标。如果没有合适的,可以在 iconfont 上寻找到合适的图标后,找 yangyj13@lenovo.com 进行添加。

    2.3.2.2 button 按钮

    按钮大小:除了在表格中的按钮要使用 size="mini" 外,其他情况使用默认大小即可。

    按钮颜色:普通的 查询/修改/操作 等按钮使用蓝色 type="primary",新增使用绿色 type="success",删除等“危险”操作使用红色 type="danger"。推荐给按钮添加图标,可在 element-ui-icon 寻找合适的图标。

    image-20211227172602560

    2.3.3 其他组件

    如果上述组件并不能满足业务需求,可以优先在网上找到合适的组件后,与 yangyj13@lenoov.com 联系后添加。

    2.4 页面布局

    2.4.1 新增/修改表单

    普通的表单,采用中间对其的方案,也就是整个表单的 label-width 设置为一样的。

    注意:一般的,新增修改使用弹窗的方式,展示表单。新增/修改可以共用代码,具体可以参考 common/system/va-config.vue

    <el-form        ref="dialogForm"        v-loading="edit.loading"        :model="edit.form"        :rules="edit.formRules"        label-width="150px"        style="padding-right: 30px;"      >  ...</el-form>

    image-20211227170528261

    2.4.2 查询表单+表格

    这种应该是最长间的需求方案了,可以参考 /common/system/user.vue,在写的时候注意以下几点:

    1. label-width 不要设置,保证标签文字开头和表格对齐。
    2. el-form 使用 :inline="true" 设置表单内容行内显示。
    3. 设置 vxe-tableheight 属性,保证表格底部贴住网页底部,又不会有滚动条(表格内允许有滚动条)
    4. 按钮也放到表单中,不要单独一行。

    最终效果如下:

    image-20211227174221673

    ]]>
    + 一、前言

    随着开发人员的不断增加,在没有规范的情况下,就会导致开发的页面不统一,不像是一个系统。为了解决这个问题,就有了此规范的出现,当然为了不影响各个功能的灵活性,此规范要求不高, 请耐心阅读,并应用到日常开发中。

    当然,如果你有更好的建议,可以通过邮件联系 yangyj13@lenovo.com,进行沟通来完善此篇规范。

    二、编程规范

    2.1 命名规范

    2.1.1 文件命名

    全部采用小写方式,以横杠分割。

    正例: resource.vueuser-info.vue

    反例: basic_data.vueEventLog.vue

    2.1.2 目录命名

    全部采用小写方式,以横杠分割。

    正例: systemship-support

    反例: errorPageComponents

    2.1.3 JS、CSS、SCSS、HTML、PNG文件命名

    全部采用小写方式,以横杠分割。

    正例: btn.scsselement-ui.scsslenovo-logo.png

    反例: leftSearch.scssLeGrid.js

    2.1.4 命名规范性

    代码中命名严禁使用拼音和英文混合的方式,更不允许直接使用中文的方式。说明: 正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音的命名方式也要避免采用。

    正例: loadingsearchFormtableHeightdmsLoadingrmb 专有名词缩写,视同英文
    反例: getLiaoPanNameDMSLoading

    2.2 插件使用

    2.2.1 eslint 代码规范

    注意:前端的代码格式化已经在 eslint 中声明了,所以确保自己已经启用了 eslint,并使 eslint 进行代码格式化。

    2.2.2 i18n 国际化

    所有展示的内容都要支持国际化。国际化内容写到 /src/lang/ 下的对应模块,通过 this.$t('xx.xx.xx') 来使用。

    英文国际化的列或标签,请使用开头字母大写的方式,如: UserIdStatusUserName

    image-20211227175812563

    2.3 组件使用

    2.3.1 table 表格

    表格组件推荐使用 vxe-table,功能更加全面,之后也会主力优化此表格。比如可编辑表格的样式经过优化:可编辑表格

    2.3.2 dialog 弹窗

    弹窗组件推荐使用 vxe-modal,代码设计更加合理,功能也更加全面。

    2.3.2 element-ui

    tablemodal 外,其他组件比如 formbuttonDateTimePicker 优先使用 element-ui

    2.3.2.1 icon 图标

    图标优先使用 element-ui 的图标。如果没有合适的,可以在 iconfont 上寻找到合适的图标后,找 yangyj13@lenovo.com 进行添加。

    2.3.2.2 button 按钮

    按钮大小:除了在表格中的按钮要使用 size="mini" 外,其他情况使用默认大小即可。

    按钮颜色:普通的 查询/修改/操作 等按钮使用蓝色 type="primary",新增使用绿色 type="success",删除等“危险”操作使用红色 type="danger"。推荐给按钮添加图标,可在 element-ui-icon 寻找合适的图标。

    image-20211227172602560

    2.3.3 其他组件

    如果上述组件并不能满足业务需求,可以优先在网上找到合适的组件后,与 yangyj13@lenoov.com 联系后添加。

    2.4 页面布局

    2.4.1 新增/修改表单

    普通的表单,采用中间对其的方案,也就是整个表单的 label-width 设置为一样的。

    注意:一般的,新增修改使用弹窗的方式,展示表单。新增/修改可以共用代码,具体可以参考 common/system/va-config.vue

    <el-form        ref="dialogForm"        v-loading="edit.loading"        :model="edit.form"        :rules="edit.formRules"        label-width="150px"        style="padding-right: 30px;"      >  ...</el-form>

    image-20211227170528261

    2.4.2 查询表单+表格

    这种应该是最长间的需求方案了,可以参考 /common/system/user.vue,在写的时候注意以下几点:

    1. label-width 不要设置,保证标签文字开头和表格对齐。
    2. el-form 使用 :inline="true" 设置表单内容行内显示。
    3. 设置 vxe-tableheight 属性,保证表格底部贴住网页底部,又不会有滚动条(表格内允许有滚动条)
    4. 按钮也放到表单中,不要单独一行。

    最终效果如下:

    image-20211227174221673

    ]]>
    @@ -4301,7 +4328,7 @@ /2016/09/30/computer-mutiple-github-account/ - 最近想要使用自己的GitHub搭建Hexo博客,同时还要使用工作的GitHub开发项目,所以在网上找寻了一些文章,在此将自己的搭建过程记录一下。

    前期工作

    两个GitHub账号(假设两个账号为one,two)
    取消Git全局设置

    $ git config --global --unset user.name$ git config --global --unset user.email

    SSH配置

    生成id_rsa私钥,id_rsa.pub公钥。one可以直接回车,默认生成 id_rsa 和 id_rsa.pub 。

    $ ssh-keygen -t rsa -C "one@xx.com"

    添加two会出现提示输入文件名,输入与默认配置不一样的文件名,如:id_rsa_two。

    $ cd ~/.ssh$ ssh-keygen -t rsa -C "two@126.com"  #  之后会提示输入文件名

    GitHub添加公钥 id_rsa.pub 、 id_rsa_two.pub,分别登陆one,two的账号,在 Account Settings 的 SSH Keys 里,点 Add SSH Keys ,将公钥(.pub文件)中的内容粘贴到 Key 中,并输入 Title。
    添加 ssh Key

    $ ssh-add ~/.ssh/id_rsa$ ssh-add ~/.ssh/id_rsa_two

    可以在添加前使用下面命令删除所有的 key

    $ ssh-add -D

    最后可以通过下面命令,查看 key 的设置

    $ ssh-add -l

    修改ssh config文件

    $ cd ~/.ssh/$ touch config

    打开 .ssh 文件夹下的 config 文件,进行配置

    #  defaultHost github.comHostName github.comUser gitIdentityFile ~/.ssh/id_rsa#  twoHost two.github.com  #  前缀名可以任意设置HostName github.comUser gitIdentityFile ~/.ssh/id_rsa_two
    • 这里必须采用这样的方式设置,否则 push 时会出现以下错误:

    ERROR: Permission to two/two.github.com.git denied to one.

    简单分析下原因,我们可以发现 ssh 客户端是通过类似:

    git@github.com:one/one.github.com.git

    这样的 Git 地址中的 User 和 Host 来识别使用哪个本地私钥的。
    很明显,如果 User 和 Host 始终为 git 和 github.com,那么就只能使用一个私钥。
    所以需要上面的方式配置,每个账号使用了自己的 Host,每个 Host 的域名做 CNAME 解析到 github.com,这样 ssh 在连接时就可以区别不同的账号了。

    $ ssh -T git@github.com        #  测试one ssh连接# Hi ***! You've successfully authenticated, but GitHub does not provide shell access.$ ssh -T git@two.github.com    #  测试two ssh连接# Hi ***! You've successfully authenticated, but GitHub does not provide shell access.

    但是这样还没有完,下面还有关联的设置。

    在Git项目中配置账号关联

    可以用 git init 或者 git clone 创建本地项目
    分别在one和two的git项目目录下,使用下面的命令设置名字和邮箱

    $ git config user.name "__name__"            #  __name__ 例如 one$ git config user.email "__email__"          #  __email__ 例如 one@126.com

    注意:由于我不知道Hexo怎样配置 局部的config,所以,我将two的config使用全局,而工作目录配置局部。

    $ git config --global user.name "__name__"            #  __name__ 例如 two$ git config --global user.email "__email__"          #  __email__ 例如 two@126.com

    查看git项目的配置

    $ git config --list

    查看 one 的 remote.origin.url=git@github.com:one/one.github.com.git
    查看 two 的 remote.origin.url=git@github.com:two/two.github.com.git
    由于 one 使用的是默认的 Host ,所以不需要修改,但是 two 使用的是 two.github.com ,则需要进行修改

    $ git remote rm origin$ git remote add origin git@two.github.com:two/two.github.com.git

    我在Hexo中的配置(使用two账号)

    deploy:    type: git    repo: git@two.github.com:two/two.github.io.git    branch: master

    上传更改

    上面所有的设置无误后,可以修改代码,然后上传了。

    $ git add -A$ git commit -m "your comments"$ git push

    如果遇到warning

    warning: push.default is unset; its implicit value is changing in Git 2.0 from ‘matching’ to ‘simple’. To squelch this messageand maintain the current behavior after the default changes, use…

    推荐使用

    $ git config --global push.default simple
    ]]>
    + 最近想要使用自己的GitHub搭建Hexo博客,同时还要使用工作的GitHub开发项目,所以在网上找寻了一些文章,在此将自己的搭建过程记录一下。

    前期工作

    两个GitHub账号(假设两个账号为one,two)
    取消Git全局设置

    $ git config --global --unset user.name$ git config --global --unset user.email

    SSH配置

    生成id_rsa私钥,id_rsa.pub公钥。one可以直接回车,默认生成 id_rsa 和 id_rsa.pub 。

    $ ssh-keygen -t rsa -C "one@xx.com"

    添加two会出现提示输入文件名,输入与默认配置不一样的文件名,如:id_rsa_two。

    $ cd ~/.ssh$ ssh-keygen -t rsa -C "two@126.com"  #  之后会提示输入文件名

    GitHub添加公钥 id_rsa.pub 、 id_rsa_two.pub,分别登陆one,two的账号,在 Account Settings 的 SSH Keys 里,点 Add SSH Keys ,将公钥(.pub文件)中的内容粘贴到 Key 中,并输入 Title。
    添加 ssh Key

    $ ssh-add ~/.ssh/id_rsa$ ssh-add ~/.ssh/id_rsa_two

    可以在添加前使用下面命令删除所有的 key

    $ ssh-add -D

    最后可以通过下面命令,查看 key 的设置

    $ ssh-add -l

    修改ssh config文件

    $ cd ~/.ssh/$ touch config

    打开 .ssh 文件夹下的 config 文件,进行配置

    #  defaultHost github.comHostName github.comUser gitIdentityFile ~/.ssh/id_rsa#  twoHost two.github.com  #  前缀名可以任意设置HostName github.comUser gitIdentityFile ~/.ssh/id_rsa_two
    • 这里必须采用这样的方式设置,否则 push 时会出现以下错误:

    ERROR: Permission to two/two.github.com.git denied to one.

    简单分析下原因,我们可以发现 ssh 客户端是通过类似:

    git@github.com:one/one.github.com.git

    这样的 Git 地址中的 User 和 Host 来识别使用哪个本地私钥的。
    很明显,如果 User 和 Host 始终为 git 和 github.com,那么就只能使用一个私钥。
    所以需要上面的方式配置,每个账号使用了自己的 Host,每个 Host 的域名做 CNAME 解析到 github.com,这样 ssh 在连接时就可以区别不同的账号了。

    $ ssh -T git@github.com        #  测试one ssh连接# Hi ***! You've successfully authenticated, but GitHub does not provide shell access.$ ssh -T git@two.github.com    #  测试two ssh连接# Hi ***! You've successfully authenticated, but GitHub does not provide shell access.

    但是这样还没有完,下面还有关联的设置。

    在Git项目中配置账号关联

    可以用 git init 或者 git clone 创建本地项目
    分别在one和two的git项目目录下,使用下面的命令设置名字和邮箱

    $ git config user.name "__name__"            #  __name__ 例如 one$ git config user.email "__email__"          #  __email__ 例如 one@126.com

    注意:由于我不知道Hexo怎样配置 局部的config,所以,我将two的config使用全局,而工作目录配置局部。

    $ git config --global user.name "__name__"            #  __name__ 例如 two$ git config --global user.email "__email__"          #  __email__ 例如 two@126.com

    查看git项目的配置

    $ git config --list

    查看 one 的 remote.origin.url=git@github.com:one/one.github.com.git
    查看 two 的 remote.origin.url=git@github.com:two/two.github.com.git
    由于 one 使用的是默认的 Host ,所以不需要修改,但是 two 使用的是 two.github.com ,则需要进行修改

    $ git remote rm origin$ git remote add origin git@two.github.com:two/two.github.com.git

    我在Hexo中的配置(使用two账号)

    deploy:    type: git    repo: git@two.github.com:two/two.github.io.git    branch: master

    上传更改

    上面所有的设置无误后,可以修改代码,然后上传了。

    $ git add -A$ git commit -m "your comments"$ git push

    如果遇到warning

    warning: push.default is unset; its implicit value is changing in Git 2.0 from ‘matching’ to ‘simple’. To squelch this messageand maintain the current behavior after the default changes, use…

    推荐使用

    $ git config --global push.default simple
    ]]>
    diff --git a/sitemap.txt b/sitemap.txt index cabd623eb..8df673c60 100644 --- a/sitemap.txt +++ b/sitemap.txt @@ -79,6 +79,7 @@ http://yelog.org/2017/09/25/%E6%90%AD%E5%BB%BAdubbo+zookeeper%E5%B9%B3%E5%8F%B0/ http://yelog.org/2017/04/15/SpringMVC-implementation-process/ http://yelog.org/2017/04/22/encryption-algorithm/ http://yelog.org/2023/12/11/springboot-jarlauncher/ +http://yelog.org/2024/07/05/Jackson%20Time%20Serializer/Deserializer/ http://yelog.org/2022/08/01/reducing-local-springcloud-base-on-nacos-and-gray-release/ http://yelog.org/2016/10/24/web-xml/ http://yelog.org/2017/08/04/mybatis-Mapper/ @@ -180,9 +181,9 @@ http://yelog.org/tags/concurrent/ http://yelog.org/tags/linux/ http://yelog.org/tags/docker/ http://yelog.org/tags/reading/ -http://yelog.org/tags/translation/ http://yelog.org/tags/jsp/ http://yelog.org/tags/jstl/ +http://yelog.org/tags/translation/ http://yelog.org/tags/k8s/ http://yelog.org/tags/mac/ http://yelog.org/tags/efficiency/ diff --git a/sitemap.xml b/sitemap.xml index ca7f1cd9c..402bd0630 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -730,6 +730,15 @@ 0.6 + + http://yelog.org/2024/07/05/Jackson%20Time%20Serializer/Deserializer/ + + 2024-07-05 + + monthly + 0.6 + + http://yelog.org/2022/08/01/reducing-local-springcloud-base-on-nacos-and-gray-release/ @@ -1614,21 +1623,21 @@ - http://yelog.org/tags/translation/ + http://yelog.org/tags/jsp/ 2024-07-05 weekly 0.2 - http://yelog.org/tags/jsp/ + http://yelog.org/tags/jstl/ 2024-07-05 weekly 0.2 - http://yelog.org/tags/jstl/ + http://yelog.org/tags/translation/ 2024-07-05 weekly 0.2 diff --git a/tags/3-hexo/index.html b/tags/3-hexo/index.html index bc247a939..9e1dbfdcf 100644 --- a/tags/3-hexo/index.html +++ b/tags/3-hexo/index.html @@ -248,7 +248,7 @@
  • 全部文章 - (163) + (164)
  • @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +
    全部文章 - (163) + (164)
    @@ -390,7 +390,7 @@
    后端 - (11) + (12)
    @@ -510,8 +510,8 @@
    - - + +
    @@ -890,6 +890,15 @@ + + Jackson 时间序列化/反序列化详解 + + + +