IceOfSummerの博客还是自己搭的博客靠谱后面一辈子的博客都在这了!

已有环境

目前是有一套 webpack 的 vue2 环境,打生产包需要 2 ~ 3 分钟左右,开发启动需要 1 分钟左右。

迁移后,打生产包仅需 1 分钟,开发启动 10 秒左右。

注意,由于 vite v5 版本需要 node18 或者 node20+,所以一般只能升级到 v4,等稳定后再升 v5从 v4 迁移

vite v4 版本只需要 node14 就可以了,我自己目前的环境是 node12,可以直接升级,不会有很多坑。

准备迁移

安装依赖

安装下面的依赖:

npm install @vitejs/plugin-vue2 vite vite-plugin-html -D
bash

修改配置文件

在项目根目录创建 vite-config 目录,然后依次创建下面的文件:

// config.ts import { UserConfig } from 'vite' import vue from '@vitejs/plugin-vue2' import { createHtmlPlugin } from 'vite-plugin-html' import { resolve } from 'path' export const env = { // 页面 context path base: process.env.NODE_ENV === 'production' ? '/app' : '' } const config: UserConfig = { plugins: [ vue(), createHtmlPlugin({ entry: 'src/main.js', template: 'index.html', inject: { data: { base: env.base } } }) ], publicDir: 'static', resolve: { alias: { '@': resolve(__dirname, '../src'), '~@': resolve(__dirname, '../src') }, extensions: ['.mjs', '.js', '.ts', '.vue'] } } export default config
typescript
// dev.config.js import { defineConfig } from 'vite' import baseConfig, { env } from './config' const PROXY_TARGET = 'https://abc.com' export default defineConfig({ ...baseConfig, base: env.base, define: { 'process.env': { NODE_ENV: 'development', BASE_API: '/app-api' } }, server: { port: 1002, proxy: { '/app-api': { target: PROXY_TARGET, changeOrigin: true, secure: false, headers: { host: new URL(PROXY_TARGET).host, Referer: `${PROXY_TARGET}/app-api/`, Origin: PROXY_TARGET } } } }, })
typescript
// prod.config.ts import { defineConfig } from 'vite' import baseConfig, { env } from './config' export default defineConfig({ ...baseConfig, base: env.base, define: { 'process.env': { NODE_ENV: 'production', BASE_API: '/app-api' } }, esbuild: { drop: ['debugger'] }, build: { outDir: "app", assetsDir: 'static', cssCodeSplit: true, emptyOutDir: true, } })
typescript

不用多说,既然都来搞 vite 升级,肯定都能一眼看懂。

之后修改 pakcage.json 的启动配置:

