git

git指令

一般来说,日常使用只要记住下图6个命令,就可以了。但是熟练使用,恐怕要记住60~100个命令。

workspace:工作区

Index/Stage:暂存区

Repository:仓库区(或本地仓库)

Remote:远程仓库

  • 述任务及修改内容,填写要发布的前后端应用和提测、上线时间,以及研发负责人、研发人员、测试负责人。
  1. 将测试报告(已解密)通过ftp上传

    • 将上传报告名加到app-sz-artemis中的ReportRecognitionServiceTest类
    • (注)报告提前解密,保证准时完成上线。上传报告名必须以模板名开头:例,xikang1.田淑梅.pdf
  2. 提交项目到dev与release分支

    • 首先commit&push sz-artemis-common 项目(因为sz-artemis-report-parser、sz-artemis-report-api、app-sz-artemis均依赖于它),然后c&p sz-artimis-report-parser,最后c&p app-sz-artemis。
    • (注)先push到dev分支,再push到release分支,release分支严格遵守格式:例,release-2019-09-05
  3. 通过Rocket.chat编译

    • 使用Rocket.chat编译,严格按照步骤3里的顺序,并注意指令正确。每编译完一个项目到http://118.178.137.55:8089/job下查看console output,是否编译成功。
    • (注)指令格式:@Gerty b sz-artemis-common;@Gerty b sz-artemis-report-parser;@Gerty b sz-artemis-api;@Gerty b test_sz-artemis
  4. 自测

    • 通过识别回归测试工具,进行自测,查看测试通过的状况以及与以往测试用例集相异的excel结果
    • 若excel结果无差异,或差异在可接受范围以内,则测试通过,并将本次测试产生的测试用例覆盖历史测试用例。
    • 测试通过则可上线。
  5. 发jira链接

    • 链接发顾璟琛审核,之后发方雷部署。

 

l

github 上传大文件100MB姿势

具体就是安装git-lfs,先下载,然后就是一顿操作:

  1. 先在web建立一个空仓库

  2. 然后建立跟仓库名一样的文件夹,并执行初始化命令:git init

  3. 然后执行git lfs install

  4. 然后添加你要上传的文件名或后缀名:git lfs track '*.zip'

  5. 然后就把生成的 .gitattributes文件,先传到远程仓库

    • git add .gitattributes
  • git commit -m 'large - init file'
    • git push -u origin master:master # 第一次要这样执行,后面再传就git push就行。
  1. 然后就可以正常添加上传大文件了!

    • git add bigfile.zip
    • git commit -m 'upload Big file.'
    • git push # 第一次要这样执行,后面再传就git push就行。

  • 删除远程仓库文件,但本地文件不删除,如

    1
    bigfile.zip
    • git rm bigfile.zip
    • git commit -m 'rm bigfile.zip'
    • git push
l

git指令

个人开发

一般来说,日常使用只要记住下图6个命令,就可以了。但是熟练使用,恐怕要记住60~100个命令。

workspace:工作区

Index/Stage:暂存区

Repository:仓库区(或本地仓库)

