前言
你是否知道不同操作系统,诸如:windows系列、unix系列的换行符(回车)的差异?
你是否在使用git时,配置过core.autocrlf参数?
换行符的差异对你项目开发的有什么影响?
接下来,以工作中采坑的一个列子,来回答上述三个问题。
背景
开发环境说明:windows+virtualbox虚拟机+虚拟机里面运行的ubuntu+ubuntu里面装的docker,编辑代码是直接在windows上使用phpstorm编辑,代码目录被挂载到了虚拟机的共享目录上,从而实现了类似文件自动同步映射,达到了在windows上编程,在linux下运行的目的!
今天拿到武汉同事提供的一个php后端项目的镜像,里面包含了openresty+php-fpm,我针对-v挂载目录参数做了自定义调整了后(因为别人的-v对应宿主机的源目录、容器内映射目录是别人的命名风格),始终docker run运行不起,准确来说应该是运行完毕,容器马上退出。
容器启动命令如下:
docker run -p 8622:8080 -e RELEASE=test --env-file /home/insight/env/pre.env -v /c/users/docker-files/insight-v2/v2-killbox:/home/work/app --name=insight-tracking -d harbor.inf.avlyun.org/inf/v2-killbox:rb-1.0.2
查看容器报错如下:
root@ubt-docker:/home/insight/env# docker logs c06bd7ff978b
: invalid optionntrypoint.sh: line 2: set: -
set: usage: set [-abefhkmnptuvxBCHP] [-o option-name] [--] [arg ...]
emmm,乍一看,真的看不出所以然,字面意思“脚本非法”,但是对应的脚本貌似名字不对,实际上容器启动后,会执行entrypoint.sh的脚本,用于针对laravel项目基于容器挂载的env环境变量配置,把这些配置替换到laravel的.env文件中!
尝试解决:
其实之前在构建自定义的nginx 支持https协议的镜像时(nginx默认配置没有https,以及没有开启openssl的支持),也遇到过同样的问题,最终解决是在Dockerfile中ENTRYPOINT以 /bin/bash结尾。但是该镜像在武汉同事的开发机上能正常运行,说明镜像本身是没有任何问题的!
请教了公司大佬,表明是容器启动时,会执行entrypoint.sh脚本,这个脚本也是放在git代码仓库里面的,报错的字面意思大概是shell脚本的文本解析有误!导致脚本运行失败,容器退出!大佬初步猜测是git关于换行的设置有误,导致虚拟机的脚本的换行符不是LF。(不愧是大佬,定位问题真的是稳准狠)
然后我本地git bash关于换行符设置是:
$ git config --global -l
user.name=wangzhiping
user.email=wangzhiping@antiy.cn
core.autocrlf=true
http.lowspeedlimit=0
http.lowspeedtime=999999
关于core.autocrlf参数额外涉及的知识点:
1.什么是CRLF和LF
CRLF 是 carriage return line feed 的缩写;中文意思是 回车换行。是windows下的换行符,对应文本符号是^M$
LF 是 line feed 的缩写,中文意思是换行。是linux下的换行符,对应的文本符号是$
2.autocrlf参数的含义
autocrlf = true 表示要求git在提交时将crlf转换为lf,而在检出时将lf转换为crlf。
autocrlf = false 表示提交和检出代码时均不进行转换
autocrlf = input 表示在提交时将crlf转换为lf,而检出时不转换
前情提要:我本地是win10,上面安装了virtualbox,开发环境是docker,docker是跑在虚拟机里面的。通过virtualbox的共享文件夹功能,做了windows下的特定目录与虚拟机的目录映射;映射后文件会双向自动同步,从而达到我在phpstorm编辑代码,变动会同步到虚拟机的代码目录!之前的s9的php脚本也是CRLF,但是却能正常执行,为啥shell脚本就不行呢?
那么,如何证明我虚拟机里面的entrypoint.sh的换行符是CRLF呢?
进入虚拟机,vi entrypoint.sh,得到:(这个脚本实际位置是windows的项目目录,通过vi打开的是映射到虚拟机下的共享目录文件,实际上是同一份文件)
如果我们想看一下每一行的结尾的换行符到底是什么符号呢,如何在vi里面显示换行符?
执行:set list
结果是"$",看到这里会不会有点蒙蔽,$就代表换行符是对的,那为啥entrypoint.sh没有被正常解析,这就矛盾了!
稍安勿躁,问题在于我们第一次打开entrypoint.sh的时候,左下角的[dos]符号,就已经告诉我们这个脚本是CRLF的换行模式了。
如何证明[dos]只会在打开CRLF换行模式的文件时,显示!
那我们直接在虚拟机里面,vi test.sh 新建一个shell脚本,随便输入一些信息,退出保存,再次打开得到的效果如下:
既然我们当前的entrypoint.sh是CRLF,那么只要我们改成LF,保障entrypoint.sh能被正常执行,是不是就能运行容器了?
那么如何修改文本的换行模式?在phpstorm里面直接点击编辑器右下角,就可更改!但是这种方式只能改一个文件,不能全局批量更改,改了之后,git会把你的改动视为一次变更,git status会发现该文件被改了(因为换行符被修改,换行符也算是代码的一部分)
进入虚拟机,再次执行:vi entrypoint.sh得到:
最后执行:
docker run -p 8622:8080 -e RELEASE=test --env-file /home/insight/env/pre.env -v /c/users/docker-files/insight-v2/v2-killbox:/home/work/app --name=insight-tracking -d harbor.inf.avlyun.org/inf/v2-killbox:rb-1.0.2
容器启动成功:
反思:
1.phpstorm打开文件,在右下角切换换行方式,这种设置方式,重启编辑器后,换行模式不会重置,相当于已经把文本的行末的换行符替换了一次,但是这种方式只能改当前文件!
如何改变整个项目的所有文本文件的换行模式呢?有没有简单的方式,难不成一个文件一个人间的去转换换行格式?
这种全局的设置方式,只能保证在编辑器内新建文本时,换行模式与设置选项保持一致!
问题是,现在本地仓库已经存在了,代码文件很多,想要变更的最简单的方式,如何批量变更换行符?
我选择的做法是,删除本地仓库,修改git的全局配置,重新拉取代码,让拉下来的代码统一为LF
变更换行模式:git config --global core.autocrlf false
2.为啥用以前用php,使用的CRLF,php脚本依旧能在虚拟机正常解析呢?不存在shell脚本的换行符错误导致解析失败的问题?!
难道是因为php脚本虽然是脚本语言,是解释执行,但实际上在执行前,源码有一个编译过程,zend引擎会把php编译成opcode?再交给zend虚拟机解释执行!
猜测是不是在编译成opcode的环节,对换行符做了适配,忽略了LF和CRLF的差异?
3.不借助编辑器,如何在linux下直接更改换行模式?
结语
希望大家看了这篇文章后,能让你对CR 与 LF 换行符有更深刻的认识。