{ // snip "scripts": { "dev": "vite --config vite-config/dev.config.ts", "start": "npm run dev", "build": "vite --config vite-config/prod.config.ts build", "lint": "eslint --fix --ext .js --ext .vue src/" }, // snip }
json

配置入口文件

主要是修改 vue 创建的方式:

new Vue({ router, store, i18n, render: h => h(App) }).$mount('#app')
javascript

然后修改我们的入口 html 文件:

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>App</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" content="ie=edge,chrome=1"> <link rel="stylesheet" type="text/css" href="/css/reset.css"> </head> <body> <div id="app"></div> <script> // 可以注入属性. window.base = '<%- base %>' </script> </body> </html>
html

至此理论上就可以直接启动了,启动后就可以根据编译的报错一个一个改了,后面就讲一下我碰到的坑。

迁移碰到的坑

静态资源获取

首先 vite 专门有个静态资源目录,如果你用的是我上面的配置,那么静态资源目录就在项目根目录下的 static 目录中。

假设有这样一个文件 static/img/hello.png,如果想要使用,直接使用根路径引用即可:

<img src="/img/hello.png">
html

而不是:

<img src="/static/img/hello.png"> <img src="<context path>/static/img/hello.png"> <img src="<context path>/img/hello.png">
html

上面几种写法均为错误写法!

对于第一种和第二种,不需要加上静态资源目录的名称,对于第三种,不需要加上 <context path>在打生产包时,如果你在配置文件中配置了 base 属性,vite 会自动给你加上!

此外下面的写法会让 vite 的自动添加路径失效:

<div style="background-image: url('/img/hello.png')"></div>
plaintext

这里直接将文件路径写在了 style 中,如果在生产模式下配置了 base,就会导致生产模式无法读取到图片,对于这种写法必须要以 css 的形式写,不能用内联样式!

'require' is not defined

这里 webpack 有两种用途:

  • 导入 nodejs 模块
  • 使用 require.context 动态导入模块或文件

对于前者,我建议赶快把 nodejs 模块全都换掉,别想着适配了,一般这种情况经常会出现在一些加密算法的库里,直接找到一个适合前端的库替换就行了。

而对于后者,就比较麻烦了...


我的项目里是这样的一个操作:

const requireAll = requireContext => requireContext.keys().map(requireContext) const req = require.context('./svg', false, /\.svg$/) requireAll(req)
javascript

上面的代码,会将 svg 目录下的所有文件注册为一个组件,然后放在页面上,然后使用 svg 的 use 来引用:

<svg> <use :xlink:href="iconName"></use> </svg>
html

这里建议直接改造,将图片直接封装成组件使用:

import { defineAsyncComponent } from 'vue' const components = {} { const files = import.meta.glob('./svg/*.svg', { query: 'component' }) for (const filesKey in files) { // remove prefix './svg/' and suffix '.svg' const key = filesKey.substring(6, filesKey.length - 4) components[key] = defineAsyncComponent(files[filesKey]) } } const getIcon = (name) => { const entity = components[name] if (!entity) { console.warn('Icon not found: ' + name) } return entity } export default getIcon
javascript

这里很容易理解,就是 getIcon 方法只需要传入文件的名称就会返回一个异步组件,然后在外部直接使用就可以了。

然后另外一个问题就来了,defineAsyncComponent 是 vue3 的 API (如果报错了,请把你的 vue2 升级到 2 的最后一个版本),那么我使用也得使用 vue3 的写法 (至少我是没找到怎么用 vue2 的写法来渲染这个组件的...):

<template> <div :class="svgClass" aria-hidden="true" > <Icon width="100%" height="100%" /> </div> </template> <script setup> import getIcon from './index' const props = defineProps({ iconClass: { type: String, required: true }, className: { type: String, default: undefined } }) const Icon = getIcon(props.iconClass) const svgClass = props.className ? 'svg-icon ' + props.className : 'svg-icon' </script>
html

vue3 写法 eslint 报错

如果直接用 vue3 setup 写法,eslint 可能会报错,但是仍然能够通过编译并使用,这里也需要一起升级一下 eslint。

安装/更新依赖:

npm i eslint@^8 eslint-plugin-vue@^9 vue-eslint-parser@^9 -D
bash

修改 eslint 配置:

module.exports = { root: true, parser: 'vue-eslint-parser', parserOptions: { sourceType: 'module' }, env: { browser: true, node: true, es6: true }, extends: ['eslint:recommended', 'plugin:vue/recommended'], rules: { // snip } }
javascript

主要是注意 parser 那个配置,别的根据自己的需求修改。

以 base64 导入文件

虽然是个很奇葩的需求,但是还是写一下。这里要求以 base64 格式导入 src (非静态资源目录)下的某个文件,默认情况下 vite 肯定是做不到的,这里要求我们自己定义插件:

// base64Loader.ts import type { Plugin } from 'rollup' import * as fs from 'fs' const base64Loader: Plugin = { name: 'base64-loader', transform(_: any, id: string) { const [path, query] = id.split('?') if (query !== 'base64') return null const data = fs.readFileSync(path) const base64 = data.toString('base64') return `export default '${base64}';` } } export default base64Loader
typescript

这段代码的作用是,如果在导入一个模块时加上了 ?base,则会以 base 格式进行导入。

然后在 vite 配置文件中配置:

// vite.config.ts import base64Loader from './base64Loader' const config: UserConfig = { plugins: [ // snip base64Loader ] // snip }
typescript

因为自己偶尔才会用用 Python 写写脚本,但是每次想写的时候就要查半天的语法...所以在这里记录一下 Python 基础的快速入门的一些东西。

高级

虚拟环境

创建虚拟环境.

python -m venv <project_name>/.venv source <project_name>/.venv/bin/activate
shell

如果你去官网看的话这里是直接将虚拟环境创建在了项目目录中,也就是直接调用了 python -m venv <project_name>,这样也可以, 但是会导致项目根目录会多出很多虚拟环境的配置文件,并且这些文件都是不会进版本控制的,所以在这里创建一个子文件夹 .venv 来管理会更好!

运行完后命令左边会出现<env_name>

(my_env) [root@192.168.0.1 my_env]#
shell

在这种状况下,所有 Python 环境均与外界隔离,包括 pip 的版本。

退出虚拟环境

使用下面的指令退出虚拟环境:

deactivate
shell

分发虚拟环境

由于 .venv 目录一般不会进入版本控制系统,所以如果想要想生产,则需要所有需要的依赖。

使用下面的指令导出/导入依赖:

# 导出 pip freeze >requirements.txt # 导入 pip install -r requirements.txt
bash

更新 pip

python3 -m pip install --upgrade pip
python

类型定义

定义主函数

def main(): print("hello") if __name__ == "__main__": main()
python

函数类型定义

from typing import Mapping def hello(val: str, map: Mapping[str, str]) -> str: pass
python

枚举

from enum import Enum class Server(Enum): SERVER_1 = ('192.168.0.1', 'root', '123456') SERVER_2 = ('192.168.0.2', 'root', '123456') def __init__(self, host: str, user: str, password: str): self.host = host self.user = user self.password = password printf(Server.SERVER_1.host)
python

鸭子类型

from typing import Protocol from typing import List # 任何拥有 close 方法的实例都可以被推进去 closeable_list: List[Closeable] = [] class Closeable(Protocol): def close(self) -> None: pass
python

常用库

远程 ssh

import paramiko ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect('192.168.0.1', username='root', password='123456') # 这里需要看情况决定是否执行 source /etc/profile,这里不会主动加载环境变量,在外面传 env 参数也没用... _, stdout, _ = ssh.exec_command('source /etc/profile; env') print(''.join(stdout.readlines()))
python

Jenkins API

使用前要给用户生成一个 token。

from jenkinsapi.jenkins import Jenkins JOB_NAME = 'xxx' SERVER = 'http://192.168.0.1:8080' jenkins = Jenkins('http://192.168.0.1:8080', 'user', 'token') params = { 'Branch': 'master' } jenkins.build_job(JOB_NAME, params) job = jenkins[JOB_NAME] qi = job.invoke(build_params=params) if qi.is_queued() or qi.is_running(): print('等待任务构建完成...') qi.block_until_complete() build = qi.get_build() if not build.is_good(): raise RuntimeError(f'Build failed, check {server}/job/{JOB_NAME}/{build.buildno}/pipeline-graph/ for more details.')
python

工具类

压缩文件夹

import zipfile import os import sys def zip_dir(directory_path: str, output_path=None): # Get the base name of the directory to include in the zip base_name = os.path.basename(directory_path) # Create a zip file at the specified output path with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zipf: # Walk through the directory and add each file or directory to the zip file for root, dirs, files in os.walk(directory_path): # Create an archive name with the top-level directory included arcname_root = os.path.join(base_name, os.path.relpath(root, start=directory_path)) # Add directory entries if not files and not dirs: # Handle the case of empty directories zipf.write(root, arcname=arcname_root + '/') for file in files: # Get the full path of the file full_path = os.path.join(root, file) # Create the relative path for the file in the zip arcname = os.path.join(arcname_root, file) zipf.write(full_path, arcname)
python

Y/N 输入确认

def input_bool(text: str, default: bool = False): tip: str exp: str if default: tip = '(Y/n)' else: tip = '(y/N)' out = input(f'{text} {tip}') if out == '': return default elif out == 'Y' or out == 'y': return True elif out == 'N' or out == 'n': return False else: return input_bool(text, default)
python

因为踩了很多坑,所以记录一下这个项目是怎么搭的。

最后用了这些东西:

  • Typescript
  • Webpack(打生产的包)
  • Eslint

因为是nodejs项目,所以这边我还研究了很久怎么在 ts 文件上打断点进行debug,在开发环境下是不会用到 webpack 的。

nodejs版本:18。

1. 初始化项目

npm init创建一个package.json文件,然后安装所需的依赖:

yarn add eslint @typescript-eslint/parser --dev yarn add typescript ts-loader ts-node tsconfig-paths --dev yarn add webpack webpack-cli source-map-support --dev
shell

之后在package.json里添加"type": "module"的属性,这样就可以直接在项目里直接使用ESModules了,这个东西在webpack里天生支持按需导入,不需要额外配置(要了解更多的话可以去搜Tree Sharking)。

2. 配置eslint

这步比较简单,就直接过了,基本没有什么坑。

// .eslintrc.cjs module.exports = { // 下面这行必须加 parser: '@typescript-eslint/parser', rules: { // 这些是我常用的一些规则 quotes: ["error", 'single'], 'key-spacing': ["error", { "beforeColon": false }], semi: [2, 'never'], 'block-spacing': 'error', 'object-curly-spacing': ["error", "always"], indent: ['error', 2] }, // 这里也要加,不然用import会报错 parserOptions: { "ecmaVersion": 7, "sourceType": "module" } };
javascript

3. 配置Typescript

这里的配置都是可以直接用的,碰到的坑在后面说。

// tsconfig.json { "include": ["src/**/*.ts"], "compilerOptions": { "module": "esnext", "lib": ["ES2022"], "isolatedModules": true, "esModuleInterop": true, "moduleResolution": "Bundler", "resolveJsonModule": true, "target": "ESNext", "strict": true, "allowJs": true, "forceConsistentCasingInFileNames": true, "baseUrl": ".", "sourceMap": true, "outDir": "dist/dev", "paths": { // 记住这个别名,待会要考 "~/*": ["./src/*"] } }, "ts-node": { "experimentalSpecifierResolution": "node", "esm": true, "transpileOnly": true } }
json

后缀问题

这里碰到的第一个坑,就是后缀问题。

这个问题只有在 nodejs + esm 才会有,什么意思呢,来看下面的代码:

// ------------------------------------------ // util.ts export default "hello world" // ------------------------------------------ // index.ts import util from './util' console.log(util)
javascript

看上去没有什么问题,然后我们用ts-node执行一下:

throw new ERR_MODULE_NOT_FOUND( ^ CustomError: Cannot find module 'xx\src\util' imported from xx\src\index.ts
shell

如果这个时候你去网上搜,你基本碰到的回答都是让你加上js后缀:

// index.ts import util from './util.js' console.log(util)
javascript

首先不说这个丑的一批,而且我后面还发现这玩意还会导致另外一个bug:在用webpack打包的时候,如果你加了js后缀,webpack会直接提醒你找不到xx/src/util.js,坑爹呢这不是!

所以肯定是不能加后缀的,然后我也是在网上翻了好久,才找到这个参数:experimentalSpecifierResolution,虽然前面带了个experimental,但其实已经很稳定了,直接在tsconfig.json中添加配置:

{ // ... "ts-node": { // 把值改为node "experimentalSpecifierResolution": "node", // 这个忘了当时为啥要加了,不加好像也不会报错 "esm": true } }
json

或者使用命令行参数:--experimental-specifier-resolution=node

加完之后,不带文件后缀也可以成功运行,webpack打包也不会有任何影响。

别名问题

可以看到我开头提供的tsconfig.json里面有个这样的配置:

{ "paths": { // 记住这个别名,待会要考 "~/*": ["./src/*"] } }
json

例如我们有这样的目录结构:

src ├── util │   └── StringUtils.ts └── index.ts
text

我们在index.ts里面导入StringUtils就可以这样写:

import StringUtils from '~/util/StringUtils'
typescript

这个功能其实可有可无,但是我就是有强迫症,就是不想用相对路径!

首先啥都不加,直接ts-node运行,居然还报错了:

throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base)); ^ CustomError: Cannot find package '~' imported from 'xxx/src/index.ts'
shell

好好好,这都能报错,去查了一下,才知道这玩意是给webpack那些玩意提供声明的:

// webpack.config.cjs module.exports = { resolve: { extensions: ['.tsx', '.ts', '.js'], alias: { '~': path.resolve(__dirname, 'src') } }, }
javascript

在这里,你只写webpack的别名配置,在 ts 里是会报错的,因为ts才不会管你webpack的配置,所以才需要我们的tsconfig.json来提供一个声明。

行,报错了我就去搜,几下就搜到了,不就是加个tsconfig-paths吗,加上命令行参数:-r tsconfig-paths/register,开跑!

结果万万没想到,又爆了相同的错。。。。

然后又翻了很久的Github(真的很久),终于被我找到了:ESM loader that handles TypeScript path mapping

复制里面的loader.js,然后修改启动命令为:node --loader ./scripts/loader.js src/index.ts,就可以正常使用别名了。

使用WebStorm调试代码

因为我们是nodejs项目,肯定是不能少了打断点调试的。

我们可以直接用tsc编译项目为js代码后,直接用Webstorm进行Debug。

因为Webstorm运行js文件基本不需要配置,直接右键点几下就跑起来了。

但是这样很傻批,我们还要分两步进行,而且 ts 文件的变动可能会导致我们在 js 文件上打的断点消失。


谢天谢地,Webstorm是真的很聪明(牛逼),我们只需要简单配置几下就可以直接在 ts 上打断点运行了。

run configuration

配完后,直接在ts文件上断点就可以停住。

4. 配置Webpack

至于为什么要用 Webpack,是因为我最后不想带着node_modules这个累赘来上生产,最后直接打包成一个文件多爽,直接node xxx.js就跑起来了。

这里是我最后用到的配置:

// webpack.config.cjs const path = require('path'); const webpack = require('webpack') module.exports = function (env, args) { return { entry: './src/index.js', target: 'node', module: { rules: [ { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/, }, ], }, resolve: { extensions: ['.tsx', '.ts', '.js'], alias: { '~': path.resolve(__dirname, 'src') } }, output: { filename: (pathData) => { return pathData.chunk.name === 'main' ? 'main.cjs' : 'libs.cjs'; }, }, plugins: [ new webpack.SourceMapDevToolPlugin({ exclude: ['libs.cjs'] }) ], optimization: { splitChunks: { chunks: 'all' }, }, }; }
javascript

注意文件后缀是cjs,不然用module.exports会报错。

打包时直接用webpack --mode=production就可以了。

配置sourcemap

在Webpack打包后,我们的所有代码都被压缩到一行了,而且变量名都变得六亲不认了,想象一下,假如运行过程中报一个错,你能定位到问题发生在哪吗。。。

所以这个时候我们需要用到 sourcemap 来对我们的代码进行索引。

直接使用 sourcemap 文件是不行的,因为这玩意是给浏览器用到,我们需要导入依赖 source-map-support 来加载sourcemap。

这里你可以把 sourcemap 分离成一个单独的文件,也可以让它内嵌到代码里面。

这里我推荐内嵌到代码里面,便于后面代码分发,没必要分出来。

在 Webpack 添加配置devtool: 'inline-source-map'

还没完,也要在tsconfig.json里面添加"sourceMap": true的配置,如果少了这一步,最终生成的 sourcemap 行数会对不上,因为这个时候 Webpack 只会对编译后的 js 文件来构建索引,而 ts 编译后的文件中,空行(一行什么内容都没有的)会被删除,因此导致行数对不上。

最后在代码入口添加加载的代码:

import sourceMapSupport from 'source-map-support' sourceMapSupport.install()
typescript

进一步压缩

如果你观察生成的文件,会发现生成 sourcemap 会导致文件变得非常大,基本会变大 5 ~ 6 倍左右。

如果你把 sourcemap 分成单独的文件,然后打开开一下,会发现 Webpack 也给 node_modules 里面的代码生成了 sourcemap!

作为一个强迫症患者,我是绝对不能忍受这种情况的!

我们肯定是想给自己的代码生成精准的 sourcemap,而第三方库,可以考虑不生成,或者只使用简单的 sourcemap。

翻了一下 Webpack 文档,发现有个 SourceMapDevToolPlugin 插件可以指定/排除为哪些模块生成 sourcemap。

试了一下,在exclude属性里面不管怎么填,都无法忽略掉node_modules

在查了一下午的文档以及翻看了源码之后,终于知到怎么配了:

// webpack.config.cjs module.exports = { output: { filename: (pathData) => { return pathData.chunk.name === 'main' ? 'main.cjs' : 'libs.cjs'; }, }, plugins: [ new webpack.SourceMapDevToolPlugin({ exclude: ['libs.cjs'] }) ], optimization: { splitChunks: { chunks: 'all' }, }, }
javascript

加上上面的配置,就可以做到把node_modules里面的代码全部打到libs.cjs中,而我们的业务代码全部打到main.cjs中,同时配置我们的SourceMapDevToolPlugin不为libs.cjs生成 sourcemap。

DLC:node20版本

之前导入模块我们为了省略后缀,在配置中添加了experimentalSpecifierResolution: node参数,在node20上,这个参数仍然可用,但是已经有了更好的替代。

文档:Loaders

并且官方也给了一个样例来代替上面的启动参数:commonjs-extension-resolution-loader

这里直接摆上我用的代码:

// extension-loader.js /** * 处理ts-node导入时必须加后缀 */ // https://github.com/nodejs/loaders-test/blob/main/commonjs-extension-resolution-loader/loader.js import { isBuiltin } from 'node:module' import { dirname } from 'node:path' import { cwd } from 'node:process' import { fileURLToPath, pathToFileURL } from 'node:url' import { promisify } from 'node:util' import resolveCallback from 'resolve/async.js' const resolveAsync = promisify(resolveCallback) const baseURL = pathToFileURL(cwd() + '/').href export async function resolve(specifier, context, next) { const { parentURL = baseURL } = context if (isBuiltin(specifier)) { return next(specifier, context) } // `resolveAsync` works with paths, not URLs if (specifier.startsWith('file://')) { specifier = fileURLToPath(specifier) } const parentPath = fileURLToPath(parentURL) let url try { const resolution = await resolveAsync(specifier, { basedir: dirname(parentPath), // For whatever reason, --experimental-specifier-resolution=node doesn't search for .mjs extensions // but it does search for index.mjs files within directories extensions: ['.js', '.json', '.node', '.mjs', '.ts'], }) url = pathToFileURL(resolution).href } catch (error) { if (error.code === 'MODULE_NOT_FOUND') { // Match Node's error code error.code = 'ERR_MODULE_NOT_FOUND' } throw error } return next(url, context) }
javascript
// path-loader.js /** * 处理ts路径别名报错 */ import { resolve as resolveTs } from 'ts-node/esm' import * as tsConfigPaths from 'tsconfig-paths' import { pathToFileURL } from 'url' const { absoluteBaseUrl, paths } = tsConfigPaths.loadConfig() const matchPath = tsConfigPaths.createMatchPath(absoluteBaseUrl, paths) export async function resolve (specifier, ctx, defaultResolve) { const match = matchPath(specifier) let realPath if (match) { realPath = pathToFileURL(`${match}`).href } else { realPath = specifier } const r = await defaultResolve(realPath, ctx) return resolveTs(r.url, ctx, defaultResolve) } export { load, transformSource } from 'ts-node/esm'
javascript
// register-hooks.js import { register } from 'node:module' register('./extension-loader.js', import.meta.url) register('./path-loader.js', import.meta.url)
javascript

然后把我们的启动命令换成:node --import register-hooks.js src/index.ts

移除掉tsconfig.json里的experimentalSpecifierResolution,然后就可以正常启动了。

最近突然碰到有关用户授权的问题了,发现自己真的是一点都不会,所有写篇博客记一下常见指令以及碰到过的坑。

1. 常用指令

1.1 用户

  • 新增用户:useradd [username]
  • 设置用户密码:passwd [username]
  • 删除用户:userdel [username]
  • 将用户添加到用户组(a为追加):usermod -aG [groupname] [username]

1.2 用户组

  • 新建用户组:groupadd [groupname]

1.3 授权

1.3.1 chown

chown用于修改文件/文件夹的所属权以及其所属的用户组。

修改文件/文件夹所属用户
chown [username] [directory/file] # 递归授权 chown -R [username] [directory]
shell
修改文件/文件夹所属用户组
chown :[usergroup] [directory/file] # 递归授权 chown -R :[usergroup] [directory]
shell
修改文件/文件夹所属用户和用户组
chown [username]:[usergroup] [directory/file] # 递归授权 chown -R [username]:[usergroup] [directory]
shell

1.3.2 chmod

chmod用于细化对所有者,用户组,以及其它用户的相关权限。

一共用三种类型的用户,它们的名字以及符号为:

  • 所有者:u
  • 用户组:g
  • 其它:o

例如使用ll输出的信息:

drwx--x--x 2 username groupname83 11月 13 15:28 folder
shell

第一部分开头的字符表示的是文件的类型:

  • 文件夹:d
  • 文件: - (一个横杠,不管是可执行文件还是普通文件)

后面连续9个字符,每3个为一组,分别表示用户所有者、用户组、其它用户的权限。 例如上面的例子:

  • 所有者:可读(r)、可写(w)、可执行(x)
  • 用户组:可执行(x)
  • 其它:可执行(x)
修改用户权限

例如要给用户组(u)添加读和写的权限:

chmod g+rw [directory/file] chmod -R g+rw [directory]
shell

移除权限:

chmod g-rw [directory/file]
shell

很简单,也就是中间有个加减符号,其实还有个等号,这个就类似于替换了,会覆盖掉之前的权限。

另外,给所有用户授权用a就行了:

chmod a+rw [directory/file]
shell

其实也可以用数字替代rwx,但是这样有点不便于记忆。。

2. 应用

2.1 文件夹授权了却打不开?

提问:是不是只要某个用户有一个文件夹的读权限,就能打开文件夹了?

大部分可能都会是这样认为的,这也确实比较符合我们的认知,都能读了,还不能打开文件夹?

那么实际呢?我们来试一下。

我们用测试用户(testuser)来进行测试:

[testuser@localhost opt]# ll drwx---r-- 2 root root 36 11月 13 16:44 backup
shell

这里我们可以看到,testuser拥有backup文件夹的读权限,尝试进入一下:

[testuser@localhost opt]$ cd backup bash: cd: backup: 权限不够
shell

发现权限是不够的...

这里也不卖关子了,这里其实是需要执行权限才能进入文件夹,这里也是比较容易忽略的一点。

2.2 可执行文件授权了却打不开?

提问:是不是某个用户只要有一个文件的可执行权限,就可以直接执行文件了?

这里就不卖关子了,答案是不一定。

同样这也是一个非常容易被忽略的问题,以为只要给了可执行权限就能执行了。

来演示一下反例:

drwx------ 2 bim root 36 11月 13 16:44 backup
shell

这里我们的backup文件夹里面放有可执行文件,但是这个文件夹没有对外授权。

[root@localhost backup]# ll 总用量 23796 -rwx-----x 1 bim root 12691576 11月 13 16:44 mysql [root@localhost backup]# pwd /opt/backup
shell

在里面的可执行文件mysql,对其它人拥有可执行权限,我们来切换用户试一下:

[root@localhost backup]# su testuser [testuser@localhost backup]$ /opt/backup/mysql bash: /opt/backup/mysql: 权限不够
shell

可以发现权限不够,即使你拥有可执行权限。

解决方法在前面也说了,只需要给可执行文件的父目录,也就是这里的backup目录授权可执行就行了,这里就不多演示了。