As a new linux 🐧 user, you might realize that there are a bunch of configuration files present in your system. These special files are called "dotfiles".
作为新的Linux用户,您可能会意识到系统中存在大量配置文件。 这些特殊文件称为“点文件”。
In this tutorial we will learn how to make a dotfiles manager and create a backup of these files on GitHub.
在本教程中,我们将学习如何制作一个dotfiles管理器以及如何在GitHub上创建这些文件的备份。
What are these .dotfiles you may ask? And why do we need them?
您可能会问这些.dotfiles是什么? 为什么我们需要它们?
Dotfiles are usually associated with specific programs installed on your system and are used to customize those programs/software.
点文件通常与系统上安装的特定程序相关联,并用于自定义那些程序/软件。
For example, if you have zsh as your default shell your will have a .zshrc
file in your HOME directory. Some other examples include:
例如,如果将zsh作为默认外壳,则HOME目录中将具有.zshrc
文件。 其他一些示例包括:
.vimrc
: this bad boi is used for configuring your VIM Editor..vimrc
:该错误的boi用于配置VIM编辑器。.bashrc
: available by default, used for changing bash settings..bashrc
:默认情况下可用,用于更改bash设置。.bash_aliases
: this file is generally used to store your command aliases..bash_aliases
:此文件通常用于存储命令别名。.gitconfig
: stores configurations related to Git..gitconfig
:存储与Git相关的配置。.gitmessage
: Used to provide a commit messsage template while usinggit commit
..gitmessage
:用于在使用git commit
时提供提交消息模板。
These .dotfiles change over time as you start customizing linux according to your needs.
当您开始根据需要定制Linux时,这些.dotfiles会随着时间而变化。
Creating a back-up of these files is necessary if in some case you mess up something 😬 and want to go back to a previous stable state. That's where VCS (Version Control Software) comes in.
如果在某些情况下您弄乱了某些东西并想回到以前的稳定状态,则必须创建这些文件的备份。 这就是VCS(版本控制软件)的出现。
Here, we will learn how to automate this task by writing a simple shell script and storing our dotfiles on GitHub.
在这里,我们将学习如何通过编写简单的shell脚本并将点文件存储在GitHub上来自动执行此任务。
![lets do it rock](https://i-blog.csdnimg.cn/blog_migrate/2c8ae3a4c01f5c637fa79c6a7e59ee2c.gif)
内容 (Contents)
第一步 (First Steps)
Oh before we move any further, let's name our script: dotman, (dot)file (man)ager. Do you like it 😅 ?
在进一步操作之前,让我们命名脚本: dotman ,(dot)file(man)ager。 你喜欢😅吗?
Before we write our first line of code, we need to lay out our requirements and design for how our shell script should work.
在编写第一行代码之前,我们需要对我们的shell脚本应该如何工作进行布局和设计。
我们的要求 (Our Requirements)
We are going to make dotman simple & easy to use. It should be able to:
我们将使dotman简单易用。 它应该能够:
Find dotfiles present inside our system 🔍.
查找我们系统中存在的dotfile🔍。
Differentiate between files present in our git repository to those on our system.
将git存储库中存在的文件与系统上的文件区分开。
Update our dotfiles repo (either push to remote or pull from it).
更新我们的dotfiles存储库(推送到远程或从中拉出)。
Be easy to use (we don't want 5 different arguments in a single script).
易于使用(我们不想在一个脚本中使用5个不同的参数)。
让可视化 (Lets Visualize)
获取依赖 (Getting Dependencies)
Git
We need Git, because we may want to go back to a previous version of our dotfile. Plus we are going to store our dotfiles in a VCS Host (GitHub/GitLab/Bitbucket/Gittea). Don't have Git Installed? Go through the following guide to learn how to install it according to your system.Git
我们需要Git,因为我们可能想回到点文件的先前版本。 另外,我们将把点文件存储在VCS主机中(GitHub / GitLab / Bitbucket / Gittea)。 还没有安装Git吗? 仔细阅读以下指南,了解如何根据您的系统进行安装。Bash
This is going to be available on your Linux/Unix/MacOS machines by default. Verify this by checking the versionbash --version
. It should be something like this. Don't worry about the version too much, as our script will work fine for Bash >=3.Bash
默认情况下,它将在您的Linux / Unix / MacOS计算机上可用。 通过检查版本bash --version
验证这一点。 应该是这样的。 不用太担心版本,因为我们的脚本对于Bash> = 3可以正常工作。
GNU bash, version 4.4.20(1)-release (i686-pc-linux-gnu)
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
开始编码 (Start Coding)
So now we have everything setup. Fire up your favorite editor/IDE.
现在,我们已完成所有设置。 启动您喜欢的编辑器/ IDE。
![coder chimpanzee](https://i-blog.csdnimg.cn/blog_migrate/3c80ca27351469987bc3d3feda596cc5.gif)
We need to declare a she bang to indicate we are going to invoke an interpreter for execution. In the start of the script include this line:
我们需要声明一个爆炸,以表明我们将调用解释器以执行。 在脚本的开头包含以下行:
#!/usr/bin/env bash
The command (program) env
is executed as a new process which then calls the command that was provided as an argument.
命令(程序) env
作为新进程执行,然后调用作为参数提供的命令。
In our case bash
is automatically started by the env process. That is its env
responsibility to find where is bash
on our system and substitute its path in the script. You could replace bash
with, for example, python
or ruby
.
在我们的情况下, bash
由env进程自动启动。 这就是它的env
责任找到哪里是bash
在我们的系统和脚本替代它的路径。 您可以将bash
替换为python
或ruby
。
Now just change file permissions to make our script executable.
现在只需更改文件许可权即可使我们的脚本可执行。
chmod +x dotman.sh
We will be using the functional style of programming in this script, that is every piece of the task is going to be inside some function(). Let's follow the flow chart we visualized above and write our first function, init_check()
.
我们将在此脚本中使用编程的功能样式,即任务的每一部分都将放在某个function()内部。 让我们按照上面可视化的流程图并编写我们的第一个函数init_check()
。
We are going to rely only on 2 inputs from the user:
我们将仅依靠来自用户的2个输入:
DOT_DEST
: the location of repository in your local system.DOT_DEST
:本地系统中存储库的位置。DOT_REPO
: the url to the remote dotfile repo.DOT_REPO
:远程点文件仓库的URL。
These 2 variables must be present inside your default shell config (.bashrc
for e.g). We will learn how to do this later in this tutorial.
这两个变量必须存在于默认的shell配置中(例如.bashrc
)。 我们将在本教程的后面部分学习如何执行此操作。
init_check() {
# Check wether its a first time use or not
if [[ -z ${DOT_REPO} && -z ${DOT_DEST} ]]; then
# show first time setup menu
# initial_setup
else
# repo_check
# manage
fi
}
The -z
option is used to check whether a variable is set or not (that is, if its available to our script or not). If it is not, then we are going to invoke our initial_setup()
function. Otherwise we will check if the repository is cloned and is present inside the DOT_DEST
folder.
-z
选项用于检查是否设置了变量(即,变量是否可用于我们的脚本)。 如果不是,那么我们将调用我们的initial_setup()
函数。 否则,我们将检查存储库是否已克隆并且是否存在于DOT_DEST
文件夹中。
Now let's code the initial_setup
function:
现在,让我们编写initial_setup
函数的代码:
initial_setup() {
echo -e "\n\nFirst time use, Set Up d○tman"
echo -e "....................................\n"
read -p "Enter dotfiles repository URL : " -r DOT_REPO
read -p "Where should I clone $(basename "${DOT_REPO}") (${HOME}/..): " -r DOT_DEST
DOT_DEST=${DOT_DEST:-$HOME}
if [[ -d "$HOME/$DOT_DEST" ]]; then
# clone the repo in the destination directory
if git -C "${HOME}/${DOT_DEST}" clone "${DOT_REPO}"; then
add_env "$DOT_REPO" "$DOT_DEST"
echo -e "\ndotman successfully configured"
goodbye
else
# invalid arguments to exit, Repository Not Found
echo -e "\n$DOT_REPO Unavailable. Exiting"
exit 1
fi
else
echo -e "\n$DOT_DEST Not a Valid directory"
exit 1
fi
}
Pretty basic, right? Now, let's go through this together and understand what's happening.
很基本吧? 现在,让我们一起经历一下,了解正在发生的事情。
The
read
startement is a shell bulitin which is used to take input from the terminal. The-p
option specifies a prompt before taking an input.read
开头是一个shell bulitin,用于从终端获取输入。-p
选项指定输入之前的提示。The next line after read is called a Parameter Expansion, If the user doesn't input DOT_DEST then the default value is assigned as
/home/username/
(If DOT_DEST is unset or null, the expansion of $HOME is substituted) Otherwise, the value entered by user is substituted.读取后的下一行称为Parameter Expansion ,如果用户未输入DOT_DEST,则默认值分配为
/home/username/
(如果DOT_DEST未设置或为null,则替换$ HOME的扩展名)否则,则用户输入的值将被替换。The
-d
inside the if statement checks whether the directory exists (or technically) the directory user provided is actually a valid path in our system or not.if语句中的
-d
检查目录是否存在(或从技术上来说)用户提供的目录实际上是我们系统中的有效路径。The
-C
option is used in git to clone the repository to a user-specified path.git中使用
-C
选项将存储库克隆到用户指定的路径。
Now let's see how to export environment variables in the function add_env()
.
现在让我们看看如何在add_env()
函数中导出环境变量。
add_env() {
# export environment variables
echo -e "\nExporting env variables DOT_DEST & DOT_REPO ..."
current_shell=$(basename "$SHELL")
if [[ $current_shell == "zsh" ]]; then
echo "export DOT_REPO=$1" >> "$HOME"/.zshrc
echo "export DOT_DEST=$2" >> "$HOME"/.zshrc
elif [[ $current_shell == "bash" ]]; then
# assume we have a fallback to bash
echo "export DOT_REPO=$1" >> "$HOME"/.bashrc
echo "export DOT_DEST=$2" >> "$HOME"/.bashrc
else
echo "Couldn't export DOT_REPO and DOT_DEST."
echo "Consider exporting them manually".
exit 1
fi
echo -e "Configuration for SHELL: $current_shell has been updated."
}
Running echo $SHELL
in your terminal will give you the path for your default shell. The basename
command is used to print the "Name" of our SHELL (that is, the actual name without any leading /).
在终端中运行echo $SHELL
将为您提供默认Shell的路径。 basename
命令用于打印SHELL的“名称”(即,没有任何前导/的实际名称)。
> echo $SHELL
/usr/bin/zsh
> basename $SHELL
zsh
The
export
is a well-used statement: it lets you export :) environment variables.export
是一个很好用的语句:它允许您导出:)环境变量。>>
is called a redirection operator, that is the output of the statement echo "export DOT_DEST=$2" is directed (appended) to the end ofzshrc
file.>>
被称为重定向运算符,即语句echo“ export DOT_DEST = $ 2”的输出被定向(附加)到zshrc
文件的末尾。
Now, once the user has completed the first time setup we need to show them the "manager" options.
现在,一旦用户完成了第一次设置,我们就需要向他们显示“经理”选项。
manage() {
while :
do
echo -e "\n[1] Show diff"
echo -e "[2] Push changed dotfiles to remote"
echo -e "[3] Pull latest changes from remote"
echo -e "[4] List all dotfiles"
echo -e "[q/Q] Quit Session"
# Default choice is [1]
read -p "What do you want me to do ? [1]: " -n 1 -r USER_INPUT
# See Parameter Expansion
USER_INPUT=${USER_INPUT:-1}
case $USER_INPUT in
[1]* ) show_diff_check;;
[2]* ) dot_push;;
[3]* ) dot_pull;;
[4]* ) find_dotfiles;;
[q/Q]* ) exit;;
* ) printf "\n%s\n" "Invalid Input, Try Again";;
esac
done
}
You are already familiar with
read
. The-n 1
option specifies what length of input is allowed, in our case the user can only input one character amongst 1, 2, 3, 4, q and Q.您已经熟悉
read
。-n 1
选项指定允许输入的长度,在本例中,用户只能输入1、2、3、4,q和Q中的一个字符。
Now we have to find all dotfiles in our HOME directory.
现在我们必须在我们的HOME目录中找到所有的点文件。
find_dotfiles() {
printf "\n"
readarray -t dotfiles < <( find "${HOME}" -maxdepth 1 -name ".*" -type f )
printf '%s\n' "${dotfiles[@]}"
}
The function is divided into 2 parts:
该功能分为2部分:
find
find
The find command you guessed right, searches for files and directories in our system. Let's understand it part by part.
您猜对的find命令在我们的系统中搜索文件和目录。 让我们对它进行部分了解。
The
-type f
options specifies that we only want to search for regular files and not directories, character or block, or device files.-type f
选项指定我们只想搜索常规文件,而不是目录,字符或块或设备文件。The
-maxdepth
option tells find to descend at most 1 level (a non-negative integer) levels of directories below the starting-points. You could search sub-directories by replacing 1 with 2, 3 etc.-maxdepth
选项告诉find在-maxdepth
以下最多将-maxdepth
1级(非负整数)。 您可以通过将1替换为2、3等来搜索子目录。-name
takes a pattern(glob) for searching. For example you can search for all.py
files:-name ".py"
.-name
采用一个模式(全局)进行搜索。 例如,您可以搜索所有.py
文件:-name ".py"
。
readarray
(also a synonym formapfile
)readarray
(也是mapfile
的同义词)reads lines from the standard input into the indexed array variable
从标准输入读取行到索引数组变量
dotfiles
.dotfiles
。The
的
-t
option removes any trailing delimiter (default newline) from each line read.-t
选项从读取的每一行中删除任何结尾的定界符(默认换行符)。
Note: If you have an older version of Bash (<4),
readarray
might not be present as a builtin. We can achieve the same functionality by using awhile
loop instead.注意:如果您具有较旧的Bash版本(<4),则
readarray
可能不作为内置readarray
出现。 我们可以通过使用while
循环来实现相同的功能。
while read -r value; do
dotfiles+=($value)
done < <( find "${HOME}" -maxdepth 1 -name ".*" -type f )
We are now going to make one of the most important functions in our script, diff_check
.
现在,我们将在脚本中diff_check
最重要的功能之一diff_check
。
diff_check() {
if [[ -z $1 ]]; then
declare -ag file_arr
fi
# dotfiles in repository
readarray -t dotfiles_repo < <( find "${HOME}/${DOT_DEST}/$(basename "${DOT_REPO}")" -maxdepth 1 -name ".*" -type f )
# check length here ?
for (( i=0; i<"${#dotfiles_repo[@]}"; i++))
do
dotfile_name=$(basename "${dotfiles_repo[$i]}")
# compare the HOME version of dotfile to that of repo
diff=$(diff -u --suppress-common-lines --color=always "${dotfiles_repo[$i]}" "${HOME}/${dotfile_name}")
if [[ $diff != "" ]]; then
if [[ $1 == "show" ]]; then
printf "\n\n%s" "Running diff between ${HOME}/${dotfile_name} and "
printf "%s\n" "${dotfiles_repo[$i]}"
printf "%s\n\n" "$diff"
fi
file_arr+=("${dotfile_name}")
fi
done
if [[ ${#file_arr} == 0 ]]; then
echo -e "\n\nNo Changes in dotfiles."
return
fi
}
show_diff_check() {
diff_check "show"
}
Our goal here is to find the dotfiles already present in the repository and compare them with the one available in our HOME directory.
我们的目标是找到存储库中已经存在的点文件,并将其与我们HOME目录中可用的点文件进行比较。
The
declare
keyword lets us create variables. The-a
option is used to create arrays and-g
tells declare to make the variables available "globally" inside the script.使用
declare
关键字可以创建变量。-a
选项用于创建数组,而-g
告诉声明使变量在脚本内“全局”可用。${#file_arr}
gives us the length of the array.${#file_arr}
给出了数组的长度。
The next important command is diff
which is used to compare files line-by-line. For example:
下一个重要的命令是diff
,它用于逐行比较文件。 例如:
> echo -e "abc\ndef\nghi" >> fileA.txt
> echo -e "abc\nlmn\nghi" >> fileB.txt
> cat fileA.txt
abc
def
ghi
> cat fileB.txt
abc
lmn
ghi
> diff -u fileA.txt fileB.txt
--- fileA.txt 2020-07-17 16:24:16.138172662 +0530
+++ fileB.txt 2020-07-17 16:24:26.686075270 +0530
@@ -1,3 +1,3 @@
abc
-def
+lmn
ghi
The dot_push()
function.
dot_push()
函数。
dot_push() {
diff_check
echo -e "\nFollowing dotfiles changed : "
for file in "${file_arr[@]}"; do
echo "$file"
cp "${HOME}/$file" "${HOME}/${DOT_DEST}/$(basename "${DOT_REPO}")"
done
dot_repo="${HOME}/${DOT_DEST}/$(basename "${DOT_REPO}")"
git -C "$dot_repo" add -A
echo -e "Enter Commit Message (Ctrl + d to save):"
commit=$(</dev/stdin)
git -C "$dot_repo" commit -m "$commit"
# Run Git Push
git -C "$dot_repo" push
}
We are overwriting files here by copying them to our dotfile repo using the cp
command.
我们通过使用cp
命令将文件复制到我们的dotfile存储库中来覆盖文件。
And finally the dot_pull()
function:
最后是dot_pull()
函数:
dot_pull() {
# pull changes (if any) from the host repo
echo -e "\nPulling dotfiles ..."
dot_repo="${HOME}/${DOT_DEST}/$(basename "${DOT_REPO}")"
echo -e "\nPulling changes in $dot_repo\n"
git -C "$dot_repo" pull origin master
}
爵士💅🏼我们的剧本 (Jazzing 💅🏼 up our script)
Up until now we have achieved what we initially visualized. But you know what, something's missing ....... 🤔
到目前为止,我们已经实现了最初的可视化效果。 但是你知道吗,缺少什么.............
Colors
色彩
![Colorful cat rainbow waves](https://media1.tenor.com/images/83bc087deefaf265255b9a6196915e8a/tenor.gif)
There are a lot of ways to do that, but the popular one is using escape sequences. But we are going to use a tool called tput
which is a human friendly interface to output colors according to the user's terminal. It is available by default in Linux/MacOS. Here is a short demo.
有很多方法可以做到这一点,但是流行的是使用转义序列 。 但是我们将使用称为tput
的工具,该工具是人性化的界面,可以根据用户的终端输出颜色。 在Linux / MacOS中默认情况下可用。 这是一个简短的演示。
To print text in bold
以粗体打印文本
echo "$(tput bold)This$(tput sgr0) word is bold"
To change background color.
更改背景颜色。
echo "$(tput setab 10)This text has green background$(tput sgr0)"
To change foreground color
更改前景色
echo "$(tput setaf 10)This text has blue color$(tput sgr0)"
You can also combine attributes.
您还可以组合属性。
echo "$(tput smul)$(tput setaf 10) This text is underlined & green $(tput rmul)$(tput sgr0)"
Let me leave this task with you: add your favorite colors in the script. Read this guide to learn and explore more about tput.
让我把这个任务留给您:在脚本中添加您喜欢的颜色。 阅读本指南可了解和探索有关tput的更多信息。
最终结果 (The End Result)
I hope you are still with me at the point. But it's the end :( and we have a nice looking dotfile manager now.
希望您现在还和我在一起。 到此为止:(和,我们现在有了一个漂亮的dotfile管理器。
![Happy and excited kermit](https://i-blog.csdnimg.cn/blog_migrate/5cf5cb821ba0b43d7e9c33b12bbec0f9.gif)
Now just run the script (if you haven't already) to see it in action.
现在只需运行脚本(如果尚未运行)即可查看其运行情况。
./dotman.sh
You can see my version of dotman if you need a reference. Feel free to create any issues if you have any questions about this tutorial or email them to me directly.
如果需要参考,可以查看我的dotman版本。 如果您对本教程有任何疑问,请随时提出任何问题,或直接将其发送给我。
I have made it available as a template so you can use it to hack your own version of dotman.
我已经将其作为模板提供,因此您可以使用它来破解自己的dotman版本。
摘要 (Summary)
Let's summarize some important things we learned in this tutorial.
让我们总结一下我们在本教程中学到的一些重要知识。
Use
basename /path/to/dir/file/
to get the filename from a path.使用
basename /path/to/dir/file/
从路径获取文件名。Use
git -C /path/to/clone/to clone https://repo.url
to clone the repository to a different directory from the current working directory.使用
git -C /path/to/clone/to clone https://repo.url
将存储git -C /path/to/clone/to clone https://repo.url
到与当前工作目录不同的目录。echo $SHELL
can be used to determine what is your default shell.echo $SHELL
可用于确定默认外壳程序是什么。Use
find
to search for files and folders in your Linux system.使用
find
在Linux系统中搜索文件和文件夹。The
diff
command is used to compare 2 files. Similar togit diff
.diff
命令用于比较2个文件。 类似于git diff
。Arrays declared inside a function are only accessible inside that function. Use the
-g
option to make them global, for exampledeclare -ag file_arr
.在函数内部声明的数组只能在该函数内部访问。 使用
-g
选项使它们成为全局declare -ag file_arr
,例如,declare -ag file_arr
。tput
can be used to display colorized text on terminal.tput
可用于在终端上显示彩色文本。
If you liked this tutorial, you can read more of my stuff at my blog. You can also connect with me on Twitter.
如果您喜欢本教程,则可以在Blog上阅读更多内容。 您也可以在Twitter上与我联系。
Happy Learning 🖖
快乐学习🖖
翻译自: https://www.freecodecamp.org/news/build-your-own-dotfiles-manager-from-scratch/