Remote:远程仓库

  1. 新建代码库

    1
    2
    # 下载一个项目和它的整个代码历史
    $ git clone [url]
  2. 配置

    Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 显示当前的Git配置
    $ git config --list

    # 编辑Git配置文件
    $ git config -e [--global]

    # 设置提交代码时的用户信息
    $ git config [--global] user.name "[name]"
    $ git config [--global] user.email "[email address]"
  3. 增加/删除文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 添加指定文件到暂存区
    $ git add [file1] [file2] ...

    # 添加指定目录到暂存区,包括子目录
    $ git add [dir]

    # 添加当前目录的所有文件到暂存区
    $ git add .

    # 添加每个变化前,都会要求确认
    # 对于同一个文件的多处变化,可以实现分次提交
    $ git add -p

    # 删除工作区文件,并且将这次删除放入暂存区
    $ git rm [file1] [file2] ...
  4. 代码提交

    1
    2
    3
    4
    5
    6
    7
    8
    # 提交暂存区到仓库区
    $ git commit -m [message]

    # 提交暂存区的指定文件到仓库区
    $ git commit [file1] [file2] ... -m [message]

    # 提交时显示所有diff信息
    $ git commit -v
  5. 分支

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    # 列出所有本地分支
    $ git branch

    # 列出所有远程分支
    $ git branch -r

    # 列出所有本地分支和远程分支
    $ git branch -a

    # 新建一个分支,但依然停留在当前分支
    $ git branch [branch-name]

    # 新建一个分支,并切换到该分支
    $ git checkout -b [branch]

    # 新建一个分支,指向指定commit
    $ git branch [branch] [commit]

    # 新建一个分支,与指定的远程分支建立追踪关系
    $ git branch --track [branch] [remote-branch]

    # 切换到指定分支,并更新工作区
    $ git checkout [branch-name]

    # 切换到上一个分支
    $ git checkout -

    # 建立追踪关系,在现有分支与指定的远程分支之间
    $ git branch --set-upstream [branch] [remote-branch]

    # 合并指定分支到当前分支
    $ git merge [branch]

    # 删除分支
    $ git branch -d [branch-name]

    # 删除远程分支
    $ git push origin --delete [branch-name]
    $ git branch -dr [remote/branch]
  6. 查看信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    # 显示有变更的文件
    $ git status

    # 显示当前分支的版本历史
    $ git log

    # 显示commit历史,以及每次commit发生变更的文件
    $ git log --stat

    # 搜索提交历史,根据关键词
    $ git log -S [keyword]

    # 显示某个文件的版本历史,包括文件改名
    $ git log --follow [file]
    $ git whatchanged [file]

    # 显示指定文件相关的每一次diff
    $ git log -p [file]

    # 显示过去5次提交
    $ git log -5 --pretty --oneline

    # 显示所有提交过的用户,按提交次数排序
    $ git shortlog -sn

    # 显示指定文件是什么人在什么时间修改过
    $ git blame [file]

    # 显示暂存区和工作区的差异
    $ git diff

    # 显示暂存区和上一个commit的差异
    $ git diff --cached [file]

    # 显示工作区与当前分支最新commit之间的差异
    $ git diff HEAD

    # 显示两次提交之间的差异
    $ git diff [first-branch]...[second-branch]

    # 显示某次提交的元数据和内容变化
    $ git show [commit]

    # 显示某次提交发生变化的文件
    $ git show --name-only [commit]

    # 显示某次提交时,某个文件的内容
    $ git show [commit]:[filename]

    # 显示当前分支的最近几次提交
    $ git reflog
  7. 远程同步

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 下载远程仓库的所有变动
    $ git fetch [remote]

    # 显示所有远程仓库
    $ git remote -v

    # 显示某个远程仓库的信息
    $ git remote show [remote]

    # 增加一个新的远程仓库,并命名
    $ git remote add [shortname] [url]

    # 取回远程仓库的变化,并与本地分支合并
    $ git pull [remote] [branch]

    # 上传本地指定分支到远程仓库
    $ git push [remote] [branch]

    # 推送所有分支到远程仓库
    $ git push [remote] --all
  8. 撤销

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    # 恢复暂存区的指定文件到工作区
    $ git checkout [file]

    # 恢复某个commit的指定文件到暂存区和工作区
    $ git checkout [commit] [file]

    # 恢复暂存区的所有文件到工作区
    $ git checkout .

    # 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
    $ git reset [file]

    # 重置暂存区与工作区,与上一次commit保持一致
    $ git reset --hard

    # 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
    $ git reset [commit]

    # 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
    $ git reset --hard [commit]

    # 重置当前HEAD为指定commit,但保持暂存区和工作区不变
    $ git reset --keep [commit]

    # 新建一个commit,用来撤销指定commit
    # 后者的所有变化都将被前者抵消,并且应用到当前分支
    $ git revert [commit]

    # 暂时将未提交的变化移除,稍后再移入
    $ git stash
    $ git stash pop

    多人合作开发

    1. 源仓库

      项目开始的时候,项目发起者构建起一个项目的最原始的仓库,称之为源仓库(origin)。

      • 源仓库有两个作用
        1. 汇总参与该项目的各个开发者的代码
        2. 存放趋于稳定和可发布的代码
      • 源仓库受到保护,开发者不直接对其进行开发工作。只有项目管理者能对其进行较高权限的操作。
    2. 开发者仓库

      如上所说,任何开发者都不会对源仓库进行直接的操作,源仓库建立以后,每个开发者需要fork一份源仓库,作为自己日常开发的仓库。

      每个开发者所fork的仓库是完全独立的,互不干扰。每个开发者仓库相当于一个源仓库实体的镜像,开发者在这个镜像中进行编码,提交到自己的仓库中,这样就可以轻易地实现团队成员之间的并行开发工作。而开发工作完成以后,开发者可以向源仓库发送pull request,请求管理员把自己的代码合并到源仓库中,这样就实现了分布式开发工作,和最后的集中式的管理。

    3. 分支 (Branch)

      git分支有两类,5种:

      1
      2
      3
      4
      5
      6
      7
      永久性分支
      master branch:主分支
      develop branch:开发分支
      临时性分支
      feature branch:功能分支
      release branch:预发布分支
      hotfix branch:bug修复分支
    4. 多人合作具体步骤

      1)clone远程代码

      1
      git clone [url]

      2)切换到develop分支,将本地新项目提交到本地develop分支,再将本地develp分支上的新建项目将上传到远程develop分支。

      1
      2
      3
      4
      git checkout develop
      git add .
      git commit -m "new branch commit"
      git push origin new branch commit

    3)开发

    • 切换到develop分支

      1
      git checkout develop
    • 分出一个功能性分支

      1
      git checkout -b function-branch
    • 在功能性分支上进行开发工作,多次commit,测试以后

    • 把做好的功能合并到develop中

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      git checkout develop
      # 回到develop分支

      git merge --no-ff function-branch
      # 把做好的功能合并到develop中

      git branch -d functio-branch
      # 删除功能性分支

      git push origin develop
      # 把develop提交到自己的远程仓库中

    4)合并远程master和develop分支

    切换到master分支,从远程pull代码,将develop分支合并到本地master分支(此时本地master分支是与远程同步的),有冲突解决,没有则罢。最后push到远程master仓库。

    (注)保证多人协作的时候尽量少出现merge conflict和污染主分支,做到以下几点:

    • 做好分工,尽量避免出现多人修改同一个文件。

    • 每个人的所有开发工作都只在自己的分支开发。

    • 每个人只允许在自己的分支直接push远程分支。

    • 合并的时候必须遵循以下条件.

      1)首先本地切换到develop分支

      1
      git checkout develop

      2)git pull

      3)那么在pull到远程的develop最新的内容之后,

      1
      git merge  [branch]

      4)如果出现confict那么清除conflict之后,commit。然后把本地develop push到远程develop。

      5)没完成一个功能就提交一次。不要累计代码。

git checkout dev

git push origin –delete release-1.00.00

l

正常上线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
git clone

git checkout dev

git checkout -b feature-function

git add .
git commit -m '结束功能开发'

git checkout dev
git merge feature-function
git push origin dev

git checkout -b release-2019-09-29
git push origin release-2019-09-29

线上发现bug 紧急上线

git checkout -b hotfix-2019-09-29 origin/master
git checkout -b hotfix-2019-09-29
git add .
git commit -m 'message'
git push origin hotfix-2019-09-29

git checkout dev
git merge hotfix-2019-09-29
git push origin dev


git branch -d feature-function
l

git 流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
正常上线
git clone

git checkout dev

git checkout -b feature-function

git add .
git commit -m '结束功能开发'

git checkout dev
git merge feature-function
git push origin dev

git checkout -b release-2019-09-29
git push origin release-2019-09-29

线上发现bug 紧急上线

git checkout -b hotfix-2019-09-29 origin/master
git checkout -b hotfix-2019-09-29
git add .
git commit -m 'message'
git push origin hotfix-2019-09-29

git checkout dev
git merge hotfix-2019-09-29
git push origin dev


git branch -d feature-function
l

idea .gitignore(git文件忽略)

idea使用git通常需要忽略一些临时文件,需要配置.gitignore插件

  1. 安装插件
    File -> Settings -> Plugins 搜索框搜索.ignore,点击安装

  2. 生成初始.ignore文件

出现如下弹框,会默认生成所选语言的常用忽略项,我这里选java,

img

生成如下文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Compiled class file
*.class

# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

idea项目一般需要自己增加如下两项

1
2
.idea/
target/

即忽略这两个文件夹即文件夹下的所有文件

现在可以使用了,提交一次测试下

发现.idea文件夹下的文件还有变更被提交,这是因为在使用gitignore之前,此文件就以及被跟踪了,这样的话需要移除跟踪,如下命令:

移除指定文件夹即文件夹下所有文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
git rm --cached --force -r .idea 
rm '.idea/artifacts/xxx_api_war.xml'
rm '.idea/artifacts/xxx_api_war_exploded.xml'
rm '.idea/artifacts/xxx_task_war.xml'
rm '.idea/artifacts/xxx_task_war_exploded.xml'
rm '.idea/libraries/Maven__ch_qos_logback_logback_classic_1_1_7.xml'
rm '.idea/libraries/Maven__ch_qos_logback_logback_core_1_1_7.xml'
rm '.idea/libraries/Maven__com_alibaba_druid_1_0_22.xml'
rm '.idea/libraries/Maven__com_alibaba_fastjson_1_2_14.xml'
rm '.idea/libraries/Maven__com_aliyun_openservices_ons_client_1_2_4.xml'
rm '.idea/libraries/Maven__com_fasterxml_aalto_xml_0_9_11.xml'
rm '.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_5_2.xml'
rm '.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_5_2.xml'
rm '.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_5_2.xml'
rm '.idea/libraries/Maven__com_fasterxml_jackson_dataformat_jackson_dataformat_xml_2_5_2.xml'
rm '.idea/libraries/Maven__com_fasterxml_jackson_module_jackson_module_jaxb_annotations_2_5_2.xml'
rm '.idea/libraries/Maven__com_google_guava_guava_19_0.xml'
rm '.idea/libraries/Maven__commons_codec_commons_codec_1_9.xml'
rm '.idea/libraries/Maven__commons_httpclient_commons_httpclient_3_1.xml'
rm '.idea/libraries/Maven__commons_logging_commons_logging_1_0_4.xml'
rm '.idea/libraries/Maven__commons_logging_commons_logging_1_2.xml'
rm '.idea/libraries/Maven__javax_servlet_javax_servlet_api_3_1_0.xml'
rm '.idea/libraries/Maven__mysql_mysql_connector_java_5_1_39.xml'
rm '.idea/libraries/Maven__org_apache_commons_commons_lang3_3_3_2.xml'
rm '.idea/libraries/Maven__org_apache_commons_commons_pool2_2_0.xml'
rm '.idea/libraries/Maven__org_apache_httpcomponents_httpclient_4_4.xml'
rm '.idea/libraries/Maven__org_apache_httpcomponents_httpcore_4_4.xml'
rm '.idea/libraries/Maven__org_aspectj_aspectjweaver_1_8_9.xml'
rm '.idea/libraries/Maven__org_codehaus_woodstox_stax2_api_3_1_4.xml'
rm '.idea/libraries/Maven__org_mongodb_mongo_java_driver_2_14_0.xml'
rm '.idea/libraries/Maven__org_mybatis_mybatis_3_4_1.xml'
rm '.idea/libraries/Maven__org_mybatis_mybatis_spring_1_3_0.xml'
rm '.idea/libraries/Maven__org_quartz_scheduler_quartz_2_2_2.xml'
rm '.idea/libraries/Maven__org_slf4j_jcl_over_slf4j_1_7_21.xml'
rm '.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_21.xml'
rm '.idea/libraries/Maven__org_springframework_data_spring_data_commons_1_12_2_RELEASE.xml'
rm '.idea/libraries/Maven__org_springframework_data_spring_data_mongodb_1_9_2_RELEASE.xml'
rm '.idea/libraries/Maven__org_springframework_spring_aop_4_3_1_RELEASE.xml'
rm '.idea/libraries/Maven__org_springframework_spring_aspects_4_3_1_RELEASE.xml'
rm '.idea/libraries/Maven__org_springframework_spring_beans_4_3_1_RELEASE.xml'
rm '.idea/libraries/Maven__org_springframework_spring_context_4_3_1_RELEASE.xml'
rm '.idea/libraries/Maven__org_springframework_spring_context_support_4_3_1_RELEASE.xml'
rm '.idea/libraries/Maven__org_springframework_spring_core_4_3_1_RELEASE.xml'
rm '.idea/libraries/Maven__org_springframework_spring_expression_4_3_1_RELEASE.xml'
rm '.idea/libraries/Maven__org_springframework_spring_jdbc_4_3_1_RELEASE.xml'
rm '.idea/libraries/Maven__org_springframework_spring_tx_4_3_1_RELEASE.xml'
rm '.idea/libraries/Maven__org_springframework_spring_web_4_3_1_RELEASE.xml'
rm '.idea/libraries/Maven__org_springframework_spring_webmvc_4_3_1_RELEASE.xml'
rm '.idea/libraries/Maven__redis_clients_jedis_2_6_2.xml'

移除指定文件:

1
2
git rm --cached --force ydq-api/ydq-api.iml
rm 'ydq-api/ydq-api.iml'

这样,表示移除成功。

现在,上面的操作进行提交:

img

移交移除文件.png

以后再做一些变更,当再次提交时,只有未被忽略的(被忽略的文件的变更再也不会被提交了)修改的文件了。

现在,idea下配置.gitignore结束。

注:
.gitignore只能忽略那些原来没有被track的文件,如果某些文件已经被纳入了版本管理中,则修改.gitignore是无效的。那么解决方法就是先把本地缓存删除(改变成未track状态),然后再提交:
输入:
git rm -r –cached filePath
git commit -m “remove xx”
或者:
git rm -r –cached .
git add .
git commit -m “update .gitignore”

来解释下几个参数 -r 是删除文件夹及其子目录 –cached 是删除暂存区里的文件而不删除工作区里的文件,第一种是删除某个文件,第二种方法就把所有暂存区里的文件删了,再加一遍,相当于更新了一遍。

l

Docker相关指令

docker镜像运行以及日志

1
2
3
❯ docker run --restart=always -d --name holer-client -e PARAMS="www.swimminghao.top 6060 51c45156bf1c4a82b4e6ffff2150b65e" swimminghao/holer-client:latest
❯ docker run -d --name holer-server --restart=always --network=host -p 600:600 -p 6060:6060 -p 6443:6443 -p 11000-11010:11000-11010 swimminghao/holer-server:latest
❯ docker logs -f 1ed9a5d6f0cc

Dockerfile 打包jar成docker镜像

1
2
3
4
5
6
7
dockerfile1
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD ./holer-client.jar holer-client.jar
RUN echo "Asia/Shanghai" > /etc/timezone
ENV PARAMS=
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Duser.timezone=GMT+8 -jar /holer-client.jar ${PARAMS}
1
2
3
4
5
6
7
8
9
dockerfile2
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD ./holer-client.jar holer-client.jar
RUN echo "Asia/Shanghai" > /etc/timezone
ENV PARAMS=
ENV JVM_XMS="256m"
ENV JVM_XMX="256m"
ENTRYPOINT java -Xms${JVM_XMS} -Xmx${JVM_XMX} -Djava.security.egd=file:/dev/./urandom -Duser.timezone=GMT+8 -jar /holer-client.jar ${PARAMS}

dockerfile和jar包放同一文件夹,再执行下面指令

1
❯ docker build -t swimminghao/holer-client:latest .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FROM openjdk:8-jdk-alpine
VOLUME /tmp

ARG JAR_FILE=./holer-server.jar
#ARG PORT=8090
ARG TIME_ZONE=Asia/Shanghai

ENV TZ=${TIME_ZONE}
ENV JVM_XMS="256m"
ENV JVM_XMX="256m"

COPY ${JAR_FILE} holer-server.jar

EXPOSE 600
EXPOSE 6060
EXPOSE 6443

ENTRYPOINT java -Xms${JVM_XMS} -Xmx${JVM_XMX} -Djava.security.egd=file:/dev/./urandom -server -jar holer-server.jar

dockerfile和jar包放同一文件夹,再执行下面指令

1
❯ docker build -t swimminghao/holer-server:latest .

docker容器停止以及镜像删除

1
2
3
4
5
❯ docker ps -a
❯ docker pull seanhongxing/holer-server
❯ docker stop f8ca94969ba0 && docker rm f8ca94969ba0
❯ docker image ls -a
❯ docker image rm f8ca94969ba0

docker镜像反推Dockerfile:

1、指令

1
❯ docker history <83d576a828a8> --format "{{.CreatedBy}}" --no-trunc |tac | awk '{if($3~/nop/){for(i=1;i<=3;i++){$i=""};print substr($0,4)}else{print "RUN",$0}}'

2、bash脚本

1
❯ bash decompile.sh seanhongxing/holer-server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash
#########################################################################
# File Name: dockerfile.sh
# Author: www.linuxea.com
# Version: 1
# Created Time: Thu 14 Feb 2019 10:52:01 AM CST
#########################################################################
case "$OSTYPE" in
linux*)
docker history --no-trunc --format "{{.CreatedBy}}" $1 | # extract information from layers
tac | # reverse the file
sed 's,^\(|3.*\)\?/bin/\(ba\)\?sh -c,RUN,' | # change /bin/(ba)?sh calls to RUN
sed 's,^RUN #(nop) *,,' | # remove RUN #(nop) calls for ENV,LABEL...
sed 's, *&& *, \\\n \&\& ,g' # pretty print multi command lines following Docker best practices
;;
darwin*)
docker history --no-trunc --format "{{.CreatedBy}}" $1 | # extract information from layers
tail -r | # reverse the file
sed -E 's,^(\|3.*)?/bin/(ba)?sh -c,RUN,' | # change /bin/(ba)?sh calls to RUN
sed 's,^RUN #(nop) *,,' | # remove RUN #(nop) calls for ENV,LABEL...
sed $'s, *&& *, \\\ \\\n \&\& ,g' # pretty print multi command lines following Docker best practices
;;
*)
echo "unknown OSTYPE: $OSTYPE"
;;
esac

docker镜像下载jar包

1
2
3
4
//镜像导出
❯ docker save -o ./client.tar seanhongxing/holer-client:latest
//容器导出
❯ docker export seanhongxing/holer-client > ./client.tar

注:分层,jar包在某一层

l

$1易混淆点

一、标准输入和参数的区别

这个问题一定是最容易让人迷惑的,具体来说,就是搞不清什么时候用管道 符| 和文件重定向> ,< ,什么时候用变量$ 。

比如说,我现在有个自动连接宽带的 shell 脚本 connect.sh ,存在我的家目录:

1
2
$ where connect.sh 
/home/fdl/bin/connect.sh

如果我想删除这个脚本,而且想少敲几次键盘,应该怎么操作呢?我曾经这
样尝试过:

1
$ where connect.sh | rm

实际上,这样操作是错误的,正确的做法应该是这样的:

1
$ rm $(where connect.sh)

前者试图将 where 的结果连接到 rm 的标准输入,后者试图将结果作为命令行参数传入。

标准输入就是编程语言中诸如 scanf 或者 readline 这种命令;而参数是指 程序的 main 函数传入的 args 字符数组

前文「Linux文件描述符」说过,管道符和重定向符是将数据作为程序的标 准输入,而 $(cmd) 是读取 cmd 命令输出的数据作为参数。

用刚才的例子说, rm 命令源代码中肯定不接受标准输入,而是接收命令行 参数,删除相应的文件。作为对比, cat 命令是既接受标准输入,又接受 命令行参数:

1
2
3
4
5
6
$ cat filename 
...file text...
$ cat < filename
...file text...
$ echo 'hello world' | cat
hello world

如果命令能够让终端阻塞,说明该命令接收标准输入,反之就是不接受,比 如你只运行 cat 命令不加任何参数,终端就会阻塞,等待你输入字符串并 回显相同的字符串。

二、后台运行程序

比如说你远程登录到服务器上,运行一个 Django web 程序:

1
$ python manager.py runserver 0.0.0.0 Listening on 0.0.0.0:8080...

现在你可以通过服务器的 IP 地址测试 Django 服务,但是终端此时就阻塞 了,你输入什么都不响应,除非输入 Ctrl-C 或者 Ctrl-/ 终止 python 进程。

可以在命令之后加一个 & 符号,这样命令行不会阻塞,可以响应你后续输 入的命令,但是如果你退出服务器的登录,就不能访问该网⻚了。

如果你想在退出服务器之后仍然能够访问web服务,应该这样写命令 (cmd &) :

1
2
3
$ (python manager.py runserver 0.0.0.0 &) Listening on 0.0.0.0:8080...

$ logout

底层原理是这样的:

每一个命令行终端都是一个 shell 进程,你在这个终端里执行的程序实际上 都是这个 shell 进程分出来的子进程。正常情况下,shell 进程会阻塞,等待 子进程退出才重新接收你输入的新的命令。加上 & 号,只是让 shell 进程不 再阻塞,可以继续响应你的新命令。但是无论如何,你如果关掉了这个 shell 命令行端口,依附于它的所有子进程都会退出。

而 (cmd &) 这样运行命令,则是将 cmd 命令挂到一个 systemd 系统守护进程名下,认 systemd 做爸爸,这样当你退出当前终端时,对于刚才的 cmd 命令就完全没有影响了。

类似的,还有一种后台运行常用的做法是这样:

1
$ nohub some_cmd &

nohub 命令也是类似的原理,不过通过我的测试,还是 (cmd &) 这种形式 更加稳定

三、单引号和双引号的区别

不同的 shell 行为会有细微区别,但有一点是确定的,对于 $ ( ) 这 几个符号,单引号包围的字符串不会做任何转义,双引号包围的字符串会转 义

shell 的行为可以测试,使用 set -x 命令,会开启 shell 的命令回显,你可 以通过回显观察 shell 到底在执行什么命令:

可⻅ echo $(cmd) 和 echo “$(cmd)” ,结果差不多,但是仍然有区别。注 意观察,双引号转义完成的结果会自动增加单引号,而前者不会。

也就是说,如果 $ 读取出的参数字符串包含空格,应该用双引号括起来, 否则就会出错

四、****sudo 找不到命令

有时候我们普通用户可以用的命令,用 sudo 加权限之后却报错 command not found:

1
2
3
4
5
$ connect.sh
network-manager: Permission denied

$ sudo connect.sh
sudo: command not found

原因在于, connect.sh 这个脚本仅存在于该用户的环境变量中:

当使用 sudo 时,系统会使用 /etc/sudoers 这个文件中规定的该用户的权 限和环境变量,而这个脚本在 /etc/sudoers 环境变量目录中当然是找不到 的。

解决方法是使用脚本文件的路径,而不是仅仅通过脚本名称:

1
$ sudo /home/fdl/bin/connect.sh
l

§1效率

输入相似文件名太麻烦

用花括号括起来的字符串用逗号连接,可以自动扩展,非常有用,直接看例子:

1
2
3
4
$ echo {one,two,three}file
onefile twofile threefile
$ echo {one,two,three}{1,2,3}
one1 one2 one3 two1 two2 two3 three1 three2 three3

你看,花括号中的每个字符都可以和之后(或之前)的字符串进行组合拼 接,注意花括号和其中的逗号不可以用空格分隔,否则会被认为是普通的字 符串对待

这个技巧有什么实际用处呢?最简单有用的就是给 cp , mv , rm 等命令扩 展参数:

1
2
3
4
5
6
7
8
9
$ cp /very/long/path/file{,.bak}
==> cp /very/long/path/file /very/long/path/file.bak
# 给 file 复制一个叫做 file.bak 的副本

$ rm file{1,3,5}.txt
# 删除 file1.txt file3.txt file5.txt

$ mv *.{c,cpp} src/
# 将所有 .c 和 .cpp 为后缀的文件移入 src 文件夹

输入路径名称太麻烦

  • cd - 返回刚才呆的目录,直接看例子吧:
1
2
3
4
5
6
7
$ pwd
/very/long/path
$ cd # 回到家目录瞅瞅
$ pwd
/home/labuladong
$ cd - # 再返回刚才那个目录 $ pwd
/very/long/path
  • 特殊命令 !$ 会替换成上一次输入的命令最后的路径,直接看例子:
1
2
3
4
5
6
7
#没有加可执行权限
$ /usr/bin/script.sh
zsh: permission denied: /usr/bin/script.sh

$ chmod +x !$
chmod +x /usr/bin/script.sh
export lessCharset-uft8

特殊命令 !* 会替换成上一次命令输入的所有文件路径,直接看例子:

1
2
3
4
5
6
# 创建了三个脚本文件
$ file script1.sh script2.sh script3.sh

# 给它们全部加上可执行权限
$ chmod +x !*
chmod +x script1.sh script2.sh script3.sh

可以在环境变量 CDPATH 中加入你常用的工作目录,当 cd 命令在当前目 录中找不到你指定的文件/目录时,会自动到 CDPATH 中的目录中寻找。

比如说我常去 /var/log 目录找日志,可以执行如下命令:

1
2
3
4
5
6
7
8
9
$ export CDPATH='~:/var/log'
# cd 命令将会在 〜 目录和 /var/log 目录扩展搜索$ pwd
/home/labuladong/musics
$ cd mysql
cd /var/log/mysql
$ pwd
/var/log/mysql
$ cd my_pictures
cd /home/labuladong/my_pictures

这个技巧是十分好用的,这样就免了经常写完整的路径名称,节约不少时间。

需要注意的是,以上操作是 bash 支持的,其他主流 shell 解释器当然都支持 扩展 cd 命令的搜索目录,但可能不是修改 CDPATH 这个变量,具体的设 置方法可以自行搜索。

输入重复命令太麻烦

** 使用特殊命令 !! ,可以自动替换成上一次使用的命令:

1
2
3
4
5
$ apt install net-tools
E: Could not open lock file - open (13: Permission denied)

$ sudo !!
sudo apt install net-tools [sudo] password for fdl:

有的命令很⻓,一时间想不起来具体参数了怎么办?

对于 bash 终端,可以使用 Ctrl+R 快捷键反向搜索历史命令,之所以说是 反向搜索,就是搜索最近一次输入的命令。

比如按下 Ctrl+R 之后,输入 sudo ,bash 就会搜索出最近一次包含 sudo 的命令,你回⻋之后就可以运行该命令了:

1
(reverse-i-search)`sudo': sudo apt install git

但是这个方法有缺点:首先,该功能似乎只有 bash 支持,我用的 zsh 作为 shell 终端,就用不了;第二,只能查找出一个(最近的)命令,如果我想 找以前的某个命令,就没办法了。

对于这种情况,我们最常用的方法是使用 history 命令配合管道符和 grep 命令来寻找某个历史命令:

1
2
3
4
5
6
# 过滤出所有包含 config 字段的历史命令
$ history | grep 'config'
7352 ./configure
7434 git config --global --unset https.proxy 9609 ifconfig
9985 clip -o | sed -z 's/\n/,\n/g' | clip
10433 cd ~/.config

你使用的所有 shell 命令都会被记录,前面的数字就表示这是第几个命令, 找到你想重复使用的命令后,也不需要复制粘贴该命令,只要使用 ! + 你 想重用的命令编号即可运行该命令
拿上面的例子,我想重新运行 git config 那条命令,就可以这样:

1
2
$ !7434
git config --global --unset https.proxy # 运行完成

我觉得 history 加管道加 grep 这样打的字还是太多,可以在 你的 shell 配置文件中( .bashrc , .zshrc 等) 中写这样一个函数:

1
2
3
his() {
history | grep "$@"
}

这样就不需要写那么多,只需要 his ‘some_keyword’ 即可搜索历史命令。

我一般不使用 bash 作为终端,我给大家推荐一款很好用的 shell 终端叫做 zsh,这也是我自己使用的 shell。这款终端还可以扩展各种插件,非常好 用,具体配置方法可自行搜索。

其他小技巧

  • **yes 命令自动输入字符y **进行确认

    我们安装某些软件的时候,可能有交互式的提问:

1
2
3
$ sudo apt install XXX
...
XXX will use 996 MB disk space, continue? [y/n]

一般情况下我们都是一路 y 到底,但如果我们想自动化一些软件的安装就很 烦,遇到这种交互式提问就卡住了,还得手动处理。

yes 命令可以帮助我们:

1
$ yes | your_cmd

这样就会一路自动 y 下去,不会停下让我们输入了。 如果你读过前文Linux 文件描述符,就知道其原理很简单:

你单独运行一下 yes 命令,发现它就是打印出一大堆字符 y,通过管道把 输出和 your_cmd 的标准输入相连接,如果 your_cmd 又提出无聊的问题, 就会从标准输入读取数据,也就会读取到一个 y 和换行符,和你手动输入 y 确认是一个效果。

  • 特殊变量 $? 记录上一次命令的返回值

在 Linux shell 中,遵循 C 语言的习惯,返回值为 0 的话就是程序正常退 出,非 0 值就是异常退出出。读取上一次命令的返回值在平时使用命令行时 感觉没什么用,但是如果你想编写一些 shell 脚本,知道返回值非常有用。

举个实际的例子,比如我的 Github 仓库 fucking-algorithm ,我需要给其中 所有 markdown 文件最下方添加上一篇、下一篇、目录三个⻚脚链接,有的 文章已经有了⻚脚,大部分都没有。

为了防止重复添加,我必须知道一个 md 文件下方是否已添加,这时候就可 以使用 $? 变量配合 grep 命令做到:

1
2
3
4
5
6
#!/bin/bash
filename=$1
# 查看文件尾部是否包含关键词
tail | grep '下一篇' $filename
# grep 查找到匹配会返回 0,找不到则返回非 0 值
[ $? -ne 0 ] && { 添加⻚脚; }
  • 特殊变量 $$ 记录当前进程的 PID

这个功能可能在平时使用时也不怎么用,但是在写 shell 脚本时也非常有 用,比如说你要在 /tmp 创建临时文件,给文件起名字一直都是非常让人 费脑子 的,这时候可以使用 $$ 变量扩展出当前进程的 PID 作为临时文件的名字。

l

[toc]

§1效率

输入相似文件名太麻烦

用花括号括起来的字符串用逗号连接,可以自动扩展,非常有用,直接看例子:

1
2
3
4
$ echo {one,two,three}file
onefile twofile threefile
$ echo {one,two,three}{1,2,3}
one1 one2 one3 two1 two2 two3 three1 three2 three3

你看,花括号中的每个字符都可以和之后(或之前)的字符串进行组合拼 接,注意花括号和其中的逗号不可以用空格分隔,否则会被认为是普通的字 符串对待

这个技巧有什么实际用处呢?最简单有用的就是给 cp , mv , rm 等命令扩 展参数:

1
2
3
4
5
6
7
8
9
$ cp /very/long/path/file{,.bak}
==> cp /very/long/path/file /very/long/path/file.bak
# 给 file 复制一个叫做 file.bak 的副本

$ rm file{1,3,5}.txt
# 删除 file1.txt file3.txt file5.txt

$ mv *.{c,cpp} src/
# 将所有 .c 和 .cpp 为后缀的文件移入 src 文件夹

输入路径名称太麻烦

  • cd - 返回刚才呆的目录,直接看例子吧:
1
2
3
4
5
6
7
$ pwd
/very/long/path
$ cd # 回到家目录瞅瞅
$ pwd
/home/labuladong
$ cd - # 再返回刚才那个目录 $ pwd
/very/long/path
  • 特殊命令 !$ 会替换成上一次输入的命令最后的路径,直接看例子:
1
2
3
4
5
6
7
#没有加可执行权限
$ /usr/bin/script.sh
zsh: permission denied: /usr/bin/script.sh

$ chmod +x !$
chmod +x /usr/bin/script.sh
export lessCharset-uft8

特殊命令 !* 会替换成上一次命令输入的所有文件路径,直接看例子:

1
2
3
4
5
6
# 创建了三个脚本文件
$ file script1.sh script2.sh script3.sh

# 给它们全部加上可执行权限
$ chmod +x !*
chmod +x script1.sh script2.sh script3.sh

可以在环境变量 CDPATH 中加入你常用的工作目录,当 cd 命令在当前目 录中找不到你指定的文件/目录时,会自动到 CDPATH 中的目录中寻找。

比如说我常去 /var/log 目录找日志,可以执行如下命令:

1
2
3
4
5
6
7
8
9
$ export CDPATH='~:/var/log'
# cd 命令将会在 〜 目录和 /var/log 目录扩展搜索$ pwd
/home/labuladong/musics
$ cd mysql
cd /var/log/mysql
$ pwd
/var/log/mysql
$ cd my_pictures
cd /home/labuladong/my_pictures

这个技巧是十分好用的,这样就免了经常写完整的路径名称,节约不少时间。

需要注意的是,以上操作是 bash 支持的,其他主流 shell 解释器当然都支持 扩展 cd 命令的搜索目录,但可能不是修改 CDPATH 这个变量,具体的设 置方法可以自行搜索。

输入重复命令太麻烦

** 使用特殊命令 !! ,可以自动替换成上一次使用的命令:

1
2
3
4
5
$ apt install net-tools
E: Could not open lock file - open (13: Permission denied)

$ sudo !!
sudo apt install net-tools [sudo] password for fdl:

有的命令很⻓,一时间想不起来具体参数了怎么办?

对于 bash 终端,可以使用 Ctrl+R 快捷键反向搜索历史命令,之所以说是 反向搜索,就是搜索最近一次输入的命令。

比如按下 Ctrl+R 之后,输入 sudo ,bash 就会搜索出最近一次包含 sudo 的命令,你回⻋之后就可以运行该命令了:

1
(reverse-i-search)`sudo': sudo apt install git

但是这个方法有缺点:首先,该功能似乎只有 bash 支持,我用的 zsh 作为 shell 终端,就用不了;第二,只能查找出一个(最近的)命令,如果我想 找以前的某个命令,就没办法了。

对于这种情况,我们最常用的方法是使用 history 命令配合管道符和 grep 命令来寻找某个历史命令:

1
2
3
4
5
6
# 过滤出所有包含 config 字段的历史命令
$ history | grep 'config'
7352 ./configure
7434 git config --global --unset https.proxy 9609 ifconfig
9985 clip -o | sed -z 's/\n/,\n/g' | clip
10433 cd ~/.config

你使用的所有 shell 命令都会被记录,前面的数字就表示这是第几个命令, 找到你想重复使用的命令后,也不需要复制粘贴该命令,只要使用 ! + 你 想重用的命令编号即可运行该命令
拿上面的例子,我想重新运行 git config 那条命令,就可以这样:

1
2
$ !7434
git config --global --unset https.proxy # 运行完成

我觉得 history 加管道加 grep 这样打的字还是太多,可以在 你的 shell 配置文件中( .bashrc , .zshrc 等) 中写这样一个函数:

1
2
3
his() {
history | grep "$@"
}

这样就不需要写那么多,只需要 his ‘some_keyword’ 即可搜索历史命令。

我一般不使用 bash 作为终端,我给大家推荐一款很好用的 shell 终端叫做 zsh,这也是我自己使用的 shell。这款终端还可以扩展各种插件,非常好 用,具体配置方法可自行搜索。

其他小技巧

  • **yes 命令自动输入字符y **进行确认

    我们安装某些软件的时候,可能有交互式的提问:

1
2
3
$ sudo apt install XXX
...
XXX will use 996 MB disk space, continue? [y/n]

一般情况下我们都是一路 y 到底,但如果我们想自动化一些软件的安装就很 烦,遇到这种交互式提问就卡住了,还得手动处理。

yes 命令可以帮助我们:

1
$ yes | your_cmd

这样就会一路自动 y 下去,不会停下让我们输入了。 如果你读过前文Linux 文件描述符,就知道其原理很简单:

你单独运行一下 yes 命令,发现它就是打印出一大堆字符 y,通过管道把 输出和 your_cmd 的标准输入相连接,如果 your_cmd 又提出无聊的问题, 就会从标准输入读取数据,也就会读取到一个 y 和换行符,和你手动输入 y 确认是一个效果。

  • 特殊变量 $? 记录上一次命令的返回值

在 Linux shell 中,遵循 C 语言的习惯,返回值为 0 的话就是程序正常退 出,非 0 值就是异常退出出。读取上一次命令的返回值在平时使用命令行时 感觉没什么用,但是如果你想编写一些 shell 脚本,知道返回值非常有用。

举个实际的例子,比如我的 Github 仓库 fucking-algorithm ,我需要给其中 所有 markdown 文件最下方添加上一篇、下一篇、目录三个⻚脚链接,有的 文章已经有了⻚脚,大部分都没有。

为了防止重复添加,我必须知道一个 md 文件下方是否已添加,这时候就可 以使用 $? 变量配合 grep 命令做到:

1
2
3
4
5
6
#!/bin/bash
filename=$1
# 查看文件尾部是否包含关键词
tail | grep '下一篇' $filename
# grep 查找到匹配会返回 0,找不到则返回非 0 值
[ $? -ne 0 ] && { 添加⻚脚; }
  • 特殊变量 $$ 记录当前进程的 PID

这个功能可能在平时使用时也不怎么用,但是在写 shell 脚本时也非常有 用,比如说你要在 /tmp 创建临时文件,给文件起名字一直都是非常让人 费脑子 的,这时候可以使用 $$ 变量扩展出当前进程的 PID 作为临时文件的名字。

l