[{"data":1,"prerenderedAt":23056},["ShallowReactive",2],{"\u002Ftutorials\u002Flinux":3,"tutorials-search\u002Ftutorials\u002Flinux":22467},{"id":4,"title":5,"body":6,"category":22456,"cover":22457,"date":22458,"description":22459,"extension":22460,"meta":22461,"navigation":3739,"path":22462,"seo":22463,"sitemap":22464,"stem":22465,"__hash__":22466},"tutorials\u002Ftutorials\u002Flinux.md","Linux 常用命令手册：按场景查的命令清单",{"type":7,"value":8,"toc":22289},"minimark",[9,14,23,45,50,176,195,199,205,210,235,250,255,287,298,303,330,334,339,365,370,385,390,409,425,429,432,535,540,557,562,604,617,621,624,776,806,809,846,850,860,866,870,875,880,898,901,935,1128,1133,1148,1153,1198,1203,1238,1247,1251,1256,1279,1289,1293,1298,1342,1354,1358,1363,1404,1409,1424,1428,1433,1465,1471,1475,1480,1512,1521,1525,1528,1533,1552,1557,1577,1582,1603,1613,1617,1622,1653,1658,1693,1697,1710,1715,1757,1762,1784,1789,1859,1863,1866,1961,1966,2003,2021,2025,2092,2095,2219,2232,2235,2295,2299,2310,2314,2328,2333,2348,2353,2380,2385,2403,2417,2422,2458,2462,2467,2503,2508,2528,2532,2537,2557,2562,2596,2606,2611,2635,2649,2653,2662,2667,2681,2842,2847,2900,2904,2909,2928,2933,2937,2942,2992,2997,3039,3049,3052,3095,3099,3114,3118,3131,3136,3165,3170,3199,3210,3379,3383,3386,3472,3478,3482,3485,3595,3599,3640,3644,3661,3695,3699,3712,3764,3781,3784,3843,3847,3856,3861,3865,3940,3984,3997,4124,4128,4133,4165,4170,4228,4238,4243,4286,4311,4315,4320,4383,4388,4435,4438,4471,4475,4491,4584,4589,4620,4640,4644,4761,4766,4837,4842,4854,4894,4897,4906,4938,4942,5048,5052,5055,5130,5153,5157,5331,5356,5359,5416,5420,5459,5463,5467,5486,5491,5543,5548,5617,5622,5754,5761,5765,5770,5807,5812,5917,5922,5947,5955,6068,6090,6095,6146,6150,6155,6204,6238,6242,6247,6346,6361,6365,6499,6502,6566,6570,6606,6611,6615,6623,6628,6746,6751,6811,6817,6932,6937,7017,7021,7029,7034,7099,7125,7130,7199,7204,7262,7294,7298,7322,7327,7391,7396,7426,7431,7514,7569,7573,7738,7742,7752,7854,7864,7899,7902,7980,7984,8005,8009,8013,8027,8148,8172,8176,8181,8209,8214,8238,8258,8262,8414,8443,8447,8593,8601,8605,8706,8733,8737,8826,8847,8850,8927,8931,8973,8977,9166,9170,9391,9395,9486,9503,9507,9612,9624,9628,9747,9758,9761,9830,9834,9855,9860,9864,9869,9968,9974,10158,10163,10285,10289,10360,10377,10381,10386,10506,10511,10556,10561,10701,10734,10738,10931,10975,10990,10995,11070,11074,11145,11149,11205,11222,11244,11247,11316,11320,11330,11335,11339,11596,11616,11620,11626,11731,11979,12000,12004,12189,12227,12244,12248,12298,12301,12331,12426,12429,12485,12518,12522,12622,12636,12639,12691,12695,12705,12709,12804,12808,12820,12874,12967,12971,13108,13135,13144,13148,13218,13222,13233,13242,13391,13399,13408,13540,13563,13582,13585,13651,13655,13671,13675,13679,13705,13836,13867,13871,13975,14018,14024,14157,14177,14181,14351,14363,14367,14631,14634,14656,14704,14708,14905,14914,14994,15009,15036,15093,15122,15126,15211,15241,15256,15278,15288,15291,15384,15388,15397,15401,15405,15450,15488,15492,15655,15681,15685,15769,15773,15789,15914,15929,15933,15971,16098,16102,16237,16243,16258,16384,16425,16429,16535,16556,16560,16566,16652,16677,16681,16729,16747,16793,16807,16810,16859,16863,16866,16870,16874,17065,17069,17153,17157,17171,17441,17445,17577,17581,17736,17755,17759,17835,17969,18000,18004,18105,18109,18115,18238,18258,18261,18326,18330,18339,18343,18434,18452,18484,18488,18589,18592,18676,18680,18777,18820,18824,18969,19002,19006,19150,19153,19342,19346,19383,19512,19537,19544,19663,19707,19710,19925,19929,19998,20172,20191,20194,20265,20269,20272,20276,20280,20292,20312,20419,20454,20458,20478,20556,20601,20605,20765,20791,20795,20882,20906,20924,20928,21021,21042,21046,21183,21207,21211,21342,21392,21396,21747,21751,22269,22272,22285],[10,11,13],"h2",{"id":12},"一开始敲命令之前终端与帮助系统","一、开始敲命令之前：终端与帮助系统",[15,16,17,18,22],"p",{},"在 Linux 中，学会「查命令」比背命令更重要。打开终端准备敲命令之前，有几个「不查不好意思问」的基础问题：眼前这行提示符是什么意思、忘了参数去哪查、为什么同一个 ",[19,20,21],"code",{},"sh"," 在不同机器上行为不一样。这一章把这几件事说清楚，后面 16 章就能直接跳着查。",[15,24,25,26,30,31,34,35,34,38,34,41,44],{},"本手册的定位是",[27,28,29],"strong",{},"速查","：每个命令一小节，先给最常见的可直接复制的写法，再解释参数，最后补一句注意事项。遇到破坏性命令（",[19,32,33],{},"rm -rf","、",[19,36,37],{},"mkfs",[19,39,40],{},"dd",[19,42,43],{},"kill -9","）会单独标出风险。",[46,47,49],"h3",{"id":48},"_1-先看提示符你是谁在哪什么身份","1. 先看提示符：你是谁、在哪、什么身份",[51,52,53,69],"table",{},[54,55,56],"thead",{},[57,58,59,63,66],"tr",{},[60,61,62],"th",{},"观察点",[60,64,65],{},"长这样",[60,67,68],{},"含义",[70,71,72,101,121,138,155],"tbody",{},[57,73,74,78,83],{},[75,76,77],"td",{},"普通用户提示符",[75,79,80],{},[19,81,82],{},"deploy@server:~$",[75,84,85,86,89,90,93,94,34,97,100],{},"用户 ",[19,87,88],{},"deploy","、主机 ",[19,91,92],{},"server","、当前目录 ",[19,95,96],{},"~",[19,98,99],{},"$"," 表示非 root",[57,102,103,106,111],{},[75,104,105],{},"root 提示符",[75,107,108],{},[19,109,110],{},"root@server:\u002Fsrv\u002Fapp#",[75,112,113,114,117,118],{},"结尾是 ",[19,115,116],{},"#","，此刻敲的命令不再需要 ",[19,119,120],{},"sudo",[57,122,123,129,132],{},[75,124,125,126],{},"只有 ",[19,127,128],{},"bash-4.4$",[75,130,131],{},"容器里常见",[75,133,134,137],{},[19,135,136],{},"$PS1"," 没配置，通常是精简镜像，功能可能缺",[57,139,140,143,148],{},[75,141,142],{},"续行提示符",[75,144,145],{},[19,146,147],{},">",[75,149,150,151,154],{},"引号或括号没闭合，",[19,152,153],{},"Ctrl-C"," 放弃当前输入",[57,156,157,160,166],{},[75,158,159],{},"光标处",[75,161,162,165],{},[19,163,164],{},"^","（vi 里）",[75,167,168,169,171,172,175],{},"光标在 ",[19,170,164],{}," 那个字符",[27,173,174],{},"上","，不在它后面",[15,177,178,179,89,181,183,184,187,188,191,192,194],{},"本手册示例统一假设：用户 ",[19,180,88],{},[19,182,92],{},"、工作目录 ",[19,185,186],{},"\u002Fsrv\u002Fapp"," 或 ",[19,189,190],{},"\u002Fhome\u002Fdeploy","、Shell 是 bash。行首的 ",[19,193,99],{}," 是提示符的一部分，复制到终端时不要把它带上。",[46,196,198],{"id":197},"_2-man-查看命令手册页","2. man —— 查看命令手册页",[15,200,201,204],{},[19,202,203],{},"man"," 是 manual（手册）的缩写，是 Linux 最权威的查命令方式。",[15,206,207],{},[27,208,209],{},"示例 1：查看 ls 的手册页",[211,212,217],"pre",{"className":213,"code":214,"language":215,"meta":216,"style":216},"language-bash shiki shiki-themes github-dark","man ls        # 打开 ls 的手册页\n","bash","",[19,218,219],{"__ignoreMap":216},[220,221,224,227,231],"span",{"class":222,"line":223},"line",1,[220,225,203],{"class":226},"svObZ",[220,228,230],{"class":229},"sU2Wk"," ls",[220,232,234],{"class":233},"sAwPA","        # 打开 ls 的手册页\n",[15,236,237,238,241,242,245,246,249],{},"打开后：",[19,239,240],{},"q"," 退出、",[19,243,244],{},"\u002F关键词"," 搜索、",[19,247,248],{},"n"," 跳到下一个匹配、空格翻页。",[15,251,252],{},[27,253,254],{},"示例 2：指定章节编号",[211,256,258],{"className":213,"code":257,"language":215,"meta":216,"style":216},"man 1 printf  # 1=用户命令，5=文件格式，8=管理命令\nman -k printf # 按关键词反查相关命令（等价于 apropos printf）\n",[19,259,260,274],{"__ignoreMap":216},[220,261,262,264,268,271],{"class":222,"line":223},[220,263,203],{"class":226},[220,265,267],{"class":266},"sDLfK"," 1",[220,269,270],{"class":229}," printf",[220,272,273],{"class":233},"  # 1=用户命令，5=文件格式，8=管理命令\n",[220,275,277,279,282,284],{"class":222,"line":276},2,[220,278,203],{"class":226},[220,280,281],{"class":266}," -k",[220,283,270],{"class":229},[220,285,286],{"class":233}," # 按关键词反查相关命令（等价于 apropos printf）\n",[15,288,289,290,293,294,297],{},"同一个名字在不同章节可能有多个手册页（比如 ",[19,291,292],{},"printf"," 既是命令又是 C 函数），",[19,295,296],{},"man -a printf"," 可以一次看完所有同名章节。",[15,299,300],{},[27,301,302],{},"示例 3：只要一行说明",[211,304,306],{"className":213,"code":305,"language":215,"meta":216,"style":216},"whatis grep   # 只输出一行描述：grep pattern lines matched\napropos firewall  # 反过来：按关键词找命令名，能列出 rsync、dump 之类\n",[19,307,308,319],{"__ignoreMap":216},[220,309,310,313,316],{"class":222,"line":223},[220,311,312],{"class":226},"whatis",[220,314,315],{"class":229}," grep",[220,317,318],{"class":233},"   # 只输出一行描述：grep pattern lines matched\n",[220,320,321,324,327],{"class":222,"line":276},[220,322,323],{"class":226},"apropos",[220,325,326],{"class":229}," firewall",[220,328,329],{"class":233},"  # 反过来：按关键词找命令名，能列出 rsync、dump 之类\n",[46,331,333],{"id":332},"_3-help-与-tldr-更快的两种查法","3. --help 与 tldr —— 更快的两种查法",[15,335,336],{},[27,337,338],{},"示例 1：选项速查",[211,340,342],{"className":213,"code":341,"language":215,"meta":216,"style":216},"ls --help        # 一屏列出全部选项，日常最常用，够用了\nls --version     # 看命令版本与所属包，GNU 核心工具会写 ls (GNU coreutils) 9.x\n",[19,343,344,355],{"__ignoreMap":216},[220,345,346,349,352],{"class":222,"line":223},[220,347,348],{"class":226},"ls",[220,350,351],{"class":266}," --help",[220,353,354],{"class":233},"        # 一屏列出全部选项，日常最常用，够用了\n",[220,356,357,359,362],{"class":222,"line":276},[220,358,348],{"class":226},[220,360,361],{"class":266}," --version",[220,363,364],{"class":233},"     # 看命令版本与所属包，GNU 核心工具会写 ls (GNU coreutils) 9.x\n",[15,366,367],{},[27,368,369],{},"示例 2：只看真实示例",[211,371,373],{"className":213,"code":372,"language":215,"meta":216,"style":216},"tldr ls          # 需要装 tldr 包；只给真实用法示例，比 man 快十倍\n",[19,374,375],{"__ignoreMap":216},[220,376,377,380,382],{"class":222,"line":223},[220,378,379],{"class":226},"tldr",[220,381,230],{"class":229},[220,383,384],{"class":233},"          # 需要装 tldr 包；只给真实用法示例，比 man 快十倍\n",[15,386,387],{},[27,388,389],{},"示例 3：超长的 GNU 手册",[211,391,393],{"className":213,"code":392,"language":215,"meta":216,"style":216},"info coreutils 'ls invocation'   # 层级导航：u 上级、n\u002Fp 前后节点\n",[19,394,395],{"__ignoreMap":216},[220,396,397,400,403,406],{"class":222,"line":223},[220,398,399],{"class":226},"info",[220,401,402],{"class":229}," coreutils",[220,404,405],{"class":229}," 'ls invocation'",[220,407,408],{"class":233},"   # 层级导航：u 上级、n\u002Fp 前后节点\n",[15,410,411,412,415,416,418,419,421,422,424],{},"实用结论：查「某个参数是否存在、拼写对不对」用 ",[19,413,414],{},"--help"," 就够了；要看退出码、示例、相关文件用 ",[19,417,203],{},"；",[19,420,414],{}," 只讲参数，不讲「我该用哪个命令」，后者靠 ",[19,423,323],{}," 和搜索引擎。",[46,426,428],{"id":427},"_4-typewhichcommand-这条命令到底从哪来","4. type、which、command —— 这条命令到底从哪来",[15,430,431],{},"Linux 的「命令」其实分三种类型，搞清类型能解释一大半「找不到命令」和「行为不对」：",[51,433,434,453],{},[54,435,436],{},[57,437,438,441,444,450],{},[60,439,440],{},"类型",[60,442,443],{},"例子",[60,445,446,449],{},[19,447,448],{},"type"," 的输出",[60,451,452],{},"说明",[70,454,455,486,513],{},[57,456,457,460,478,483],{},[75,458,459],{},"Shell 内建命令",[75,461,462,465,466,465,469,465,472,465,475],{},[19,463,464],{},"cd"," ",[19,467,468],{},"echo",[19,470,471],{},"export",[19,473,474],{},"source",[19,476,477],{},"alias",[75,479,480],{},[19,481,482],{},"cd is a shell builtin",[75,484,485],{},"由 Shell 进程自己实现，改不了当前目录这类事的只能做成内建",[57,487,488,491,501,506],{},[75,489,490],{},"外部命令（可执行文件）",[75,492,493,465,495,465,498],{},[19,494,348],{},[19,496,497],{},"grep",[19,499,500],{},"systemctl",[75,502,503],{},[19,504,505],{},"grep is \u002Fusr\u002Fbin\u002Fgrep",[75,507,508,509,512],{},"在 ",[19,510,511],{},"PATH"," 里找文件，fork 子进程执行",[57,514,515,518,527,532],{},[75,516,517],{},"函数 \u002F 别名",[75,519,520,521,34,524],{},"自定义 ",[19,522,523],{},"ll()",[19,525,526],{},"alias rm='rm -i'",[75,528,529],{},[19,530,531],{},"ll is a function",[75,533,534],{},"只在当前 Shell 生效，脚本里看不到",[15,536,537],{},[27,538,539],{},"示例 1：排查「为什么我的 ls 带颜色」",[211,541,543],{"className":213,"code":542,"language":215,"meta":216,"style":216},"type -a ls    # 把别名\u002F函数\u002F内建\u002F文件全部来源列出来\n",[19,544,545],{"__ignoreMap":216},[220,546,547,549,552,554],{"class":222,"line":223},[220,548,448],{"class":266},[220,550,551],{"class":266}," -a",[220,553,230],{"class":229},[220,555,556],{"class":233},"    # 把别名\u002F函数\u002F内建\u002F文件全部来源列出来\n",[15,558,559],{},[27,560,561],{},"示例 2：脚本里的标准写法",[211,563,565],{"className":213,"code":564,"language":215,"meta":216,"style":216},"command -v python3   # 只返回可执行路径；if ! command -v python3 >\u002Fdev\u002Fnull; then …; fi\nwhich curl           # 只在 PATH 找文件（外部命令），看不到内建与别名，脚本别用它\nwhereis nginx        # 顺带给出二进制、源码、man 页位置，装完不知道手册在哪时用\n",[19,566,567,581,592],{"__ignoreMap":216},[220,568,569,572,575,578],{"class":222,"line":223},[220,570,571],{"class":266},"command",[220,573,574],{"class":266}," -v",[220,576,577],{"class":229}," python3",[220,579,580],{"class":233},"   # 只返回可执行路径；if ! command -v python3 >\u002Fdev\u002Fnull; then …; fi\n",[220,582,583,586,589],{"class":222,"line":276},[220,584,585],{"class":266},"which",[220,587,588],{"class":229}," curl",[220,590,591],{"class":233},"           # 只在 PATH 找文件（外部命令），看不到内建与别名，脚本别用它\n",[220,593,595,598,601],{"class":222,"line":594},3,[220,596,597],{"class":226},"whereis",[220,599,600],{"class":229}," nginx",[220,602,603],{"class":233},"        # 顺带给出二进制、源码、man 页位置，装完不知道手册在哪时用\n",[15,605,606,608,609,612,613,616],{},[19,607,348],{}," 这类命令既是外部命令又常被别名覆盖，交互式排查首选 ",[19,610,611],{},"type -a","，写脚本判断用 ",[19,614,615],{},"command -v","。",[46,618,620],{"id":619},"_5-发行版差异会体现在哪","5. 发行版差异会体现在哪",[15,622,623],{},"同一句命令在不同发行版上可能失效，差异集中在四个地方：",[51,625,626,642],{},[54,627,628],{},[57,629,630,633,636,639],{},[60,631,632],{},"方面",[60,634,635],{},"Debian\u002FUbuntu",[60,637,638],{},"RHEL\u002FRocky\u002FFedora",[60,640,641],{},"Alpine",[70,643,644,674,699,716,736,761],{},[57,645,646,649,657,669],{},[75,647,648],{},"包管理",[75,650,651,34,654],{},[19,652,653],{},"apt",[19,655,656],{},"dpkg",[75,658,659,662,663,34,666],{},[19,660,661],{},"dnf","\u002F",[19,664,665],{},"yum",[19,667,668],{},"rpm",[75,670,671],{},[19,672,673],{},"apk",[57,675,676,679,682,684],{},[75,677,678],{},"C 库与命令风格",[75,680,681],{},"GNU coreutils",[75,683,681],{},[75,685,686,689,690,34,692,34,695,698],{},[27,687,688],{},"BusyBox","（",[19,691,348],{},[19,693,694],{},"find",[19,696,697],{},"sed"," 参数是精简版）",[57,700,701,705,708,710],{},[75,702,703],{},[19,704,120],{},[75,706,707],{},"默认可用",[75,709,707],{},[75,711,712,713],{},"默认没有，需 ",[19,714,715],{},"apk add sudo",[57,717,718,721,726,730],{},[75,719,720],{},"服务管理",[75,722,723,725],{},[19,724,500],{},"（容器内常缺）",[75,727,728],{},[19,729,500],{},[75,731,732,733],{},"多用 OpenRC 或 ",[19,734,735],{},"supervisord",[57,737,738,741,747,752],{},[75,739,740],{},"防火墙后端",[75,742,743,746],{},[19,744,745],{},"ufw"," 常见",[75,748,749,746],{},[19,750,751],{},"firewalld",[75,753,754,662,757,760],{},[19,755,756],{},"iptables",[19,758,759],{},"nftables"," 裸用",[57,762,763,766,768,770],{},[75,764,765],{},"Shell 默认",[75,767,215],{},[75,769,215],{},[75,771,772,773,775],{},"ash（",[19,774,21],{}," 指向 BusyBox ash）",[15,777,778,781,782,785,786,789,790,793,794,797,798,801,802,805],{},[19,779,780],{},"head -n"," 这类基础参数四处一致；差异大的是 ",[19,783,784],{},"sed -i","（BusyBox 要求 ",[19,787,788],{},"-i ''"," 或干脆不支持同时给后缀）、",[19,791,792],{},"date -d","（BusyBox 只支持少数格式）、",[19,795,796],{},"find -printf","（BusyBox 无）。写跨发行版的脚本时，第一行用 ",[19,799,800],{},"#!\u002Fbin\u002Fsh"," 就别用 bash 语法，用 ",[19,803,804],{},"#!\u002Fbin\u002Fbash"," 就要确认机器上有 bash。",[46,807,808],{"id":808},"踩坑提醒",[810,811,812,830,839],"ul",{},[813,814,815,817,818,820,821,824,825,827,828,616],"li",{},[19,816,203],{}," 页在容器里经常是空的（没装 ",[19,819,203],{}," 和手册包）：",[19,822,823],{},"apt install man-db manpages","，或临时靠 ",[19,826,414],{}," 与 ",[19,829,379],{},[813,831,832,833,34,835,838],{},"帮助文本分「BSD 版」与「GNU 版」两套，macOS 的 ",[19,834,784],{},[19,836,837],{},"date -j"," 与 Linux 不通用，别把网上抄的 macOS 写法放到服务器上。",[813,840,841,843,844,424],{},[19,842,414],{}," 只讲参数，不讲「我该用哪个命令」。后者靠 ",[19,845,323],{},[10,847,849],{"id":848},"二文件与目录lscdcpmvrmln","二、文件与目录：ls、cd、cp、mv、rm、ln",[15,851,852,853,856,857,859],{},"日常一半的终端时间花在「这个目录里有什么」「把这个文件挪到那里」。这一章的命令都写成可以直接复制粘贴的形式，重点在于看清 ",[19,854,855],{},"ls -l"," 的每个字段、理解复制与移动的差别，以及明白 ",[19,858,33],{}," 为什么不能随手敲。",[861,862],"demo-html",{"height":863,"src":864,"title":865},620,"\u002Ftutorials\u002Flinux\u002Fdemos\u002F02-files.html","示例 02：ls 各字段怎么读、目录的进出与搬动",[46,867,869],{"id":868},"_1-ls-列出目录内容","1. ls —— 列出目录内容",[15,871,872,874],{},[19,873,348],{}," 是 Linux 中使用频率最高的命令之一，用于列出目录中的文件。",[15,876,877],{},[27,878,879],{},"示例 1：列出详细信息（最常用）",[211,881,883],{"className":213,"code":882,"language":215,"meta":216,"style":216},"ls -lh \u002Fsrv\u002Fapp    # -l 长格式列出，-h 文件大小以 K\u002FM\u002FG 人读格式显示\n",[19,884,885],{"__ignoreMap":216},[220,886,887,889,892,895],{"class":222,"line":223},[220,888,348],{"class":226},[220,890,891],{"class":266}," -lh",[220,893,894],{"class":229}," \u002Fsrv\u002Fapp",[220,896,897],{"class":233},"    # -l 长格式列出，-h 文件大小以 K\u002FM\u002FG 人读格式显示\n",[15,899,900],{},"输出长这样，十个字符的权限串是本章唯一需要「背」的东西：",[211,902,906],{"className":903,"code":904,"language":905,"meta":216,"style":216},"language-console shiki shiki-themes github-dark","$ ls -l \u002Fsrv\u002Fapp\n-rw-r--r-- 1 deploy deploy  1.2K Oct  4 09:12 app.py\ndrwxr-xr-x 3 deploy deploy  4.0K Oct  4 09:20 static\nlrwxrwxrwx 1 deploy deploy    11 Oct  4 09:21 current -> releases\u002Fv7\n-rwsr-xr-x 1 root   root   144.0K Mar 14  2024 helper\n","console",[19,907,908,913,918,923,929],{"__ignoreMap":216},[220,909,910],{"class":222,"line":223},[220,911,912],{},"$ ls -l \u002Fsrv\u002Fapp\n",[220,914,915],{"class":222,"line":276},[220,916,917],{},"-rw-r--r-- 1 deploy deploy  1.2K Oct  4 09:12 app.py\n",[220,919,920],{"class":222,"line":594},[220,921,922],{},"drwxr-xr-x 3 deploy deploy  4.0K Oct  4 09:20 static\n",[220,924,926],{"class":222,"line":925},4,[220,927,928],{},"lrwxrwxrwx 1 deploy deploy    11 Oct  4 09:21 current -> releases\u002Fv7\n",[220,930,932],{"class":222,"line":931},5,[220,933,934],{},"-rwsr-xr-x 1 root   root   144.0K Mar 14  2024 helper\n",[51,936,937,949],{},[54,938,939],{},[57,940,941,944,947],{},[60,942,943],{},"位置",[60,945,946],{},"内容",[60,948,68],{},[70,950,951,981,997,1010,1022,1040,1063,1088,1107],{},[57,952,953,956,978],{},[75,954,955],{},"第 1 字符",[75,957,958,465,961,465,964,465,967,465,970,465,973,465,976],{},[19,959,960],{},"-",[19,962,963],{},"d",[19,965,966],{},"l",[19,968,969],{},"c",[19,971,972],{},"b",[19,974,975],{},"s",[19,977,15],{},[75,979,980],{},"类型：普通文件 \u002F 目录 \u002F 链接 \u002F 字符设备 \u002F 块设备 \u002F socket \u002F 管道",[57,982,983,986,991],{},[75,984,985],{},"第 2-4",[75,987,988],{},[19,989,990],{},"rwx",[75,992,993,994,996],{},"属主（第三列的 ",[19,995,88],{},"）的权限",[57,998,999,1002,1007],{},[75,1000,1001],{},"第 5-7",[75,1003,1004],{},[19,1005,1006],{},"r--",[75,1008,1009],{},"属组（第四列）的权限",[57,1011,1012,1015,1019],{},[75,1013,1014],{},"第 8-10",[75,1016,1017],{},[19,1018,1006],{},[75,1020,1021],{},"其他人的权限",[57,1023,1024,1027,1034],{},[75,1025,1026],{},"第 2\u002F5\u002F8 位",[75,1028,1029,187,1031],{},[19,1030,975],{},[19,1032,1033],{},"S",[75,1035,1036,1037,1039],{},"setuid（属主位）\u002F setgid（属组位）；大写 ",[19,1038,1033],{}," 表示「有标记但没有对应 x」",[57,1041,1042,1045,1053],{},[75,1043,1044],{},"最后一位",[75,1046,1047,187,1050],{},[19,1048,1049],{},"t",[19,1051,1052],{},"T",[75,1054,1055,1056,689,1059,1062],{},"sticky bit，常见于 ",[19,1057,1058],{},"\u002Ftmp",[19,1060,1061],{},"drwxrwxrwt","）",[57,1064,1065,1068,1077],{},[75,1066,1067],{},"第 2 列",[75,1069,1070,1073,1074],{},[19,1071,1072],{},"1"," \u002F ",[19,1075,1076],{},"3",[75,1078,1079,1080,1083,1084,1087],{},"硬链接数；目录至少 2（",[19,1081,1082],{},"."," 和 ",[19,1085,1086],{},"..","），所以看到 1 说明不是目录",[57,1089,1090,1093,1101],{},[75,1091,1092],{},"第 6 列",[75,1094,1095,1073,1098],{},[19,1096,1097],{},"1.2K",[19,1099,1100],{},"4.0K",[75,1102,1103,1104],{},"文件字节数；目录显示的是它自己的 inode 表大小，",[27,1105,1106],{},"不含内容",[57,1108,1109,1112,1117],{},[75,1110,1111],{},"第 7 列",[75,1113,1114],{},[19,1115,1116],{},"Oct 4 09:12",[75,1118,1119,1120,1123,1124,1127],{},"mtime（内容最后改动时间）；",[19,1121,1122],{},"ls -lu"," 看 atime，",[19,1125,1126],{},"ls -lc"," 看 ctime（元数据变更）",[15,1129,1130],{},[27,1131,1132],{},"示例 2：显示隐藏文件",[211,1134,1136],{"className":213,"code":1135,"language":215,"meta":216,"style":216},"ls -la    # -a 包含以 . 开头的隐藏文件；不加时不显示\n",[19,1137,1138],{"__ignoreMap":216},[220,1139,1140,1142,1145],{"class":222,"line":223},[220,1141,348],{"class":226},[220,1143,1144],{"class":266}," -la",[220,1146,1147],{"class":233},"    # -a 包含以 . 开头的隐藏文件；不加时不显示\n",[15,1149,1150],{},[27,1151,1152],{},"示例 3：按不同方式排序",[211,1154,1156],{"className":213,"code":1155,"language":215,"meta":216,"style":216},"ls -lt           # -t 按修改时间，最新在前\nls -lS           # -S 按大小\nls -lr           # -r 反向排序\nls -lX           # -X 按扩展名\n",[19,1157,1158,1168,1178,1188],{"__ignoreMap":216},[220,1159,1160,1162,1165],{"class":222,"line":223},[220,1161,348],{"class":226},[220,1163,1164],{"class":266}," -lt",[220,1166,1167],{"class":233},"           # -t 按修改时间，最新在前\n",[220,1169,1170,1172,1175],{"class":222,"line":276},[220,1171,348],{"class":226},[220,1173,1174],{"class":266}," -lS",[220,1176,1177],{"class":233},"           # -S 按大小\n",[220,1179,1180,1182,1185],{"class":222,"line":594},[220,1181,348],{"class":226},[220,1183,1184],{"class":266}," -lr",[220,1186,1187],{"class":233},"           # -r 反向排序\n",[220,1189,1190,1192,1195],{"class":222,"line":925},[220,1191,348],{"class":226},[220,1193,1194],{"class":266}," -lX",[220,1196,1197],{"class":233},"           # -X 按扩展名\n",[15,1199,1200],{},[27,1201,1202],{},"示例 4：方便脚本与管道",[211,1204,1206],{"className":213,"code":1205,"language":215,"meta":216,"style":216},"ls -1                              # -1 单列输出，方便接管道\nls -Q                              # -Q 每个名字加引号，含空格的文件名安全\nls --time-style=long-iso           # 日期变成 2026-10-04 09:12，脚本里更好解析\n",[19,1207,1208,1218,1228],{"__ignoreMap":216},[220,1209,1210,1212,1215],{"class":222,"line":223},[220,1211,348],{"class":226},[220,1213,1214],{"class":266}," -1",[220,1216,1217],{"class":233},"                              # -1 单列输出，方便接管道\n",[220,1219,1220,1222,1225],{"class":222,"line":276},[220,1221,348],{"class":226},[220,1223,1224],{"class":266}," -Q",[220,1226,1227],{"class":233},"                              # -Q 每个名字加引号，含空格的文件名安全\n",[220,1229,1230,1232,1235],{"class":222,"line":594},[220,1231,348],{"class":226},[220,1233,1234],{"class":266}," --time-style=long-iso",[220,1236,1237],{"class":233},"           # 日期变成 2026-10-04 09:12，脚本里更好解析\n",[15,1239,1240,1241,1243,1244,616],{},"注意：",[19,1242,348],{}," 无参数时隐藏文件不显示；目录显示的大小不含内容，看总占用要用 ",[19,1245,1246],{},"du -sh",[46,1248,1250],{"id":1249},"_2-pwd-打印当前目录","2. pwd —— 打印当前目录",[15,1252,1253],{},[27,1254,1255],{},"示例 1：我在哪",[211,1257,1259],{"className":213,"code":1258,"language":215,"meta":216,"style":216},"pwd         # 打印当前工作目录的绝对路径\npwd -P      # -P 解析掉所有符号链接，给出真实路径\n",[19,1260,1261,1269],{"__ignoreMap":216},[220,1262,1263,1266],{"class":222,"line":223},[220,1264,1265],{"class":266},"pwd",[220,1267,1268],{"class":233},"         # 打印当前工作目录的绝对路径\n",[220,1270,1271,1273,1276],{"class":222,"line":276},[220,1272,1265],{"class":266},[220,1274,1275],{"class":266}," -P",[220,1277,1278],{"class":233},"      # -P 解析掉所有符号链接，给出真实路径\n",[15,1280,1281,1282,1284,1285,1288],{},"Shell 有个同名内建的 ",[19,1283,1265],{},"，",[19,1286,1287],{},"pwd -P"," 走的是外部命令版本。",[46,1290,1292],{"id":1291},"_3-cd-切换目录","3. cd —— 切换目录",[15,1294,1295],{},[27,1296,1297],{},"示例 1：几种常用跳法",[211,1299,1301],{"className":213,"code":1300,"language":215,"meta":216,"style":216},"cd \u002Fsrv\u002Fapp    # 绝对路径\ncd ~           # 回用户主目录（cd 不带参数效果相同）\ncd ..          # 上一层\ncd -           # 回到上一个待过的目录\n",[19,1302,1303,1312,1322,1332],{"__ignoreMap":216},[220,1304,1305,1307,1309],{"class":222,"line":223},[220,1306,464],{"class":266},[220,1308,894],{"class":229},[220,1310,1311],{"class":233},"    # 绝对路径\n",[220,1313,1314,1316,1319],{"class":222,"line":276},[220,1315,464],{"class":266},[220,1317,1318],{"class":229}," ~",[220,1320,1321],{"class":233},"           # 回用户主目录（cd 不带参数效果相同）\n",[220,1323,1324,1326,1329],{"class":222,"line":594},[220,1325,464],{"class":266},[220,1327,1328],{"class":229}," ..",[220,1330,1331],{"class":233},"          # 上一层\n",[220,1333,1334,1336,1339],{"class":222,"line":925},[220,1335,464],{"class":266},[220,1337,1338],{"class":229}," -",[220,1340,1341],{"class":233},"           # 回到上一个待过的目录\n",[15,1343,1240,1344,1346,1347,1350,1351,1353],{},[19,1345,464],{}," 是内建命令，只对当前 Shell 生效——在子 shell（比如脚本、",[19,1348,1349],{},"( … )","）里 ",[19,1352,464],{}," 改不了父目录。",[46,1355,1357],{"id":1356},"_4-mkdir-与-rmdir-建目录删空目录","4. mkdir 与 rmdir —— 建目录、删空目录",[15,1359,1360],{},[27,1361,1362],{},"示例 1：创建多级目录",[211,1364,1366],{"className":213,"code":1365,"language":215,"meta":216,"style":216},"mkdir build          # 建单层目录，父目录不存在会失败\nmkdir -p a\u002Fb\u002Fc       # -p 逐级创建，已存在也不报错\nmkdir -p logs\u002F{2026..2028}   # 花括号展开，一次建三年目录\n",[19,1367,1368,1379,1392],{"__ignoreMap":216},[220,1369,1370,1373,1376],{"class":222,"line":223},[220,1371,1372],{"class":226},"mkdir",[220,1374,1375],{"class":229}," build",[220,1377,1378],{"class":233},"          # 建单层目录，父目录不存在会失败\n",[220,1380,1381,1383,1386,1389],{"class":222,"line":276},[220,1382,1372],{"class":226},[220,1384,1385],{"class":266}," -p",[220,1387,1388],{"class":229}," a\u002Fb\u002Fc",[220,1390,1391],{"class":233},"       # -p 逐级创建，已存在也不报错\n",[220,1393,1394,1396,1398,1401],{"class":222,"line":594},[220,1395,1372],{"class":226},[220,1397,1385],{"class":266},[220,1399,1400],{"class":229}," logs\u002F{2026..2028}",[220,1402,1403],{"class":233},"   # 花括号展开，一次建三年目录\n",[15,1405,1406],{},[27,1407,1408],{},"示例 2：删除目录",[211,1410,1412],{"className":213,"code":1411,"language":215,"meta":216,"style":216},"rmdir build          # 只能删空目录，非空就报错——比 rm -r 安全得多\n",[19,1413,1414],{"__ignoreMap":216},[220,1415,1416,1419,1421],{"class":222,"line":223},[220,1417,1418],{"class":226},"rmdir",[220,1420,1375],{"class":229},[220,1422,1423],{"class":233},"          # 只能删空目录，非空就报错——比 rm -r 安全得多\n",[46,1425,1427],{"id":1426},"_5-touch-创建空文件或更新时间戳","5. touch —— 创建空文件或更新时间戳",[15,1429,1430],{},[27,1431,1432],{},"示例 1：两个用途",[211,1434,1436],{"className":213,"code":1435,"language":215,"meta":216,"style":216},"touch app.log                    # 文件不存在就建空的；存在就更新 mtime\ntouch -d \"2026-10-01\" f          # -d 指定时间戳\n",[19,1437,1438,1449],{"__ignoreMap":216},[220,1439,1440,1443,1446],{"class":222,"line":223},[220,1441,1442],{"class":226},"touch",[220,1444,1445],{"class":229}," app.log",[220,1447,1448],{"class":233},"                    # 文件不存在就建空的；存在就更新 mtime\n",[220,1450,1451,1453,1456,1459,1462],{"class":222,"line":276},[220,1452,1442],{"class":226},[220,1454,1455],{"class":266}," -d",[220,1457,1458],{"class":229}," \"2026-10-01\"",[220,1460,1461],{"class":229}," f",[220,1463,1464],{"class":233},"          # -d 指定时间戳\n",[15,1466,1467,1468,1470],{},"部署时常用 ",[19,1469,1442],{},"「戳一下」源文件触发增量构建。",[46,1472,1474],{"id":1473},"_6-tree-树状展示目录","6. tree —— 树状展示目录",[15,1476,1477],{},[27,1478,1479],{},"示例 1：限制深度",[211,1481,1483],{"className":213,"code":1482,"language":215,"meta":216,"style":216},"tree -L 2                  # -L 2 只显示两层，目录巨大时防止刷屏\ntree -aI '__pycache__'     # -a 含隐藏文件，-I 用正则排除\n",[19,1484,1485,1499],{"__ignoreMap":216},[220,1486,1487,1490,1493,1496],{"class":222,"line":223},[220,1488,1489],{"class":226},"tree",[220,1491,1492],{"class":266}," -L",[220,1494,1495],{"class":266}," 2",[220,1497,1498],{"class":233},"                  # -L 2 只显示两层，目录巨大时防止刷屏\n",[220,1500,1501,1503,1506,1509],{"class":222,"line":276},[220,1502,1489],{"class":226},[220,1504,1505],{"class":266}," -aI",[220,1507,1508],{"class":229}," '__pycache__'",[220,1510,1511],{"class":233},"     # -a 含隐藏文件，-I 用正则排除\n",[15,1513,1240,1514,1516,1517,1520],{},[19,1515,1489],{}," 可能未安装（",[19,1518,1519],{},"apt install tree","）。",[46,1522,1524],{"id":1523},"_7-cp-复制","7. cp —— 复制",[15,1526,1527],{},"复制 = 原件保留、再拷贝一份。最常用的就两种写法：",[15,1529,1530],{},[27,1531,1532],{},"示例 1：只复制内容",[211,1534,1536],{"className":213,"code":1535,"language":215,"meta":216,"style":216},"cp app.py \u002Ftmp\u002Fapp.py    # 目标权限按 umask 重算，属主变成执行者；生产备份别用这个\n",[19,1537,1538],{"__ignoreMap":216},[220,1539,1540,1543,1546,1549],{"class":222,"line":223},[220,1541,1542],{"class":226},"cp",[220,1544,1545],{"class":229}," app.py",[220,1547,1548],{"class":229}," \u002Ftmp\u002Fapp.py",[220,1550,1551],{"class":233},"    # 目标权限按 umask 重算，属主变成执行者；生产备份别用这个\n",[15,1553,1554],{},[27,1555,1556],{},"示例 2：完整备份（保留属性）",[211,1558,1560],{"className":213,"code":1559,"language":215,"meta":216,"style":216},"cp -a src\u002F backup\u002F       # -a = -rp --preserve=links,timestamps：递归 + 保权限\u002F时间戳\u002F链接\n",[19,1561,1562],{"__ignoreMap":216},[220,1563,1564,1566,1568,1571,1574],{"class":222,"line":223},[220,1565,1542],{"class":226},[220,1567,551],{"class":266},[220,1569,1570],{"class":229}," src\u002F",[220,1572,1573],{"class":229}," backup\u002F",[220,1575,1576],{"class":233},"       # -a = -rp --preserve=links,timestamps：递归 + 保权限\u002F时间戳\u002F链接\n",[15,1578,1579],{},[27,1580,1581],{},"示例 3：防覆盖",[211,1583,1585],{"className":213,"code":1584,"language":215,"meta":216,"style":216},"cp -i a.txt \u002Ftmp\u002F        # -i 目标已存在时先问；脚本里建议加，或直接先判断存在\n",[19,1586,1587],{"__ignoreMap":216},[220,1588,1589,1591,1594,1597,1600],{"class":222,"line":223},[220,1590,1542],{"class":226},[220,1592,1593],{"class":266}," -i",[220,1595,1596],{"class":229}," a.txt",[220,1598,1599],{"class":229}," \u002Ftmp\u002F",[220,1601,1602],{"class":233},"        # -i 目标已存在时先问；脚本里建议加，或直接先判断存在\n",[15,1604,1605,1606,1608,1609,1612],{},"跨机复制目录不要直接用 ",[19,1607,1542],{},"，用 ",[19,1610,1611],{},"rsync -a src\u002F host:\u002Fsrv\u002Fapp\u002F","（见第十三章），它保留属性且可断点续传。",[46,1614,1616],{"id":1615},"_8-mv-移动与改名","8. mv —— 移动与改名",[15,1618,1619],{},[27,1620,1621],{},"示例 1：改名 \u002F 移动",[211,1623,1625],{"className":213,"code":1624,"language":215,"meta":216,"style":216},"mv a.txt old\u002Fa_2026-10-04.txt   # 同分区改名是改目录项，瞬时完成\nmv big.iso \u002Fmnt\u002Fusb\u002F            # 跨分区是先复制再删，慢且占双份空间\n",[19,1626,1627,1640],{"__ignoreMap":216},[220,1628,1629,1632,1634,1637],{"class":222,"line":223},[220,1630,1631],{"class":226},"mv",[220,1633,1596],{"class":229},[220,1635,1636],{"class":229}," old\u002Fa_2026-10-04.txt",[220,1638,1639],{"class":233},"   # 同分区改名是改目录项，瞬时完成\n",[220,1641,1642,1644,1647,1650],{"class":222,"line":276},[220,1643,1631],{"class":226},[220,1645,1646],{"class":229}," big.iso",[220,1648,1649],{"class":229}," \u002Fmnt\u002Fusb\u002F",[220,1651,1652],{"class":233},"            # 跨分区是先复制再删，慢且占双份空间\n",[15,1654,1655],{},[27,1656,1657],{},"示例 2：控制覆盖行为",[211,1659,1661],{"className":213,"code":1660,"language":215,"meta":216,"style":216},"mv -n a b    # -n 目标存在就不覆盖\nmv -b a b    # -b 覆盖前先把目标备份一份\n",[19,1662,1663,1679],{"__ignoreMap":216},[220,1664,1665,1667,1670,1673,1676],{"class":222,"line":223},[220,1666,1631],{"class":226},[220,1668,1669],{"class":266}," -n",[220,1671,1672],{"class":229}," a",[220,1674,1675],{"class":229}," b",[220,1677,1678],{"class":233},"    # -n 目标存在就不覆盖\n",[220,1680,1681,1683,1686,1688,1690],{"class":222,"line":276},[220,1682,1631],{"class":226},[220,1684,1685],{"class":266}," -b",[220,1687,1672],{"class":229},[220,1689,1675],{"class":229},[220,1691,1692],{"class":233},"    # -b 覆盖前先把目标备份一份\n",[46,1694,1696],{"id":1695},"_9-rm-删除危险命令先读三条铁律","9. rm —— 删除（危险命令，先读三条铁律）",[15,1698,1699,1702,1703,662,1706,1709],{},[19,1700,1701],{},"rm"," 删除是不可逆的：它直接 unlink inode，文件系统层面没有回收站；",[19,1704,1705],{},"debugfs",[19,1707,1708],{},"photorec"," 抢救成功率低且需要立刻停止写入。",[15,1711,1712],{},[27,1713,1714],{},"示例 1：安全地删",[211,1716,1718],{"className":213,"code":1717,"language":215,"meta":216,"style":216},"rm -i notes.txt      # -i 每个文件都确认\nrm -I build\u002F         # -I 只在删超过 3 个文件或递归时问一次，日常推荐\nrm -rf build\u002F        # -r 递归、-f 强制 —— 不可逆，谨慎\n",[19,1719,1720,1732,1745],{"__ignoreMap":216},[220,1721,1722,1724,1726,1729],{"class":222,"line":223},[220,1723,1701],{"class":226},[220,1725,1593],{"class":266},[220,1727,1728],{"class":229}," notes.txt",[220,1730,1731],{"class":233},"      # -i 每个文件都确认\n",[220,1733,1734,1736,1739,1742],{"class":222,"line":276},[220,1735,1701],{"class":226},[220,1737,1738],{"class":266}," -I",[220,1740,1741],{"class":229}," build\u002F",[220,1743,1744],{"class":233},"         # -I 只在删超过 3 个文件或递归时问一次，日常推荐\n",[220,1746,1747,1749,1752,1754],{"class":222,"line":594},[220,1748,1701],{"class":226},[220,1750,1751],{"class":266}," -rf",[220,1753,1741],{"class":229},[220,1755,1756],{"class":233},"        # -r 递归、-f 强制 —— 不可逆，谨慎\n",[15,1758,1759],{},[27,1760,1761],{},"示例 2：删除前先看一眼范围",[211,1763,1765],{"className":213,"code":1764,"language":215,"meta":216,"style":216},"find build -type f -print    # 确认范围再动手，比事后找回容易；确定无误后把 -print 换成 -delete\n",[19,1766,1767],{"__ignoreMap":216},[220,1768,1769,1771,1773,1776,1778,1781],{"class":222,"line":223},[220,1770,694],{"class":226},[220,1772,1375],{"class":229},[220,1774,1775],{"class":266}," -type",[220,1777,1461],{"class":229},[220,1779,1780],{"class":266}," -print",[220,1782,1783],{"class":233},"    # 确认范围再动手，比事后找回容易；确定无误后把 -print 换成 -delete\n",[15,1785,1786,1788],{},[19,1787,33],{}," 的三条铁律：",[1790,1791,1792,1816,1843],"ol",{},[813,1793,1794,616,1797,1800,1801,1804,1805,1808,1809,1812,1813,616],{},[27,1795,1796],{},"路径必须先补全再回车",[19,1798,1799],{},"rm -rf $DIR\u002F"," 在 ",[19,1802,1803],{},"$DIR"," 为空时变成 ",[19,1806,1807],{},"rm -rf \u002F","（新版 coreutils 会拒绝，但 ",[19,1810,1811],{},"rm -rf \u002Fhome\u002Fdeploy\u002F"," 不会）。安全写法：",[19,1814,1815],{},"rm -rf \"${DIR:?变量未设置}\u002F\"*",[813,1817,1818,1824,1825,1828,1829,1832,1833,1836,1837,1839,1840,1842],{},[27,1819,1820,1821,1823],{},"不要在 ",[19,1822,464],{}," 失败的目录里执行","。脚本里写 ",[19,1826,1827],{},"cd \u002Fsrv\u002Fapp && rm -rf cache"," 用 ",[19,1830,1831],{},"&&","，别用 ",[19,1834,1835],{},";","——",[19,1838,464],{}," 失败时 ",[19,1841,1835],{}," 后面的命令会在当前目录执行。",[813,1844,1845,1848,1849,1852,1853,1855,1856,616],{},[27,1846,1847],{},"保护参数","：",[19,1850,1851],{},"--preserve-root","（默认开启，防 ",[19,1854,1807],{},"）；手生时永远先加 ",[19,1857,1858],{},"-i",[46,1860,1862],{"id":1861},"_10-ln-软链接与硬链接","10. ln —— 软链接与硬链接",[15,1864,1865],{},"Linux 的链接分两种，用法和性质完全不同：",[51,1867,1868,1881],{},[54,1869,1870],{},[57,1871,1872,1875,1878],{},[60,1873,1874],{},"维度",[60,1876,1877],{},"软链接（symbolic）",[60,1879,1880],{},"硬链接（hard）",[70,1882,1883,1898,1909,1923,1936,1947],{},[57,1884,1885,1888,1893],{},[75,1886,1887],{},"创建",[75,1889,1890],{},[19,1891,1892],{},"ln -s \u002Fsrv\u002Fapp\u002Freleases\u002Fv7 current",[75,1894,1895],{},[19,1896,1897],{},"ln big.txt big.bak",[57,1899,1900,1903,1906],{},[75,1901,1902],{},"本质",[75,1904,1905],{},"独立 inode，存的是路径字符串",[75,1907,1908],{},"同一 inode 的第二个目录项",[57,1910,1911,1914,1920],{},[75,1912,1913],{},"目标删了",[75,1915,1916,1917,1919],{},"变成悬空链接，",[19,1918,855],{}," 显示红色",[75,1921,1922],{},"内容仍在，链接数减 1；归零才真删数据",[57,1924,1925,1928,1931],{},[75,1926,1927],{},"跨文件系统",[75,1929,1930],{},"可以（存路径）",[75,1932,1933],{},[27,1934,1935],{},"不可以",[57,1937,1938,1941,1944],{},[75,1939,1940],{},"指向目录",[75,1942,1943],{},"可以",[75,1945,1946],{},"一般禁止（root 也不建议）",[57,1948,1949,1952,1958],{},[75,1950,1951],{},"权限位",[75,1953,1954,1957],{},[19,1955,1956],{},"lrwxrwxrwx","（固定，实际权限看目标）",[75,1959,1960],{},"与目标共享权限",[15,1962,1963],{},[27,1964,1965],{},"示例 1：发布目录的经典用法",[211,1967,1969],{"className":213,"code":1968,"language":215,"meta":216,"style":216},"ln -s \u002Fsrv\u002Fapp\u002Freleases\u002Fv7 current    # -s 建软链接，「版本目录 + current 指针」的发布方式\nln -sfn \u002Fsrv\u002Fapp\u002Freleases\u002Fv8 current  # 切换版本：-f 覆盖已存在的同名链接，-n 把它当普通文件处理\n",[19,1970,1971,1988],{"__ignoreMap":216},[220,1972,1973,1976,1979,1982,1985],{"class":222,"line":223},[220,1974,1975],{"class":226},"ln",[220,1977,1978],{"class":266}," -s",[220,1980,1981],{"class":229}," \u002Fsrv\u002Fapp\u002Freleases\u002Fv7",[220,1983,1984],{"class":229}," current",[220,1986,1987],{"class":233},"    # -s 建软链接，「版本目录 + current 指针」的发布方式\n",[220,1989,1990,1992,1995,1998,2000],{"class":222,"line":276},[220,1991,1975],{"class":226},[220,1993,1994],{"class":266}," -sfn",[220,1996,1997],{"class":229}," \u002Fsrv\u002Fapp\u002Freleases\u002Fv8",[220,1999,1984],{"class":229},[220,2001,2002],{"class":233},"  # 切换版本：-f 覆盖已存在的同名链接，-n 把它当普通文件处理\n",[15,2004,1240,2005,2008,2009,2012,2013,2016,2017,2020],{},[19,2006,2007],{},"-n"," 不能省——否则 ",[19,2010,2011],{},"ln -s 新目标 current"," 会把新链接建到 ",[19,2014,2015],{},"current\u002F"," 目录",[27,2018,2019],{},"里面","去，这是最经典的坑。",[46,2022,2024],{"id":2023},"_11-路径的三种写法与通配符","11. 路径的三种写法与通配符",[51,2026,2027,2039],{},[54,2028,2029],{},[57,2030,2031,2034,2036],{},[60,2032,2033],{},"写法",[60,2035,443],{},[60,2037,2038],{},"展开时机",[70,2040,2041,2054,2073],{},[57,2042,2043,2046,2051],{},[75,2044,2045],{},"绝对路径",[75,2047,2048],{},[19,2049,2050],{},"\u002Fsrv\u002Fapp\u002Fapp.py",[75,2052,2053],{},"永远有效，脚本里首选",[57,2055,2056,2059,2067],{},[75,2057,2058],{},"相对路径",[75,2060,2061,34,2064],{},[19,2062,2063],{},".\u002Fstatic\u002Flogo.png",[19,2065,2066],{},"..\u002F..\u002Flogs\u002Fapp.log",[75,2068,2069,2070,2072],{},"依赖当前目录，",[19,2071,464],{}," 后就变了",[57,2074,2075,2078,2086],{},[75,2076,2077],{},"主目录",[75,2079,2080,689,2083,2085],{},[19,2081,2082],{},"~\u002Fdownloads\u002Fa.zip",[19,2084,96],{}," 在最前才生效）",[75,2087,2088,2089],{},"Shell 展开为 ",[19,2090,2091],{},"$HOME",[15,2093,2094],{},"通配符（glob）与花括号展开：",[51,2096,2097,2110],{},[54,2098,2099],{},[57,2100,2101,2104,2107],{},[60,2102,2103],{},"模式",[60,2105,2106],{},"匹配",[60,2108,2109],{},"备注",[70,2111,2112,2137,2153,2172,2188,2203],{},[57,2113,2114,2119,2126],{},[75,2115,2116],{},[19,2117,2118],{},"*.log",[75,2120,2121,2122,2125],{},"所有以 ",[19,2123,2124],{},".log"," 结尾",[75,2127,2128,2129,2132,2133,2136],{},"没有匹配时 bash 默认",[27,2130,2131],{},"原样保留","该字符串（导致奇怪报错）；",[19,2134,2135],{},"shopt -s nullglob"," 让它展开为空",[57,2138,2139,2144,2147],{},[75,2140,2141],{},[19,2142,2143],{},"app?.py",[75,2145,2146],{},"单字符占位",[75,2148,2149,2152],{},[19,2150,2151],{},"app12.py"," 不匹配",[57,2154,2155,2160,2163],{},[75,2156,2157],{},[19,2158,2159],{},"[0-9]*",[75,2161,2162],{},"字符集开头",[75,2164,2165,187,2168,2171],{},[19,2166,2167],{},"[!0-9]*",[19,2169,2170],{},"[^0-9]*"," 取反",[57,2173,2174,2179,2182],{},[75,2175,2176],{},[19,2177,2178],{},"**\u002F*.js",[75,2180,2181],{},"递归",[75,2183,2184,2185],{},"需要 ",[19,2186,2187],{},"shopt -s globstar extglob",[57,2189,2190,2195,2200],{},[75,2191,2192],{},[19,2193,2194],{},"{a,b}.conf",[75,2196,2197],{},[19,2198,2199],{},"a.conf b.conf",[75,2201,2202],{},"花括号展开发生在通配之前，适合批量建文件",[57,2204,2205,2210,2213],{},[75,2206,2207],{},[19,2208,2209],{},"file_{1..5}.tmp",[75,2211,2212],{},"5 个名字",[75,2214,2215,2218],{},[19,2216,2217],{},"mkdir -p logs\u002F{2026..2028}"," 一次建三年目录",[15,2220,2221,2222,2225,2226,187,2229,616],{},"空格与特殊字符的文件名永远用引号包：",[19,2223,2224],{},"cp \"my report 2026.pdf\" \u002Ftmp\u002F","。确认目录里有带换行的怪文件时用 ",[19,2227,2228],{},"ls -Q",[19,2230,2231],{},"find . -print0",[46,2233,808],{"id":2234},"踩坑提醒-1",[810,2236,2237,2246,2268,2284],{},[813,2238,2239,2241,2242,2245],{},[19,2240,855],{}," 看到目录大小 4.0K 不代表里面只有 4KB，看总占用用 ",[19,2243,2244],{},"du -sh 目录","（第十四章）。",[813,2247,2248,2250,2251,2254,2255,2257,2258,2260,2261,2263,2264,2267],{},[19,2249,1542],{}," 到已存在的同名文件会",[27,2252,2253],{},"静默覆盖","，脚本里加 ",[19,2256,1858],{}," 或先判断存在；",[19,2259,1631],{}," 同样，",[19,2262,2007],{}," 表示不覆盖，",[19,2265,2266],{},"-b"," 覆盖前备份。",[813,2269,2270,2271,2273,2274,2276,2277,2280,2281,616],{},"别名陷阱：很多发行版给 root 配了 ",[19,2272,526],{},"，交互式很爽，脚本里 ",[19,2275,1701],{}," 卡在确认上。脚本开头 ",[19,2278,2279],{},"unalias -a 2>\u002Fdev\u002Fnull"," 或直接写 ",[19,2282,2283],{},"\u002Fbin\u002Frm",[813,2285,2286,2287,2290,2291,2294],{},"用 ",[19,2288,2289],{},"ls | xargs rm"," 处理带空格的文件名会出大事，正确做法是 ",[19,2292,2293],{},"find … -print0 | xargs -0 rm --","（第六章、第八章）。",[10,2296,2298],{"id":2297},"三查看文件内容catheadtaillessfile","三、查看文件内容：cat、head、tail、less、file",[15,2300,2301,2302,2305,2306,2309],{},"看日志、看配置、看二进制里嵌的字符串——目的不同，工具差别很大。选错工具的后果通常是：",[19,2303,2304],{},"cat"," 一个 8GB 日志把终端刷爆，或者用 ",[19,2307,2308],{},"less"," 打开后不知道怎么退出。",[46,2311,2313],{"id":2312},"_1-cat-一次性输出整个文件","1. cat —— 一次性输出整个文件",[15,2315,2316,2318,2319,2321,2322,187,2325,1520],{},[19,2317,2304],{}," 适合短文件；长文件请用 ",[19,2320,2308],{},"，超大文件先看体积（",[19,2323,2324],{},"ls -lh",[19,2326,2327],{},"wc -c",[15,2329,2330],{},[27,2331,2332],{},"示例 1：直接查看",[211,2334,2336],{"className":213,"code":2335,"language":215,"meta":216,"style":216},"cat \u002Fetc\u002Fnginx\u002Fnginx.conf    # 整个文件输出到终端\n",[19,2337,2338],{"__ignoreMap":216},[220,2339,2340,2342,2345],{"class":222,"line":223},[220,2341,2304],{"class":226},[220,2343,2344],{"class":229}," \u002Fetc\u002Fnginx\u002Fnginx.conf",[220,2346,2347],{"class":233},"    # 整个文件输出到终端\n",[15,2349,2350],{},[27,2351,2352],{},"示例 2：带行号",[211,2354,2356],{"className":213,"code":2355,"language":215,"meta":216,"style":216},"cat -n app.py    # -n 每行前面加行号（空行也编号）\nnl file          # 类似但默认只给非空行编号，nl -ba 则全部编号\n",[19,2357,2358,2369],{"__ignoreMap":216},[220,2359,2360,2362,2364,2366],{"class":222,"line":223},[220,2361,2304],{"class":226},[220,2363,1669],{"class":266},[220,2365,1545],{"class":229},[220,2367,2368],{"class":233},"    # -n 每行前面加行号（空行也编号）\n",[220,2370,2371,2374,2377],{"class":222,"line":276},[220,2372,2373],{"class":226},"nl",[220,2375,2376],{"class":229}," file",[220,2378,2379],{"class":233},"          # 类似但默认只给非空行编号，nl -ba 则全部编号\n",[15,2381,2382],{},[27,2383,2384],{},"示例 3：查看不可见字符（查 Windows 换行必备）",[211,2386,2388],{"className":213,"code":2387,"language":215,"meta":216,"style":216},"cat -A Makefile    # 行尾显示 $、Tab 显示 ^I、CR 显示 ^M\n",[19,2389,2390],{"__ignoreMap":216},[220,2391,2392,2394,2397,2400],{"class":222,"line":223},[220,2393,2304],{"class":226},[220,2395,2396],{"class":266}," -A",[220,2398,2399],{"class":229}," Makefile",[220,2401,2402],{"class":233},"    # 行尾显示 $、Tab 显示 ^I、CR 显示 ^M\n",[15,2404,2405,2406,2409,2410,187,2413,2416],{},"看到 ",[19,2407,2408],{},"^M$"," 就说明文件是 CRLF 行尾，用 ",[19,2411,2412],{},"sed -i 's\u002F\\r$\u002F\u002F' file",[19,2414,2415],{},"dos2unix file"," 处理。",[15,2418,2419],{},[27,2420,2421],{},"示例 4：拼接文件",[211,2423,2425],{"className":213,"code":2424,"language":215,"meta":216,"style":216},"cat part_* > full.bin    # 顺序按参数顺序（glob 展开按名字排序）\ntac access.log           # tac 按行倒序输出，看倒序日志很方便\n",[19,2426,2427,2447],{"__ignoreMap":216},[220,2428,2429,2431,2434,2437,2441,2444],{"class":222,"line":223},[220,2430,2304],{"class":226},[220,2432,2433],{"class":229}," part_",[220,2435,2436],{"class":266},"*",[220,2438,2440],{"class":2439},"snl16"," >",[220,2442,2443],{"class":229}," full.bin",[220,2445,2446],{"class":233},"    # 顺序按参数顺序（glob 展开按名字排序）\n",[220,2448,2449,2452,2455],{"class":222,"line":276},[220,2450,2451],{"class":226},"tac",[220,2453,2454],{"class":229}," access.log",[220,2456,2457],{"class":233},"           # tac 按行倒序输出，看倒序日志很方便\n",[46,2459,2461],{"id":2460},"_2-head-只看开头","2. head —— 只看开头",[15,2463,2464],{},[27,2465,2466],{},"示例 1：前 N 行",[211,2468,2470],{"className":213,"code":2469,"language":215,"meta":216,"style":216},"head -n 200 big.csv    # 前 200 行；-n 指定行数，默认 10 行\nhead -c 512 file       # -c 按字节取\n",[19,2471,2472,2488],{"__ignoreMap":216},[220,2473,2474,2477,2479,2482,2485],{"class":222,"line":223},[220,2475,2476],{"class":226},"head",[220,2478,1669],{"class":266},[220,2480,2481],{"class":266}," 200",[220,2483,2484],{"class":229}," big.csv",[220,2486,2487],{"class":233},"    # 前 200 行；-n 指定行数，默认 10 行\n",[220,2489,2490,2492,2495,2498,2500],{"class":222,"line":276},[220,2491,2476],{"class":226},[220,2493,2494],{"class":266}," -c",[220,2496,2497],{"class":266}," 512",[220,2499,2376],{"class":229},[220,2501,2502],{"class":233},"       # -c 按字节取\n",[15,2504,2505],{},[27,2506,2507],{},"示例 2：去掉末尾几行",[211,2509,2511],{"className":213,"code":2510,"language":215,"meta":216,"style":216},"head -n -5 data.txt    # 输出除最后 5 行以外的全部内容（GNU 扩展，BusyBox 不支持）\n",[19,2512,2513],{"__ignoreMap":216},[220,2514,2515,2517,2519,2522,2525],{"class":222,"line":223},[220,2516,2476],{"class":226},[220,2518,1669],{"class":266},[220,2520,2521],{"class":266}," -5",[220,2523,2524],{"class":229}," data.txt",[220,2526,2527],{"class":233},"    # 输出除最后 5 行以外的全部内容（GNU 扩展，BusyBox 不支持）\n",[46,2529,2531],{"id":2530},"_3-tail-只看末尾与实时跟踪","3. tail —— 只看末尾与实时跟踪",[15,2533,2534],{},[27,2535,2536],{},"示例 1：最后 N 行",[211,2538,2540],{"className":213,"code":2539,"language":215,"meta":216,"style":216},"tail -n 50 app.log     # 默认 10 行\n",[19,2541,2542],{"__ignoreMap":216},[220,2543,2544,2547,2549,2552,2554],{"class":222,"line":223},[220,2545,2546],{"class":226},"tail",[220,2548,1669],{"class":266},[220,2550,2551],{"class":266}," 50",[220,2553,1445],{"class":229},[220,2555,2556],{"class":233},"     # 默认 10 行\n",[15,2558,2559],{},[27,2560,2561],{},"示例 2：实时跟踪日志（最常用的排障动作）",[211,2563,2565],{"className":213,"code":2564,"language":215,"meta":216,"style":216},"tail -f -n 200 app.log      # -f 跟踪追加内容，Ctrl-C 退出\ntail -F \u002Fvar\u002Flog\u002Fsyslog     # -F 在文件被 logrotate 轮转后会重新打开，生产环境用大写 F\n",[19,2566,2567,2583],{"__ignoreMap":216},[220,2568,2569,2571,2574,2576,2578,2580],{"class":222,"line":223},[220,2570,2546],{"class":226},[220,2572,2573],{"class":266}," -f",[220,2575,1669],{"class":266},[220,2577,2481],{"class":266},[220,2579,1445],{"class":229},[220,2581,2582],{"class":233},"      # -f 跟踪追加内容，Ctrl-C 退出\n",[220,2584,2585,2587,2590,2593],{"class":222,"line":276},[220,2586,2546],{"class":226},[220,2588,2589],{"class":266}," -F",[220,2591,2592],{"class":229}," \u002Fvar\u002Flog\u002Fsyslog",[220,2594,2595],{"class":233},"     # -F 在文件被 logrotate 轮转后会重新打开，生产环境用大写 F\n",[15,2597,2598,2601,2602,2605],{},[19,2599,2600],{},"-f"," 在日志轮转后会",[27,2603,2604],{},"静默停止","，这是「tail 看着没动静其实服务在写新文件」的常见原因。",[15,2607,2608],{},[27,2609,2610],{},"示例 3：跟踪 + 过滤",[211,2612,2614],{"className":213,"code":2613,"language":215,"meta":216,"style":216},"tail -f app.log | grep --line-buffered ERROR\n",[19,2615,2616],{"__ignoreMap":216},[220,2617,2618,2620,2622,2624,2627,2629,2632],{"class":222,"line":223},[220,2619,2546],{"class":226},[220,2621,2573],{"class":266},[220,2623,1445],{"class":229},[220,2625,2626],{"class":2439}," |",[220,2628,315],{"class":226},[220,2630,2631],{"class":266}," --line-buffered",[220,2633,2634],{"class":229}," ERROR\n",[15,2636,2637,2638,2641,2642,2645,2646,616],{},"不加 ",[19,2639,2640],{},"--line-buffered"," 会因为管道缓冲看起来「卡住了」。看多文件时 ",[19,2643,2644],{},"tail -f"," 会在两个文件间轮流转屏，容易看漏；逐个终端各开一个，或用 ",[19,2647,2648],{},"multitail",[46,2650,2652],{"id":2651},"_4-less-分页阅读","4. less —— 分页阅读",[15,2654,2655,2657,2658,2661],{},[19,2656,2308],{}," 是看长文件的标准答案（名字来自「LESS is more」）；",[19,2659,2660],{},"more"," 只能往前翻，除了「机器上只装了它」之外没有理由选它。",[15,2663,2664],{},[27,2665,2666],{},"示例 1：打开文件",[211,2668,2670],{"className":213,"code":2669,"language":215,"meta":216,"style":216},"less file    # 打开后按 q 退出——新手最常见的卡住点\n",[19,2671,2672],{"__ignoreMap":216},[220,2673,2674,2676,2678],{"class":222,"line":223},[220,2675,2308],{"class":226},[220,2677,2376],{"class":229},[220,2679,2680],{"class":233},"    # 打开后按 q 退出——新手最常见的卡住点\n",[51,2682,2683,2697],{},[54,2684,2685],{},[57,2686,2687,2690,2693,2695],{},[60,2688,2689],{},"键",[60,2691,2692],{},"作用",[60,2694,2689],{},[60,2696,2692],{},[70,2698,2699,2721,2742,2759,2779,2800,2823],{},[57,2700,2701,2708,2711,2718],{},[75,2702,2703,1073,2706],{},[19,2704,2705],{},"Space",[19,2707,972],{},[75,2709,2710],{},"下\u002F上一页",[75,2712,2713,1073,2716],{},[19,2714,2715],{},"g",[19,2717,2715],{},[75,2719,2720],{},"跳到第一行",[57,2722,2723,2731,2734,2739],{},[75,2724,2725,1073,2728],{},[19,2726,2727],{},"Enter",[19,2729,2730],{},"y",[75,2732,2733],{},"下\u002F上一行",[75,2735,2736],{},[19,2737,2738],{},"G",[75,2740,2741],{},"跳到最后一行",[57,2743,2744,2748,2751,2756],{},[75,2745,2746],{},[19,2747,244],{},[75,2749,2750],{},"向下搜索",[75,2752,2753],{},[19,2754,2755],{},"?关键词",[75,2757,2758],{},"向上搜索",[57,2760,2761,2768,2771,2776],{},[75,2762,2763,1073,2765],{},[19,2764,248],{},[19,2766,2767],{},"N",[75,2769,2770],{},"下一个 \u002F 上一个匹配",[75,2772,2773],{},[19,2774,2775],{},"&关键词",[75,2777,2778],{},"只显示匹配行（过滤视图）",[57,2780,2781,2788,2791,2795],{},[75,2782,2783,1073,2785],{},[19,2784,963],{},[19,2786,2787],{},"u",[75,2789,2790],{},"半页下\u002F上",[75,2792,2793],{},[19,2794,240],{},[75,2796,2797],{},[27,2798,2799],{},"退出",[57,2801,2802,2807,2812,2817],{},[75,2803,2804],{},[19,2805,2806],{},"F",[75,2808,2809,2810,1062],{},"进入跟踪模式（类似 ",[19,2811,2644],{},[75,2813,2814],{},[19,2815,2816],{},"v",[75,2818,2286,2819,2822],{},[19,2820,2821],{},"$EDITOR"," 打开当前文件",[57,2824,2825,2831,2834,2839],{},[75,2826,2827,2830],{},[19,2828,2829],{},"-N","（启动参数）",[75,2832,2833],{},"显示行号",[75,2835,2836],{},[19,2837,2838],{},"Ctrl-G",[75,2840,2841],{},"查看当前位置信息",[15,2843,2844],{},[27,2845,2846],{},"示例 2：常用启动写法",[211,2848,2850],{"className":213,"code":2849,"language":215,"meta":216,"style":216},"less -N file        # 显示行号\nless +G file        # 打开即停在底部\nless +F app.log     # 打开即跟踪，像 tail -f，按 Ctrl-C 回到翻页\nless -S file        # 长行不折行，用左右箭头横向滚\n",[19,2851,2852,2864,2876,2888],{"__ignoreMap":216},[220,2853,2854,2856,2859,2861],{"class":222,"line":223},[220,2855,2308],{"class":226},[220,2857,2858],{"class":266}," -N",[220,2860,2376],{"class":229},[220,2862,2863],{"class":233},"        # 显示行号\n",[220,2865,2866,2868,2871,2873],{"class":222,"line":276},[220,2867,2308],{"class":226},[220,2869,2870],{"class":229}," +G",[220,2872,2376],{"class":229},[220,2874,2875],{"class":233},"        # 打开即停在底部\n",[220,2877,2878,2880,2883,2885],{"class":222,"line":594},[220,2879,2308],{"class":226},[220,2881,2882],{"class":229}," +F",[220,2884,1445],{"class":229},[220,2886,2887],{"class":233},"     # 打开即跟踪，像 tail -f，按 Ctrl-C 回到翻页\n",[220,2889,2890,2892,2895,2897],{"class":222,"line":925},[220,2891,2308],{"class":226},[220,2893,2894],{"class":266}," -S",[220,2896,2376],{"class":229},[220,2898,2899],{"class":233},"        # 长行不折行，用左右箭头横向滚\n",[46,2901,2903],{"id":2902},"_5-file-判断文件的真实类型","5. file —— 判断文件的真实类型",[15,2905,2906],{},[27,2907,2908],{},"示例 1：不看扩展名看文件头",[211,2910,2912],{"className":213,"code":2911,"language":215,"meta":216,"style":216},"file img.png app.so    # 输出如：PNG image data, 800 x 600, 8-bit\u002Fcolor RGB\n",[19,2913,2914],{"__ignoreMap":216},[220,2915,2916,2919,2922,2925],{"class":222,"line":223},[220,2917,2918],{"class":226},"file",[220,2920,2921],{"class":229}," img.png",[220,2923,2924],{"class":229}," app.so",[220,2926,2927],{"class":233},"    # 输出如：PNG image data, 800 x 600, 8-bit\u002Fcolor RGB\n",[15,2929,2930,2932],{},[19,2931,2918],{}," 判断的是文件头（magic number），因此「扩展名 jpg 实际是 png」这种问题只有它能发现。",[46,2934,2936],{"id":2935},"_6-odhexdumpxxdstrings-看二进制","6. od、hexdump、xxd、strings —— 看二进制",[15,2938,2939],{},[27,2940,2941],{},"示例 1：字节转储",[211,2943,2945],{"className":213,"code":2944,"language":215,"meta":216,"style":216},"od -c -N 64 file          # 八进制\u002F字符转储，\\n、\\0 直接可见；-N 64 只看前 64 字节\nhexdump -C -n 128 file    # 经典十六进制视图：左偏移、中 16 字节、右 ASCII\nxxd file                  # 同上；xxd -r hex.txt bin 还能把 hex 文本反向写回文件\n",[19,2946,2947,2964,2982],{"__ignoreMap":216},[220,2948,2949,2952,2954,2956,2959,2961],{"class":222,"line":223},[220,2950,2951],{"class":226},"od",[220,2953,2494],{"class":266},[220,2955,2858],{"class":266},[220,2957,2958],{"class":266}," 64",[220,2960,2376],{"class":229},[220,2962,2963],{"class":233},"          # 八进制\u002F字符转储，\\n、\\0 直接可见；-N 64 只看前 64 字节\n",[220,2965,2966,2969,2972,2974,2977,2979],{"class":222,"line":276},[220,2967,2968],{"class":226},"hexdump",[220,2970,2971],{"class":266}," -C",[220,2973,1669],{"class":266},[220,2975,2976],{"class":266}," 128",[220,2978,2376],{"class":229},[220,2980,2981],{"class":233},"    # 经典十六进制视图：左偏移、中 16 字节、右 ASCII\n",[220,2983,2984,2987,2989],{"class":222,"line":594},[220,2985,2986],{"class":226},"xxd",[220,2988,2376],{"class":229},[220,2990,2991],{"class":233},"                  # 同上；xxd -r hex.txt bin 还能把 hex 文本反向写回文件\n",[15,2993,2994],{},[27,2995,2996],{},"示例 2：抽出可打印字符串",[211,2998,3000],{"className":213,"code":2999,"language":215,"meta":216,"style":216},"strings -n 6 app.so                        # 默认最短 4 个字符，-n 6 减少噪音\nstrings -a \u002Fusr\u002Fbin\u002Fnginx | grep -E '^https?:\u002F\u002F'   # 找嵌在二进制里的路径、报错文本、版本号\n",[19,3001,3002,3017],{"__ignoreMap":216},[220,3003,3004,3007,3009,3012,3014],{"class":222,"line":223},[220,3005,3006],{"class":226},"strings",[220,3008,1669],{"class":266},[220,3010,3011],{"class":266}," 6",[220,3013,2924],{"class":229},[220,3015,3016],{"class":233},"                        # 默认最短 4 个字符，-n 6 减少噪音\n",[220,3018,3019,3021,3023,3026,3028,3030,3033,3036],{"class":222,"line":276},[220,3020,3006],{"class":226},[220,3022,551],{"class":266},[220,3024,3025],{"class":229}," \u002Fusr\u002Fbin\u002Fnginx",[220,3027,2626],{"class":2439},[220,3029,315],{"class":226},[220,3031,3032],{"class":266}," -E",[220,3034,3035],{"class":229}," '^https?:\u002F\u002F'",[220,3037,3038],{"class":233},"   # 找嵌在二进制里的路径、报错文本、版本号\n",[15,3040,3041,3042,34,3045,3048],{},"压缩包不用先解压也能看：",[19,3043,3044],{},"zcat app.log.gz",[19,3046,3047],{},"zless app.log.gz","（第九章详述）。",[46,3050,808],{"id":3051},"踩坑提醒-2",[810,3053,3054,3068,3078],{},[813,3055,3056,3057,3059,3060,3062,3063,3065,3066,616],{},"看完日志不记得怎么退出 ",[19,3058,2308],{},"：按 ",[19,3061,240],{},"。卡在任何分页程序里，第一反应都是 ",[19,3064,240],{},"，不行再 ",[19,3067,153],{},[813,3069,3070,3072,3073,187,3075,3077],{},[19,3071,2304],{}," 大文件不会「只看前几行」，它会全部刷到终端，几 GB 的日志能让终端卡几十秒——先用 ",[19,3074,2327],{},[19,3076,2324],{}," 确认体积。",[813,3079,3080,3081,1848,3084,3087,3088,3090,3091,187,3093,2416],{},"复制出来的配置「看着一样但跑不通」，九成是行尾 ",[19,3082,3083],{},"CRLF",[19,3085,3086],{},"cat -A"," 看到 ",[19,3089,2408],{}," 就用 ",[19,3092,2412],{},[19,3094,2415],{},[10,3096,3098],{"id":3097},"四最小编辑器集nano-与够用的-vim","四、最小编辑器集：nano 与够用的 vim",[15,3100,3101,3102,3105,3106,3109,3110,3113],{},"服务器上手第一道坎往往不是命令而是编辑器：改一行配置，进了 ",[19,3103,3104],{},"vi"," 出不来，或者不知道怎么保存。这一章只讲两个编辑器——",[19,3107,3108],{},"nano","（新手，键位写在屏幕底部）和 ",[19,3111,3112],{},"vim","（服务器上一定会遇到）——目标是「能改一行、能存盘、能退出」。",[46,3115,3117],{"id":3116},"_1-nano-键位就写在屏幕底部","1. nano —— 键位就写在屏幕底部",[15,3119,3120,3123,3124,3126,3127,3130],{},[19,3121,3122],{},"nano file"," 打开后，屏幕底部的 ",[19,3125,164],{}," 代表 ",[19,3128,3129],{},"Ctrl","，照着按就行。",[15,3132,3133],{},[27,3134,3135],{},"示例 1：最常用的四个键",[211,3137,3139],{"className":213,"code":3138,"language":215,"meta":216,"style":216},"nano \u002Fetc\u002Fnginx\u002Fnginx.conf   # 打开文件\n# Ctrl-O 然后 Enter —— 保存（写入当前文件名）\n# Ctrl-X —— 退出（有改动会问是否保存）\n# Ctrl-W —— 搜索（Where Is）\n",[19,3140,3141,3150,3155,3160],{"__ignoreMap":216},[220,3142,3143,3145,3147],{"class":222,"line":223},[220,3144,3108],{"class":226},[220,3146,2344],{"class":229},[220,3148,3149],{"class":233},"   # 打开文件\n",[220,3151,3152],{"class":222,"line":276},[220,3153,3154],{"class":233},"# Ctrl-O 然后 Enter —— 保存（写入当前文件名）\n",[220,3156,3157],{"class":222,"line":594},[220,3158,3159],{"class":233},"# Ctrl-X —— 退出（有改动会问是否保存）\n",[220,3161,3162],{"class":222,"line":925},[220,3163,3164],{"class":233},"# Ctrl-W —— 搜索（Where Is）\n",[15,3166,3167],{},[27,3168,3169],{},"示例 2：带参数打开",[211,3171,3173],{"className":213,"code":3172,"language":215,"meta":216,"style":216},"nano +42 \u002Fetc\u002Fnginx\u002Fnginx.conf   # 打开即跳到 42 行\nnano -l file                     # 保存时自动补末尾换行\n",[19,3174,3175,3187],{"__ignoreMap":216},[220,3176,3177,3179,3182,3184],{"class":222,"line":223},[220,3178,3108],{"class":226},[220,3180,3181],{"class":229}," +42",[220,3183,2344],{"class":229},[220,3185,3186],{"class":233},"   # 打开即跳到 42 行\n",[220,3188,3189,3191,3194,3196],{"class":222,"line":276},[220,3190,3108],{"class":226},[220,3192,3193],{"class":266}," -l",[220,3195,2376],{"class":229},[220,3197,3198],{"class":233},"                     # 保存时自动补末尾换行\n",[15,3200,3201,3202,3205,3206,3209],{},"想永久显示行号，在 ",[19,3203,3204],{},"~\u002F.nanorc"," 写 ",[19,3207,3208],{},"set linenumbers","。完整按键表：",[51,3211,3212,3225],{},[54,3213,3214],{},[57,3215,3216,3219,3221,3223],{},[60,3217,3218],{},"按键",[60,3220,2692],{},[60,3222,3218],{},[60,3224,2692],{},[70,3226,3227,3248,3269,3293,3317,3339,3361],{},[57,3228,3229,3237,3240,3245],{},[75,3230,3231,3234,3235],{},[19,3232,3233],{},"Ctrl-O"," 然后 ",[19,3236,2727],{},[75,3238,3239],{},"写入（保存）当前文件名",[75,3241,3242],{},[19,3243,3244],{},"Ctrl-X",[75,3246,3247],{},"退出（有改动会问是否保存）",[57,3249,3250,3255,3261,3266],{},[75,3251,3252],{},[19,3253,3254],{},"Ctrl-W",[75,3256,3257,3258,1062],{},"搜索（",[19,3259,3260],{},"Where Is",[75,3262,3263],{},[19,3264,3265],{},"Alt-W",[75,3267,3268],{},"重复上一次搜索",[57,3270,3271,3279,3282,3287],{},[75,3272,3273,1073,3276],{},[19,3274,3275],{},"Ctrl-K",[19,3277,3278],{},"Ctrl-U",[75,3280,3281],{},"剪切一行 \u002F 粘贴回来",[75,3283,3284],{},[19,3285,3286],{},"Ctrl-^",[75,3288,3289,3290,1062],{},"替换（",[19,3291,3292],{},"Replace",[57,3294,3295,3303,3306,3314],{},[75,3296,3297,1073,3300],{},[19,3298,3299],{},"Ctrl-A",[19,3301,3302],{},"Ctrl-E",[75,3304,3305],{},"行首 \u002F 行尾",[75,3307,3308,1073,3311],{},[19,3309,3310],{},"Alt-A",[19,3312,3313],{},"Alt-E",[75,3315,3316],{},"设标记起点 \u002F 终点（选块）",[57,3318,3319,3328,3331,3336],{},[75,3320,3321,3324,3325,1062],{},[19,3322,3323],{},"Ctrl-Shift-6","（或 ",[19,3326,3327],{},"Alt-6",[75,3329,3330],{},"复制选中块",[75,3332,3333],{},[19,3334,3335],{},"Alt-Shift-3",[75,3337,3338],{},"删除选中块",[57,3340,3341,3346,3349,3354],{},[75,3342,3343],{},[19,3344,3345],{},"Alt-\u002F",[75,3347,3348],{},"撤销",[75,3350,3351,3353],{},[19,3352,3313],{},"（重复）",[75,3355,3356,3357,3360],{},"重做（",[19,3358,3359],{},"Ctrl-6"," 旧版）",[57,3362,3363,3368,3371,3376],{},[75,3364,3365],{},[19,3366,3367],{},"Ctrl-T",[75,3369,3370],{},"打开拼写检查",[75,3372,3373],{},[19,3374,3375],{},"Ctrl-R",[75,3377,3378],{},"把另一个文件插进来",[46,3380,3382],{"id":3381},"_2-vim-先分清三种模式","2. vim —— 先分清三种模式",[15,3384,3385],{},"vim 的行为取决于当前模式，模式之间靠这几个键切换：",[51,3387,3388,3403],{},[54,3389,3390],{},[57,3391,3392,3394,3397,3400],{},[60,3393,2103],{},[60,3395,3396],{},"进入",[60,3398,3399],{},"能干什么",[60,3401,3402],{},"屏幕底部特征",[70,3404,3405,3427,3451],{},[57,3406,3407,3410,3417,3424],{},[75,3408,3409],{},"普通模式（Normal）",[75,3411,3412,3413,3416],{},"启动即在此；",[19,3414,3415],{},"Esc"," 回到",[75,3418,3419,3420,3423],{},"移动、删除、复制粘贴、",[19,3421,3422],{},":"," 命令",[75,3425,3426],{},"无提示",[57,3428,3429,3432,3443,3446],{},[75,3430,3431],{},"插入模式（Insert）",[75,3433,3434,465,3437,465,3440],{},[19,3435,3436],{},"i",[19,3438,3439],{},"a",[19,3441,3442],{},"o",[75,3444,3445],{},"打字",[75,3447,3448],{},[19,3449,3450],{},"-- INSERT --",[57,3452,3453,3456,3466,3469],{},[75,3454,3455],{},"命令行模式",[75,3457,3458,3459,465,3461,465,3463],{},"普通模式按 ",[19,3460,3422],{},[19,3462,662],{},[19,3464,3465],{},"?",[75,3467,3468],{},"存盘、退出、搜索、替换",[75,3470,3471],{},"光标跳到最底行",[15,3473,3474,3475,3477],{},"屏幕底部出现 ",[19,3476,3450],{}," 就说明你正在打字而不是在下命令——这是所有 vim 卡住的根源。",[46,3479,3481],{"id":3480},"_3-vim-够用的十二个键位","3. vim 够用的十二个键位",[15,3483,3484],{},"按「进得去、改得了、出得来」排序：",[211,3486,3488],{"className":213,"code":3487,"language":215,"meta":216,"style":216},"i          # 在光标前进入插入模式（开始打字）\na          # 在光标后插入\no          # 在下方开一个新行并插入（加新配置行必备）\nEsc        # 回到普通模式——打完字的第一件事\n:w         # 保存（write）\n:q         # 退出（有未保存改动会拒绝）\n:q!        # 强制退出并丢弃改动——「怎么退出 vim」的正确答案\n:wq        # 保存并退出（也可以按 ZZ）\nx          # 普通模式下删除光标处一个字符\ndd         # 剪切一行（复制用 yy，粘贴用 p）\nu          # 撤销（重做 Ctrl-R）\n\u002F关键词    # 向下搜索，按 n 跳下一个（? 向上）\ngg         # 跳到文件第一行（G 跳最后一行）\n",[19,3489,3490,3497,3504,3511,3518,3526,3535,3544,3553,3562,3570,3578,3586],{"__ignoreMap":216},[220,3491,3492,3494],{"class":222,"line":223},[220,3493,3436],{"class":226},[220,3495,3496],{"class":233},"          # 在光标前进入插入模式（开始打字）\n",[220,3498,3499,3501],{"class":222,"line":276},[220,3500,3439],{"class":226},[220,3502,3503],{"class":233},"          # 在光标后插入\n",[220,3505,3506,3508],{"class":222,"line":594},[220,3507,3442],{"class":226},[220,3509,3510],{"class":233},"          # 在下方开一个新行并插入（加新配置行必备）\n",[220,3512,3513,3515],{"class":222,"line":925},[220,3514,3415],{"class":226},[220,3516,3517],{"class":233},"        # 回到普通模式——打完字的第一件事\n",[220,3519,3520,3523],{"class":222,"line":931},[220,3521,3522],{"class":226},":w",[220,3524,3525],{"class":233},"         # 保存（write）\n",[220,3527,3529,3532],{"class":222,"line":3528},6,[220,3530,3531],{"class":226},":q",[220,3533,3534],{"class":233},"         # 退出（有未保存改动会拒绝）\n",[220,3536,3538,3541],{"class":222,"line":3537},7,[220,3539,3540],{"class":226},":q!",[220,3542,3543],{"class":233},"        # 强制退出并丢弃改动——「怎么退出 vim」的正确答案\n",[220,3545,3547,3550],{"class":222,"line":3546},8,[220,3548,3549],{"class":226},":wq",[220,3551,3552],{"class":233},"        # 保存并退出（也可以按 ZZ）\n",[220,3554,3556,3559],{"class":222,"line":3555},9,[220,3557,3558],{"class":226},"x",[220,3560,3561],{"class":233},"          # 普通模式下删除光标处一个字符\n",[220,3563,3565,3567],{"class":222,"line":3564},10,[220,3566,40],{"class":226},[220,3568,3569],{"class":233},"         # 剪切一行（复制用 yy，粘贴用 p）\n",[220,3571,3573,3575],{"class":222,"line":3572},11,[220,3574,2787],{"class":226},[220,3576,3577],{"class":233},"          # 撤销（重做 Ctrl-R）\n",[220,3579,3581,3583],{"class":222,"line":3580},12,[220,3582,244],{"class":226},[220,3584,3585],{"class":233},"    # 向下搜索，按 n 跳下一个（? 向上）\n",[220,3587,3589,3592],{"class":222,"line":3588},13,[220,3590,3591],{"class":226},"gg",[220,3593,3594],{"class":233},"         # 跳到文件第一行（G 跳最后一行）\n",[46,3596,3598],{"id":3597},"_4-进阶但极常用的四个改配置真的会用到","4. 进阶但极常用的四个（改配置真的会用到）",[211,3600,3602],{"className":213,"code":3601,"language":215,"meta":216,"style":216},":%s\u002Fold\u002Fnew\u002Fg      # 全文替换；加 c 变 :%s\u002Fold\u002Fnew\u002Fgc 逐个确认\n:10,20s\u002F^\u002F# \u002F      # 只在 10–20 行行首加注释（范围替换）\n:30                # 跳到 30 行；:10,30d 删除 10–30 行\nv                  # 可视模式（Ctrl-V 是列块模式，批量给多行加前缀）\n",[19,3603,3604,3614,3625,3633],{"__ignoreMap":216},[220,3605,3606,3608,3611],{"class":222,"line":223},[220,3607,3422],{"class":266},[220,3609,3610],{"class":229},"%s\u002Fold\u002Fnew\u002Fg",[220,3612,3613],{"class":233},"      # 全文替换；加 c 变 :%s\u002Fold\u002Fnew\u002Fgc 逐个确认\n",[220,3615,3616,3619,3622],{"class":222,"line":276},[220,3617,3618],{"class":226},":10,20s\u002F^\u002F#",[220,3620,3621],{"class":229}," \u002F",[220,3623,3624],{"class":233},"      # 只在 10–20 行行首加注释（范围替换）\n",[220,3626,3627,3630],{"class":222,"line":594},[220,3628,3629],{"class":226},":30",[220,3631,3632],{"class":233},"                # 跳到 30 行；:10,30d 删除 10–30 行\n",[220,3634,3635,3637],{"class":222,"line":925},[220,3636,2816],{"class":226},[220,3638,3639],{"class":233},"                  # 可视模式（Ctrl-V 是列块模式，批量给多行加前缀）\n",[46,3641,3643],{"id":3642},"_5-怎么退出-vim这个梗的正确解法","5. 「怎么退出 vim」——这个梗的正确解法",[15,3645,3646,3647,3649,3650,3652,3653,465,3655,465,3657,3660],{},"网上「如何从 vim 退出」的笑话，真实成因是：用户按了 ",[19,3648,3436],{}," 进入插入模式，改完直接敲 ",[19,3651,3540],{},"，结果 ",[19,3654,3422],{},[19,3656,240],{},[19,3658,3659],{},"!"," 被当成普通文本写进了文件。解法固定三步：",[1790,3662,3663,3669,3680],{},[813,3664,3665,3666,3668],{},"连按 ",[19,3667,3415],{}," 两三次（保证回到普通模式）；",[813,3670,3671,3672,3234,3674,3676,3677,3679],{},"输入 ",[19,3673,3540],{},[19,3675,2727],{},"（不想保留改动）或 ",[19,3678,3549],{},"（保留）；",[813,3681,3682,3683,662,3686,3689,3690,3692,3693,616],{},"如果底部显示 ",[19,3684,3685],{},"E37",[19,3687,3688],{},"E1734"," 之类的报错，说明还有未保存内容或输入被当命令解析，先 ",[19,3691,2787],{}," 撤销刚才误打的字符再 ",[19,3694,3540],{},[46,3696,3698],{"id":3697},"_6-只有-vi-时的应急改行","6. 只有 vi 时的应急改行",[15,3700,3701,3702,3704,3705,3707,3708,3711],{},"精简镜像里 ",[19,3703,3112],{}," 可能不存在（只有硬链接的 ",[19,3706,3104],{},"，且是 ",[19,3709,3710],{},"vi-compatible"," 模式），改一行配置有两种不动编辑器的办法：",[211,3713,3715],{"className":213,"code":3714,"language":215,"meta":216,"style":216},"sed -i.bak 's\u002F^# *listen_port.*\u002Flisten_port 9080;\u002F' \u002Fetc\u002Fapp\u002Fapp.conf\n# -i.bak 原地修改并先留 .bak 备份；s 命令末尾的分号是给 nginx 的语法，别多别少\n\nprintf '%s\\n' 'user deploy;' 'worker_processes auto;' > \u002Fetc\u002Fapp\u002Fapp.conf.new\n# 小配置文件可以直接重建\n",[19,3716,3717,3730,3735,3741,3759],{"__ignoreMap":216},[220,3718,3719,3721,3724,3727],{"class":222,"line":223},[220,3720,697],{"class":226},[220,3722,3723],{"class":266}," -i.bak",[220,3725,3726],{"class":229}," 's\u002F^# *listen_port.*\u002Flisten_port 9080;\u002F'",[220,3728,3729],{"class":229}," \u002Fetc\u002Fapp\u002Fapp.conf\n",[220,3731,3732],{"class":222,"line":276},[220,3733,3734],{"class":233},"# -i.bak 原地修改并先留 .bak 备份；s 命令末尾的分号是给 nginx 的语法，别多别少\n",[220,3736,3737],{"class":222,"line":594},[220,3738,3740],{"emptyLinePlaceholder":3739},true,"\n",[220,3742,3743,3745,3748,3751,3754,3756],{"class":222,"line":925},[220,3744,292],{"class":266},[220,3746,3747],{"class":229}," '%s\\n'",[220,3749,3750],{"class":229}," 'user deploy;'",[220,3752,3753],{"class":229}," 'worker_processes auto;'",[220,3755,2440],{"class":2439},[220,3757,3758],{"class":229}," \u002Fetc\u002Fapp\u002Fapp.conf.new\n",[220,3760,3761],{"class":222,"line":931},[220,3762,3763],{"class":233},"# 小配置文件可以直接重建\n",[15,3765,3766,3767,3770,3771,3773,3774,34,3776,34,3778,3780],{},"改完先验语法再重载：",[19,3768,3769],{},"nginx -t && systemctl reload nginx","。真要用 ",[19,3772,3104],{}," 应急，键位与 vim 完全一致（",[19,3775,3436],{},[19,3777,3415],{},[19,3779,3549],{},"），只是缺多级撤销和语法高亮。",[46,3782,808],{"id":3783},"踩坑提醒-3",[810,3785,3786,3803,3820,3832],{},[813,3787,3788,3790,3791,3794,3795,3798,3799,3802],{},[19,3789,3522],{}," 报 ",[19,3792,3793],{},"E45: 'readonly' option is set","：文件不可写。别退出去 ",[19,3796,3797],{},"sudo vim"," 重来，直接 ",[19,3800,3801],{},":w !sudo tee %"," 就能以 root 写回当前缓冲。",[813,3804,3805,3807,3808,3810,3811,3813,3814,3817,3818,616],{},[19,3806,3531],{}," 拒绝退出报 ",[19,3809,3685],{},"，别急着 ",[19,3812,3540],{}," 丢掉半小时改动，先 ",[19,3815,3816],{},":w!","；确实不要了再 ",[19,3819,3540],{},[813,3821,3822,3823,3825,3826,3829,3830,616],{},"vim 保存后文件属主变成 root（用 ",[19,3824,3797],{}," 改的），导致应用写不了日志——要么 ",[19,3827,3828],{},"chown"," 回去，要么改用 ",[19,3831,3801],{},[813,3833,3834,3835,3838,3839,3842],{},"编辑器里「粘贴的代码乱七八糟缩进」：vim 需要 ",[19,3836,3837],{},":set paste","（或新版 ",[19,3840,3841],{},":set pastetoggle=\u003CF3>","），否则会自动缩进把每行叠歪。",[10,3844,3846],{"id":3845},"五权限与属主chmodchownsudo","五、权限与属主：chmod、chown、sudo",[15,3848,3849,3852,3853,3855],{},[19,3850,3851],{},"Permission denied"," 是 Linux 里出现频率最高的错误，而它的成因至少有六种：文件权限、目录缺 ",[19,3854,3558],{},"、属主不对、SELinux、capabilities、进程用的不是你以为的用户。这一章先把「读权限位」练熟，再给一套安全的修改顺序。",[861,3857],{"height":3858,"src":3859,"title":3860},640,"\u002Ftutorials\u002Flinux\u002Fdemos\u002F05-permissions.html","示例 05：读懂权限位、chmod 两套写法与 Permission denied 排查",[46,3862,3864],{"id":3863},"_1-先学会读权限位","1. 先学会读权限位",[51,3866,3867,3880],{},[54,3868,3869],{},[57,3870,3871,3874,3877],{},[60,3872,3873],{},"字符位",[60,3875,3876],{},"对文件",[60,3878,3879],{},"对目录",[70,3881,3882,3904,3921],{},[57,3883,3884,3889,3894],{},[75,3885,3886],{},[19,3887,3888],{},"r",[75,3890,3891,3892,1062],{},"能读内容（",[19,3893,2304],{},[75,3895,3896,3897,3900,3901,3903],{},"能",[27,3898,3899],{},"列出","名字（",[19,3902,348],{}," 目录本身）",[57,3905,3906,3911,3914],{},[75,3907,3908],{},[19,3909,3910],{},"w",[75,3912,3913],{},"能改内容（写同一文件）",[75,3915,3916,3917,3920],{},"能在里面",[27,3918,3919],{},"创建\u002F删除","文件（真正的删除权看目录，不看文件！）",[57,3922,3923,3927,3933],{},[75,3924,3925],{},[19,3926,3558],{},[75,3928,3929,3930,3932],{},"能执行（配合 ",[19,3931,3888],{}," 才能跑脚本）",[75,3934,3896,3935,689,3937,3939],{},[27,3936,3396],{},[19,3938,464],{},"）并访问其中的已知文件名",[15,3941,3942,3943,616,3949,3952,3953,3955,3956,3790,3959,3961,3962,3965,3966,3968,3969,3971,3972,3975,3976,3979,3980,3983],{},"关键结论：",[27,3944,3945,3946,3948],{},"目录的 ",[19,3947,3558],{}," 比文件本身的权限更决定性",[19,3950,3951],{},"chmod 644 app.sh"," 之后脚本不能跑是缺文件 ",[19,3954,3558],{},"；而 ",[19,3957,3958],{},".\u002Fapp.sh",[19,3960,3851],{}," 有时是它的",[27,3963,3964],{},"父目录","缺 ",[19,3967,3558],{},"——没有 ",[19,3970,3558],{}," 就无法解析路径，哪怕文件是 777。典型事故：家目录 ",[19,3973,3974],{},"chmod 700 ~"," 之后，别人 ",[19,3977,3978],{},"cat \u002Fhome\u002Fdeploy\u002Fx.txt"," 必失败，即使 ",[19,3981,3982],{},"x.txt"," 是 644。",[15,3985,3986,3987,34,3990,34,3993,3996],{},"数字法：",[19,3988,3989],{},"r=4",[19,3991,3992],{},"w=2",[19,3994,3995],{},"x=1","，每组三位相加：",[51,3998,3999,4011],{},[54,4000,4001],{},[57,4002,4003,4006,4008],{},[60,4004,4005],{},"数字",[60,4007,68],{},[60,4009,4010],{},"常见用途",[70,4012,4013,4028,4043,4058,4076,4091,4106],{},[57,4014,4015,4020,4025],{},[75,4016,4017],{},[19,4018,4019],{},"644",[75,4021,4022],{},[19,4023,4024],{},"rw-r--r--",[75,4026,4027],{},"普通文件默认（配置、图片、代码）",[57,4029,4030,4035,4040],{},[75,4031,4032],{},[19,4033,4034],{},"755",[75,4036,4037],{},[19,4038,4039],{},"rwxr-xr-x",[75,4041,4042],{},"可执行文件、脚本、目录默认",[57,4044,4045,4050,4055],{},[75,4046,4047],{},[19,4048,4049],{},"600",[75,4051,4052],{},[19,4053,4054],{},"rw-------",[75,4056,4057],{},"私钥、含密码的配置",[57,4059,4060,4065,4070],{},[75,4061,4062],{},[19,4063,4064],{},"700",[75,4066,4067],{},[19,4068,4069],{},"rwx------",[75,4071,4072,4075],{},[19,4073,4074],{},".ssh"," 目录、只给自己看的目录",[57,4077,4078,4083,4088],{},[75,4079,4080],{},[19,4081,4082],{},"664",[75,4084,4085],{},[19,4086,4087],{},"rw-rw-r--",[75,4089,4090],{},"团队协作目录里的文件（配合组权限）",[57,4092,4093,4098,4103],{},[75,4094,4095],{},[19,4096,4097],{},"775",[75,4099,4100],{},[19,4101,4102],{},"rwxrwxr-x",[75,4104,4105],{},"组内共享的目录",[57,4107,4108,4113,4118],{},[75,4109,4110],{},[19,4111,4112],{},"777",[75,4114,4115],{},[19,4116,4117],{},"rwxrwxrwx",[75,4119,4120,4123],{},[27,4121,4122],{},"不要用","，见下",[46,4125,4127],{"id":4126},"_2-chmod-修改权限","2. chmod —— 修改权限",[15,4129,4130],{},[27,4131,4132],{},"示例 1：数字法（整套设置）",[211,4134,4136],{"className":213,"code":4135,"language":215,"meta":216,"style":216},"chmod 644 app.conf     # 属主可读写，属组和其他人只读\nchmod 755 deploy.sh    # 属主可执行，其他人可读可执行\n",[19,4137,4138,4152],{"__ignoreMap":216},[220,4139,4140,4143,4146,4149],{"class":222,"line":223},[220,4141,4142],{"class":226},"chmod",[220,4144,4145],{"class":266}," 644",[220,4147,4148],{"class":229}," app.conf",[220,4150,4151],{"class":233},"     # 属主可读写，属组和其他人只读\n",[220,4153,4154,4156,4159,4162],{"class":222,"line":276},[220,4155,4142],{"class":226},[220,4157,4158],{"class":266}," 755",[220,4160,4161],{"class":229}," deploy.sh",[220,4163,4164],{"class":233},"    # 属主可执行，其他人可读可执行\n",[15,4166,4167],{},[27,4168,4169],{},"示例 2：符号法（改单个位，安全且不牵连其他位，推荐）",[211,4171,4173],{"className":213,"code":4172,"language":215,"meta":216,"style":216},"chmod u+x deploy.sh       # u=属主 +执行位\nchmod g+w app.sh          # g=属组 加写权限\nchmod o-rwx private.conf  # o=其他人 去掉全部权限；a=全部人\nchmod -R a+rX \u002Fsrv\u002Fapp    # 大写 X：只给目录或已是可执行文件加 x —— 修目录树的最佳写法\n",[19,4174,4175,4187,4200,4213],{"__ignoreMap":216},[220,4176,4177,4179,4182,4184],{"class":222,"line":223},[220,4178,4142],{"class":226},[220,4180,4181],{"class":229}," u+x",[220,4183,4161],{"class":229},[220,4185,4186],{"class":233},"       # u=属主 +执行位\n",[220,4188,4189,4191,4194,4197],{"class":222,"line":276},[220,4190,4142],{"class":226},[220,4192,4193],{"class":229}," g+w",[220,4195,4196],{"class":229}," app.sh",[220,4198,4199],{"class":233},"          # g=属组 加写权限\n",[220,4201,4202,4204,4207,4210],{"class":222,"line":594},[220,4203,4142],{"class":226},[220,4205,4206],{"class":229}," o-rwx",[220,4208,4209],{"class":229}," private.conf",[220,4211,4212],{"class":233},"  # o=其他人 去掉全部权限；a=全部人\n",[220,4214,4215,4217,4220,4223,4225],{"class":222,"line":925},[220,4216,4142],{"class":226},[220,4218,4219],{"class":266}," -R",[220,4221,4222],{"class":229}," a+rX",[220,4224,894],{"class":229},[220,4226,4227],{"class":233},"    # 大写 X：只给目录或已是可执行文件加 x —— 修目录树的最佳写法\n",[15,4229,4230,4233,4234,4237],{},[19,4231,4232],{},"chmod -R a+rX \u002Fsrv\u002Fapp"," 是「让所有人能读，但只给目录和已有的可执行文件加执行位」的标准解法；写成 ",[19,4235,4236],{},"chmod -R 777"," 会把配置文件也变可执行，还会打开目录写权限。",[15,4239,4240],{},[27,4241,4242],{},"示例 3：特殊位",[211,4244,4246],{"className":213,"code":4245,"language":215,"meta":216,"style":216},"chmod u+s helper    # setuid：执行时以文件属主（root）身份，如 passwd\nchmod g+s dir       # setgid：目录里新建文件继承目录的组，团队共享目录用\nchmod o+t dir       # sticky：目录里只有属主能删自己文件，如 \u002Ftmp\n",[19,4247,4248,4261,4274],{"__ignoreMap":216},[220,4249,4250,4252,4255,4258],{"class":222,"line":223},[220,4251,4142],{"class":226},[220,4253,4254],{"class":229}," u+s",[220,4256,4257],{"class":229}," helper",[220,4259,4260],{"class":233},"    # setuid：执行时以文件属主（root）身份，如 passwd\n",[220,4262,4263,4265,4268,4271],{"class":222,"line":276},[220,4264,4142],{"class":226},[220,4266,4267],{"class":229}," g+s",[220,4269,4270],{"class":229}," dir",[220,4272,4273],{"class":233},"       # setgid：目录里新建文件继承目录的组，团队共享目录用\n",[220,4275,4276,4278,4281,4283],{"class":222,"line":594},[220,4277,4142],{"class":226},[220,4279,4280],{"class":229}," o+t",[220,4282,4270],{"class":229},[220,4284,4285],{"class":233},"       # sticky：目录里只有属主能删自己文件，如 \u002Ftmp\n",[15,4287,4288,4294,4295,4298,4299,3324,4302,662,4304,4306,4307,4310],{},[27,4289,4290,4293],{},[19,4291,4292],{},"chmod 777"," 的真实风险","：任何本地用户都能改你的代码和脚本（配合 cron 或 web 目录可直接提权），多数服务（sshd、nginx）还会因为权限过宽拒绝启动。正确替代顺序：先 ",[19,4296,4297],{},"chown -R appuser:appgroup"," 找对属主，再 ",[19,4300,4301],{},"chmod -R a+rX",[19,4303,4034],{},[19,4305,4097],{},"）给最小权限。setuid 文件如果被写权限打开，等于本机任何用户能拿到 root，排查：",[19,4308,4309],{},"find \u002F -xdev -perm -4000 -type f 2>\u002Fdev\u002Fnull","（顺便看看有没有不认识的）。",[46,4312,4314],{"id":4313},"_3-chown-与-chgrp-修改属主属组","3. chown 与 chgrp —— 修改属主属组",[15,4316,4317],{},[27,4318,4319],{},"示例 1：改属主、改属组",[211,4321,4323],{"className":213,"code":4322,"language":215,"meta":216,"style":216},"chown deploy:app app.py        # 同时改属主和属组；普通用户不能把文件送给别人，只有 root 能\nchgrp -R www-data logs         # 只改属组；比 chown 温和，不需要 root 也能改自己组的\nchown -R app:app \u002Fsrv\u002Fapp      # -R 递归整棵目录树\nchown -h link newtarget        # -h 改软链接本身，而不是它指向的文件\n",[19,4324,4325,4337,4353,4367],{"__ignoreMap":216},[220,4326,4327,4329,4332,4334],{"class":222,"line":223},[220,4328,3828],{"class":226},[220,4330,4331],{"class":229}," deploy:app",[220,4333,1545],{"class":229},[220,4335,4336],{"class":233},"        # 同时改属主和属组；普通用户不能把文件送给别人，只有 root 能\n",[220,4338,4339,4342,4344,4347,4350],{"class":222,"line":276},[220,4340,4341],{"class":226},"chgrp",[220,4343,4219],{"class":266},[220,4345,4346],{"class":229}," www-data",[220,4348,4349],{"class":229}," logs",[220,4351,4352],{"class":233},"         # 只改属组；比 chown 温和，不需要 root 也能改自己组的\n",[220,4354,4355,4357,4359,4362,4364],{"class":222,"line":594},[220,4356,3828],{"class":226},[220,4358,4219],{"class":266},[220,4360,4361],{"class":229}," app:app",[220,4363,894],{"class":229},[220,4365,4366],{"class":233},"      # -R 递归整棵目录树\n",[220,4368,4369,4371,4374,4377,4380],{"class":222,"line":925},[220,4370,3828],{"class":226},[220,4372,4373],{"class":266}," -h",[220,4375,4376],{"class":229}," link",[220,4378,4379],{"class":229}," newtarget",[220,4381,4382],{"class":233},"        # -h 改软链接本身，而不是它指向的文件\n",[15,4384,4385],{},[27,4386,4387],{},"示例 2：大范围改动的安全网",[211,4389,4391],{"className":213,"code":4390,"language":215,"meta":216,"style":216},"chown -R --from=root app \u002Fsrv\u002Fapp       # --from 只改当前属主是 root 的文件，避免误改新加的文件\nfind \u002Fsrv\u002Fapp ! -user deploy -print | head   # 递归前先用它跑一遍确认范围\n",[19,4392,4393,4410],{"__ignoreMap":216},[220,4394,4395,4397,4399,4402,4405,4407],{"class":222,"line":223},[220,4396,3828],{"class":226},[220,4398,4219],{"class":266},[220,4400,4401],{"class":266}," --from=root",[220,4403,4404],{"class":229}," app",[220,4406,894],{"class":229},[220,4408,4409],{"class":233},"       # --from 只改当前属主是 root 的文件，避免误改新加的文件\n",[220,4411,4412,4414,4416,4419,4422,4425,4427,4429,4432],{"class":222,"line":276},[220,4413,694],{"class":226},[220,4415,894],{"class":229},[220,4417,4418],{"class":229}," !",[220,4420,4421],{"class":266}," -user",[220,4423,4424],{"class":229}," deploy",[220,4426,1780],{"class":266},[220,4428,2626],{"class":2439},[220,4430,4431],{"class":226}," head",[220,4433,4434],{"class":233},"   # 递归前先用它跑一遍确认范围\n",[15,4436,4437],{},"改属主时的三个雷：",[1790,4439,4440,4446,4455],{},[813,4441,4442,4445],{},[19,4443,4444],{},"chown -R"," 在目录树里遇到别人正在写的数据库文件会打断服务——先停服务再改。",[813,4447,4448,4449,4452,4453,1520],{},"递归前",[27,4450,4451],{},"先跑一遍确认范围","（上面的 ",[19,4454,694],{},[813,4456,4457,34,4460,4463,4464,4467,4468,4470],{},[19,4458,4459],{},"\u002Fsrv",[19,4461,4462],{},"\u002Fvar\u002Fwww"," 这类由包管理器维护的目录，",[19,4465,4466],{},"chown -R root:root"," 之后可能连 ",[19,4469,653],{}," 都跑不动。",[46,4472,4474],{"id":4473},"_4-umask-新文件的默认权限","4. umask —— 新文件的默认权限",[15,4476,4477,4480,4481,4484,4485,4487,4488,616],{},[19,4478,4479],{},"umask"," 是「新建文件时要屏蔽掉的位」。文件最多 ",[19,4482,4483],{},"666","，目录最多 ",[19,4486,4112],{},"，然后按位与 ",[19,4489,4490],{},"~umask",[51,4492,4493,4508],{},[54,4494,4495],{},[57,4496,4497,4499,4502,4505],{},[60,4498,4479],{},[60,4500,4501],{},"新文件",[60,4503,4504],{},"新目录",[60,4506,4507],{},"场景",[70,4509,4510,4528,4546,4564],{},[57,4511,4512,4517,4521,4525],{},[75,4513,4514],{},[19,4515,4516],{},"022",[75,4518,4519],{},[19,4520,4019],{},[75,4522,4523],{},[19,4524,4034],{},[75,4526,4527],{},"默认（系统用户、多数发行版）",[57,4529,4530,4535,4539,4543],{},[75,4531,4532],{},[19,4533,4534],{},"002",[75,4536,4537],{},[19,4538,4082],{},[75,4540,4541],{},[19,4542,4097],{},[75,4544,4545],{},"团队协作组（配合共同属组）",[57,4547,4548,4553,4557,4561],{},[75,4549,4550],{},[19,4551,4552],{},"077",[75,4554,4555],{},[19,4556,4049],{},[75,4558,4559],{},[19,4560,4064],{},[75,4562,4563],{},"安全敏感环境",[57,4565,4566,4571,4576,4581],{},[75,4567,4568],{},[19,4569,4570],{},"027",[75,4572,4573],{},[19,4574,4575],{},"640",[75,4577,4578],{},[19,4579,4580],{},"750",[75,4582,4583],{},"常见服务器加固基线",[15,4585,4586],{},[27,4587,4588],{},"示例 1：查看与设置",[211,4590,4592],{"className":213,"code":4591,"language":215,"meta":216,"style":216},"umask        # 查看，输出如 0002\numask -S     # 符号形式（u=rwx,g=rx,o=），好读\numask 027    # 只对当前 Shell 及其子进程生效，写进 ~\u002F.bashrc 才持久\n",[19,4593,4594,4601,4610],{"__ignoreMap":216},[220,4595,4596,4598],{"class":222,"line":223},[220,4597,4479],{"class":266},[220,4599,4600],{"class":233},"        # 查看，输出如 0002\n",[220,4602,4603,4605,4607],{"class":222,"line":276},[220,4604,4479],{"class":266},[220,4606,2894],{"class":266},[220,4608,4609],{"class":233},"     # 符号形式（u=rwx,g=rx,o=），好读\n",[220,4611,4612,4614,4617],{"class":222,"line":594},[220,4613,4479],{"class":266},[220,4615,4616],{"class":266}," 027",[220,4618,4619],{"class":233},"    # 只对当前 Shell 及其子进程生效，写进 ~\u002F.bashrc 才持久\n",[15,4621,4622,4623,4625,4626,4629,4630,4632,4633,4635,4636,4639],{},"坑：",[19,4624,4479],{}," 只影响",[27,4627,4628],{},"新建","文件，不改动已存在的文件；用 ",[19,4631,1372],{}," 建了一半才发现 umask 不对，用 ",[19,4634,4301],{}," 补。systemd 服务可以单独设 ",[19,4637,4638],{},"UMask=0027","（在 unit 里），与登录 Shell 的 umask 无关——很多人卡在这里。",[46,4641,4643],{"id":4642},"_5-sudo-与-su-换用户的方式不同","5. sudo 与 su —— 换用户的方式不同",[51,4645,4646,4670],{},[54,4647,4648],{},[57,4649,4650,4652,4657,4665],{},[60,4651,1874],{},[60,4653,4654],{},[19,4655,4656],{},"sudo cmd",[60,4658,4659,1073,4662],{},[19,4660,4661],{},"sudo -i",[19,4663,4664],{},"su -",[60,4666,4667],{},[19,4668,4669],{},"su cmd",[70,4671,4672,4695,4715,4733,4747],{},[57,4673,4674,4677,4684,4692],{},[75,4675,4676],{},"需要谁的密码",[75,4678,4679,4680,4683],{},"通常",[27,4681,4682],{},"自己","的密码",[75,4685,4686,4688,4689,4691],{},[19,4687,4661],{}," 自己；",[19,4690,4664],{}," 目标用户（root）",[75,4693,4694],{},"目标用户",[57,4696,4697,4700,4706,4709],{},[75,4698,4699],{},"环境",[75,4701,4702,4703,1062],{},"保留大部分（",[19,4704,4705],{},"secure_path",[75,4707,4708],{},"完整登录环境",[75,4710,4711,4712,4714],{},"不完整（",[19,4713,4664],{}," 才完整）",[57,4716,4717,4720,4727,4730],{},[75,4718,4719],{},"审计",[75,4721,4722,4723,4726],{},"每条命令写进 ",[19,4724,4725],{},"\u002Fvar\u002Flog\u002Fauth.log"," 或 journal",[75,4728,4729],{},"记会话",[75,4731,4732],{},"只记会话",[57,4734,4735,4738,4741,4744],{},[75,4736,4737],{},"可精细授权",[75,4739,4740],{},"能（只允许某些命令）",[75,4742,4743],{},"等价于给整个 shell",[75,4745,4746],{},"否",[57,4748,4749,4752,4755,4758],{},[75,4750,4751],{},"典型用途",[75,4753,4754],{},"跑一条特权命令",[75,4756,4757],{},"长期在 root 下操作",[75,4759,4760],{},"容器里 root 切普通用户",[15,4762,4763],{},[27,4764,4765],{},"示例 1：常用写法",[211,4767,4769],{"className":213,"code":4768,"language":215,"meta":216,"style":216},"sudo !!                            # 上一条命令忘了加 sudo，重跑\nsudo -u appuser .\u002Fworker.sh        # 以某个服务用户身份跑（比给它 root 权限安全）\nsudo -i                            # 拿到 root 的登录 shell（读 \u002Froot\u002F.profile）\nsudo -l                            # 列出我当前被允许做什么（排查「为什么这个 sudo 不行」）\nsudo -v                            # 只刷新凭据缓存，测试密码对不对\nsudo --preserve-env=PATH cmd       # 需要保留自己的 PATH 时（默认 sudo 会重置）\n",[19,4770,4771,4781,4797,4806,4815,4824],{"__ignoreMap":216},[220,4772,4773,4775,4778],{"class":222,"line":223},[220,4774,120],{"class":226},[220,4776,4777],{"class":229}," !!",[220,4779,4780],{"class":233},"                            # 上一条命令忘了加 sudo，重跑\n",[220,4782,4783,4785,4788,4791,4794],{"class":222,"line":276},[220,4784,120],{"class":226},[220,4786,4787],{"class":266}," -u",[220,4789,4790],{"class":229}," appuser",[220,4792,4793],{"class":229}," .\u002Fworker.sh",[220,4795,4796],{"class":233},"        # 以某个服务用户身份跑（比给它 root 权限安全）\n",[220,4798,4799,4801,4803],{"class":222,"line":594},[220,4800,120],{"class":226},[220,4802,1593],{"class":266},[220,4804,4805],{"class":233},"                            # 拿到 root 的登录 shell（读 \u002Froot\u002F.profile）\n",[220,4807,4808,4810,4812],{"class":222,"line":925},[220,4809,120],{"class":226},[220,4811,3193],{"class":266},[220,4813,4814],{"class":233},"                            # 列出我当前被允许做什么（排查「为什么这个 sudo 不行」）\n",[220,4816,4817,4819,4821],{"class":222,"line":931},[220,4818,120],{"class":226},[220,4820,574],{"class":266},[220,4822,4823],{"class":233},"                            # 只刷新凭据缓存，测试密码对不对\n",[220,4825,4826,4828,4831,4834],{"class":222,"line":3528},[220,4827,120],{"class":226},[220,4829,4830],{"class":266}," --preserve-env=PATH",[220,4832,4833],{"class":229}," cmd",[220,4835,4836],{"class":233},"       # 需要保留自己的 PATH 时（默认 sudo 会重置）\n",[15,4838,4839],{},[27,4840,4841],{},"示例 2：改 sudoers 的正确流程",[15,4843,4844,465,4847,4853],{},[19,4845,4846],{},"\u002Fetc\u002Fsudoers",[27,4848,4849,4850,4852],{},"永远不要直接 ",[19,4851,3112],{}," 改","——语法错了会把所有 sudo 权限锁死。",[211,4855,4857],{"className":213,"code":4856,"language":215,"meta":216,"style":216},"sudo visudo -c                        # 先检查现有语法\nsudo visudo                           # 用 visudo 打开（编辑完保存前自动校验）\nsudo visudo -f \u002Fetc\u002Fsudoers.d\u002Fdeploy  # 最好：单独放一个 drop-in 文件\n",[19,4858,4859,4871,4880],{"__ignoreMap":216},[220,4860,4861,4863,4866,4868],{"class":222,"line":223},[220,4862,120],{"class":226},[220,4864,4865],{"class":229}," visudo",[220,4867,2494],{"class":266},[220,4869,4870],{"class":233},"                        # 先检查现有语法\n",[220,4872,4873,4875,4877],{"class":222,"line":276},[220,4874,120],{"class":226},[220,4876,4865],{"class":229},[220,4878,4879],{"class":233},"                           # 用 visudo 打开（编辑完保存前自动校验）\n",[220,4881,4882,4884,4886,4888,4891],{"class":222,"line":594},[220,4883,120],{"class":226},[220,4885,4865],{"class":229},[220,4887,2573],{"class":266},[220,4889,4890],{"class":229}," \u002Fetc\u002Fsudoers.d\u002Fdeploy",[220,4892,4893],{"class":233},"  # 最好：单独放一个 drop-in 文件\n",[15,4895,4896],{},"drop-in 里写权限要最小化：",[211,4898,4900],{"className":903,"code":4899,"language":905,"meta":216,"style":216},"deploy ALL=(ALL) NOPASSWD: \u002Fbin\u002Fsystemctl restart nginx, \u002Fbin\u002Fsystemctl status nginx\n",[19,4901,4902],{"__ignoreMap":216},[220,4903,4904],{"class":222,"line":223},[220,4905,4899],{},[15,4907,4908,4911,4912,4914,4915,689,4918,4921,4922,1284,4925,4928,4929,1083,4931,4933,4934,4937],{},[19,4909,4910],{},"visudo"," 默认用 ",[19,4913,3104],{},"；想换 ",[19,4916,4917],{},"EDITOR=nano sudo -E visudo",[19,4919,4920],{},"-E"," 保留环境变量，否则换不了）。sudoers 目录里",[27,4923,4924],{},"最后加载的文件生效",[19,4926,4927],{},"\u002Fetc\u002Fsudoers.d\u002F"," 里的覆盖主文件，命名别带 ",[19,4930,1082],{},[19,4932,96],{},"（会被忽略，",[19,4935,4936],{},"README"," 那种文件写权限进去会炸）。",[46,4939,4941],{"id":4940},"_6-特殊权限位一览","6. 特殊权限位一览",[51,4943,4944,4961],{},[54,4945,4946],{},[57,4947,4948,4951,4953,4956,4958],{},[60,4949,4950],{},"位",[60,4952,4005],{},[60,4954,4955],{},"表现",[60,4957,2692],{},[60,4959,4960],{},"常见例子",[70,4962,4963,4997,5024],{},[57,4964,4965,4968,4973,4981,4988],{},[75,4966,4967],{},"setuid",[75,4969,4970],{},[19,4971,4972],{},"4000",[75,4974,4975,4976,689,4978,1062],{},"属主执行位是 ",[19,4977,975],{},[19,4979,4980],{},"rwsr-xr-x",[75,4982,4983,4984,4987],{},"执行时以",[27,4985,4986],{},"文件属主","身份（root）",[75,4989,4990,34,4993,4996],{},[19,4991,4992],{},"passwd",[19,4994,4995],{},"ping","（旧式）",[57,4998,4999,5002,5007,5015,5018],{},[75,5000,5001],{},"setgid",[75,5003,5004],{},[19,5005,5006],{},"2000",[75,5008,5009,5010,689,5012,1062],{},"属组执行位是 ",[19,5011,975],{},[19,5013,5014],{},"rwxr-sr-x",[75,5016,5017],{},"文件：执行时以属组身份；目录：新建文件继承目录的组",[75,5019,5020,5021],{},"团队共享目录 ",[19,5022,5023],{},"chmod g+s \u002Fsrv\u002Fshared",[57,5025,5026,5029,5034,5041,5044],{},[75,5027,5028],{},"sticky",[75,5030,5031],{},[19,5032,5033],{},"1000",[75,5035,5036,5037,689,5039,1062],{},"其他人执行位是 ",[19,5038,1049],{},[19,5040,1061],{},[75,5042,5043],{},"目录里只有属主能删自己的文件",[75,5045,5046],{},[19,5047,1058],{},[46,5049,5051],{"id":5050},"_7-setfacl-getfacl-既要给第三个人读又不要给其他人","7. setfacl \u002F getfacl —— 「既要给第三个人读又不要给其他人」",[15,5053,5054],{},"普通权限只有三档，遇到「给某个组加读写、其他人不动」就需要 ACL：",[211,5056,5058],{"className":213,"code":5057,"language":215,"meta":216,"style":216},"getfacl \u002Fsrv\u002Fapp\u002Fdata                          # 查看（含默认 ACL）\nsetfacl -m u:backup:rx \u002Fsrv\u002Fapp\u002Fdata          # -m 加一条：给用户 backup 读+执行\nsetfacl -m d:g:deploy:rwX \u002Fsrv\u002Fshared         # d: 默认 ACL，新建文件自动继承\nsetfacl -x u:backup \u002Fsrv\u002Fapp\u002Fdata             # -x 删一条\nsetfacl -R -b \u002Fsrv\u002Fapp                        # -b 递归清空所有 ACL\n",[19,5059,5060,5071,5087,5102,5117],{"__ignoreMap":216},[220,5061,5062,5065,5068],{"class":222,"line":223},[220,5063,5064],{"class":226},"getfacl",[220,5066,5067],{"class":229}," \u002Fsrv\u002Fapp\u002Fdata",[220,5069,5070],{"class":233},"                          # 查看（含默认 ACL）\n",[220,5072,5073,5076,5079,5082,5084],{"class":222,"line":276},[220,5074,5075],{"class":226},"setfacl",[220,5077,5078],{"class":266}," -m",[220,5080,5081],{"class":229}," u:backup:rx",[220,5083,5067],{"class":229},[220,5085,5086],{"class":233},"          # -m 加一条：给用户 backup 读+执行\n",[220,5088,5089,5091,5093,5096,5099],{"class":222,"line":594},[220,5090,5075],{"class":226},[220,5092,5078],{"class":266},[220,5094,5095],{"class":229}," d:g:deploy:rwX",[220,5097,5098],{"class":229}," \u002Fsrv\u002Fshared",[220,5100,5101],{"class":233},"         # d: 默认 ACL，新建文件自动继承\n",[220,5103,5104,5106,5109,5112,5114],{"class":222,"line":925},[220,5105,5075],{"class":226},[220,5107,5108],{"class":266}," -x",[220,5110,5111],{"class":229}," u:backup",[220,5113,5067],{"class":229},[220,5115,5116],{"class":233},"             # -x 删一条\n",[220,5118,5119,5121,5123,5125,5127],{"class":222,"line":931},[220,5120,5075],{"class":226},[220,5122,4219],{"class":266},[220,5124,1685],{"class":266},[220,5126,894],{"class":229},[220,5128,5129],{"class":233},"                        # -b 递归清空所有 ACL\n",[15,5131,5132,5134,5135,5138,5139,5142,5143,5146,5147,5149,5150,5152],{},[19,5133,855],{}," 末尾出现 ",[19,5136,5137],{},"+","（如 ",[19,5140,5141],{},"-rw-rw-r--+","）就说明有 ACL。坑：文件系统必须挂载带 ",[19,5144,5145],{},"acl"," 选项（现代 ext4\u002Fxfs 默认支持），NFS 上还要服务端配合；容器里 ",[19,5148,5075],{}," 常因缺 ",[19,5151,5145],{}," 包报错。",[46,5154,5156],{"id":5155},"_8-为什么-permission-denied排查清单","8. 「为什么 Permission denied」排查清单",[51,5158,5159,5172],{},[54,5160,5161],{},[57,5162,5163,5166,5169],{},[60,5164,5165],{},"检查顺序",[60,5167,5168],{},"命令",[60,5170,5171],{},"判断",[70,5173,5174,5193,5213,5226,5252,5273,5292,5311],{},[57,5175,5176,5179,5184],{},[75,5177,5178],{},"1. 谁在执行这条操作",[75,5180,5181],{},[19,5182,5183],{},"ps -o user= -p $(pgrep -f worker)",[75,5185,5186,5187,5190,5191],{},"nginx 是 ",[19,5188,5189],{},"www-data","，你以为是 ",[19,5192,88],{},[57,5194,5195,5202,5207],{},[75,5196,5197,5198,5201],{},"2. 文件本身与",[27,5199,5200],{},"所有父目录","的权限",[75,5203,5204],{},[19,5205,5206],{},"namei -l \u002Fsrv\u002Fapp\u002Flogs\u002Fapp.log",[75,5208,5209,5210,5212],{},"一眼看全链路，父目录缺 ",[19,5211,3558],{}," 立刻现形",[57,5214,5215,5218,5223],{},[75,5216,5217],{},"3. 属主属组是否符合预期",[75,5219,5220],{},[19,5221,5222],{},"stat -c '%U %G %a' file",[75,5224,5225],{},"部署脚本用 root 建的文件，进程写不了",[57,5227,5228,5231,5239],{},[75,5229,5230],{},"4. 是否 SELinux\u002FAppArmor 拦的",[75,5232,5233,418,5236],{},[19,5234,5235],{},"sudo ausearch -m avc -ts recent",[19,5237,5238],{},"getenforce",[75,5240,5241,5242,689,5245,1062],{},"临时验证：",[19,5243,5244],{},"sudo setenforce 0",[27,5246,5247,5248,5251],{},"测完必须 ",[19,5249,5250],{},"setenforce 1"," 恢复",[57,5253,5254,5263,5268],{},[75,5255,5256,5257,187,5260],{},"5. 文件系统挂载是否 ",[19,5258,5259],{},"ro",[19,5261,5262],{},"noexec",[75,5264,5265],{},[19,5266,5267],{},"findmnt -T \u002Fsrv\u002Fapp",[75,5269,2405,5270,5272],{},[19,5271,5259],{}," 就是磁盘只读挂载（第十四章）",[57,5274,5275,5278,5283],{},[75,5276,5277],{},"6. 磁盘\u002Finode 满（报的是别的错但很像权限）",[75,5279,5280],{},[19,5281,5282],{},"df -h; df -i",[75,5284,5285,5288,5289],{},[19,5286,5287],{},"No space left on device"," vs ",[19,5290,5291],{},"Read-only file system",[57,5293,5294,5303,5308],{},[75,5295,5296,5297,5299,5300,5302],{},"7. 目录缺 ",[19,5298,3558],{},"、文件缺 ",[19,5301,3888],{}," 的具体组合",[75,5304,5305],{},[19,5306,5307],{},"sudo -u appuser cat \u002Fsrv\u002Fapp\u002Fx",[75,5309,5310],{},"用真实身份复现最快",[57,5312,5313,5316,5328],{},[75,5314,5315],{},"8. 服务被沙箱限制",[75,5317,5318,5321,5322,34,5325],{},[19,5319,5320],{},"systemctl cat myapp"," 看 ",[19,5323,5324],{},"ProtectSystem=",[19,5326,5327],{},"ReadWritePaths=",[75,5329,5330],{},"systemd 加固参数导致写不了",[15,5332,5333,5334,5336,5337,5340,5341,5343,5344,5347,5348,5351,5352,5355],{},"SELinux 一句话说明：",[19,5335,5238],{}," 返回 ",[19,5338,5339],{},"Enforcing"," 时，即使权限位全对也可能被安全上下文拒绝，典型表现是 nginx 读得了 ",[19,5342,186],{}," 里 644 的文件却报 403。正确修法不是关掉 SELinux，而是 ",[19,5345,5346],{},"ls -Z"," 看上下文、",[19,5349,5350],{},"semanage fcontext"," + ",[19,5353,5354],{},"restorecon"," 打标签。RHEL 系默认开，Debian\u002FUbuntu 默认没开。",[46,5357,808],{"id":5358},"踩坑提醒-4",[810,5360,5361,5372,5393,5405],{},[813,5362,5363,5364,5367,5368,5371],{},"改完组权限发现没生效：组成员变更",[27,5365,5366],{},"不会作用于已登录的会话","，重新登录或 ",[19,5369,5370],{},"newgrp 组名","（第十二章）。",[813,5373,5374,5377,5378,5381,5382,5385,5386,5389,5390,5392],{},[19,5375,5376],{},"chmod -R 755"," 把 ",[19,5379,5380],{},"~\u002F.ssh"," 也改了，结果 ",[19,5383,5384],{},"ssh"," 立刻连不上：",[19,5387,5388],{},"authorized_keys"," 要 600、",[19,5391,4074],{}," 要 700。",[813,5394,5395,5396,5398,5399,689,5401,5404],{},"目录给了 ",[19,5397,3910],{}," 没给 ",[19,5400,3558],{},[19,5402,5403],{},"-wx","）是极少见的组合：能创建\u002F删除文件但不能列出内容，排查时容易误判，一般不这么配。",[813,5406,5407,5408,5411,5412,5415],{},"权限问题一律用 ",[19,5409,5410],{},"namei -l 全路径"," 起手，比一层层 ",[19,5413,5414],{},"ls -ld"," 快且不会漏。",[10,5417,5419],{"id":5418},"六查找文件findlocategrep-r","六、查找文件：find、locate、grep -r",[15,5421,5422,5423,34,5425,34,5428,616,5430,5432,5433,616],{},"「这个日志到底在哪」「谁把配置塞到系统里的」「按名字找命令」——三类需求对应 ",[19,5424,694],{},[19,5426,5427],{},"locate",[19,5429,448],{},[19,5431,694],{}," 参数多但有固定套路：",[27,5434,5435,5436,5439,5440,662,5443,662,5446,5449,5450,662,5453,662,5456,1062],{},"先缩小范围（路径 + ",[19,5437,5438],{},"-maxdepth","），再定条件（",[19,5441,5442],{},"-name",[19,5444,5445],{},"-type",[19,5447,5448],{},"-mtime","），最后才加动作（",[19,5451,5452],{},"-print",[19,5454,5455],{},"-exec",[19,5457,5458],{},"-delete",[861,5460],{"height":863,"src":5461,"title":5462},"\u002Ftutorials\u002Flinux\u002Fdemos\u002F06-find.html","示例 06：find 的条件、动作与 -exec \u002F -delete 的差别",[46,5464,5466],{"id":5465},"_1-find-按名字找","1. find —— 按名字找",[15,5468,5469,5471,5472,5475,5476,5478,5479,5482,5483,5485],{},[19,5470,694],{}," 的基本格式是 ",[19,5473,5474],{},"find 起点 条件… 动作…","，条件之间默认是「与」，",[19,5477,3659],{}," 取反，",[19,5480,5481],{},"-o"," 或（注意 ",[19,5484,5481],{}," 要用括号分组）。",[15,5487,5488],{},[27,5489,5490],{},"示例 1：按文件名查",[211,5492,5494],{"className":213,"code":5493,"language":215,"meta":216,"style":216},"find \u002Fsrv\u002Fapp -name '*.log'     # -name 按 glob 匹配文件名；模式必须加引号，否则先被 Shell 展开就废了\nfind . -iname '*.conf'          # -iname 忽略大小写\nfind \u002Fetc -path '\u002Fetc\u002Fnginx\u002F*'  # -path 匹配全路径（glob）；-regex 是全路径正则（GNU）\n",[19,5495,5496,5511,5527],{"__ignoreMap":216},[220,5497,5498,5500,5502,5505,5508],{"class":222,"line":223},[220,5499,694],{"class":226},[220,5501,894],{"class":229},[220,5503,5504],{"class":266}," -name",[220,5506,5507],{"class":229}," '*.log'",[220,5509,5510],{"class":233},"     # -name 按 glob 匹配文件名；模式必须加引号，否则先被 Shell 展开就废了\n",[220,5512,5513,5515,5518,5521,5524],{"class":222,"line":276},[220,5514,694],{"class":226},[220,5516,5517],{"class":229}," .",[220,5519,5520],{"class":266}," -iname",[220,5522,5523],{"class":229}," '*.conf'",[220,5525,5526],{"class":233},"          # -iname 忽略大小写\n",[220,5528,5529,5531,5534,5537,5540],{"class":222,"line":594},[220,5530,694],{"class":226},[220,5532,5533],{"class":229}," \u002Fetc",[220,5535,5536],{"class":266}," -path",[220,5538,5539],{"class":229}," '\u002Fetc\u002Fnginx\u002F*'",[220,5541,5542],{"class":233},"  # -path 匹配全路径（glob）；-regex 是全路径正则（GNU）\n",[15,5544,5545],{},[27,5546,5547],{},"示例 2：先缩小范围（好习惯）",[211,5549,5551],{"className":213,"code":5550,"language":215,"meta":216,"style":216},"find \u002F -maxdepth 3 -name 'id_rsa'    # -maxdepth 限定层数，要放在条件最前面；放后面会先全扫一遍再截\nfind \u002F -xdev -name '*.env'           # -xdev 不跨文件系统，避免扫进 \u002Fproc、\u002Fmnt、备份盘\nfind . -path .\u002F.git -prune -o -name '*.js' -print   # -prune 跳过子树，比 -not -path 快很多\n",[19,5552,5553,5573,5590],{"__ignoreMap":216},[220,5554,5555,5557,5559,5562,5565,5567,5570],{"class":222,"line":223},[220,5556,694],{"class":226},[220,5558,3621],{"class":229},[220,5560,5561],{"class":266}," -maxdepth",[220,5563,5564],{"class":266}," 3",[220,5566,5504],{"class":266},[220,5568,5569],{"class":229}," 'id_rsa'",[220,5571,5572],{"class":233},"    # -maxdepth 限定层数，要放在条件最前面；放后面会先全扫一遍再截\n",[220,5574,5575,5577,5579,5582,5584,5587],{"class":222,"line":276},[220,5576,694],{"class":226},[220,5578,3621],{"class":229},[220,5580,5581],{"class":266}," -xdev",[220,5583,5504],{"class":266},[220,5585,5586],{"class":229}," '*.env'",[220,5588,5589],{"class":233},"           # -xdev 不跨文件系统，避免扫进 \u002Fproc、\u002Fmnt、备份盘\n",[220,5591,5592,5594,5596,5598,5601,5604,5607,5609,5612,5614],{"class":222,"line":594},[220,5593,694],{"class":226},[220,5595,5517],{"class":229},[220,5597,5536],{"class":266},[220,5599,5600],{"class":229}," .\u002F.git",[220,5602,5603],{"class":266}," -prune",[220,5605,5606],{"class":266}," -o",[220,5608,5504],{"class":266},[220,5610,5611],{"class":229}," '*.js'",[220,5613,1780],{"class":266},[220,5615,5616],{"class":233},"   # -prune 跳过子树，比 -not -path 快很多\n",[15,5618,5619],{},[27,5620,5621],{},"示例 3：按类型 \u002F 大小 \u002F 时间 \u002F 属主 \u002F 权限",[211,5623,5625],{"className":213,"code":5624,"language":215,"meta":216,"style":216},"find \u002Fvar -type l                      # -type：f 文件 d 目录 l 链接 s socket b\u002Fc 设备 p 管道\nfind . -size +100M                     # -size 按大小：+\u002F- 号，c 字节、k KiB、M MiB\nfind logs -mtime +7                    # -mtime 按修改天数：-7 七天以内，+7 超过七天，7 正好第七天（按 24h 计，不是日历天！）\nfind . -mmin -10                       # -mmin 按分钟：最近 10 分钟改过，排查「刚刚谁在写文件」很好用\nfind \u002Fetc -newer \u002Fetc\u002Fhostname         # -newer 比某个文件新，比 -mtime 精确；配合 touch -d 造时间锚点\nfind \u002Fhome -user deploy                # -user\u002F-group 按属主；-uid 1001 用于用户已被删除的情况\nfind \u002F -xdev -perm -4000 -type f       # -perm：-4000 至少含这些位（找 setuid），644 完全等于\nfind . -type d -empty                  # -empty 空文件\u002F空目录，清理部署留下的空目录\n",[19,5626,5627,5642,5657,5672,5687,5702,5716,5737],{"__ignoreMap":216},[220,5628,5629,5631,5634,5636,5639],{"class":222,"line":223},[220,5630,694],{"class":226},[220,5632,5633],{"class":229}," \u002Fvar",[220,5635,1775],{"class":266},[220,5637,5638],{"class":229}," l",[220,5640,5641],{"class":233},"                      # -type：f 文件 d 目录 l 链接 s socket b\u002Fc 设备 p 管道\n",[220,5643,5644,5646,5648,5651,5654],{"class":222,"line":276},[220,5645,694],{"class":226},[220,5647,5517],{"class":229},[220,5649,5650],{"class":266}," -size",[220,5652,5653],{"class":229}," +100M",[220,5655,5656],{"class":233},"                     # -size 按大小：+\u002F- 号，c 字节、k KiB、M MiB\n",[220,5658,5659,5661,5663,5666,5669],{"class":222,"line":594},[220,5660,694],{"class":226},[220,5662,4349],{"class":229},[220,5664,5665],{"class":266}," -mtime",[220,5667,5668],{"class":229}," +7",[220,5670,5671],{"class":233},"                    # -mtime 按修改天数：-7 七天以内，+7 超过七天，7 正好第七天（按 24h 计，不是日历天！）\n",[220,5673,5674,5676,5678,5681,5684],{"class":222,"line":925},[220,5675,694],{"class":226},[220,5677,5517],{"class":229},[220,5679,5680],{"class":266}," -mmin",[220,5682,5683],{"class":266}," -10",[220,5685,5686],{"class":233},"                       # -mmin 按分钟：最近 10 分钟改过，排查「刚刚谁在写文件」很好用\n",[220,5688,5689,5691,5693,5696,5699],{"class":222,"line":931},[220,5690,694],{"class":226},[220,5692,5533],{"class":229},[220,5694,5695],{"class":266}," -newer",[220,5697,5698],{"class":229}," \u002Fetc\u002Fhostname",[220,5700,5701],{"class":233},"         # -newer 比某个文件新，比 -mtime 精确；配合 touch -d 造时间锚点\n",[220,5703,5704,5706,5709,5711,5713],{"class":222,"line":3528},[220,5705,694],{"class":226},[220,5707,5708],{"class":229}," \u002Fhome",[220,5710,4421],{"class":266},[220,5712,4424],{"class":229},[220,5714,5715],{"class":233},"                # -user\u002F-group 按属主；-uid 1001 用于用户已被删除的情况\n",[220,5717,5718,5720,5722,5724,5727,5730,5732,5734],{"class":222,"line":3537},[220,5719,694],{"class":226},[220,5721,3621],{"class":229},[220,5723,5581],{"class":266},[220,5725,5726],{"class":266}," -perm",[220,5728,5729],{"class":266}," -4000",[220,5731,1775],{"class":266},[220,5733,1461],{"class":229},[220,5735,5736],{"class":233},"       # -perm：-4000 至少含这些位（找 setuid），644 完全等于\n",[220,5738,5739,5741,5743,5745,5748,5751],{"class":222,"line":3546},[220,5740,694],{"class":226},[220,5742,5517],{"class":229},[220,5744,1775],{"class":266},[220,5746,5747],{"class":229}," d",[220,5749,5750],{"class":266}," -empty",[220,5752,5753],{"class":233},"                  # -empty 空文件\u002F空目录，清理部署留下的空目录\n",[15,5755,5756,5757,5760],{},"想精确到「最近 7 天」，最直观的是 ",[19,5758,5759],{},"-newermt \"2026-09-27\"","（强烈推荐），不受 24 小时窗口影响。",[46,5762,5764],{"id":5763},"_2-find-的动作-print-exec-delete","2. find 的动作：-print、-exec、-delete",[15,5766,5767],{},[27,5768,5769],{},"示例 1：先看范围，再动手",[211,5771,5773],{"className":213,"code":5772,"language":215,"meta":216,"style":216},"find build -type f -print              # 每个结果一行\nfind . -name '*.tmp' -print0           # -print0 用 \\0 分隔，交给 xargs -0（第八章），处理怪文件名安全\n",[19,5774,5775,5790],{"__ignoreMap":216},[220,5776,5777,5779,5781,5783,5785,5787],{"class":222,"line":223},[220,5778,694],{"class":226},[220,5780,1375],{"class":229},[220,5782,1775],{"class":266},[220,5784,1461],{"class":229},[220,5786,1780],{"class":266},[220,5788,5789],{"class":233},"              # 每个结果一行\n",[220,5791,5792,5794,5796,5798,5801,5804],{"class":222,"line":276},[220,5793,694],{"class":226},[220,5795,5517],{"class":229},[220,5797,5504],{"class":266},[220,5799,5800],{"class":229}," '*.tmp'",[220,5802,5803],{"class":266}," -print0",[220,5805,5806],{"class":233},"           # -print0 用 \\0 分隔，交给 xargs -0（第八章），处理怪文件名安全\n",[15,5808,5809],{},[27,5810,5811],{},"示例 2：对结果执行命令",[211,5813,5815],{"className":213,"code":5814,"language":215,"meta":216,"style":216},"find . -name '*.tmp' -exec rm -f {} \\;         # -exec … \\; 每个文件 fork 一次；{} 替换为路径\nfind . -name '*.tmp' -exec chmod 644 {} +      # -exec … + 把多个路径塞进一次调用，删\u002Fchmod 大量文件时快得多\nfind . -name '*.jpg' -exec convert {} {}.png \\; # 文件少、命令重时用逐个执行\nfind . -name '*.tmp' -ok rm {} \\;              # -ok 每个都问一次，手生时兜底\n",[19,5816,5817,5844,5869,5895],{"__ignoreMap":216},[220,5818,5819,5821,5823,5825,5827,5830,5833,5835,5838,5841],{"class":222,"line":223},[220,5820,694],{"class":226},[220,5822,5517],{"class":229},[220,5824,5504],{"class":266},[220,5826,5800],{"class":229},[220,5828,5829],{"class":266}," -exec",[220,5831,5832],{"class":229}," rm",[220,5834,2573],{"class":266},[220,5836,5837],{"class":229}," {}",[220,5839,5840],{"class":266}," \\;",[220,5842,5843],{"class":233},"         # -exec … \\; 每个文件 fork 一次；{} 替换为路径\n",[220,5845,5846,5848,5850,5852,5854,5856,5859,5861,5863,5866],{"class":222,"line":276},[220,5847,694],{"class":226},[220,5849,5517],{"class":229},[220,5851,5504],{"class":266},[220,5853,5800],{"class":229},[220,5855,5829],{"class":266},[220,5857,5858],{"class":229}," chmod",[220,5860,4145],{"class":266},[220,5862,5837],{"class":229},[220,5864,5865],{"class":229}," +",[220,5867,5868],{"class":233},"      # -exec … + 把多个路径塞进一次调用，删\u002Fchmod 大量文件时快得多\n",[220,5870,5871,5873,5875,5877,5880,5882,5885,5887,5890,5892],{"class":222,"line":594},[220,5872,694],{"class":226},[220,5874,5517],{"class":229},[220,5876,5504],{"class":266},[220,5878,5879],{"class":229}," '*.jpg'",[220,5881,5829],{"class":266},[220,5883,5884],{"class":229}," convert",[220,5886,5837],{"class":229},[220,5888,5889],{"class":229}," {}.png",[220,5891,5840],{"class":266},[220,5893,5894],{"class":233}," # 文件少、命令重时用逐个执行\n",[220,5896,5897,5899,5901,5903,5905,5908,5910,5912,5914],{"class":222,"line":925},[220,5898,694],{"class":226},[220,5900,5517],{"class":229},[220,5902,5504],{"class":266},[220,5904,5800],{"class":229},[220,5906,5907],{"class":266}," -ok",[220,5909,5832],{"class":229},[220,5911,5837],{"class":229},[220,5913,5840],{"class":266},[220,5915,5916],{"class":233},"              # -ok 每个都问一次，手生时兜底\n",[15,5918,5919],{},[27,5920,5921],{},"示例 3：直接删",[211,5923,5925],{"className":213,"code":5924,"language":215,"meta":216,"style":216},"find logs -name '*.log' -mtime +7 -delete    # -delete 内置删除，不 fork，隐含 -depth（先删文件后删目录）\n",[19,5926,5927],{"__ignoreMap":216},[220,5928,5929,5931,5933,5935,5937,5939,5941,5944],{"class":222,"line":223},[220,5930,694],{"class":226},[220,5932,4349],{"class":229},[220,5934,5504],{"class":266},[220,5936,5507],{"class":229},[220,5938,5665],{"class":266},[220,5940,5668],{"class":229},[220,5942,5943],{"class":266}," -delete",[220,5945,5946],{"class":233},"    # -delete 内置删除，不 fork，隐含 -depth（先删文件后删目录）\n",[15,5948,5949,827,5951,5954],{},[19,5950,5458],{},[19,5952,5953],{},"-exec rm {} +"," 的差别值得记牢：",[51,5956,5957,5971],{},[54,5958,5959],{},[57,5960,5961,5963,5967],{},[60,5962,1874],{},[60,5964,5965],{},[19,5966,5458],{},[60,5968,5969],{},[19,5970,5953],{},[70,5972,5973,5986,6001,6018,6034,6048],{},[57,5974,5975,5978,5981],{},[75,5976,5977],{},"速度",[75,5979,5980],{},"快（无 fork，find 自己 unlink）",[75,5982,5983,5984],{},"每个批次 fork 一次 ",[19,5985,1701],{},[57,5987,5988,5991,5994],{},[75,5989,5990],{},"能否跑外部命令",[75,5992,5993],{},"不能",[75,5995,5996,5997,6000],{},"能（",[19,5998,5999],{},"-exec sh -c '…'"," 任意组合）",[57,6002,6003,6006,6015],{},[75,6004,6005],{},"隐含行为",[75,6007,6008,6009,1284,6012],{},"自动 ",[19,6010,6011],{},"-depth",[27,6013,6014],{},"先删文件后删目录",[75,6016,6017],{},"需自己保证顺序",[57,6019,6020,6022,6028],{},[75,6021,3879],{},[75,6023,6024,6025,1062],{},"只能删空目录（非空报 ",[19,6026,6027],{},"Directory not empty",[75,6029,6030,6033],{},[19,6031,6032],{},"rm -r"," 能连子树一起删",[57,6035,6036,6039,6042],{},[75,6037,6038],{},"误删风险",[75,6040,6041],{},"条件写错就当场删完",[75,6043,6044,6045,6047],{},"同样当场删，但可以换成 ",[19,6046,5452],{}," 先看",[57,6049,6050,6053,6061],{},[75,6051,6052],{},"安全建议",[75,6054,6055],{},[27,6056,6057,6058,6060],{},"永远先跑一遍去掉 ",[19,6059,5458],{}," 的版本",[75,6062,6063,6064,6067],{},"同上；",[19,6065,6066],{},"-exec rm -i"," 可加确认",[15,6069,6070,6071,827,6073,6075,6076,6079,6080,6082,6083,6086,6087,616],{},"危险的是 ",[19,6072,5458],{},[19,6074,5481],{}," 组合时优先级误解：",[19,6077,6078],{},"find . -name a -o -name b -delete"," 里 ",[19,6081,5458],{}," 只作用于 ",[19,6084,6085],{},"-name b","，要用括号 ",[19,6088,6089],{},"\\( -name a -o -name b \\) -delete",[15,6091,6092],{},[27,6093,6094],{},"示例 4：格式化输出做报表",[211,6096,6098],{"className":213,"code":6097,"language":215,"meta":216,"style":216},"find \u002F -xdev -type f -size +200M -printf '%s\\t%p\\n' | sort -rn | head\n# -printf 常用转义：%p 全路径、%f 文件名、%h 所在目录、%s 字节、%m 八进制权限、\n# %u\u002F%g 属主\u002F组、%TY-%Tm-%Td 日期、%i inode 号（找硬链接）——这是「谁占了空间」的组合拳\n",[19,6099,6100,6136,6141],{"__ignoreMap":216},[220,6101,6102,6104,6106,6108,6110,6112,6114,6117,6120,6123,6125,6128,6131,6133],{"class":222,"line":223},[220,6103,694],{"class":226},[220,6105,3621],{"class":229},[220,6107,5581],{"class":266},[220,6109,1775],{"class":266},[220,6111,1461],{"class":229},[220,6113,5650],{"class":266},[220,6115,6116],{"class":229}," +200M",[220,6118,6119],{"class":266}," -printf",[220,6121,6122],{"class":229}," '%s\\t%p\\n'",[220,6124,2626],{"class":2439},[220,6126,6127],{"class":226}," sort",[220,6129,6130],{"class":266}," -rn",[220,6132,2626],{"class":2439},[220,6134,6135],{"class":226}," head\n",[220,6137,6138],{"class":222,"line":276},[220,6139,6140],{"class":233},"# -printf 常用转义：%p 全路径、%f 文件名、%h 所在目录、%s 字节、%m 八进制权限、\n",[220,6142,6143],{"class":222,"line":594},[220,6144,6145],{"class":233},"# %u\u002F%g 属主\u002F组、%TY-%Tm-%Td 日期、%i inode 号（找硬链接）——这是「谁占了空间」的组合拳\n",[46,6147,6149],{"id":6148},"_3-locate-靠数据库快但不实时","3. locate —— 靠数据库，快但不实时",[15,6151,6152],{},[27,6153,6154],{},"示例 1：秒查全盘",[211,6156,6158],{"className":213,"code":6157,"language":215,"meta":216,"style":216},"locate nginx.conf        # 从数据库查，结果可能包含已删除的文件\nlocate -i 'id_rsa*'      # -i 忽略大小写 + glob；默认按子串匹配，-r '^\u002Fetc\u002F' 用正则\nlocate -b '\\.conf'       # -b 只按文件名基名匹配（不被当成路径一部分），少了误报\nsudo updatedb            # 刚建的文件查不到？手动重建数据库\n",[19,6159,6160,6170,6182,6194],{"__ignoreMap":216},[220,6161,6162,6164,6167],{"class":222,"line":223},[220,6163,5427],{"class":226},[220,6165,6166],{"class":229}," nginx.conf",[220,6168,6169],{"class":233},"        # 从数据库查，结果可能包含已删除的文件\n",[220,6171,6172,6174,6176,6179],{"class":222,"line":276},[220,6173,5427],{"class":226},[220,6175,1593],{"class":266},[220,6177,6178],{"class":229}," 'id_rsa*'",[220,6180,6181],{"class":233},"      # -i 忽略大小写 + glob；默认按子串匹配，-r '^\u002Fetc\u002F' 用正则\n",[220,6183,6184,6186,6188,6191],{"class":222,"line":594},[220,6185,5427],{"class":226},[220,6187,1685],{"class":266},[220,6189,6190],{"class":229}," '\\.conf'",[220,6192,6193],{"class":233},"       # -b 只按文件名基名匹配（不被当成路径一部分），少了误报\n",[220,6195,6196,6198,6201],{"class":222,"line":925},[220,6197,120],{"class":226},[220,6199,6200],{"class":229}," updatedb",[220,6202,6203],{"class":233},"            # 刚建的文件查不到？手动重建数据库\n",[15,6205,6206,6207,6210,6211,34,6214,6217,6218,6220,6221,6223,6224,6227,6228,6231,6232,6235,6236,616],{},"数据库由 ",[19,6208,6209],{},"\u002Fetc\u002Fupdatedb.conf"," 决定（",[19,6212,6213],{},"PRUNEFS",[19,6215,6216],{},"PRUNEPATHS","），NFS\u002FFUSE\u002F容器卷通常被排除，所以「",[19,6219,5427],{}," 找不到但 ",[19,6222,694],{}," 能找到」多半是这个原因。没装时：Debian ",[19,6225,6226],{},"apt install mlocate","、RHEL 系 ",[19,6229,6230],{},"dnf install mlocate","（新版是 ",[19,6233,6234],{},"plocate","）、Alpine 一般直接用 ",[19,6237,694],{},[46,6239,6241],{"id":6240},"_4-grep-r-按内容找文件","4. grep -r —— 按内容找文件",[15,6243,6244],{},[27,6245,6246],{},"示例 1：哪些文件里有某个词",[211,6248,6250],{"className":213,"code":6249,"language":215,"meta":216,"style":216},"grep -rl 'bind_port' \u002Fetc                  # -r 递归、-l 只列文件名\ngrep -rl --include='*.conf' 'listen' \u002Fetc  # 限制文件类型\ngrep -rL 'strict' conf.d                   # -L 反向：输出没有匹配的文件名\ngrep -rn 'bind_port' \u002Fetc                  # -n 显示行号，直接看匹配在哪一行\ngrep -rlZ 'A' dir | xargs -0 grep -l 'B'   # 找两个词都在的文件，注意 -Z\u002F-0 配对\n",[19,6251,6252,6267,6287,6303,6316],{"__ignoreMap":216},[220,6253,6254,6256,6259,6262,6264],{"class":222,"line":223},[220,6255,497],{"class":226},[220,6257,6258],{"class":266}," -rl",[220,6260,6261],{"class":229}," 'bind_port'",[220,6263,5533],{"class":229},[220,6265,6266],{"class":233},"                  # -r 递归、-l 只列文件名\n",[220,6268,6269,6271,6273,6276,6279,6282,6284],{"class":222,"line":276},[220,6270,497],{"class":226},[220,6272,6258],{"class":266},[220,6274,6275],{"class":266}," --include=",[220,6277,6278],{"class":229},"'*.conf'",[220,6280,6281],{"class":229}," 'listen'",[220,6283,5533],{"class":229},[220,6285,6286],{"class":233},"  # 限制文件类型\n",[220,6288,6289,6291,6294,6297,6300],{"class":222,"line":594},[220,6290,497],{"class":226},[220,6292,6293],{"class":266}," -rL",[220,6295,6296],{"class":229}," 'strict'",[220,6298,6299],{"class":229}," conf.d",[220,6301,6302],{"class":233},"                   # -L 反向：输出没有匹配的文件名\n",[220,6304,6305,6307,6309,6311,6313],{"class":222,"line":925},[220,6306,497],{"class":226},[220,6308,6130],{"class":266},[220,6310,6261],{"class":229},[220,6312,5533],{"class":229},[220,6314,6315],{"class":233},"                  # -n 显示行号，直接看匹配在哪一行\n",[220,6317,6318,6320,6323,6326,6328,6330,6333,6336,6338,6340,6343],{"class":222,"line":931},[220,6319,497],{"class":226},[220,6321,6322],{"class":266}," -rlZ",[220,6324,6325],{"class":229}," 'A'",[220,6327,4270],{"class":229},[220,6329,2626],{"class":2439},[220,6331,6332],{"class":226}," xargs",[220,6334,6335],{"class":266}," -0",[220,6337,315],{"class":229},[220,6339,3193],{"class":266},[220,6341,6342],{"class":229}," 'B'",[220,6344,6345],{"class":233},"   # 找两个词都在的文件，注意 -Z\u002F-0 配对\n",[15,6347,6348,6349,6352,6353,6356,6357,6360],{},"装了 ",[19,6350,6351],{},"ripgrep"," 的话 ",[19,6354,6355],{},"rg -l bind_port"," 默认尊重 ",[19,6358,6359],{},".gitignore"," 且快一个数量级，开发机上推荐；服务器上不一定有，别写进部署脚本。",[46,6362,6364],{"id":6363},"_5-查命令在哪which-whereis-type","5. 查命令在哪：which \u002F whereis \u002F type",[51,6366,6367,6381],{},[54,6368,6369],{},[57,6370,6371,6373,6376,6378],{},[60,6372,5168],{},[60,6374,6375],{},"会找到",[60,6377,443],{},[60,6379,6380],{},"局限",[70,6382,6383,6403,6419,6436,6456,6481],{},[57,6384,6385,6390,6395,6400],{},[75,6386,6387],{},[19,6388,6389],{},"which python3",[75,6391,6392,6394],{},[19,6393,511],{}," 里的可执行文件",[75,6396,6397],{},[19,6398,6399],{},"\u002Fusr\u002Fbin\u002Fpython3",[75,6401,6402],{},"看不到别名、函数、内建；不是 POSIX 标准",[57,6404,6405,6410,6413,6416],{},[75,6406,6407],{},[19,6408,6409],{},"type -a python3",[75,6411,6412],{},"全部来源（别名\u002F函数\u002F内建\u002F文件）",[75,6414,6415],{},"三条一起列出",[75,6417,6418],{},"交互式排查首选",[57,6420,6421,6426,6429,6433],{},[75,6422,6423],{},[19,6424,6425],{},"command -v python3",[75,6427,6428],{},"只输出路径（脚本标准写法）",[75,6430,6431],{},[19,6432,6399],{},[75,6434,6435],{},"内建会输出名字本身",[57,6437,6438,6443,6446,6451],{},[75,6439,6440],{},[19,6441,6442],{},"whereis nginx",[75,6444,6445],{},"二进制 + 源码 + man 页",[75,6447,6448],{},[19,6449,6450],{},"\u002Fusr\u002Fsbin\u002Fnginx \u002Fusr\u002Fshare\u002Fman\u002Fman8\u002Fnginx.8.gz",[75,6452,6453,6454],{},"按固定目录猜，不看 ",[19,6455,511],{},[57,6457,6458,6463,6466,6475],{},[75,6459,6460],{},[19,6461,6462],{},"apropos backup",[75,6464,6465],{},"帮助文本里含关键词的命令名",[75,6467,6468,34,6471,6474],{},[19,6469,6470],{},"rsync",[19,6472,6473],{},"dump","…",[75,6476,2184,6477,6480],{},[19,6478,6479],{},"man-db"," 索引存在",[57,6482,6483,6488,6491,6496],{},[75,6484,6485],{},[19,6486,6487],{},"readlink -f $(command -v python3)",[75,6489,6490],{},"剥开所有软链接到真实文件",[75,6492,6493],{},[19,6494,6495],{},"\u002Fusr\u002Fbin\u002Fpython3.12",[75,6497,6498],{},"排查 alternatives 时常用",[46,6500,808],{"id":6501},"踩坑提醒-5",[810,6503,6504,6519,6531,6547],{},[813,6505,6506,6508,6509,6511,6512,6515,6516,6518],{},[19,6507,694],{}," 的 ",[19,6510,5442],{}," 忘加引号：",[19,6513,6514],{},"find \u002F -name *.log"," 会被当前目录里的 ",[19,6517,2118],{}," 先展开，结果扫的是无关名字。",[813,6520,6521,6524,6525,187,6528,6530],{},[19,6522,6523],{},"-mtime -7"," 与「最近 7 天」的直觉不完全一致（按 24 小时窗口而非日历天），要精确到分钟用 ",[19,6526,6527],{},"-mmin -10080",[19,6529,5759],{},"（后者最直观，强烈推荐）。",[813,6532,6533,6535,6536,6539,6540,6543,6544,616],{},[19,6534,694],{}," 输出直接喂给 ",[19,6537,6538],{},"xargs"," 会被空格\u002F换行坑到，一律 ",[19,6541,6542],{},"-print0"," 配 ",[19,6545,6546],{},"xargs -0",[813,6548,508,6549,6551,6552,6554,6555,6557,6558,6561,6562,6565],{},[19,6550,662],{}," 上跑 ",[19,6553,694],{}," 会大量报 ",[19,6556,3851],{},"：加 ",[19,6559,6560],{},"2>\u002Fdev\u002Fnull"," 静音，或用 ",[19,6563,6564],{},"-xdev"," 限制在当前文件系统。",[10,6567,6569],{"id":6568},"七文本处理三剑客grepsedawk","七、文本处理三剑客：grep、sed、awk",[15,6571,6572,6573,6575,6576,6578,6579,6582,6583,34,6586,34,6589,34,6592,34,6594,6597,6598,827,6600,6602,6603,616],{},"日志分析、配置批改、数据统计，靠的就是 ",[19,6574,497],{},"（找）、",[19,6577,697],{},"（改行）、",[19,6580,6581],{},"awk","（按列算）。再加五个小工具 ",[19,6584,6585],{},"sort",[19,6587,6588],{},"uniq",[19,6590,6591],{},"cut",[19,6593,57],{},[19,6595,6596],{},"wc","，能解决 95% 的「文本加工」需求。",[19,6599,6581],{},[19,6601,697],{}," 的分工一句话：",[27,6604,6605],{},"改文本用 sed，算数据用 awk",[861,6607],{"height":6608,"src":6609,"title":6610},700,"\u002Ftutorials\u002Flinux\u002Fdemos\u002F07-text-tools.html","示例 07：grep \u002F sed \u002F awk 三连与日志统计",[46,6612,6614],{"id":6613},"_1-grep-查找匹配的行","1. grep —— 查找匹配的行",[15,6616,6617,6619,6620,616],{},[19,6618,497],{}," 基本格式：",[19,6621,6622],{},"grep [选项] 模式 文件…",[15,6624,6625],{},[27,6626,6627],{},"示例 1：基础查找",[211,6629,6631],{"className":213,"code":6630,"language":215,"meta":216,"style":216},"grep 'ERROR' app.log         # 输出包含 ERROR 的行\ngrep -i error app.log        # -i 忽略大小写\ngrep -n 'listen' nginx.conf  # -n 显示行号\ngrep -c 'ERROR' app.log      # -c 只输出匹配的行数\ngrep -w 'id' data.txt        # -w 整词匹配，不会命中 width\ngrep -v '^#' app.conf        # -v 反选（排除），常用它去注释\ngrep -F '1.2.3' version.txt  # -F 按字面字符串匹配（不当正则），最快最安全\ngrep -m 1 'Start' big.log    # -m 1 匹配到第一条就停，大文件里找开头很有用\n",[19,6632,6633,6645,6659,6672,6685,6700,6714,6729],{"__ignoreMap":216},[220,6634,6635,6637,6640,6642],{"class":222,"line":223},[220,6636,497],{"class":226},[220,6638,6639],{"class":229}," 'ERROR'",[220,6641,1445],{"class":229},[220,6643,6644],{"class":233},"         # 输出包含 ERROR 的行\n",[220,6646,6647,6649,6651,6654,6656],{"class":222,"line":276},[220,6648,497],{"class":226},[220,6650,1593],{"class":266},[220,6652,6653],{"class":229}," error",[220,6655,1445],{"class":229},[220,6657,6658],{"class":233},"        # -i 忽略大小写\n",[220,6660,6661,6663,6665,6667,6669],{"class":222,"line":594},[220,6662,497],{"class":226},[220,6664,1669],{"class":266},[220,6666,6281],{"class":229},[220,6668,6166],{"class":229},[220,6670,6671],{"class":233},"  # -n 显示行号\n",[220,6673,6674,6676,6678,6680,6682],{"class":222,"line":925},[220,6675,497],{"class":226},[220,6677,2494],{"class":266},[220,6679,6639],{"class":229},[220,6681,1445],{"class":229},[220,6683,6684],{"class":233},"      # -c 只输出匹配的行数\n",[220,6686,6687,6689,6692,6695,6697],{"class":222,"line":931},[220,6688,497],{"class":226},[220,6690,6691],{"class":266}," -w",[220,6693,6694],{"class":229}," 'id'",[220,6696,2524],{"class":229},[220,6698,6699],{"class":233},"        # -w 整词匹配，不会命中 width\n",[220,6701,6702,6704,6706,6709,6711],{"class":222,"line":3528},[220,6703,497],{"class":226},[220,6705,574],{"class":266},[220,6707,6708],{"class":229}," '^#'",[220,6710,4148],{"class":229},[220,6712,6713],{"class":233},"        # -v 反选（排除），常用它去注释\n",[220,6715,6716,6718,6720,6723,6726],{"class":222,"line":3537},[220,6717,497],{"class":226},[220,6719,2589],{"class":266},[220,6721,6722],{"class":229}," '1.2.3'",[220,6724,6725],{"class":229}," version.txt",[220,6727,6728],{"class":233},"  # -F 按字面字符串匹配（不当正则），最快最安全\n",[220,6730,6731,6733,6735,6737,6740,6743],{"class":222,"line":3546},[220,6732,497],{"class":226},[220,6734,5078],{"class":266},[220,6736,267],{"class":266},[220,6738,6739],{"class":229}," 'Start'",[220,6741,6742],{"class":229}," big.log",[220,6744,6745],{"class":233},"    # -m 1 匹配到第一条就停，大文件里找开头很有用\n",[15,6747,6748],{},[27,6749,6750],{},"示例 2：正则匹配",[211,6752,6754],{"className":213,"code":6753,"language":215,"meta":216,"style":216},"grep -E 'ERROR|FATAL' app.log                  # -E 扩展正则，或直接用 |（等价 egrep，但别再用 egrep）\ngrep -oE '[0-9]{1,3}(\\.[0-9]{1,3}){3}' access.log  # -o 只输出匹配到的部分——抠 IP\ngrep '\\broot\\b' pass.conf                      # \\b 词边界（GNU）\ngrep '[[:space:]]$' conf                       # 找行尾空格（[[:digit:]] 类写法不受 locale 影响）\n",[19,6755,6756,6770,6785,6798],{"__ignoreMap":216},[220,6757,6758,6760,6762,6765,6767],{"class":222,"line":223},[220,6759,497],{"class":226},[220,6761,3032],{"class":266},[220,6763,6764],{"class":229}," 'ERROR|FATAL'",[220,6766,1445],{"class":229},[220,6768,6769],{"class":233},"                  # -E 扩展正则，或直接用 |（等价 egrep，但别再用 egrep）\n",[220,6771,6772,6774,6777,6780,6782],{"class":222,"line":276},[220,6773,497],{"class":226},[220,6775,6776],{"class":266}," -oE",[220,6778,6779],{"class":229}," '[0-9]{1,3}(\\.[0-9]{1,3}){3}'",[220,6781,2454],{"class":229},[220,6783,6784],{"class":233},"  # -o 只输出匹配到的部分——抠 IP\n",[220,6786,6787,6789,6792,6795],{"class":222,"line":594},[220,6788,497],{"class":226},[220,6790,6791],{"class":229}," '\\broot\\b'",[220,6793,6794],{"class":229}," pass.conf",[220,6796,6797],{"class":233},"                      # \\b 词边界（GNU）\n",[220,6799,6800,6802,6805,6808],{"class":222,"line":925},[220,6801,497],{"class":226},[220,6803,6804],{"class":229}," '[[:space:]]$'",[220,6806,6807],{"class":229}," conf",[220,6809,6810],{"class":233},"                       # 找行尾空格（[[:digit:]] 类写法不受 locale 影响）\n",[15,6812,6813,6814,6816],{},"正则元字符记忆表（ERE，即 ",[19,6815,4920],{}," 模式）：",[51,6818,6819,6829],{},[54,6820,6821],{},[57,6822,6823,6825,6827],{},[60,6824,2033],{},[60,6826,68],{},[60,6828,443],{},[70,6830,6831,6851,6870,6886,6901,6917],{},[57,6832,6833,6843,6846],{},[75,6834,6835,465,6837,465,6839,465,6841],{},[19,6836,1082],{},[19,6838,2436],{},[19,6840,5137],{},[19,6842,3465],{},[75,6844,6845],{},"任意字符 \u002F 0 或多 \u002F 1 或多 \u002F 0 或 1",[75,6847,6848],{},[19,6849,6850],{},"^GET .* 200$",[57,6852,6853,6861,6864],{},[75,6854,6855,34,6858],{},[19,6856,6857],{},"[0-9]",[19,6859,6860],{},"[[:digit:]]",[75,6862,6863],{},"字符类（后者不受 locale 影响）",[75,6865,6866,6869],{},[19,6867,6868],{},"grep '[[:space:]]$' conf"," 找行尾空格",[57,6871,6872,6877,6880],{},[75,6873,6874],{},[19,6875,6876],{},"{n,m}",[75,6878,6879],{},"出现次数区间",[75,6881,6882,6885],{},[19,6883,6884],{},"grep -E '[0-9]{1,3}(\\.[0-9]{1,3}){3}'"," 粗查 IP",[57,6887,6888,6893,6896],{},[75,6889,6890],{},[19,6891,6892],{},"|",[75,6894,6895],{},"或",[75,6897,6898],{},[19,6899,6900],{},"-E 'warn|error'",[57,6902,6903,6909,6911],{},[75,6904,6905,465,6907],{},[19,6906,164],{},[19,6908,99],{},[75,6910,3305],{},[75,6912,6913,6916],{},[19,6914,6915],{},"grep -c '^$'"," 数空行",[57,6918,6919,6924,6927],{},[75,6920,6921],{},[19,6922,6923],{},"\\b",[75,6925,6926],{},"词边界（GNU）",[75,6928,6929],{},[19,6930,6931],{},"grep '\\broot\\b'",[15,6933,6934],{},[27,6935,6936],{},"示例 3：上下文与多文件",[211,6938,6940],{"className":213,"code":6939,"language":215,"meta":216,"style":216},"grep -C 3 'Traceback' app.log    # -C 3 上下各 3 行；-A 只有后、-B 只有前\ngrep -rl 'TODO' src              # -l 只输出文件名\ngrep -q 'pattern' f && echo 有    # -q 不输出只给状态码，脚本判断「有没有搜到」专用\ntail -f app.log | grep --line-buffered ERROR   # 实时管道必备 --line-buffered\n",[19,6941,6942,6958,6973,6997],{"__ignoreMap":216},[220,6943,6944,6946,6948,6950,6953,6955],{"class":222,"line":223},[220,6945,497],{"class":226},[220,6947,2971],{"class":266},[220,6949,5564],{"class":266},[220,6951,6952],{"class":229}," 'Traceback'",[220,6954,1445],{"class":229},[220,6956,6957],{"class":233},"    # -C 3 上下各 3 行；-A 只有后、-B 只有前\n",[220,6959,6960,6962,6964,6967,6970],{"class":222,"line":276},[220,6961,497],{"class":226},[220,6963,6258],{"class":266},[220,6965,6966],{"class":229}," 'TODO'",[220,6968,6969],{"class":229}," src",[220,6971,6972],{"class":233},"              # -l 只输出文件名\n",[220,6974,6975,6977,6980,6983,6985,6989,6991,6994],{"class":222,"line":594},[220,6976,497],{"class":226},[220,6978,6979],{"class":266}," -q",[220,6981,6982],{"class":229}," 'pattern'",[220,6984,1461],{"class":229},[220,6986,6988],{"class":6987},"s95oV"," && ",[220,6990,468],{"class":266},[220,6992,6993],{"class":229}," 有",[220,6995,6996],{"class":233},"    # -q 不输出只给状态码，脚本判断「有没有搜到」专用\n",[220,6998,6999,7001,7003,7005,7007,7009,7011,7014],{"class":222,"line":925},[220,7000,2546],{"class":226},[220,7002,2573],{"class":266},[220,7004,1445],{"class":229},[220,7006,2626],{"class":2439},[220,7008,315],{"class":226},[220,7010,2631],{"class":266},[220,7012,7013],{"class":229}," ERROR",[220,7015,7016],{"class":233},"   # 实时管道必备 --line-buffered\n",[46,7018,7020],{"id":7019},"_2-sed-按行替换与删除","2. sed —— 按行替换与删除",[15,7022,7023,7025,7026,616],{},[19,7024,697],{}," 把输入按行读进「模式空间」，执行脚本后输出。核心是 ",[19,7027,7028],{},"s\u002F找\u002F换\u002F标志",[15,7030,7031],{},[27,7032,7033],{},"示例 1：替换",[211,7035,7037],{"className":213,"code":7036,"language":215,"meta":216,"style":216},"sed 's\u002Fold\u002Fnew\u002F' file         # 每行第一个匹配替换；只输出到终端，不改文件\nsed 's\u002Fold\u002Fnew\u002Fg' file        # g 每行全部替换——不加 g 是常见遗漏\nsed 's\\|\u002Fa\u002Fb\\|\u002Fa\u002Fc\\|g' file   # 换分隔符避开路径里的 \u002F，也可用 #、@\nsed 's\u002Fx\u002Fy\u002F2' file            # 只替换每行第 2 个匹配\nsed -E 's\u002F([0-9]+)\u002F[\\1]\u002Fg'    # -E 扩展正则；() 分组、\\1 回引用、& 代表整段匹配\n",[19,7038,7039,7051,7063,7075,7087],{"__ignoreMap":216},[220,7040,7041,7043,7046,7048],{"class":222,"line":223},[220,7042,697],{"class":226},[220,7044,7045],{"class":229}," 's\u002Fold\u002Fnew\u002F'",[220,7047,2376],{"class":229},[220,7049,7050],{"class":233},"         # 每行第一个匹配替换；只输出到终端，不改文件\n",[220,7052,7053,7055,7058,7060],{"class":222,"line":276},[220,7054,697],{"class":226},[220,7056,7057],{"class":229}," 's\u002Fold\u002Fnew\u002Fg'",[220,7059,2376],{"class":229},[220,7061,7062],{"class":233},"        # g 每行全部替换——不加 g 是常见遗漏\n",[220,7064,7065,7067,7070,7072],{"class":222,"line":594},[220,7066,697],{"class":226},[220,7068,7069],{"class":229}," 's\\|\u002Fa\u002Fb\\|\u002Fa\u002Fc\\|g'",[220,7071,2376],{"class":229},[220,7073,7074],{"class":233},"   # 换分隔符避开路径里的 \u002F，也可用 #、@\n",[220,7076,7077,7079,7082,7084],{"class":222,"line":925},[220,7078,697],{"class":226},[220,7080,7081],{"class":229}," 's\u002Fx\u002Fy\u002F2'",[220,7083,2376],{"class":229},[220,7085,7086],{"class":233},"            # 只替换每行第 2 个匹配\n",[220,7088,7089,7091,7093,7096],{"class":222,"line":931},[220,7090,697],{"class":226},[220,7092,3032],{"class":266},[220,7094,7095],{"class":229}," 's\u002F([0-9]+)\u002F[\\1]\u002Fg'",[220,7097,7098],{"class":233},"    # -E 扩展正则；() 分组、\\1 回引用、& 代表整段匹配\n",[15,7100,7101,7102,7104,7105,465,7107,465,7109,7112,7113,465,7116,465,7119,1284,7122,7124],{},"注意 BRE（默认）与 ERE（",[19,7103,4920],{},"）的区别：BRE 里 ",[19,7106,5137],{},[19,7108,3465],{},[19,7110,7111],{},"{}"," 要写成 ",[19,7114,7115],{},"\\+",[19,7117,7118],{},"\\?",[19,7120,7121],{},"\\{",[19,7123,4920],{}," 后反过来不转义。",[15,7126,7127],{},[27,7128,7129],{},"示例 2：取行与删行",[211,7131,7133],{"className":213,"code":7132,"language":215,"meta":216,"style":216},"sed -n '10,20p' file      # -n 关掉默认输出 + p 只打印 10–20 行\nsed '5d' file             # d 删第 5 行；'5,9d' 删区间\nsed '\u002F^$\u002Fd' file          # 删空行（匹配用 \u002F正则\u002F 代替行号）\nsed '\u002Fpattern\u002Fd' file     # 删含 pattern 的行；想删它的下一行用 pattern\u002F{n;d}\nsed -n '\u002FERROR\u002F,+3p' file # 匹配行及其后 3 行（GNU 扩展 +N）\n",[19,7134,7135,7149,7161,7173,7185],{"__ignoreMap":216},[220,7136,7137,7139,7141,7144,7146],{"class":222,"line":223},[220,7138,697],{"class":226},[220,7140,1669],{"class":266},[220,7142,7143],{"class":229}," '10,20p'",[220,7145,2376],{"class":229},[220,7147,7148],{"class":233},"      # -n 关掉默认输出 + p 只打印 10–20 行\n",[220,7150,7151,7153,7156,7158],{"class":222,"line":276},[220,7152,697],{"class":226},[220,7154,7155],{"class":229}," '5d'",[220,7157,2376],{"class":229},[220,7159,7160],{"class":233},"             # d 删第 5 行；'5,9d' 删区间\n",[220,7162,7163,7165,7168,7170],{"class":222,"line":594},[220,7164,697],{"class":226},[220,7166,7167],{"class":229}," '\u002F^$\u002Fd'",[220,7169,2376],{"class":229},[220,7171,7172],{"class":233},"          # 删空行（匹配用 \u002F正则\u002F 代替行号）\n",[220,7174,7175,7177,7180,7182],{"class":222,"line":925},[220,7176,697],{"class":226},[220,7178,7179],{"class":229}," '\u002Fpattern\u002Fd'",[220,7181,2376],{"class":229},[220,7183,7184],{"class":233},"     # 删含 pattern 的行；想删它的下一行用 pattern\u002F{n;d}\n",[220,7186,7187,7189,7191,7194,7196],{"class":222,"line":931},[220,7188,697],{"class":226},[220,7190,1669],{"class":266},[220,7192,7193],{"class":229}," '\u002FERROR\u002F,+3p'",[220,7195,2376],{"class":229},[220,7197,7198],{"class":233}," # 匹配行及其后 3 行（GNU 扩展 +N）\n",[15,7200,7201],{},[27,7202,7203],{},"示例 3：原地修改（先留备份！）",[211,7205,7207],{"className":213,"code":7206,"language":215,"meta":216,"style":216},"cp \u002Fetc\u002Fssh\u002Fsshd_config \u002Fetc\u002Fssh\u002Fsshd_config.$(date +%F)\nsed -i 's\u002F^# *PasswordAuthentication yes\u002FPasswordAuthentication no\u002F' \u002Fetc\u002Fssh\u002Fsshd_config\ndiff \u002Fetc\u002Fssh\u002Fsshd_config.$(date +%F) \u002Fetc\u002Fssh\u002Fsshd_config\n",[19,7208,7209,7231,7243],{"__ignoreMap":216},[220,7210,7211,7213,7216,7219,7222,7225,7228],{"class":222,"line":223},[220,7212,1542],{"class":226},[220,7214,7215],{"class":229}," \u002Fetc\u002Fssh\u002Fsshd_config",[220,7217,7218],{"class":229}," \u002Fetc\u002Fssh\u002Fsshd_config.",[220,7220,7221],{"class":6987},"$(",[220,7223,7224],{"class":226},"date",[220,7226,7227],{"class":229}," +%F",[220,7229,7230],{"class":6987},")\n",[220,7232,7233,7235,7237,7240],{"class":222,"line":276},[220,7234,697],{"class":226},[220,7236,1593],{"class":266},[220,7238,7239],{"class":229}," 's\u002F^# *PasswordAuthentication yes\u002FPasswordAuthentication no\u002F'",[220,7241,7242],{"class":229}," \u002Fetc\u002Fssh\u002Fsshd_config\n",[220,7244,7245,7248,7250,7252,7254,7256,7259],{"class":222,"line":594},[220,7246,7247],{"class":226},"diff",[220,7249,7218],{"class":229},[220,7251,7221],{"class":6987},[220,7253,7224],{"class":226},[220,7255,7227],{"class":229},[220,7257,7258],{"class":6987},") ",[220,7260,7261],{"class":229},"\u002Fetc\u002Fssh\u002Fsshd_config\n",[15,7263,7264,7266,7267,7270,7271,7273,7274,7277,7278,616,7280,7282,7283,7286,7287,7290,7291,7293],{},[19,7265,1858],{}," 表示 in-place 原地修改，写成 ",[19,7268,7269],{},"-i.bak"," 会自动留一份备份。差异提醒：BusyBox 不支持 ",[19,7272,1858],{}," 带后缀（写成 ",[19,7275,7276],{},"-i -e","）；macOS 必须给 ",[19,7279,788],{},[19,7281,1858],{}," 会",[27,7284,7285],{},"重建文件","（新 inode），所以正在被 ",[19,7288,7289],{},"tail -F"," 跟的日志、以硬链接方式共享的文件、被进程按 inode 持有的文件都会受影响；nginx 之类的配置文件改完必须 reload 才生效。安全习惯：先不带 ",[19,7292,1858],{}," 跑一遍确认输出，再动手。",[46,7295,7297],{"id":7296},"_3-awk-按列算数据","3. awk —— 按列算数据",[15,7299,7300,7302,7303,7306,7307,7310,7311,7314,7315,7318,7319,616],{},[19,7301,6581],{}," 默认以空白分列，",[19,7304,7305],{},"$1"," 是第一列、",[19,7308,7309],{},"$0"," 是整行、",[19,7312,7313],{},"NF"," 是列数、",[19,7316,7317],{},"NR"," 是行号；结构是 ",[19,7320,7321],{},"'条件 {动作}'",[15,7323,7324],{},[27,7325,7326],{},"示例 1：取列",[211,7328,7330],{"className":213,"code":7329,"language":215,"meta":216,"style":216},"awk '{print $1, $9}' access.log      # 打印第 1 列（客户端 IP）和第 9 列（状态码）\nawk '{print $NF}' file               # $NF 是最后一列（列数不定时必备）\nawk -F'\"' '{print $6}' access.log    # -F 用 \" 分列，第 6 段常是 URL\nawk -F'\\t' '$5==\"WARN\"' logs.tsv     # 制表符分隔 + 精确匹配某列\n",[19,7331,7332,7344,7356,7373],{"__ignoreMap":216},[220,7333,7334,7336,7339,7341],{"class":222,"line":223},[220,7335,6581],{"class":226},[220,7337,7338],{"class":229}," '{print $1, $9}'",[220,7340,2454],{"class":229},[220,7342,7343],{"class":233},"      # 打印第 1 列（客户端 IP）和第 9 列（状态码）\n",[220,7345,7346,7348,7351,7353],{"class":222,"line":276},[220,7347,6581],{"class":226},[220,7349,7350],{"class":229}," '{print $NF}'",[220,7352,2376],{"class":229},[220,7354,7355],{"class":233},"               # $NF 是最后一列（列数不定时必备）\n",[220,7357,7358,7360,7362,7365,7368,7370],{"class":222,"line":594},[220,7359,6581],{"class":226},[220,7361,2589],{"class":266},[220,7363,7364],{"class":229},"'\"'",[220,7366,7367],{"class":229}," '{print $6}'",[220,7369,2454],{"class":229},[220,7371,7372],{"class":233},"    # -F 用 \" 分列，第 6 段常是 URL\n",[220,7374,7375,7377,7379,7382,7385,7388],{"class":222,"line":925},[220,7376,6581],{"class":226},[220,7378,2589],{"class":266},[220,7380,7381],{"class":229},"'\\t'",[220,7383,7384],{"class":229}," '$5==\"WARN\"'",[220,7386,7387],{"class":229}," logs.tsv",[220,7389,7390],{"class":233},"     # 制表符分隔 + 精确匹配某列\n",[15,7392,7393],{},[27,7394,7395],{},"示例 2：按条件过滤",[211,7397,7399],{"className":213,"code":7398,"language":215,"meta":216,"style":216},"awk '$9 == 500' access.log           # 只输出状态码 500 的行\nawk '$3 > 1000 {print $1}' stat.txt  # 数值条件（字符串自动当数）\n",[19,7400,7401,7413],{"__ignoreMap":216},[220,7402,7403,7405,7408,7410],{"class":222,"line":223},[220,7404,6581],{"class":226},[220,7406,7407],{"class":229}," '$9 == 500'",[220,7409,2454],{"class":229},[220,7411,7412],{"class":233},"           # 只输出状态码 500 的行\n",[220,7414,7415,7417,7420,7423],{"class":222,"line":276},[220,7416,6581],{"class":226},[220,7418,7419],{"class":229}," '$3 > 1000 {print $1}'",[220,7421,7422],{"class":229}," stat.txt",[220,7424,7425],{"class":233},"  # 数值条件（字符串自动当数）\n",[15,7427,7428],{},[27,7429,7430],{},"示例 3：聚合统计（awk 最经典的用法）",[211,7432,7434],{"className":213,"code":7433,"language":215,"meta":216,"style":216},"awk '{a[$1]++} END {for (k in a) print a[k], k}' access.log | sort -rn | head\n# 按第 1 列计数、输出「次数 名字」再排序——Top N 统计的万能骨架\n\nawk 'NR>1 {sum += $2} END {print sum\u002F(NR-1)}' data.txt   # 跳过表头求平均\nawk -F, 'END {print NR}' data.csv                        # 数行数（比 wc -l 稳，能处理无末尾换行）\nawk 'count[$7]++ > 0 {print $7} END {exit}' access.log   # 找出第一个重复出现的 URL\nawk 'BEGIN {OFS=\",\"} {print $1,$2}'                      # OFS 控制输出分隔符\n",[19,7435,7436,7455,7460,7464,7476,7492,7504],{"__ignoreMap":216},[220,7437,7438,7440,7443,7445,7447,7449,7451,7453],{"class":222,"line":223},[220,7439,6581],{"class":226},[220,7441,7442],{"class":229}," '{a[$1]++} END {for (k in a) print a[k], k}'",[220,7444,2454],{"class":229},[220,7446,2626],{"class":2439},[220,7448,6127],{"class":226},[220,7450,6130],{"class":266},[220,7452,2626],{"class":2439},[220,7454,6135],{"class":226},[220,7456,7457],{"class":222,"line":276},[220,7458,7459],{"class":233},"# 按第 1 列计数、输出「次数 名字」再排序——Top N 统计的万能骨架\n",[220,7461,7462],{"class":222,"line":594},[220,7463,3740],{"emptyLinePlaceholder":3739},[220,7465,7466,7468,7471,7473],{"class":222,"line":925},[220,7467,6581],{"class":226},[220,7469,7470],{"class":229}," 'NR>1 {sum += $2} END {print sum\u002F(NR-1)}'",[220,7472,2524],{"class":229},[220,7474,7475],{"class":233},"   # 跳过表头求平均\n",[220,7477,7478,7480,7483,7486,7489],{"class":222,"line":931},[220,7479,6581],{"class":226},[220,7481,7482],{"class":266}," -F,",[220,7484,7485],{"class":229}," 'END {print NR}'",[220,7487,7488],{"class":229}," data.csv",[220,7490,7491],{"class":233},"                        # 数行数（比 wc -l 稳，能处理无末尾换行）\n",[220,7493,7494,7496,7499,7501],{"class":222,"line":3528},[220,7495,6581],{"class":226},[220,7497,7498],{"class":229}," 'count[$7]++ > 0 {print $7} END {exit}'",[220,7500,2454],{"class":229},[220,7502,7503],{"class":233},"   # 找出第一个重复出现的 URL\n",[220,7505,7506,7508,7511],{"class":222,"line":3537},[220,7507,6581],{"class":226},[220,7509,7510],{"class":229}," 'BEGIN {OFS=\",\"} {print $1,$2}'",[220,7512,7513],{"class":233},"                      # OFS 控制输出分隔符\n",[15,7515,7516,7517,662,7520,7523,7524,7527,7528,7531,7532,34,7535,7538,7539,7541,7542,34,7545,34,7548,662,7551,34,7554,34,7557,34,7560,5351,7563,662,7566,616],{},"内置变量与函数速记：",[19,7518,7519],{},"FS",[19,7521,7522],{},"OFS"," 输入输出分隔符、",[19,7525,7526],{},"ORS"," 输出记录分隔符（设 ",[19,7529,7530],{},"\"\""," 可拼接行）、",[19,7533,7534],{},"FILENAME",[19,7536,7537],{},"FNR","（当前文件的行号，多文件时与 ",[19,7540,7317],{}," 不同）、",[19,7543,7544],{},"length()",[19,7546,7547],{},"split()",[19,7549,7550],{},"sub()",[19,7552,7553],{},"gsub()",[19,7555,7556],{},"sprintf()",[19,7558,7559],{},"toupper()",[19,7561,7562],{},"match()",[19,7564,7565],{},"RSTART",[19,7567,7568],{},"RLENGTH",[46,7570,7572],{"id":7571},"_4-sortuniqcuttrwcpaste-五个过滤器","4. sort、uniq、cut、tr、wc、paste —— 五个过滤器",[211,7574,7576],{"className":213,"code":7575,"language":215,"meta":216,"style":216},"sort file                # 默认字典序；受 locale 影响，脚本里加 LC_ALL=C 保证按字节稳定\nsort -n                  # 按数字排（否则 10 排在 9 前面）\nsort -h                  # 按人读大小排：2K 1G 10M 能正确排\nsort -r                  # 降序\nsort -V                  # 版本号感知：v1.10 排在 v1.9 后\nsort -t: -k3,3n \u002Fetc\u002Fpasswd    # -t 指定分隔符；-k3,3n 按第 3 列数字排。注意 ,3 不能省——\n                               # 只写 -k3 会从第 3 列一直排到行尾，结果和预期不符\nsort -u                  # 去重（需已排序，等价 sort | uniq）\nsort -m                  # 合并多个已排序文件\n\nuniq -c file             # 合并「相邻」重复行并计数——所以前面必须先 sort，否则看起来完全没去重\ncut -d: -f1 \u002Fetc\u002Fpasswd  # -d 分隔符 + -f 取列；-f1-3 区间、--complement 取反列；只支持单字符分隔符\ntr -d '\\r' \u003C win.txt     # tr 逐字符替换\u002F删除：删 CR、tr ' ' '\\n' 一行变多行、tr a-z A-Z 转大写\nwc -l file               # 行数；-w 词数、-c 字节数；多文件会额外给 total 行\npaste a.txt b.txt        # 按列并文件；-d, 指定分隔、-s 转置行列\n",[19,7577,7578,7587,7596,7605,7615,7625,7641,7646,7655,7664,7668,7679,7694,7712,7724],{"__ignoreMap":216},[220,7579,7580,7582,7584],{"class":222,"line":223},[220,7581,6585],{"class":226},[220,7583,2376],{"class":229},[220,7585,7586],{"class":233},"                # 默认字典序；受 locale 影响，脚本里加 LC_ALL=C 保证按字节稳定\n",[220,7588,7589,7591,7593],{"class":222,"line":276},[220,7590,6585],{"class":226},[220,7592,1669],{"class":266},[220,7594,7595],{"class":233},"                  # 按数字排（否则 10 排在 9 前面）\n",[220,7597,7598,7600,7602],{"class":222,"line":594},[220,7599,6585],{"class":226},[220,7601,4373],{"class":266},[220,7603,7604],{"class":233},"                  # 按人读大小排：2K 1G 10M 能正确排\n",[220,7606,7607,7609,7612],{"class":222,"line":925},[220,7608,6585],{"class":226},[220,7610,7611],{"class":266}," -r",[220,7613,7614],{"class":233},"                  # 降序\n",[220,7616,7617,7619,7622],{"class":222,"line":931},[220,7618,6585],{"class":226},[220,7620,7621],{"class":266}," -V",[220,7623,7624],{"class":233},"                  # 版本号感知：v1.10 排在 v1.9 后\n",[220,7626,7627,7629,7632,7635,7638],{"class":222,"line":3528},[220,7628,6585],{"class":226},[220,7630,7631],{"class":266}," -t:",[220,7633,7634],{"class":266}," -k3,3n",[220,7636,7637],{"class":229}," \u002Fetc\u002Fpasswd",[220,7639,7640],{"class":233},"    # -t 指定分隔符；-k3,3n 按第 3 列数字排。注意 ,3 不能省——\n",[220,7642,7643],{"class":222,"line":3537},[220,7644,7645],{"class":233},"                               # 只写 -k3 会从第 3 列一直排到行尾，结果和预期不符\n",[220,7647,7648,7650,7652],{"class":222,"line":3546},[220,7649,6585],{"class":226},[220,7651,4787],{"class":266},[220,7653,7654],{"class":233},"                  # 去重（需已排序，等价 sort | uniq）\n",[220,7656,7657,7659,7661],{"class":222,"line":3555},[220,7658,6585],{"class":226},[220,7660,5078],{"class":266},[220,7662,7663],{"class":233},"                  # 合并多个已排序文件\n",[220,7665,7666],{"class":222,"line":3564},[220,7667,3740],{"emptyLinePlaceholder":3739},[220,7669,7670,7672,7674,7676],{"class":222,"line":3572},[220,7671,6588],{"class":226},[220,7673,2494],{"class":266},[220,7675,2376],{"class":229},[220,7677,7678],{"class":233},"             # 合并「相邻」重复行并计数——所以前面必须先 sort，否则看起来完全没去重\n",[220,7680,7681,7683,7686,7689,7691],{"class":222,"line":3580},[220,7682,6591],{"class":226},[220,7684,7685],{"class":266}," -d:",[220,7687,7688],{"class":266}," -f1",[220,7690,7637],{"class":229},[220,7692,7693],{"class":233},"  # -d 分隔符 + -f 取列；-f1-3 区间、--complement 取反列；只支持单字符分隔符\n",[220,7695,7696,7698,7700,7703,7706,7709],{"class":222,"line":3588},[220,7697,57],{"class":226},[220,7699,1455],{"class":266},[220,7701,7702],{"class":229}," '\\r'",[220,7704,7705],{"class":2439}," \u003C",[220,7707,7708],{"class":229}," win.txt",[220,7710,7711],{"class":233},"     # tr 逐字符替换\u002F删除：删 CR、tr ' ' '\\n' 一行变多行、tr a-z A-Z 转大写\n",[220,7713,7715,7717,7719,7721],{"class":222,"line":7714},14,[220,7716,6596],{"class":226},[220,7718,3193],{"class":266},[220,7720,2376],{"class":229},[220,7722,7723],{"class":233},"               # 行数；-w 词数、-c 字节数；多文件会额外给 total 行\n",[220,7725,7727,7730,7732,7735],{"class":222,"line":7726},15,[220,7728,7729],{"class":226},"paste",[220,7731,1596],{"class":229},[220,7733,7734],{"class":229}," b.txt",[220,7736,7737],{"class":233},"        # 按列并文件；-d, 指定分隔、-s 转置行列\n",[46,7739,7741],{"id":7740},"_5-三个日志统计的一行命令照抄可用","5. 三个日志统计的一行命令（照抄可用）",[15,7743,7744,7745,7748,7749,1520],{},"场景：",[19,7746,7747],{},"\u002Fsrv\u002Fapp\u002Flogs\u002Faccess.log","，nginx combined 格式（",[19,7750,7751],{},"IP - - [时间] \"方法 路径 协议\" 状态 字节 \"referer\" \"ua\"",[211,7753,7755],{"className":213,"code":7754,"language":215,"meta":216,"style":216},"awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10    # 1) 哪个 IP 请求最多（Top 10）\nawk '{print $9}' access.log | grep -v '^2' | sort | uniq -c | sort -rn  # 2) 状态码分布（只看非 2xx）\nawk '{print $NF, $7}' access.log | sort -rn | head -20                 # 3) 最慢的 20 个请求（假设 $NF 是耗时）\n",[19,7756,7757,7792,7829],{"__ignoreMap":216},[220,7758,7759,7761,7764,7766,7768,7770,7772,7775,7777,7779,7781,7783,7785,7787,7789],{"class":222,"line":223},[220,7760,6581],{"class":226},[220,7762,7763],{"class":229}," '{print $1}'",[220,7765,2454],{"class":229},[220,7767,2626],{"class":2439},[220,7769,6127],{"class":226},[220,7771,2626],{"class":2439},[220,7773,7774],{"class":226}," uniq",[220,7776,2494],{"class":266},[220,7778,2626],{"class":2439},[220,7780,6127],{"class":226},[220,7782,6130],{"class":266},[220,7784,2626],{"class":2439},[220,7786,4431],{"class":226},[220,7788,5683],{"class":266},[220,7790,7791],{"class":233},"    # 1) 哪个 IP 请求最多（Top 10）\n",[220,7793,7794,7796,7799,7801,7803,7805,7807,7810,7812,7814,7816,7818,7820,7822,7824,7826],{"class":222,"line":276},[220,7795,6581],{"class":226},[220,7797,7798],{"class":229}," '{print $9}'",[220,7800,2454],{"class":229},[220,7802,2626],{"class":2439},[220,7804,315],{"class":226},[220,7806,574],{"class":266},[220,7808,7809],{"class":229}," '^2'",[220,7811,2626],{"class":2439},[220,7813,6127],{"class":226},[220,7815,2626],{"class":2439},[220,7817,7774],{"class":226},[220,7819,2494],{"class":266},[220,7821,2626],{"class":2439},[220,7823,6127],{"class":226},[220,7825,6130],{"class":266},[220,7827,7828],{"class":233},"  # 2) 状态码分布（只看非 2xx）\n",[220,7830,7831,7833,7836,7838,7840,7842,7844,7846,7848,7851],{"class":222,"line":594},[220,7832,6581],{"class":226},[220,7834,7835],{"class":229}," '{print $NF, $7}'",[220,7837,2454],{"class":229},[220,7839,2626],{"class":2439},[220,7841,6127],{"class":226},[220,7843,6130],{"class":266},[220,7845,2626],{"class":2439},[220,7847,4431],{"class":226},[220,7849,7850],{"class":266}," -20",[220,7852,7853],{"class":233},"                 # 3) 最慢的 20 个请求（假设 $NF 是耗时）\n",[15,7855,7856,7857,7860,7861,7863],{},"三条命令的结构都是「",[27,7858,7859],{},"取列 → 归并计数 → 排序 → 截断","」，学会一条就等于学会一片。想按小时聚合访问量，用 ",[19,7862,5481],{}," 抠出时间里的小时：",[211,7865,7867],{"className":213,"code":7866,"language":215,"meta":216,"style":216},"grep -oE '\\[[^] ]+ [0-9]{2}' access.log | awk '{print $2}' | sort | uniq -c\n",[19,7868,7869],{"__ignoreMap":216},[220,7870,7871,7873,7875,7878,7880,7882,7885,7888,7890,7892,7894,7896],{"class":222,"line":223},[220,7872,497],{"class":226},[220,7874,6776],{"class":266},[220,7876,7877],{"class":229}," '\\[[^] ]+ [0-9]{2}'",[220,7879,2454],{"class":229},[220,7881,2626],{"class":2439},[220,7883,7884],{"class":226}," awk",[220,7886,7887],{"class":229}," '{print $2}'",[220,7889,2626],{"class":2439},[220,7891,6127],{"class":226},[220,7893,2626],{"class":2439},[220,7895,7774],{"class":226},[220,7897,7898],{"class":266}," -c\n",[46,7900,808],{"id":7901},"踩坑提醒-6",[810,7903,7904,7919,7937,7952,7965],{},[813,7905,7906,1800,7908,7910,7911,7914,7915,7918],{},[19,7907,497],{},[19,7909,21],{}," 脚本里用 ",[19,7912,7913],{},"grep -E","，别再用废弃的 ",[19,7916,7917],{},"egrep","（新版会打 warning）。",[813,7920,7921,7922,7925,7926,7929,7930,7933,7934,616],{},"管道里最后一个命令的退出码才是 ",[19,7923,7924],{},"$?","（第八章讲 ",[19,7927,7928],{},"pipefail","）。判断「有没有搜到」用 ",[19,7931,7932],{},"grep -q","：它不输出只给状态码，",[19,7935,7936],{},"if grep -q 'pattern' f; then",[813,7938,7939,7941,7942,7945,7946,7948,7949,7951],{},[19,7940,784],{}," 无备份直接改生产配置出事后无从回滚；养成 ",[19,7943,7944],{},"sed -i.bak"," 或先 ",[19,7947,1542],{}," 的习惯，改完 ",[19,7950,7247],{}," 一眼。",[813,7953,7954,7956,7957,7960,7961,7964],{},[19,7955,6581],{}," 里字符串比较要写 ",[19,7958,7959],{},"$5 == \"WARN\""," 而不是 ",[19,7962,7963],{},"$5 = \"WARN\"","——一个等号是赋值，永远为真，这是 awk 最隐蔽的错误。",[813,7966,7967,7969,7970,7972,7973,7976,7977,1520],{},[19,7968,6588],{}," 忘 ",[19,7971,6585],{}," 会得到「看起来完全没去重」的结果；反之 ",[19,7974,7975],{},"sort -u"," 会静默改变顺序语义（等价 ",[19,7978,7979],{},"sort | uniq",[10,7981,7983],{"id":7982},"八管道重定向与命令组合","八、管道、重定向与命令组合",[15,7985,7986,7987,8004],{},"Shell 的表达能力几乎全在这一章：",[27,7988,7989,7990,7992,7993,34,7995,7998,7999,34,8001,1062],{},"把 stdout 接给下一个命令（",[19,7991,6892],{},"），把输出\u002F错误写到别处（",[19,7994,147],{},[19,7996,7997],{},"2>","），把几条命令串成逻辑（",[19,8000,1831],{},[19,8002,8003],{},"||","。搞不清这三个，脚本就只能写成猜谜。",[861,8006],{"height":863,"src":8007,"title":8008},"\u002Ftutorials\u002Flinux\u002Fdemos\u002F08-pipes.html","示例 08：重定向、管道与 tee 的行为差异",[46,8010,8012],{"id":8011},"_1-重定向-三个流与-2","1. 重定向 —— 三个流与 > 、>>、2>",[15,8014,8015,8016,8019,8020,8022,8023,8026],{},"每个进程有三个标准流：",[19,8017,8018],{},"0"," stdin、",[19,8021,1072],{}," stdout、",[19,8024,8025],{},"2"," stderr。",[211,8028,8030],{"className":213,"code":8029,"language":215,"meta":216,"style":216},"cmd > out.txt            # stdout 覆盖写文件\ncmd >> out.txt           # 追加——日志文件必备，> 会把历史冲掉\ncmd 2> err.txt           # stderr 单独写文件，只看错误\ncmd > out.txt 2>&1       # stderr 并入同一个 stdout 再一起进文件。经典写法，顺序不能反！\ncmd 2>&1 > out.txt       # 反例：stderr 去了终端，stdout 才进文件（按从左到右解析）\ncmd &> out.txt           # 两者都进文件（bash 简写）；POSIX sh 里没有，讲可移植时别用\ncmd > \u002Fdev\u002Fnull 2>&1     # 全部丢进黑洞，让 cron 安静运行\ncmd \u003C in.txt             # stdin 来自文件\ncmd 1>&2                 # stdout 改道 stderr，让日志统一被 2> 捕获\n",[19,8031,8032,8045,8057,8070,8084,8097,8112,8126,8138],{"__ignoreMap":216},[220,8033,8034,8037,8039,8042],{"class":222,"line":223},[220,8035,8036],{"class":226},"cmd",[220,8038,2440],{"class":2439},[220,8040,8041],{"class":229}," out.txt",[220,8043,8044],{"class":233},"            # stdout 覆盖写文件\n",[220,8046,8047,8049,8052,8054],{"class":222,"line":276},[220,8048,8036],{"class":226},[220,8050,8051],{"class":2439}," >>",[220,8053,8041],{"class":229},[220,8055,8056],{"class":233},"           # 追加——日志文件必备，> 会把历史冲掉\n",[220,8058,8059,8061,8064,8067],{"class":222,"line":594},[220,8060,8036],{"class":226},[220,8062,8063],{"class":2439}," 2>",[220,8065,8066],{"class":229}," err.txt",[220,8068,8069],{"class":233},"           # stderr 单独写文件，只看错误\n",[220,8071,8072,8074,8076,8078,8081],{"class":222,"line":925},[220,8073,8036],{"class":226},[220,8075,2440],{"class":2439},[220,8077,8041],{"class":229},[220,8079,8080],{"class":2439}," 2>&1",[220,8082,8083],{"class":233},"       # stderr 并入同一个 stdout 再一起进文件。经典写法，顺序不能反！\n",[220,8085,8086,8088,8090,8092,8094],{"class":222,"line":931},[220,8087,8036],{"class":226},[220,8089,8080],{"class":2439},[220,8091,2440],{"class":2439},[220,8093,8041],{"class":229},[220,8095,8096],{"class":233},"       # 反例：stderr 去了终端，stdout 才进文件（按从左到右解析）\n",[220,8098,8099,8101,8104,8106,8109],{"class":222,"line":3528},[220,8100,8036],{"class":226},[220,8102,8103],{"class":6987}," &",[220,8105,147],{"class":2439},[220,8107,8108],{"class":6987}," out.txt           ",[220,8110,8111],{"class":233},"# 两者都进文件（bash 简写）；POSIX sh 里没有，讲可移植时别用\n",[220,8113,8114,8116,8118,8121,8123],{"class":222,"line":3537},[220,8115,8036],{"class":226},[220,8117,2440],{"class":2439},[220,8119,8120],{"class":229}," \u002Fdev\u002Fnull",[220,8122,8080],{"class":2439},[220,8124,8125],{"class":233},"     # 全部丢进黑洞，让 cron 安静运行\n",[220,8127,8128,8130,8132,8135],{"class":222,"line":3546},[220,8129,8036],{"class":226},[220,8131,7705],{"class":2439},[220,8133,8134],{"class":229}," in.txt",[220,8136,8137],{"class":233},"             # stdin 来自文件\n",[220,8139,8140,8142,8145],{"class":222,"line":3555},[220,8141,8036],{"class":226},[220,8143,8144],{"class":2439}," 1>&2",[220,8146,8147],{"class":233},"                 # stdout 改道 stderr，让日志统一被 2> 捕获\n",[15,8149,8150,8153,8154,8157,8158,827,8161,8163,8164,1848,8166,8168,8169,8171],{},[19,8151,8152],{},">|"," 强制覆盖（即使开了 ",[19,8155,8156],{},"set -o noclobber","）；",[19,8159,8160],{},"&>",[19,8162,8152],{}," 都是 bash 扩展。判断命令失败用 ",[19,8165,7924],{},[19,8167,8018],{}," 成功、非 0 失败，具体数字看 ",[19,8170,203],{}," 的 EXIT STATUS 章节。",[46,8173,8175],{"id":8174},"_2-tee-既上屏又落盘","2. tee —— 既上屏又落盘",[15,8177,8178],{},[27,8179,8180],{},"示例 1：边看边存",[211,8182,8184],{"className":213,"code":8183,"language":215,"meta":216,"style":216},"make install 2>&1 | tee -a \u002Fvar\u002Flog\u002Fdeploy.log     # -a 追加；不加就是覆盖\n",[19,8185,8186],{"__ignoreMap":216},[220,8187,8188,8191,8194,8196,8198,8201,8203,8206],{"class":222,"line":223},[220,8189,8190],{"class":226},"make",[220,8192,8193],{"class":229}," install",[220,8195,8080],{"class":2439},[220,8197,2626],{"class":2439},[220,8199,8200],{"class":226}," tee",[220,8202,551],{"class":266},[220,8204,8205],{"class":229}," \u002Fvar\u002Flog\u002Fdeploy.log",[220,8207,8208],{"class":233},"     # -a 追加；不加就是覆盖\n",[15,8210,8211],{},[27,8212,8213],{},"示例 2：绕过「>> 需要提权」的问题",[211,8215,8217],{"className":213,"code":8216,"language":215,"meta":216,"style":216},"echo \"10.0.0.5 internal\" | sudo tee -a \u002Fetc\u002Fhosts\n",[19,8218,8219],{"__ignoreMap":216},[220,8220,8221,8223,8226,8228,8231,8233,8235],{"class":222,"line":223},[220,8222,468],{"class":266},[220,8224,8225],{"class":229}," \"10.0.0.5 internal\"",[220,8227,2626],{"class":2439},[220,8229,8230],{"class":226}," sudo",[220,8232,8200],{"class":229},[220,8234,551],{"class":266},[220,8236,8237],{"class":229}," \u002Fetc\u002Fhosts\n",[15,8239,8240,8243,8244,8247,8248,8250,8251,616,8254,8257],{},[19,8241,8242],{},"sudo some-cmd > \u002Froot\u002Fsecret.txt"," 的重定向是",[27,8245,8246],{},"当前 Shell","（无特权）执行的，所以会报 ",[19,8249,3851],{},"——正确写法就是用 ",[19,8252,8253],{},"| sudo tee file",[19,8255,8256],{},"tee \u002Fdev\u002Fstderr"," 可把内容转错误流。",[46,8259,8261],{"id":8260},"_3-xargs-把参数交给命令","3. xargs —— 把参数交给命令",[211,8263,8265],{"className":213,"code":8264,"language":215,"meta":216,"style":216},"find . -name '*.tmp' -print0 | xargs -0 rm -f --\n# 标准拼接：-0 处理空格\u002F换行（与 -print0 配对使用）；-- 防文件名被当参数（-rf 之类）\n\nseq 1 9 | xargs -n 3 cmd      # -n 3 每次最多传 3 个参数\nfind . -name '*.jpg' -print0 | xargs -0 -P 8 -n 1 convert   # -P 8 并发 8 个进程，适合下载\u002F压缩\necho a b c | xargs -I{} sh -c 'tar czf {}.tgz {}'           # -I{} 把参数放在指定位置，每行起一个 shell\nfind . -name '*.tmp' -print0 | xargs -0 -r rm --            # -r 空输入不执行（GNU 特性），避免 cmd 空跑\nxargs --show-limits           # 查 ARG_MAX；超限时报 Argument list too long\n",[19,8266,8267,8292,8297,8301,8324,8356,8376,8404],{"__ignoreMap":216},[220,8268,8269,8271,8273,8275,8277,8279,8281,8283,8285,8287,8289],{"class":222,"line":223},[220,8270,694],{"class":226},[220,8272,5517],{"class":229},[220,8274,5504],{"class":266},[220,8276,5800],{"class":229},[220,8278,5803],{"class":266},[220,8280,2626],{"class":2439},[220,8282,6332],{"class":226},[220,8284,6335],{"class":266},[220,8286,5832],{"class":229},[220,8288,2573],{"class":266},[220,8290,8291],{"class":266}," --\n",[220,8293,8294],{"class":222,"line":276},[220,8295,8296],{"class":233},"# 标准拼接：-0 处理空格\u002F换行（与 -print0 配对使用）；-- 防文件名被当参数（-rf 之类）\n",[220,8298,8299],{"class":222,"line":594},[220,8300,3740],{"emptyLinePlaceholder":3739},[220,8302,8303,8306,8308,8311,8313,8315,8317,8319,8321],{"class":222,"line":925},[220,8304,8305],{"class":226},"seq",[220,8307,267],{"class":266},[220,8309,8310],{"class":266}," 9",[220,8312,2626],{"class":2439},[220,8314,6332],{"class":226},[220,8316,1669],{"class":266},[220,8318,5564],{"class":266},[220,8320,4833],{"class":229},[220,8322,8323],{"class":233},"      # -n 3 每次最多传 3 个参数\n",[220,8325,8326,8328,8330,8332,8334,8336,8338,8340,8342,8344,8347,8349,8351,8353],{"class":222,"line":931},[220,8327,694],{"class":226},[220,8329,5517],{"class":229},[220,8331,5504],{"class":266},[220,8333,5879],{"class":229},[220,8335,5803],{"class":266},[220,8337,2626],{"class":2439},[220,8339,6332],{"class":226},[220,8341,6335],{"class":266},[220,8343,1275],{"class":266},[220,8345,8346],{"class":266}," 8",[220,8348,1669],{"class":266},[220,8350,267],{"class":266},[220,8352,5884],{"class":229},[220,8354,8355],{"class":233},"   # -P 8 并发 8 个进程，适合下载\u002F压缩\n",[220,8357,8358,8360,8362,8364,8367,8369,8371,8373],{"class":222,"line":3528},[220,8359,468],{"class":266},[220,8361,1672],{"class":229},[220,8363,1675],{"class":229},[220,8365,8366],{"class":229}," c",[220,8368,2626],{"class":2439},[220,8370,6332],{"class":226},[220,8372,1738],{"class":266},[220,8374,8375],{"class":6987},"{} sh -c 'tar czf {}.tgz {}'           # -I{} 把参数放在指定位置，每行起一个 shell\n",[220,8377,8378,8380,8382,8384,8386,8388,8390,8392,8394,8396,8398,8401],{"class":222,"line":3537},[220,8379,694],{"class":226},[220,8381,5517],{"class":229},[220,8383,5504],{"class":266},[220,8385,5800],{"class":229},[220,8387,5803],{"class":266},[220,8389,2626],{"class":2439},[220,8391,6332],{"class":226},[220,8393,6335],{"class":266},[220,8395,7611],{"class":266},[220,8397,5832],{"class":229},[220,8399,8400],{"class":266}," --",[220,8402,8403],{"class":233},"            # -r 空输入不执行（GNU 特性），避免 cmd 空跑\n",[220,8405,8406,8408,8411],{"class":222,"line":3546},[220,8407,6538],{"class":226},[220,8409,8410],{"class":266}," --show-limits",[220,8412,8413],{"class":233},"           # 查 ARG_MAX；超限时报 Argument list too long\n",[15,8415,8416,8417,8420,8421,8423,8424,8426,8427,662,8429,1520,8431,8439,8440,616],{},"xargs 与 ",[19,8418,8419],{},"-exec … +"," 的取舍：两者都能批量；",[19,8422,5455],{}," 更直白、不用考虑管道，",[19,8425,6538],{}," 更灵活（能在中间做 ",[19,8428,497],{},[19,8430,6585],{},[27,8432,8433,8434,662,8436,8438],{},"只要文件名可能带空格或换行，就必须用 ",[19,8435,6542],{},[19,8437,6546],{}," 这一对","，或者 ",[19,8441,8442],{},"find -exec",[46,8444,8446],{"id":8445},"_4-与子-shell","4. && 、|| 、; 与子 shell",[211,8448,8450],{"className":213,"code":8449,"language":215,"meta":216,"style":216},"nginx -t && systemctl reload nginx             # && 前者成功才跑后者——改完配置先验证\nsystemctl is-active app || systemctl restart app   # || 前者失败才跑后者\ncd \u002Ftmp ; make                                 # ; 无条件依次执行（脚本里危险，见踩坑）\nlong_task &                                    # & 后台执行；关终端会收到 SIGHUP\n( cd \u002Ftmp && make )                            # 子 shell：里面的 cd 不影响当前目录\n{ cmd1; cmd2; } > log                          # 分组但同 shell（变量能保留）；{ 后有空格、要带 ;\necho $(date +%F)                               # 命令替换：$() 可嵌套，新代码一律用它\ntime make -j4                                  # 统计耗时（bash 内建的 time 更准，-p 给 POSIX 格式）\n",[19,8451,8452,8472,8495,8510,8521,8540,8565,8582],{"__ignoreMap":216},[220,8453,8454,8457,8460,8462,8464,8467,8469],{"class":222,"line":223},[220,8455,8456],{"class":226},"nginx",[220,8458,8459],{"class":266}," -t",[220,8461,6988],{"class":6987},[220,8463,500],{"class":226},[220,8465,8466],{"class":229}," reload",[220,8468,600],{"class":229},[220,8470,8471],{"class":233},"             # && 前者成功才跑后者——改完配置先验证\n",[220,8473,8474,8476,8479,8481,8484,8487,8490,8492],{"class":222,"line":276},[220,8475,500],{"class":226},[220,8477,8478],{"class":229}," is-active",[220,8480,4404],{"class":229},[220,8482,8483],{"class":2439}," ||",[220,8485,8486],{"class":226}," systemctl",[220,8488,8489],{"class":229}," restart",[220,8491,4404],{"class":229},[220,8493,8494],{"class":233},"   # || 前者失败才跑后者\n",[220,8496,8497,8499,8502,8505,8507],{"class":222,"line":594},[220,8498,464],{"class":266},[220,8500,8501],{"class":229}," \u002Ftmp",[220,8503,8504],{"class":6987}," ; ",[220,8506,8190],{"class":226},[220,8508,8509],{"class":233},"                                 # ; 无条件依次执行（脚本里危险，见踩坑）\n",[220,8511,8512,8515,8518],{"class":222,"line":925},[220,8513,8514],{"class":226},"long_task",[220,8516,8517],{"class":6987}," &                                    ",[220,8519,8520],{"class":233},"# & 后台执行；关终端会收到 SIGHUP\n",[220,8522,8523,8526,8528,8530,8532,8534,8537],{"class":222,"line":931},[220,8524,8525],{"class":6987},"( ",[220,8527,464],{"class":266},[220,8529,8501],{"class":229},[220,8531,6988],{"class":6987},[220,8533,8190],{"class":226},[220,8535,8536],{"class":6987}," )                            ",[220,8538,8539],{"class":233},"# 子 shell：里面的 cd 不影响当前目录\n",[220,8541,8542,8545,8548,8551,8554,8557,8559,8562],{"class":222,"line":3528},[220,8543,8544],{"class":6987},"{ ",[220,8546,8547],{"class":226},"cmd1",[220,8549,8550],{"class":6987},"; ",[220,8552,8553],{"class":226},"cmd2",[220,8555,8556],{"class":6987},"; } ",[220,8558,147],{"class":2439},[220,8560,8561],{"class":6987}," log                          ",[220,8563,8564],{"class":233},"# 分组但同 shell（变量能保留）；{ 后有空格、要带 ;\n",[220,8566,8567,8569,8572,8574,8576,8579],{"class":222,"line":3537},[220,8568,468],{"class":266},[220,8570,8571],{"class":6987}," $(",[220,8573,7224],{"class":226},[220,8575,7227],{"class":229},[220,8577,8578],{"class":6987},")                               ",[220,8580,8581],{"class":233},"# 命令替换：$() 可嵌套，新代码一律用它\n",[220,8583,8584,8587,8590],{"class":222,"line":3546},[220,8585,8586],{"class":2439},"time",[220,8588,8589],{"class":6987}," make -j4                                  ",[220,8591,8592],{"class":233},"# 统计耗时（bash 内建的 time 更准，-p 给 POSIX 格式）\n",[15,8594,8595,8598,8599,616],{},[19,8596,8597],{},"$()"," 与反引号功能基本相同，但反引号内部的反斜杠和嵌套容易出错，新代码一律 ",[19,8600,8597],{},[46,8602,8604],{"id":8603},"_5-here-doc-here-string","5. here doc \u002F here string",[211,8606,8608],{"className":213,"code":8607,"language":215,"meta":216,"style":216},"cat > \u002Ftmp\u002Frun.sh \u003C\u003C'EOF'     # here doc：把多行喂给命令的 stdin\n#!\u002Fbin\u002Fbash\nset -euo pipefail\ncd \u002Fsrv\u002Fapp && make deploy\nEOF\n\nwc -c \u003C\u003C\u003C \"hello world\"       # here string：单行输入，等价 echo \"abc\" | wc -c\n\nsudo tee \u002Fetc\u002Fprofile.d\u002Fjava.sh >\u002Fdev\u002Fnull \u003C\u003C'EOF'    # \u003C\u003C'EOF' 原样不展开变量\nexport JAVA_HOME=\u002Fusr\u002Flib\u002Fjvm\u002Fjava-17\nexport PATH=\"$JAVA_HOME\u002Fbin:$PATH\"\nEOF\n",[19,8609,8610,8628,8633,8638,8643,8648,8652,8667,8671,8692,8697,8702],{"__ignoreMap":216},[220,8611,8612,8614,8616,8619,8622,8625],{"class":222,"line":223},[220,8613,2304],{"class":226},[220,8615,2440],{"class":2439},[220,8617,8618],{"class":229}," \u002Ftmp\u002Frun.sh",[220,8620,8621],{"class":2439}," \u003C\u003C",[220,8623,8624],{"class":229},"'EOF'",[220,8626,8627],{"class":233},"     # here doc：把多行喂给命令的 stdin\n",[220,8629,8630],{"class":222,"line":276},[220,8631,8632],{"class":229},"#!\u002Fbin\u002Fbash\n",[220,8634,8635],{"class":222,"line":594},[220,8636,8637],{"class":229},"set -euo pipefail\n",[220,8639,8640],{"class":222,"line":925},[220,8641,8642],{"class":229},"cd \u002Fsrv\u002Fapp && make deploy\n",[220,8644,8645],{"class":222,"line":931},[220,8646,8647],{"class":229},"EOF\n",[220,8649,8650],{"class":222,"line":3528},[220,8651,3740],{"emptyLinePlaceholder":3739},[220,8653,8654,8656,8658,8661,8664],{"class":222,"line":3537},[220,8655,6596],{"class":226},[220,8657,2494],{"class":266},[220,8659,8660],{"class":2439}," \u003C\u003C\u003C",[220,8662,8663],{"class":229}," \"hello world\"",[220,8665,8666],{"class":233},"       # here string：单行输入，等价 echo \"abc\" | wc -c\n",[220,8668,8669],{"class":222,"line":3546},[220,8670,3740],{"emptyLinePlaceholder":3739},[220,8672,8673,8675,8677,8680,8682,8685,8687,8689],{"class":222,"line":3555},[220,8674,120],{"class":226},[220,8676,8200],{"class":229},[220,8678,8679],{"class":229}," \u002Fetc\u002Fprofile.d\u002Fjava.sh",[220,8681,2440],{"class":2439},[220,8683,8684],{"class":229},"\u002Fdev\u002Fnull",[220,8686,8621],{"class":2439},[220,8688,8624],{"class":229},[220,8690,8691],{"class":233},"    # \u003C\u003C'EOF' 原样不展开变量\n",[220,8693,8694],{"class":222,"line":3564},[220,8695,8696],{"class":229},"export JAVA_HOME=\u002Fusr\u002Flib\u002Fjvm\u002Fjava-17\n",[220,8698,8699],{"class":222,"line":3572},[220,8700,8701],{"class":229},"export PATH=\"$JAVA_HOME\u002Fbin:$PATH\"\n",[220,8703,8704],{"class":222,"line":3580},[220,8705,8647],{"class":229},[15,8707,8708,8711,8712,8715,8716,8718,8719,8722,8723,8726,8727,8730,8731,1520],{},[19,8709,8710],{},"\u003C\u003CEOF","（不带引号）会对内容做变量展开，",[19,8713,8714],{},"\u003C\u003C'EOF'"," 原样保留——写含 ",[19,8717,2091],{}," 的配置文件时用后者。",[19,8720,8721],{},"\u003C\u003C-EOF"," 允许脚本里用 ",[27,8724,8725],{},"Tab"," 缩进（只剥 Tab，不剥空格）。here doc 结束的标记必须",[27,8728,8729],{},"独占一行、顶格、无尾随空格","，否则 Shell 会继续等输入（表现为卡在 ",[19,8732,147],{},[46,8734,8736],{"id":8735},"_6-后台任务与脱离终端","6. 后台任务与脱离终端",[211,8738,8740],{"className":213,"code":8739,"language":215,"meta":216,"style":216},"long_task &                                  # 直接后台；关掉终端就收到 SIGHUP 会死\nnohup long_task > run.log 2>&1 &             # 标准做法：nohup 忽略 SIGHUP\njobs -l                                      # 看后台列表：[1]  28414 Running …\nfg %1                                        # 把 1 号作业调回前台（bg %1 继续后台）；%1 是作业号不是 PID\ndisown -h %1                                 # 已经跑着的任务从当前 Shell 解绑的补救\ntmux new -d -s build 'make deploy'           # 更好的方案：可重连，服务器场景首选\n",[19,8741,8742,8752,8773,8783,8794,8806],{"__ignoreMap":216},[220,8743,8744,8746,8749],{"class":222,"line":223},[220,8745,8514],{"class":226},[220,8747,8748],{"class":6987}," &                                  ",[220,8750,8751],{"class":233},"# 直接后台；关掉终端就收到 SIGHUP 会死\n",[220,8753,8754,8757,8760,8762,8765,8767,8770],{"class":222,"line":276},[220,8755,8756],{"class":226},"nohup",[220,8758,8759],{"class":229}," long_task",[220,8761,2440],{"class":2439},[220,8763,8764],{"class":229}," run.log",[220,8766,8080],{"class":2439},[220,8768,8769],{"class":6987}," &             ",[220,8771,8772],{"class":233},"# 标准做法：nohup 忽略 SIGHUP\n",[220,8774,8775,8778,8780],{"class":222,"line":594},[220,8776,8777],{"class":266},"jobs",[220,8779,3193],{"class":266},[220,8781,8782],{"class":233},"                                      # 看后台列表：[1]  28414 Running …\n",[220,8784,8785,8788,8791],{"class":222,"line":925},[220,8786,8787],{"class":266},"fg",[220,8789,8790],{"class":229}," %1",[220,8792,8793],{"class":233},"                                        # 把 1 号作业调回前台（bg %1 继续后台）；%1 是作业号不是 PID\n",[220,8795,8796,8799,8801,8803],{"class":222,"line":931},[220,8797,8798],{"class":266},"disown",[220,8800,4373],{"class":266},[220,8802,8790],{"class":229},[220,8804,8805],{"class":233},"                                 # 已经跑着的任务从当前 Shell 解绑的补救\n",[220,8807,8808,8811,8814,8816,8818,8820,8823],{"class":222,"line":3528},[220,8809,8810],{"class":226},"tmux",[220,8812,8813],{"class":229}," new",[220,8815,1455],{"class":266},[220,8817,1978],{"class":266},[220,8819,1375],{"class":229},[220,8821,8822],{"class":229}," 'make deploy'",[220,8824,8825],{"class":233},"           # 更好的方案：可重连，服务器场景首选\n",[15,8827,8828,8831,8832,8835,8836,8839,8840,8842,8843,8846],{},[19,8829,8830],{},"Ctrl-Z"," 是",[27,8833,8834],{},"挂起","（SIGTSTP）不是退出，进程还在占内存；忘记处理会留一堆 ",[19,8837,8838],{},"Stopped"," 进程，",[19,8841,8787],{}," 回来或 ",[19,8844,8845],{},"kill %1"," 收掉。",[46,8848,808],{"id":8849},"踩坑提醒-7",[810,8851,8852,8870,8879,8899,8910],{},[813,8853,8854,8855,8858,8859,827,8861,8863,8864,8867,8868,616],{},"「明明加了 ",[19,8856,8857],{},"2>&1"," 还是没抓到错误」：顺序写反了。",[19,8860,147],{},[19,8862,8857],{}," 按从左到右解析，",[19,8865,8866],{},"cmd 2>&1 > f"," 里 stderr 指向的是终端的那个 ",[19,8869,1072],{},[813,8871,8872,827,8875,8878],{},[19,8873,8874],{},"rm -rf dir && echo done",[19,8876,8877],{},"rm -rf dir; echo done"," 在脚本里的差别可能决定你是否覆盖了错误目录（第二章）。",[813,8880,8881,8883,8884,8887,8888,8891,8892,8894,8895,8898],{},[19,8882,6538],{}," 不加 ",[19,8885,8886],{},"-r"," 时，空输入也会执行一次命令——",[19,8889,8890],{},"find … | xargs rm"," 在极端情况下等价于 ",[19,8893,1701],{}," 无参数（报错），但换成 ",[19,8896,8897],{},"xargs chmod 777"," 就会拿当前目录开刀。",[813,8900,8901,8902,8905,8906,8909],{},"变量赋值不要留空格：",[19,8903,8904],{},"NAME=deploy"," 正确，",[19,8907,8908],{},"NAME = deploy"," 会被当成「执行名为 NAME 的命令」。",[813,8911,8912,8913,662,8916,8919,8920,8923,8924,1520],{},"在管道里 ",[19,8914,8915],{},"if",[19,8917,8918],{},"while read"," 循环会跑在子 shell，循环里改的变量出来后消失（bash 4.2+ 可用 ",[19,8921,8922],{},"lastpipe","，或把管道换成重定向 ",[19,8925,8926],{},"done \u003C file",[10,8928,8930],{"id":8929},"九压缩与归档targzipzip","九、压缩与归档：tar、gzip、zip",[15,8932,8933,34,8936,34,8939,8942,8943,8946,8947,8950,8951,662,8954,8946,8957,8960,8961,6508,8963,662,8966,662,8969,8972],{},[19,8934,8935],{},".tar.gz",[19,8937,8938],{},".zip",[19,8940,8941],{},".xz"," 三种东西经常被混为一谈：",[19,8944,8945],{},"tar"," 负责",[27,8948,8949],{},"打包","（把多个文件合成一个，不改内容），",[19,8952,8953],{},"gzip",[19,8955,8956],{},"xz",[27,8958,8959],{},"压缩","（单流算法）。",[19,8962,8945],{},[19,8964,8965],{},"-z",[19,8967,8968],{},"-j",[19,8970,8971],{},"-J"," 只是顺手帮你调了一下压缩器。",[46,8974,8976],{"id":8975},"_1-tar-的字母到底管什么","1. tar 的字母到底管什么",[51,8978,8979,8991],{},[54,8980,8981],{},[57,8982,8983,8986,8988],{},[60,8984,8985],{},"字母",[60,8987,68],{},[60,8989,8990],{},"单独用",[70,8992,8993,9010,9025,9039,9053,9067,9089,9101,9119,9135,9151],{},[57,8994,8995,8999,9004],{},[75,8996,8997],{},[19,8998,969],{},[75,9000,9001,9003],{},[27,9002,969],{},"reate 创建归档",[75,9005,9006,9007,9009],{},"与 ",[19,9008,3558],{}," 互斥",[57,9011,9012,9016,9022],{},[75,9013,9014],{},[19,9015,3558],{},[75,9017,9018,9019,9021],{},"e",[27,9020,3558],{},"tract 解包",[75,9023,9024],{},"—",[57,9026,9027,9031,9036],{},[75,9028,9029],{},[19,9030,1049],{},[75,9032,9033,9035],{},[27,9034,1049],{},"able of contents 列内容",[75,9037,9038],{},"不解包先看清单",[57,9040,9041,9045,9050],{},[75,9042,9043],{},[19,9044,3888],{},[75,9046,9047,9049],{},[27,9048,3888],{},"epend 追加（不能压缩）",[75,9051,9052],{},"少用",[57,9054,9055,9059,9064],{},[75,9056,9057],{},[19,9058,2787],{},[75,9060,9061,9063],{},[27,9062,2787],{},"pdate 只追加更新的文件",[75,9065,9066],{},"类似增量备份",[57,9068,9069,9074,9083],{},[75,9070,9071],{},[19,9072,9073],{},"f",[75,9075,9076,9077,9079,9080],{},"归档",[27,9078,9073],{},"ile 名，",[27,9081,9082],{},"必须紧跟文件名",[75,9084,9085,9088],{},[19,9086,9087],{},"-f -"," 表示 stdin\u002Fstdout",[57,9090,9091,9095,9098],{},[75,9092,9093],{},[19,9094,2816],{},[75,9096,9097],{},"verbose 打印过程",[75,9099,9100],{},"看进度用",[57,9102,9103,9108,9116],{},[75,9104,9105],{},[19,9106,9107],{},"z",[75,9109,9110,9111,662,9113,1062],{},"走 gzip（",[19,9112,8935],{},[19,9114,9115],{},".tgz",[75,9117,9118],{},"速度快、体积一般",[57,9120,9121,9126,9132],{},[75,9122,9123],{},[19,9124,9125],{},"j",[75,9127,9128,9129,1062],{},"走 bzip2（",[19,9130,9131],{},".tar.bz2",[75,9133,9134],{},"慢、体积小",[57,9136,9137,9142,9148],{},[75,9138,9139],{},[19,9140,9141],{},"J",[75,9143,9144,9145,1062],{},"走 xz（",[19,9146,9147],{},".tar.xz",[75,9149,9150],{},"最慢、最小，发行版源码包常用",[57,9152,9153,9157,9163],{},[75,9154,9155],{},[19,9156,3439],{},[75,9158,9159,9160,1062],{},"按后缀自动选压缩器（",[19,9161,9162],{},"--auto-compress",[75,9164,9165],{},"GNU tar 1.15+ 创建时可用",[46,9167,9169],{"id":9168},"_2-tar-常用写法","2. tar 常用写法",[211,9171,9173],{"className":213,"code":9172,"language":215,"meta":216,"style":216},"tar cf app.tar \u002Fsrv\u002Fapp         # 打包不压缩：c 创建、f 后跟归档名\ntar czf app.tar.gz \u002Fsrv\u002Fapp     # 打包 + gzip；验证用 tar tzf app.tar.gz\ntar cJf app.tar.xz \u002Fsrv\u002Fapp     # 打包 + xz：最小、最慢\ntar caf app.tar.xz \u002Fsrv\u002Fapp     # a 按后缀自动选压缩器，不容易打错\n\ntar xzf app.tar.gz -C \u002Fsrv\u002Fapp  # 解包到指定目录：x 解、-C 目标目录（目录需已存在，-C 不会自动建）\ntar tzf app.tar.gz | head       # 解包前先看清单——必须的一步！没有顶层目录的归档（tarbomb）\n                                # 会把一堆文件直接散在当前目录；新版 tar 可用 --one-top-level 自动套一层\ntar xzf app.tar.gz etc\u002Fnginx\u002Fnginx.conf   # 只解一个文件，路径按 tar tzf 里看到的原样写\ntar xzf app.tar.gz -C \u002Ftmp --wildcards 'etc\u002Fnginx\u002F*'   # 只取归档里的某个目录\n\ntar czf app.tgz --exclude='.\u002Fnode_modules' --exclude='*.log' .   # 排除若干：--exclude 必须在创建之前给出\ntar czf app.tgz -X \u002Fsrv\u002Fexclude.txt .                             # 排除清单来自文件，一行一个 glob\nsudo tar xzf app.tar.gz -C \u002F      # 保留权限解包：不加 sudo 时属主会变执行者\ntar czhf a.tgz dir                # -h 把软链接指向的内容打进去（默认只存链接本身）\n",[19,9174,9175,9190,9205,9220,9234,9238,9254,9270,9275,9289,9310,9314,9339,9358,9376],{"__ignoreMap":216},[220,9176,9177,9179,9182,9185,9187],{"class":222,"line":223},[220,9178,8945],{"class":226},[220,9180,9181],{"class":229}," cf",[220,9183,9184],{"class":229}," app.tar",[220,9186,894],{"class":229},[220,9188,9189],{"class":233},"         # 打包不压缩：c 创建、f 后跟归档名\n",[220,9191,9192,9194,9197,9200,9202],{"class":222,"line":276},[220,9193,8945],{"class":226},[220,9195,9196],{"class":229}," czf",[220,9198,9199],{"class":229}," app.tar.gz",[220,9201,894],{"class":229},[220,9203,9204],{"class":233},"     # 打包 + gzip；验证用 tar tzf app.tar.gz\n",[220,9206,9207,9209,9212,9215,9217],{"class":222,"line":594},[220,9208,8945],{"class":226},[220,9210,9211],{"class":229}," cJf",[220,9213,9214],{"class":229}," app.tar.xz",[220,9216,894],{"class":229},[220,9218,9219],{"class":233},"     # 打包 + xz：最小、最慢\n",[220,9221,9222,9224,9227,9229,9231],{"class":222,"line":925},[220,9223,8945],{"class":226},[220,9225,9226],{"class":229}," caf",[220,9228,9214],{"class":229},[220,9230,894],{"class":229},[220,9232,9233],{"class":233},"     # a 按后缀自动选压缩器，不容易打错\n",[220,9235,9236],{"class":222,"line":931},[220,9237,3740],{"emptyLinePlaceholder":3739},[220,9239,9240,9242,9245,9247,9249,9251],{"class":222,"line":3528},[220,9241,8945],{"class":226},[220,9243,9244],{"class":229}," xzf",[220,9246,9199],{"class":229},[220,9248,2971],{"class":266},[220,9250,894],{"class":229},[220,9252,9253],{"class":233},"  # 解包到指定目录：x 解、-C 目标目录（目录需已存在，-C 不会自动建）\n",[220,9255,9256,9258,9261,9263,9265,9267],{"class":222,"line":3537},[220,9257,8945],{"class":226},[220,9259,9260],{"class":229}," tzf",[220,9262,9199],{"class":229},[220,9264,2626],{"class":2439},[220,9266,4431],{"class":226},[220,9268,9269],{"class":233},"       # 解包前先看清单——必须的一步！没有顶层目录的归档（tarbomb）\n",[220,9271,9272],{"class":222,"line":3546},[220,9273,9274],{"class":233},"                                # 会把一堆文件直接散在当前目录；新版 tar 可用 --one-top-level 自动套一层\n",[220,9276,9277,9279,9281,9283,9286],{"class":222,"line":3555},[220,9278,8945],{"class":226},[220,9280,9244],{"class":229},[220,9282,9199],{"class":229},[220,9284,9285],{"class":229}," etc\u002Fnginx\u002Fnginx.conf",[220,9287,9288],{"class":233},"   # 只解一个文件，路径按 tar tzf 里看到的原样写\n",[220,9290,9291,9293,9295,9297,9299,9301,9304,9307],{"class":222,"line":3564},[220,9292,8945],{"class":226},[220,9294,9244],{"class":229},[220,9296,9199],{"class":229},[220,9298,2971],{"class":266},[220,9300,8501],{"class":229},[220,9302,9303],{"class":266}," --wildcards",[220,9305,9306],{"class":229}," 'etc\u002Fnginx\u002F*'",[220,9308,9309],{"class":233},"   # 只取归档里的某个目录\n",[220,9311,9312],{"class":222,"line":3572},[220,9313,3740],{"emptyLinePlaceholder":3739},[220,9315,9316,9318,9320,9323,9326,9329,9331,9334,9336],{"class":222,"line":3580},[220,9317,8945],{"class":226},[220,9319,9196],{"class":229},[220,9321,9322],{"class":229}," app.tgz",[220,9324,9325],{"class":266}," --exclude=",[220,9327,9328],{"class":229},"'.\u002Fnode_modules'",[220,9330,9325],{"class":266},[220,9332,9333],{"class":229},"'*.log'",[220,9335,5517],{"class":229},[220,9337,9338],{"class":233},"   # 排除若干：--exclude 必须在创建之前给出\n",[220,9340,9341,9343,9345,9347,9350,9353,9355],{"class":222,"line":3588},[220,9342,8945],{"class":226},[220,9344,9196],{"class":229},[220,9346,9322],{"class":229},[220,9348,9349],{"class":266}," -X",[220,9351,9352],{"class":229}," \u002Fsrv\u002Fexclude.txt",[220,9354,5517],{"class":229},[220,9356,9357],{"class":233},"                             # 排除清单来自文件，一行一个 glob\n",[220,9359,9360,9362,9365,9367,9369,9371,9373],{"class":222,"line":7714},[220,9361,120],{"class":226},[220,9363,9364],{"class":229}," tar",[220,9366,9244],{"class":229},[220,9368,9199],{"class":229},[220,9370,2971],{"class":266},[220,9372,3621],{"class":229},[220,9374,9375],{"class":233},"      # 保留权限解包：不加 sudo 时属主会变执行者\n",[220,9377,9378,9380,9383,9386,9388],{"class":222,"line":7726},[220,9379,8945],{"class":226},[220,9381,9382],{"class":229}," czhf",[220,9384,9385],{"class":229}," a.tgz",[220,9387,4270],{"class":229},[220,9389,9390],{"class":233},"                # -h 把软链接指向的内容打进去（默认只存链接本身）\n",[46,9392,9394],{"id":9393},"_3-tar-与管道目录到目录的流式搬运","3. tar 与管道：目录到目录的流式搬运",[211,9396,9398],{"className":213,"code":9397,"language":215,"meta":216,"style":216},"tar cf - -C \u002Fsrv\u002Fapp . | tar xf - -C \u002Fsrv\u002Fapp-backup    # 目录到目录流式复制，保留权限，不落地中间文件\ntar czf - -C \u002Fsrv app | ssh deploy@backup 'cat > \u002Fbackup\u002Fapp.tgz'   # 跨机实时打包传过去\nssh deploy@server 'tar czf - \u002Fsrv\u002Fapp' | tar xzf - -C \u002Flocal\u002Fbackup  # 反过来，从远端拉一个目录\n",[19,9399,9400,9431,9460],{"__ignoreMap":216},[220,9401,9402,9404,9406,9408,9410,9412,9414,9416,9418,9421,9423,9425,9428],{"class":222,"line":223},[220,9403,8945],{"class":226},[220,9405,9181],{"class":229},[220,9407,1338],{"class":229},[220,9409,2971],{"class":266},[220,9411,894],{"class":229},[220,9413,5517],{"class":229},[220,9415,2626],{"class":2439},[220,9417,9364],{"class":226},[220,9419,9420],{"class":229}," xf",[220,9422,1338],{"class":229},[220,9424,2971],{"class":266},[220,9426,9427],{"class":229}," \u002Fsrv\u002Fapp-backup",[220,9429,9430],{"class":233},"    # 目录到目录流式复制，保留权限，不落地中间文件\n",[220,9432,9433,9435,9437,9439,9441,9444,9446,9448,9451,9454,9457],{"class":222,"line":276},[220,9434,8945],{"class":226},[220,9436,9196],{"class":229},[220,9438,1338],{"class":229},[220,9440,2971],{"class":266},[220,9442,9443],{"class":229}," \u002Fsrv",[220,9445,4404],{"class":229},[220,9447,2626],{"class":2439},[220,9449,9450],{"class":226}," ssh",[220,9452,9453],{"class":229}," deploy@backup",[220,9455,9456],{"class":229}," 'cat > \u002Fbackup\u002Fapp.tgz'",[220,9458,9459],{"class":233},"   # 跨机实时打包传过去\n",[220,9461,9462,9464,9467,9470,9472,9474,9476,9478,9480,9483],{"class":222,"line":594},[220,9463,5384],{"class":226},[220,9465,9466],{"class":229}," deploy@server",[220,9468,9469],{"class":229}," 'tar czf - \u002Fsrv\u002Fapp'",[220,9471,2626],{"class":2439},[220,9473,9364],{"class":226},[220,9475,9244],{"class":229},[220,9477,1338],{"class":229},[220,9479,2971],{"class":266},[220,9481,9482],{"class":229}," \u002Flocal\u002Fbackup",[220,9484,9485],{"class":233},"  # 反过来，从远端拉一个目录\n",[15,9487,9488,9491,9492,9494,9495,9491,9498,9500,9501,616],{},[19,9489,9490],{},"tar cf -"," 里的 ",[19,9493,960],{}," 是 stdout，",[19,9496,9497],{},"xf -",[19,9499,960],{}," 是 stdin。这种写法的价值：不产生临时文件、能跨过空间不足的目标盘、管道里还能插一层 ",[19,9502,8953],{},[46,9504,9506],{"id":9505},"_4-gzipbzip2xzzstd-单文件压缩器","4. gzip、bzip2、xz、zstd —— 单文件压缩器",[211,9508,9510],{"className":213,"code":9509,"language":215,"meta":216,"style":216},"gzip -9 app.log        # 压缩并替换原文件：-1 最快、-9 最小（默认 6）；-k 保留原文件\ngunzip app.log.gz      # 解压（等价 gzip -d）；解压后 .gz 消失\nbzip2 -9 file          # 更高压缩、更慢，大文件慢到不可用\nxz -9e file            # 当前通用最优：-9e 额外努力；解压很快\nzstd -19 file          # 现代首选（快 + 小），服务器未必装，Alpine 仓库有\nzcat app.log.gz        # 不解压直接看\nzgrep -h 'ERROR' \u002Fvar\u002Flog\u002F*.gz   # 不解压直接搜——查历史日志必备，-h 去掉文件名前缀\nzless app.log.gz       # 分页看压缩包（less 配了 LESSOPEN 也能直接开 .gz）\n",[19,9511,9512,9524,9535,9547,9559,9572,9582,9602],{"__ignoreMap":216},[220,9513,9514,9516,9519,9521],{"class":222,"line":223},[220,9515,8953],{"class":226},[220,9517,9518],{"class":266}," -9",[220,9520,1445],{"class":229},[220,9522,9523],{"class":233},"        # 压缩并替换原文件：-1 最快、-9 最小（默认 6）；-k 保留原文件\n",[220,9525,9526,9529,9532],{"class":222,"line":276},[220,9527,9528],{"class":226},"gunzip",[220,9530,9531],{"class":229}," app.log.gz",[220,9533,9534],{"class":233},"      # 解压（等价 gzip -d）；解压后 .gz 消失\n",[220,9536,9537,9540,9542,9544],{"class":222,"line":594},[220,9538,9539],{"class":226},"bzip2",[220,9541,9518],{"class":266},[220,9543,2376],{"class":229},[220,9545,9546],{"class":233},"          # 更高压缩、更慢，大文件慢到不可用\n",[220,9548,9549,9551,9554,9556],{"class":222,"line":925},[220,9550,8956],{"class":226},[220,9552,9553],{"class":266}," -9e",[220,9555,2376],{"class":229},[220,9557,9558],{"class":233},"            # 当前通用最优：-9e 额外努力；解压很快\n",[220,9560,9561,9564,9567,9569],{"class":222,"line":931},[220,9562,9563],{"class":226},"zstd",[220,9565,9566],{"class":266}," -19",[220,9568,2376],{"class":229},[220,9570,9571],{"class":233},"          # 现代首选（快 + 小），服务器未必装，Alpine 仓库有\n",[220,9573,9574,9577,9579],{"class":222,"line":3528},[220,9575,9576],{"class":226},"zcat",[220,9578,9531],{"class":229},[220,9580,9581],{"class":233},"        # 不解压直接看\n",[220,9583,9584,9587,9589,9591,9594,9596,9599],{"class":222,"line":3537},[220,9585,9586],{"class":226},"zgrep",[220,9588,4373],{"class":266},[220,9590,6639],{"class":229},[220,9592,9593],{"class":229}," \u002Fvar\u002Flog\u002F",[220,9595,2436],{"class":266},[220,9597,9598],{"class":229},".gz",[220,9600,9601],{"class":233},"   # 不解压直接搜——查历史日志必备，-h 去掉文件名前缀\n",[220,9603,9604,9607,9609],{"class":222,"line":3546},[220,9605,9606],{"class":226},"zless",[220,9608,9531],{"class":229},[220,9610,9611],{"class":233},"       # 分页看压缩包（less 配了 LESSOPEN 也能直接开 .gz）\n",[15,9613,9614,9616,9617,9619,9620,9623],{},[19,9615,9586],{}," 处理多个 ",[19,9618,9598],{}," 时按文件名顺序扫，配合 ",[19,9621,9622],{},"ls -r"," 可以从最新往回找，找完立刻停。",[46,9625,9627],{"id":9626},"_5-zip-unzip","5. zip \u002F unzip",[211,9629,9631],{"className":213,"code":9630,"language":215,"meta":216,"style":216},"zip -r site.zip dist\u002F          # 压缩目录：-r 递归；-9 最高压缩、-q 安静\nzip -ru site.zip dist\u002F         # -u 只加更新的文件（zip 支持增量更新归档）\nzip -erP 'pass' site.zip dist\u002F # 加密码：传统 ZipCrypto 很弱，敏感数据别用 zip\nunzip -l site.zip              # -l 只列内容不解包\nunzip site.zip -d \u002Fsrv\u002Fapp     # -d 解压到目录；目录不存在会创建（与 tar -C 相反）\nunzip -o site.zip              # -o 全覆盖 \u002F -n 不覆盖——默认会逐个问，脚本里必须指定\nzipinfo -1 site.zip            # 比 unzip -l 灵活：-h 头部、-s 大小列表\nzip -FF broken.zip --out fixed.zip   # 传输截断时抢救\n",[19,9632,9633,9649,9663,9680,9692,9705,9716,9728],{"__ignoreMap":216},[220,9634,9635,9638,9640,9643,9646],{"class":222,"line":223},[220,9636,9637],{"class":226},"zip",[220,9639,7611],{"class":266},[220,9641,9642],{"class":229}," site.zip",[220,9644,9645],{"class":229}," dist\u002F",[220,9647,9648],{"class":233},"          # 压缩目录：-r 递归；-9 最高压缩、-q 安静\n",[220,9650,9651,9653,9656,9658,9660],{"class":222,"line":276},[220,9652,9637],{"class":226},[220,9654,9655],{"class":266}," -ru",[220,9657,9642],{"class":229},[220,9659,9645],{"class":229},[220,9661,9662],{"class":233},"         # -u 只加更新的文件（zip 支持增量更新归档）\n",[220,9664,9665,9667,9670,9673,9675,9677],{"class":222,"line":594},[220,9666,9637],{"class":226},[220,9668,9669],{"class":266}," -erP",[220,9671,9672],{"class":229}," 'pass'",[220,9674,9642],{"class":229},[220,9676,9645],{"class":229},[220,9678,9679],{"class":233}," # 加密码：传统 ZipCrypto 很弱，敏感数据别用 zip\n",[220,9681,9682,9685,9687,9689],{"class":222,"line":925},[220,9683,9684],{"class":226},"unzip",[220,9686,3193],{"class":266},[220,9688,9642],{"class":229},[220,9690,9691],{"class":233},"              # -l 只列内容不解包\n",[220,9693,9694,9696,9698,9700,9702],{"class":222,"line":931},[220,9695,9684],{"class":226},[220,9697,9642],{"class":229},[220,9699,1455],{"class":266},[220,9701,894],{"class":229},[220,9703,9704],{"class":233},"     # -d 解压到目录；目录不存在会创建（与 tar -C 相反）\n",[220,9706,9707,9709,9711,9713],{"class":222,"line":3528},[220,9708,9684],{"class":226},[220,9710,5606],{"class":266},[220,9712,9642],{"class":229},[220,9714,9715],{"class":233},"              # -o 全覆盖 \u002F -n 不覆盖——默认会逐个问，脚本里必须指定\n",[220,9717,9718,9721,9723,9725],{"class":222,"line":3537},[220,9719,9720],{"class":226},"zipinfo",[220,9722,1214],{"class":266},[220,9724,9642],{"class":229},[220,9726,9727],{"class":233},"            # 比 unzip -l 灵活：-h 头部、-s 大小列表\n",[220,9729,9730,9732,9735,9738,9741,9744],{"class":222,"line":3546},[220,9731,9637],{"class":226},[220,9733,9734],{"class":266}," -FF",[220,9736,9737],{"class":229}," broken.zip",[220,9739,9740],{"class":266}," --out",[220,9742,9743],{"class":229}," fixed.zip",[220,9745,9746],{"class":233},"   # 传输截断时抢救\n",[15,9748,9749,827,9751,9753,9754,9757],{},[19,9750,8945],{},[19,9752,9637],{}," 的选择：",[27,9755,9756],{},"Linux 服务器之间用 tar.gz\u002Fxz（保留权限、时间戳、软链接），要给 Windows 或走浏览器下载用 zip","。zip 不保存 Unix 权限位，解压后脚本全部丢执行位。",[46,9759,808],{"id":9760},"踩坑提醒-8",[810,9762,9763,9784,9801,9816,9825],{},[813,9764,9765,9767,9768,9770,9771,9774,9775,9777,9778,418,9781,9783],{},[19,9766,8945],{}," 忘记 ",[19,9769,2600],{}," 后跟文件名（如 ",[19,9772,9773],{},"tar -czf app","）会把 ",[19,9776,960],{}," 当成文件，或报 ",[19,9779,9780],{},"Cowardly refusing to create an empty archive",[19,9782,9073],{}," 永远放参数最后。",[813,9785,9786,9787,9789,9790,34,9793,9796,9797,9800],{},"打包整个 ",[19,9788,662],{}," 会卡在 ",[19,9791,9792],{},"\u002Fproc",[19,9794,9795],{},"\u002Fsys"," 上：加 ",[19,9798,9799],{},"--exclude={\u002Fproc,\u002Fsys,\u002Fdev,\u002Frun}","，或直接用专门的备份工具。",[813,9802,9803,9804,9806,9807,9809,9810,6508,9813,616],{},"压缩前先看体积：",[19,9805,1246],{},"；对一个正在被写入的日志压缩会得到两份都不完整的文件，先 ",[19,9808,1542],{}," 快照或用 ",[19,9811,9812],{},"logrotate",[19,9814,9815],{},"copytruncate",[813,9817,9818,827,9821,9824],{},[19,9819,9820],{},"gzip -9",[19,9822,9823],{},"-6"," 的体积差常在 1–3%，时间差却是几倍，别为了「更小」把部署脚本拖慢。",[813,9826,9827,9829],{},[19,9828,8935],{}," 想追加文件是不可能的（压缩流不能追加），需要重打包——这是很多人踩过的设计限制。",[10,9831,9833],{"id":9832},"十进程pstopkill","十、进程：ps、top、kill",[15,9835,9836,9837,9839,9840,9843,9844,9847,9848,9851,9852,616],{},"「服务没起来」的第一现场永远是进程列表：它在不在、属主是谁、CPU 吃了多少、状态是 ",[19,9838,1033],{}," 还是 ",[19,9841,9842],{},"Z","。这一章把 ",[19,9845,9846],{},"ps"," 的每一列、",[19,9849,9850],{},"top"," 的每个数字、信号的优先级讲清楚，然后给出",[27,9853,9854],{},"杀进程的正确顺序",[861,9856],{"height":9857,"src":9858,"title":9859},680,"\u002Ftutorials\u002Flinux\u002Fdemos\u002F10-processes.html","示例 10：ps 各列含义、top 读数与 kill 的正确顺序",[46,9861,9863],{"id":9862},"_1-ps-抓一份进程快照","1. ps —— 抓一份进程快照",[15,9865,9866,9868],{},[19,9867,9846],{}," 有两种参数风格，效果接近：",[211,9870,9872],{"className":213,"code":9871,"language":215,"meta":216,"style":216},"ps aux     # BSD 风格：USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND\nps -ef     # POSIX\u002FUNIX 风格：UID PID PPID C STIME TTY TIME CMD，带父进程号\nps -fp 28414                    # 看单个 PID 的详情（含父进程）\nps -u deploy -o pid,cmd         # 只看某个用户起了什么\nps aux --sort=-rss | head       # 按内存排序，找吃内存的大户\nps -eo pid,ppid,user,%cpu,rss,etime,cmd --sort=-%cpu    # 自定义列（最推荐，脚本里稳）\npstree -ap 28414                # 进程树看父子关系（-a 带命令行）\n",[19,9873,9874,9884,9894,9907,9923,9939,9955],{"__ignoreMap":216},[220,9875,9876,9878,9881],{"class":222,"line":223},[220,9877,9846],{"class":226},[220,9879,9880],{"class":229}," aux",[220,9882,9883],{"class":233},"     # BSD 风格：USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND\n",[220,9885,9886,9888,9891],{"class":222,"line":276},[220,9887,9846],{"class":226},[220,9889,9890],{"class":266}," -ef",[220,9892,9893],{"class":233},"     # POSIX\u002FUNIX 风格：UID PID PPID C STIME TTY TIME CMD，带父进程号\n",[220,9895,9896,9898,9901,9904],{"class":222,"line":594},[220,9897,9846],{"class":226},[220,9899,9900],{"class":266}," -fp",[220,9902,9903],{"class":266}," 28414",[220,9905,9906],{"class":233},"                    # 看单个 PID 的详情（含父进程）\n",[220,9908,9909,9911,9913,9915,9917,9920],{"class":222,"line":925},[220,9910,9846],{"class":226},[220,9912,4787],{"class":266},[220,9914,4424],{"class":229},[220,9916,5606],{"class":266},[220,9918,9919],{"class":229}," pid,cmd",[220,9921,9922],{"class":233},"         # 只看某个用户起了什么\n",[220,9924,9925,9927,9929,9932,9934,9936],{"class":222,"line":931},[220,9926,9846],{"class":226},[220,9928,9880],{"class":229},[220,9930,9931],{"class":266}," --sort=-rss",[220,9933,2626],{"class":2439},[220,9935,4431],{"class":226},[220,9937,9938],{"class":233},"       # 按内存排序，找吃内存的大户\n",[220,9940,9941,9943,9946,9949,9952],{"class":222,"line":3528},[220,9942,9846],{"class":226},[220,9944,9945],{"class":266}," -eo",[220,9947,9948],{"class":229}," pid,ppid,user,%cpu,rss,etime,cmd",[220,9950,9951],{"class":266}," --sort=-%cpu",[220,9953,9954],{"class":233},"    # 自定义列（最推荐，脚本里稳）\n",[220,9956,9957,9960,9963,9965],{"class":222,"line":3537},[220,9958,9959],{"class":226},"pstree",[220,9961,9962],{"class":266}," -ap",[220,9964,9903],{"class":266},[220,9966,9967],{"class":233},"                # 进程树看父子关系（-a 带命令行）\n",[15,9969,9970,9973],{},[19,9971,9972],{},"ps aux"," 各列含义：",[51,9975,9976,9988],{},[54,9977,9978],{},[57,9979,9980,9983,9985],{},[60,9981,9982],{},"列",[60,9984,68],{},[60,9986,9987],{},"读法与坑",[70,9989,9990,10008,10025,10042,10058,10071,10086,10101,10114,10127,10140],{},[57,9991,9992,9997,10000],{},[75,9993,9994],{},[19,9995,9996],{},"USER",[75,9998,9999],{},"属主用户",[75,10001,10002,10003,662,10005,10007],{},"服务真实用户常是 ",[19,10004,5189],{},[19,10006,8456],{},"，不是你",[57,10009,10010,10015,10018],{},[75,10011,10012],{},[19,10013,10014],{},"PID",[75,10016,10017],{},"进程号",[75,10019,10020,10021,10024],{},"唯一，",[19,10022,10023],{},"kill"," 用它",[57,10026,10027,10032,10039],{},[75,10028,10029],{},[19,10030,10031],{},"%CPU",[75,10033,10034,10035,10038],{},"自进程启动以来的",[27,10036,10037],{},"平均"," CPU 占比",[75,10040,10041],{},"单核满载 = 100；8 核全用可到 800；多核机器上超 100 完全正常",[57,10043,10044,10049,10052],{},[75,10045,10046],{},[19,10047,10048],{},"%MEM",[75,10050,10051],{},"RSS 占物理内存百分比",[75,10053,9006,10054,10057],{},[19,10055,10056],{},"RSS"," 一致，别拿它当「真实占用」",[57,10059,10060,10065,10068],{},[75,10061,10062],{},[19,10063,10064],{},"VSZ",[75,10066,10067],{},"虚拟地址空间大小（KB）",[75,10069,10070],{},"包含未实际分配的、映射的、共享的，JVM\u002F数据库常见几个 G 而 RSS 很小",[57,10072,10073,10077,10080],{},[75,10074,10075],{},[19,10076,10056],{},[75,10078,10079],{},"常驻物理内存（KB）",[75,10081,10082,10085],{},[27,10083,10084],{},"实际占用看这一列","；共享库会被多个进程重复计",[57,10087,10088,10093,10096],{},[75,10089,10090],{},[19,10091,10092],{},"TTY",[75,10094,10095],{},"控制终端",[75,10097,10098,10100],{},[19,10099,3465],{}," 表示无终端（服务\u002F后台进程）",[57,10102,10103,10108,10111],{},[75,10104,10105],{},[19,10106,10107],{},"STAT",[75,10109,10110],{},"状态码",[75,10112,10113],{},"见下表，排障关键",[57,10115,10116,10121,10124],{},[75,10117,10118],{},[19,10119,10120],{},"START",[75,10122,10123],{},"启动时刻",[75,10125,10126],{},"显示日期表示不是今天起的",[57,10128,10129,10134,10137],{},[75,10130,10131],{},[19,10132,10133],{},"TIME",[75,10135,10136],{},"累计 CPU 时间（不是墙上时间）",[75,10138,10139],{},"跑了三天但 TIME 只有 5 分钟 = 一直在等 IO",[57,10141,10142,10147,10150],{},[75,10143,10144],{},[19,10145,10146],{},"COMMAND",[75,10148,10149],{},"命令行",[75,10151,10152,10154,10155,10157],{},[19,10153,9846],{}," 只显示一次快照，",[19,10156,9850],{}," 会跟着改",[15,10159,10160,10162],{},[19,10161,10107],{}," 常见组合：",[51,10164,10165,10176],{},[54,10166,10167],{},[57,10168,10169,10172,10174],{},[60,10170,10171],{},"码",[60,10173,68],{},[60,10175,452],{},[70,10177,10178,10191,10203,10221,10236,10252,10267],{},[57,10179,10180,10185,10188],{},[75,10181,10182],{},[19,10183,10184],{},"R",[75,10186,10187],{},"运行\u002F可运行",[75,10189,10190],{},"在 CPU 上或在队列里",[57,10192,10193,10197,10200],{},[75,10194,10195],{},[19,10196,1033],{},[75,10198,10199],{},"可中断睡眠",[75,10201,10202],{},"绝大多数正常服务的状态",[57,10204,10205,10210,10215],{},[75,10206,10207],{},[19,10208,10209],{},"D",[75,10211,10212],{},[27,10213,10214],{},"不可中断睡眠",[75,10216,10217,10218,10220],{},"通常在等磁盘\u002FNFS；",[19,10219,43],{}," 对它无效，这是「进程杀不掉」的头号原因",[57,10222,10223,10227,10230],{},[75,10224,10225],{},[19,10226,1052],{},[75,10228,10229],{},"停止",[75,10231,10232,10233,10235],{},"被 ",[19,10234,8830],{}," 或 SIGSTOP 挂起",[57,10237,10238,10242,10245],{},[75,10239,10240],{},[19,10241,9842],{},[75,10243,10244],{},"僵尸（defunct）",[75,10246,10247,10248,10251],{},"已退出但父进程没 ",[19,10249,10250],{},"wait","；无法再被杀，只能让父进程回收",[57,10253,10254,10261,10264],{},[75,10255,10256,1073,10259],{},[19,10257,10258],{},"\u003C",[19,10260,2767],{},[75,10262,10263],{},"高优先级 \u002F 低优先级",[75,10265,10266],{},"nice 值的标记",[57,10268,10269,10277,10280],{},[75,10270,10271,1073,10273,1073,10275],{},[19,10272,966],{},[19,10274,975],{},[19,10276,5137],{},[75,10278,10279],{},"多线程 \u002F 会话首领 \u002F 前台进程组",[75,10281,10282,10284],{},[19,10283,975],{}," 常见于服务进程",[46,10286,10288],{"id":10287},"_2-pgrep-与-pidof-找-pid","2. pgrep 与 pidof —— 找 PID",[211,10290,10292],{"className":213,"code":10291,"language":215,"meta":216,"style":216},"pgrep -f 'gunicorn.*app'     # -f 按正则匹配整条命令行；不加 -f 只看程序名（15 字符截断）\npgrep -u deploy -f worker    # 叠加用户限定，更精确\npgrep -l -f nginx            # -l 同时打印进程名，快速核对是不是自己要的\npidof nginx                  # 按可执行文件名精确列出所有 PID：28414 28415\npidstat -p 28414 1           # 持续采样某进程的 CPU\u002FIO（sysstat 包），排「这个进程到底在忙什么」\n",[19,10293,10294,10307,10323,10336,10346],{"__ignoreMap":216},[220,10295,10296,10299,10301,10304],{"class":222,"line":223},[220,10297,10298],{"class":226},"pgrep",[220,10300,2573],{"class":266},[220,10302,10303],{"class":229}," 'gunicorn.*app'",[220,10305,10306],{"class":233},"     # -f 按正则匹配整条命令行；不加 -f 只看程序名（15 字符截断）\n",[220,10308,10309,10311,10313,10315,10317,10320],{"class":222,"line":276},[220,10310,10298],{"class":226},[220,10312,4787],{"class":266},[220,10314,4424],{"class":229},[220,10316,2573],{"class":266},[220,10318,10319],{"class":229}," worker",[220,10321,10322],{"class":233},"    # 叠加用户限定，更精确\n",[220,10324,10325,10327,10329,10331,10333],{"class":222,"line":594},[220,10326,10298],{"class":226},[220,10328,3193],{"class":266},[220,10330,2573],{"class":266},[220,10332,600],{"class":229},[220,10334,10335],{"class":233},"            # -l 同时打印进程名，快速核对是不是自己要的\n",[220,10337,10338,10341,10343],{"class":222,"line":925},[220,10339,10340],{"class":226},"pidof",[220,10342,600],{"class":229},[220,10344,10345],{"class":233},"                  # 按可执行文件名精确列出所有 PID：28414 28415\n",[220,10347,10348,10351,10353,10355,10357],{"class":222,"line":931},[220,10349,10350],{"class":226},"pidstat",[220,10352,1385],{"class":266},[220,10354,9903],{"class":266},[220,10356,267],{"class":266},[220,10358,10359],{"class":233},"           # 持续采样某进程的 CPU\u002FIO（sysstat 包），排「这个进程到底在忙什么」\n",[15,10361,2286,10362,10365,10366,10369,10370,10373,10374,10376],{},[19,10363,10364],{},"pgrep -f"," 时的自杀风险：",[19,10367,10368],{},"pkill -f 'tail -f app.log'"," 可能匹配到执行这条命令的 shell 本身。稳妥写法是加 ",[19,10371,10372],{},"-x","（精确名）或用 ",[19,10375,10298],{}," 先列出确认。",[46,10378,10380],{"id":10379},"_3-top-实时视图","3. top —— 实时视图",[15,10382,10383,10385],{},[19,10384,9850],{}," 的头部五区域与关键读数：",[51,10387,10388,10401],{},[54,10389,10390],{},[57,10391,10392,10395,10398],{},[60,10393,10394],{},"区域",[60,10396,10397],{},"样例",[60,10399,10400],{},"怎么读",[70,10402,10403,10416,10429,10459,10479],{},[57,10404,10405,10408,10413],{},[75,10406,10407],{},"第 1 行",[75,10409,10410],{},[19,10411,10412],{},"top - 09:24:31 up 14 days, 3:12, 2 users, load average: 0.32, 0.41, 0.38",[75,10414,10415],{},"当前时间、运行时长、登录数、1\u002F5\u002F15 分钟平均负载",[57,10417,10418,10421,10426],{},[75,10419,10420],{},"第 2 行",[75,10422,10423],{},[19,10424,10425],{},"Tasks: 183 total, 1 running, 182 sleeping, 0 stopped, 0 zombie",[75,10427,10428],{},"zombie 非 0 说明有父进程不回收",[57,10430,10431,10434,10439],{},[75,10432,10433],{},"第 3 行",[75,10435,10436],{},[19,10437,10438],{},"%Cpu(s): 12.3 us, 3.1 sy, 0.0 ni, 83.9 id, 0.4 wa, 0.0 hi, 0.3 si",[75,10440,10441,10444,10445,10448,10449,10452,10453],{},[19,10442,10443],{},"us"," 用户、",[19,10446,10447],{},"sy"," 内核、",[19,10450,10451],{},"id"," 空闲、",[27,10454,10455,10458],{},[19,10456,10457],{},"wa"," 高等待说明磁盘瓶颈",[57,10460,10461,10464,10472],{},[75,10462,10463],{},"第 4-5 行",[75,10465,10466,1073,10469],{},[19,10467,10468],{},"MiB Mem: 7861.4 total, 214.3 free, 3210.5 used, 4436.6 buff\u002Fcache",[19,10470,10471],{},"MiB Swap: 2048.0 total, 1900.0 free",[75,10473,10474,10475,10478],{},"看可用内存要算 ",[19,10476,10477],{},"free + buff\u002Fcache","（第十七章细讲）",[57,10480,10481,10484,10489],{},[75,10482,10483],{},"进程表",[75,10485,10486],{},[19,10487,10488],{},"PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND",[75,10490,10491,10494,10495,10498,10499,10502,10503],{},[19,10492,10493],{},"VIRT"," 虚拟、",[19,10496,10497],{},"RES"," 常驻、",[19,10500,10501],{},"SHR"," 共享；",[27,10504,10505],{},"真正吃的是 RES",[15,10507,10508],{},[27,10509,10510],{},"示例 1：常用启动方式",[211,10512,10514],{"className":213,"code":10513,"language":215,"meta":216,"style":216},"top -p 28414          # 只看某个进程\ntop -u deploy         # 只看某个用户\ntop -b -n 1 > snap.txt  # 批处理模式抓一次快照，脚本必备\n",[19,10515,10516,10527,10538],{"__ignoreMap":216},[220,10517,10518,10520,10522,10524],{"class":222,"line":223},[220,10519,9850],{"class":226},[220,10521,1385],{"class":266},[220,10523,9903],{"class":266},[220,10525,10526],{"class":233},"          # 只看某个进程\n",[220,10528,10529,10531,10533,10535],{"class":222,"line":276},[220,10530,9850],{"class":226},[220,10532,4787],{"class":266},[220,10534,4424],{"class":229},[220,10536,10537],{"class":233},"         # 只看某个用户\n",[220,10539,10540,10542,10544,10546,10548,10550,10553],{"class":222,"line":594},[220,10541,9850],{"class":226},[220,10543,1685],{"class":266},[220,10545,1669],{"class":266},[220,10547,267],{"class":266},[220,10549,2440],{"class":2439},[220,10551,10552],{"class":229}," snap.txt",[220,10554,10555],{"class":233},"  # 批处理模式抓一次快照，脚本必备\n",[15,10557,10558],{},[27,10559,10560],{},"示例 2：交互键（进入 top 之后按）",[51,10562,10563,10575],{},[54,10564,10565],{},[57,10566,10567,10569,10571,10573],{},[60,10568,2689],{},[60,10570,2692],{},[60,10572,2689],{},[60,10574,2692],{},[70,10576,10577,10592,10615,10634,10651,10667,10684],{},[57,10578,10579,10583,10585,10589],{},[75,10580,10581],{},[19,10582,240],{},[75,10584,2799],{},[75,10586,10587],{},[19,10588,1072],{},[75,10590,10591],{},"展开每个 CPU 核心",[57,10593,10594,10602,10605,10612],{},[75,10595,10596,1073,10599],{},[19,10597,10598],{},"P",[19,10600,10601],{},"M",[75,10603,10604],{},"按 CPU \u002F 内存排序",[75,10606,10607,10609,10610,1062],{},[19,10608,1052],{},"（小写 ",[19,10611,1049],{},[75,10613,10614],{},"切换头部显示",[57,10616,10617,10621,10624,10631],{},[75,10618,10619],{},[19,10620,969],{},[75,10622,10623],{},"显示完整命令行",[75,10625,10626,1073,10628],{},[19,10627,2787],{},[19,10629,10630],{},"U",[75,10632,10633],{},"只看某用户",[57,10635,10636,10640,10643,10648],{},[75,10637,10638],{},[19,10639,9073],{},[75,10641,10642],{},"选择显示哪些列",[75,10644,10645],{},[19,10646,10647],{},"k",[75,10649,10650],{},"对某 PID 发信号（会二次确认）",[57,10652,10653,10657,10660,10664],{},[75,10654,10655],{},[19,10656,3888],{},[75,10658,10659],{},"renice 某 PID",[75,10661,10662],{},[19,10663,2738],{},[75,10665,10666],{},"切换到某个 cpuset",[57,10668,10669,10674,10677,10681],{},[75,10670,10671],{},[19,10672,10673],{},"H",[75,10675,10676],{},"显示线程",[75,10678,10679],{},[19,10680,2715],{},[75,10682,10683],{},"保存\u002F切换视野组",[57,10685,10686,10689,10692,10698],{},[75,10687,10688],{},"空格",[75,10690,10691],{},"立即刷新",[75,10693,10694,662,10696],{},[19,10695,3442],{},[19,10697,10184],{},[75,10699,10700],{},"改变刷新间隔 \u002F 反向排序",[15,10702,10703,689,10706,10709,10710,10713,10714,10717,10718,10721,10722,10725,10726,10728,10729,10731,10732,1520],{},[19,10704,10705],{},"htop",[19,10707,10708],{},"apt install htop","）好在：彩色树状视图、鼠标可点、",[19,10711,10712],{},"F4"," 过滤、",[19,10715,10716],{},"F9"," 送信号、",[19,10719,10720],{},"F6"," 排序、",[19,10723,10724],{},"Ctrl-L"," 重绘。",[19,10727,10031],{}," 超 100 的解释：一个 8 线程的进程把 2 个核跑满会显示 200%，多核机器上 ",[19,10730,9850],{}," 的 100% 指的是「一个核」，不是总机器的 1\u002F8；判断是否真忙要看展开后的每核利用率（按 ",[19,10733,1072],{},[46,10735,10737],{"id":10736},"_4-kill-与信号-正确顺序三步别跳","4. kill 与信号 —— 正确顺序（三步，别跳）",[51,10739,10740,10759],{},[54,10741,10742],{},[57,10743,10744,10747,10750,10753,10756],{},[60,10745,10746],{},"信号",[60,10748,10749],{},"编号",[60,10751,10752],{},"默认行为",[60,10754,10755],{},"能不能被捕获",[60,10757,10758],{},"用法",[70,10760,10761,10784,10803,10823,10846,10868,10891,10910],{},[57,10762,10763,10768,10770,10776,10778],{},[75,10764,10765],{},[19,10766,10767],{},"HUP",[75,10769,1072],{},[75,10771,10772,10773],{},"挂断 \u002F 让服务",[27,10774,10775],{},"重读配置",[75,10777,3896],{},[75,10779,10780,10783],{},[19,10781,10782],{},"kill -HUP 28414","（老式重载，nginx 仍支持）",[57,10785,10786,10791,10793,10798,10800],{},[75,10787,10788],{},[19,10789,10790],{},"INT",[75,10792,8025],{},[75,10794,10795,10796,1062],{},"中断（等同 ",[19,10797,153],{},[75,10799,3896],{},[75,10801,10802],{},"交互场景",[57,10804,10805,10810,10812,10818,10820],{},[75,10806,10807],{},[19,10808,10809],{},"QUIT",[75,10811,1076],{},[75,10813,10814,10815,1062],{},"退出并 dump core（",[19,10816,10817],{},"Ctrl-\\",[75,10819,3896],{},[75,10821,10822],{},"有些服务用它输出栈",[57,10824,10825,10830,10835,10838,10840],{},[75,10826,10827],{},[19,10828,10829],{},"TERM",[75,10831,10832],{},[27,10833,10834],{},"15",[75,10836,10837],{},"请求终止（默认）",[75,10839,3896],{},[75,10841,10842,10845],{},[19,10843,10844],{},"kill 28414"," 就是它",[57,10847,10848,10853,10858,10861,10865],{},[75,10849,10850],{},[19,10851,10852],{},"KILL",[75,10854,10855],{},[27,10856,10857],{},"9",[75,10859,10860],{},"立刻杀死，内核回收",[75,10862,10863],{},[27,10864,5993],{},[75,10866,10867],{},"最后手段，见下",[57,10869,10870,10875,10878,10881,10885],{},[75,10871,10872],{},[19,10873,10874],{},"STOP",[75,10876,10877],{},"19（x86 Linux）",[75,10879,10880],{},"强制暂停，不可捕获",[75,10882,10883],{},[27,10884,5993],{},[75,10886,10887,10888,5251],{},"冻结进程做调试；停止状态要 ",[19,10889,10890],{},"CONT",[57,10892,10893,10897,10900,10903,10905],{},[75,10894,10895],{},[19,10896,10890],{},[75,10898,10899],{},"18",[75,10901,10902],{},"恢复被 STOP 的进程",[75,10904,9024],{},[75,10906,10907],{},[19,10908,10909],{},"kill -CONT 28414",[57,10911,10912,10920,10923,10926,10928],{},[75,10913,10914,662,10917],{},[19,10915,10916],{},"USR1",[19,10918,10919],{},"USR2",[75,10921,10922],{},"10\u002F12",[75,10924,10925],{},"自定义",[75,10927,3896],{},[75,10929,10930],{},"各服务含义不同（看服务文档）",[211,10932,10934],{"className":213,"code":10933,"language":215,"meta":216,"style":216},"kill 28414                  # ① SIGTERM（15）：给程序机会关连接、刷缓冲、清 pid 文件\nsleep 10; ps -p 28414       # ② 等 10 秒确认，没退出再说\nkill -9 28414               # ③ 仍不掉才 SIGKILL：内核硬拆，不做清理\n",[19,10935,10936,10945,10964],{"__ignoreMap":216},[220,10937,10938,10940,10942],{"class":222,"line":223},[220,10939,10023],{"class":266},[220,10941,9903],{"class":266},[220,10943,10944],{"class":233},"                  # ① SIGTERM（15）：给程序机会关连接、刷缓冲、清 pid 文件\n",[220,10946,10947,10950,10953,10955,10957,10959,10961],{"class":222,"line":276},[220,10948,10949],{"class":226},"sleep",[220,10951,10952],{"class":266}," 10",[220,10954,8550],{"class":6987},[220,10956,9846],{"class":226},[220,10958,1385],{"class":266},[220,10960,9903],{"class":266},[220,10962,10963],{"class":233},"       # ② 等 10 秒确认，没退出再说\n",[220,10965,10966,10968,10970,10972],{"class":222,"line":594},[220,10967,10023],{"class":266},[220,10969,9518],{"class":266},[220,10971,9903],{"class":266},[220,10973,10974],{"class":233},"               # ③ 仍不掉才 SIGKILL：内核硬拆，不做清理\n",[15,10976,10977,10979,10980,10983,10984,10986,10987,10989],{},[19,10978,43],{}," 的代价：不执行信号处理函数 → 临时文件\u002F锁文件\u002Fpid 文件残留、半写的数据库文件、子进程变孤儿、共享内存段没释放。很多「服务重启后卡在启动中」其实是 ",[19,10981,10982],{},"-9"," 留下的锁没清。对 ",[19,10985,10209],{}," 状态进程 ",[19,10988,10982],{}," 也无效——它在等内核 IO，只能等 IO 完成或修好底层（磁盘\u002F网络存储）。",[15,10991,10992],{},[27,10993,10994],{},"其他常用发法",[211,10996,10998],{"className":213,"code":10997,"language":215,"meta":216,"style":216},"kill -15 28414              # 显式指定信号，等价 kill 28414\nkill -TERM -28414           # 负号 = 整个进程组，清理派生一堆子进程的任务\nkill %1                     # 按当前 Shell 的作业号\nkill -0 28414               # 不发送，只测进程在不在且有没有权限——脚本里的标准存活检查\nkillall nginx               # 按名字（精确匹配，默认区分大小写）；killall -u deploy vim\npkill -TERM -f 'gunicorn app'   # 按命令行正则，先温和来一遍；确认杀不掉再上 -9\n",[19,10999,11000,11012,11025,11034,11045,11055],{"__ignoreMap":216},[220,11001,11002,11004,11007,11009],{"class":222,"line":223},[220,11003,10023],{"class":266},[220,11005,11006],{"class":266}," -15",[220,11008,9903],{"class":266},[220,11010,11011],{"class":233},"              # 显式指定信号，等价 kill 28414\n",[220,11013,11014,11016,11019,11022],{"class":222,"line":276},[220,11015,10023],{"class":266},[220,11017,11018],{"class":266}," -TERM",[220,11020,11021],{"class":266}," -28414",[220,11023,11024],{"class":233},"           # 负号 = 整个进程组，清理派生一堆子进程的任务\n",[220,11026,11027,11029,11031],{"class":222,"line":594},[220,11028,10023],{"class":266},[220,11030,8790],{"class":229},[220,11032,11033],{"class":233},"                     # 按当前 Shell 的作业号\n",[220,11035,11036,11038,11040,11042],{"class":222,"line":925},[220,11037,10023],{"class":266},[220,11039,6335],{"class":266},[220,11041,9903],{"class":266},[220,11043,11044],{"class":233},"               # 不发送，只测进程在不在且有没有权限——脚本里的标准存活检查\n",[220,11046,11047,11050,11052],{"class":222,"line":931},[220,11048,11049],{"class":226},"killall",[220,11051,600],{"class":229},[220,11053,11054],{"class":233},"               # 按名字（精确匹配，默认区分大小写）；killall -u deploy vim\n",[220,11056,11057,11060,11062,11064,11067],{"class":222,"line":3528},[220,11058,11059],{"class":226},"pkill",[220,11061,11018],{"class":266},[220,11063,2573],{"class":266},[220,11065,11066],{"class":229}," 'gunicorn app'",[220,11068,11069],{"class":233},"   # 按命令行正则，先温和来一遍；确认杀不掉再上 -9\n",[46,11071,11073],{"id":11072},"_5-nicereniceionice-调优先级","5. nice、renice、ionice —— 调优先级",[211,11075,11077],{"className":213,"code":11076,"language":215,"meta":216,"style":216},"nice -n 10 .\u002Fbatch.sh           # 以低优先级启动：-20（最高）到 19（最低），0 是默认\nrenice -n 10 -p 28414           # 运行中调优先级；调高需要 root；-u deploy 批量改某用户所有进程\nionice -c 3 -p 28414            # 调 IO 优先级（-c3 空闲级），备份任务必备，cfq\u002Fbfq 调度器下才有效\nchrt --deadline 0 1000000 cmd   # 实时调度类——一般业务别碰，会把机器拖死\n",[19,11078,11079,11094,11110,11126],{"__ignoreMap":216},[220,11080,11081,11084,11086,11088,11091],{"class":222,"line":223},[220,11082,11083],{"class":226},"nice",[220,11085,1669],{"class":266},[220,11087,10952],{"class":266},[220,11089,11090],{"class":229}," .\u002Fbatch.sh",[220,11092,11093],{"class":233},"           # 以低优先级启动：-20（最高）到 19（最低），0 是默认\n",[220,11095,11096,11099,11101,11103,11105,11107],{"class":222,"line":276},[220,11097,11098],{"class":226},"renice",[220,11100,1669],{"class":266},[220,11102,10952],{"class":266},[220,11104,1385],{"class":266},[220,11106,9903],{"class":266},[220,11108,11109],{"class":233},"           # 运行中调优先级；调高需要 root；-u deploy 批量改某用户所有进程\n",[220,11111,11112,11115,11117,11119,11121,11123],{"class":222,"line":594},[220,11113,11114],{"class":226},"ionice",[220,11116,2494],{"class":266},[220,11118,5564],{"class":266},[220,11120,1385],{"class":266},[220,11122,9903],{"class":266},[220,11124,11125],{"class":233},"            # 调 IO 优先级（-c3 空闲级），备份任务必备，cfq\u002Fbfq 调度器下才有效\n",[220,11127,11128,11131,11134,11137,11140,11142],{"class":222,"line":925},[220,11129,11130],{"class":226},"chrt",[220,11132,11133],{"class":266}," --deadline",[220,11135,11136],{"class":266}," 0",[220,11138,11139],{"class":266}," 1000000",[220,11141,4833],{"class":229},[220,11143,11144],{"class":233},"   # 实时调度类——一般业务别碰，会把机器拖死\n",[46,11146,11148],{"id":11147},"_6-僵尸与孤儿","6. 僵尸与孤儿",[211,11150,11152],{"className":213,"code":11151,"language":215,"meta":216,"style":216},"ps -eo pid,ppid,stat,cmd | awk '$3 ~ \u002FZ\u002F'          # 找僵尸进程（\u003Cdefunct> 的那些）\nps -eo pid,ppid,cmd | awk '$2 == 1'                # 看被 init 收养的孤儿\nvmstat 1 5                                         # 顺带一眼看全局\n",[19,11153,11154,11173,11192],{"__ignoreMap":216},[220,11155,11156,11158,11160,11163,11165,11167,11170],{"class":222,"line":223},[220,11157,9846],{"class":226},[220,11159,9945],{"class":266},[220,11161,11162],{"class":229}," pid,ppid,stat,cmd",[220,11164,2626],{"class":2439},[220,11166,7884],{"class":226},[220,11168,11169],{"class":229}," '$3 ~ \u002FZ\u002F'",[220,11171,11172],{"class":233},"          # 找僵尸进程（\u003Cdefunct> 的那些）\n",[220,11174,11175,11177,11179,11182,11184,11186,11189],{"class":222,"line":276},[220,11176,9846],{"class":226},[220,11178,9945],{"class":266},[220,11180,11181],{"class":229}," pid,ppid,cmd",[220,11183,2626],{"class":2439},[220,11185,7884],{"class":226},[220,11187,11188],{"class":229}," '$2 == 1'",[220,11190,11191],{"class":233},"                # 看被 init 收养的孤儿\n",[220,11193,11194,11197,11199,11202],{"class":222,"line":594},[220,11195,11196],{"class":226},"vmstat",[220,11198,267],{"class":266},[220,11200,11201],{"class":266}," 5",[220,11203,11204],{"class":233},"                                         # 顺带一眼看全局\n",[15,11206,11207,11208,11211,11212,11214,11215,662,11218,11221],{},"僵尸进程",[27,11209,11210],{},"不占 CPU 和内存","，只占一个 PID 表项，但会持续消耗父进程的资源并可能填满 PID。正确处理不是杀僵尸（杀不掉），而是：让父进程 ",[19,11213,10250],{},"（修代码里的信号\u002F子进程回收）、或重启父进程；如果父进程是 ",[19,11216,11217],{},"init",[19,11219,11220],{},"systemd"," 且仍在回收，僵尸会自己消失。孤儿进程是被过继给 PID 1 的活着的孩子，本身无害。",[15,11223,11224,11227,11228,11230,11231,11233,11234,662,11237,11240,11241,11243],{},[19,11225,11226],{},"vmstat 1 5"," 的四列：",[19,11229,3888],{},"（运行队列长度，持续大于核数说明 CPU 不够）、",[19,11232,972],{},"（阻塞在 IO 的进程数）、",[19,11235,11236],{},"si",[19,11238,11239],{},"so","（swap 进出，非 0 说明内存紧张）、",[19,11242,10457],{},"（IO 等待百分比）。这几列在第十七章排障时是主力。",[46,11245,808],{"id":11246},"踩坑提醒-9",[810,11248,11249,11265,11280,11294,11306],{},[813,11250,11251,6508,11253,8831,11255,11258,11259,187,11261,11264],{},[19,11252,9972],{},[19,11254,10031],{},[27,11256,11257],{},"平均值","，抓瞬时值用 ",[19,11260,9850],{},[19,11262,11263],{},"pidstat 1","；「ps 显示 3% 但机器很卡」通常是采样时机问题。",[813,11266,11267,11269,11270,11272,11273,11276,11277,11279],{},[19,11268,10064],{}," 大而 ",[19,11271,10056],{}," 小不代表泄漏：JVM、mmap 大文件、",[19,11274,11275],{},"malloc"," 池都会虚高，判断泄漏看 ",[19,11278,10056],{}," 的长期趋势。",[813,11281,11282,11283,11285,11286,11289,11290,11293],{},"别对 systemd 托管的服务用 ",[19,11284,43],{},"：systemd 会按 ",[19,11287,11288],{},"Restart="," 立刻拉起来，看起来像「杀不死」。正确做法是 ",[19,11291,11292],{},"systemctl stop","（第十一章）。",[813,11295,11296,11299,11300,11302,11303,11305],{},[19,11297,11298],{},"pkill -f"," 加 ",[19,11301,120],{}," 时容易误杀自己的 SSH 会话（匹配到了命令行里的字符串）。执行前 ",[19,11304,10298],{}," 同一条件先看结果。",[813,11307,11308,11309,11311,11312,11315],{},"僵尸 + 高负载的组合，根因常常是磁盘 ",[19,11310,10209],{}," 状态进程堆积，先用 ",[19,11313,11314],{},"ps -eo stat,cmd | grep '^D'"," 确认。",[10,11317,11319],{"id":11318},"十一服务与定时任务systemctljournalctlcrontab","十一、服务与定时任务：systemctl、journalctl、crontab",[15,11321,11322,11323,11325,11326,11329],{},"现代 Linux 的服务由 systemd 管，日志由 journald 收，定时任务由 cron 跑。这一章覆盖三件套，重点在 ",[19,11324,500],{}," 的正确用法与 ",[19,11327,11328],{},"journalctl"," 的过滤参数——它们决定了你能不能在三分钟内定位「服务为什么起不来」。",[861,11331],{"height":11332,"src":11333,"title":11334},660,"\u002Ftutorials\u002Flinux\u002Fdemos\u002F11-services.html","示例 11：systemctl 与 journalctl 的常用序列",[46,11336,11338],{"id":11337},"_1-systemctl-管理服务","1. systemctl —— 管理服务",[211,11340,11342],{"className":213,"code":11341,"language":215,"meta":216,"style":216},"systemctl status nginx          # 看状态、最近日志、主 PID：绿=active、红=failed、黄=activating\nsystemctl start nginx           # 启动；不改变开机是否自启\nsystemctl stop nginx            # 停止：发 ExecStop（无则 SIGTERM），超时 90s 后 SIGKILL\nsystemctl restart nginx         # 重启，有中断；能 reload 就别 restart\nsystemctl reload nginx          # 重读配置不断服务；服务必须支持 ExecReload，否则报错\nsystemctl reload-or-restart nginx   # 支持就 reload，否则 restart——部署脚本里的标准写法\nsystemctl enable nginx          # 设开机自启：只建符号链接，不会启动；加 --now 顺便起\nsystemctl disable nginx         # 取消自启：不会停正在运行的服务；加 --now 一起停\nsystemctl enable --now app      # 部署时统一这么写\nsystemctl is-active app         # 查是否运行（--quiet 只要状态码，可进 if）\nsystemctl is-enabled nginx      # 查是否自启：enabled\u002Fdisabled\u002Fstatic\u002Fmasked\nsystemctl daemon-reload         # 改了 unit 文件后必须执行！不 reload 则新配置不生效\nsystemctl list-units --type=service --state=failed    # 只看失败的单元\nsystemctl list-unit-files --type=service              # 列所有可 enable 的单元（看开机状态）\nsystemctl cat nginx             # 打印 unit 文件内容（含 drop-in 合并结果）\nsystemctl edit nginx            # 建 drop-in 覆盖（推荐，不动原文件），保存后自动 daemon-reload\nsystemctl mask nginx            # 彻底禁死（连手动 start 都不行）；unmask 恢复\nsystemctl list-dependencies nginx    # 依赖树：「谁还没起来导致我起不来」\nsystemctl show nginx -p ExecMainStartTimestamp,MainPID   # 查任意属性，脚本取 PID\u002F启动时间\nsystemctl list-timers --all     # 看 systemd 定时器（比 cron 更好管的一类任务）\n",[19,11343,11344,11356,11368,11380,11391,11402,11414,11426,11438,11452,11463,11475,11485,11501,11513,11525,11538,11551,11564,11582],{"__ignoreMap":216},[220,11345,11346,11348,11351,11353],{"class":222,"line":223},[220,11347,500],{"class":226},[220,11349,11350],{"class":229}," status",[220,11352,600],{"class":229},[220,11354,11355],{"class":233},"          # 看状态、最近日志、主 PID：绿=active、红=failed、黄=activating\n",[220,11357,11358,11360,11363,11365],{"class":222,"line":276},[220,11359,500],{"class":226},[220,11361,11362],{"class":229}," start",[220,11364,600],{"class":229},[220,11366,11367],{"class":233},"           # 启动；不改变开机是否自启\n",[220,11369,11370,11372,11375,11377],{"class":222,"line":594},[220,11371,500],{"class":226},[220,11373,11374],{"class":229}," stop",[220,11376,600],{"class":229},[220,11378,11379],{"class":233},"            # 停止：发 ExecStop（无则 SIGTERM），超时 90s 后 SIGKILL\n",[220,11381,11382,11384,11386,11388],{"class":222,"line":925},[220,11383,500],{"class":226},[220,11385,8489],{"class":229},[220,11387,600],{"class":229},[220,11389,11390],{"class":233},"         # 重启，有中断；能 reload 就别 restart\n",[220,11392,11393,11395,11397,11399],{"class":222,"line":931},[220,11394,500],{"class":226},[220,11396,8466],{"class":229},[220,11398,600],{"class":229},[220,11400,11401],{"class":233},"          # 重读配置不断服务；服务必须支持 ExecReload，否则报错\n",[220,11403,11404,11406,11409,11411],{"class":222,"line":3528},[220,11405,500],{"class":226},[220,11407,11408],{"class":229}," reload-or-restart",[220,11410,600],{"class":229},[220,11412,11413],{"class":233},"   # 支持就 reload，否则 restart——部署脚本里的标准写法\n",[220,11415,11416,11418,11421,11423],{"class":222,"line":3537},[220,11417,500],{"class":226},[220,11419,11420],{"class":229}," enable",[220,11422,600],{"class":229},[220,11424,11425],{"class":233},"          # 设开机自启：只建符号链接，不会启动；加 --now 顺便起\n",[220,11427,11428,11430,11433,11435],{"class":222,"line":3546},[220,11429,500],{"class":226},[220,11431,11432],{"class":229}," disable",[220,11434,600],{"class":229},[220,11436,11437],{"class":233},"         # 取消自启：不会停正在运行的服务；加 --now 一起停\n",[220,11439,11440,11442,11444,11447,11449],{"class":222,"line":3555},[220,11441,500],{"class":226},[220,11443,11420],{"class":229},[220,11445,11446],{"class":266}," --now",[220,11448,4404],{"class":229},[220,11450,11451],{"class":233},"      # 部署时统一这么写\n",[220,11453,11454,11456,11458,11460],{"class":222,"line":3564},[220,11455,500],{"class":226},[220,11457,8478],{"class":229},[220,11459,4404],{"class":229},[220,11461,11462],{"class":233},"         # 查是否运行（--quiet 只要状态码，可进 if）\n",[220,11464,11465,11467,11470,11472],{"class":222,"line":3572},[220,11466,500],{"class":226},[220,11468,11469],{"class":229}," is-enabled",[220,11471,600],{"class":229},[220,11473,11474],{"class":233},"      # 查是否自启：enabled\u002Fdisabled\u002Fstatic\u002Fmasked\n",[220,11476,11477,11479,11482],{"class":222,"line":3580},[220,11478,500],{"class":226},[220,11480,11481],{"class":229}," daemon-reload",[220,11483,11484],{"class":233},"         # 改了 unit 文件后必须执行！不 reload 则新配置不生效\n",[220,11486,11487,11489,11492,11495,11498],{"class":222,"line":3588},[220,11488,500],{"class":226},[220,11490,11491],{"class":229}," list-units",[220,11493,11494],{"class":266}," --type=service",[220,11496,11497],{"class":266}," --state=failed",[220,11499,11500],{"class":233},"    # 只看失败的单元\n",[220,11502,11503,11505,11508,11510],{"class":222,"line":7714},[220,11504,500],{"class":226},[220,11506,11507],{"class":229}," list-unit-files",[220,11509,11494],{"class":266},[220,11511,11512],{"class":233},"              # 列所有可 enable 的单元（看开机状态）\n",[220,11514,11515,11517,11520,11522],{"class":222,"line":7726},[220,11516,500],{"class":226},[220,11518,11519],{"class":229}," cat",[220,11521,600],{"class":229},[220,11523,11524],{"class":233},"             # 打印 unit 文件内容（含 drop-in 合并结果）\n",[220,11526,11528,11530,11533,11535],{"class":222,"line":11527},16,[220,11529,500],{"class":226},[220,11531,11532],{"class":229}," edit",[220,11534,600],{"class":229},[220,11536,11537],{"class":233},"            # 建 drop-in 覆盖（推荐，不动原文件），保存后自动 daemon-reload\n",[220,11539,11541,11543,11546,11548],{"class":222,"line":11540},17,[220,11542,500],{"class":226},[220,11544,11545],{"class":229}," mask",[220,11547,600],{"class":229},[220,11549,11550],{"class":233},"            # 彻底禁死（连手动 start 都不行）；unmask 恢复\n",[220,11552,11554,11556,11559,11561],{"class":222,"line":11553},18,[220,11555,500],{"class":226},[220,11557,11558],{"class":229}," list-dependencies",[220,11560,600],{"class":229},[220,11562,11563],{"class":233},"    # 依赖树：「谁还没起来导致我起不来」\n",[220,11565,11567,11569,11572,11574,11576,11579],{"class":222,"line":11566},19,[220,11568,500],{"class":226},[220,11570,11571],{"class":229}," show",[220,11573,600],{"class":229},[220,11575,1385],{"class":266},[220,11577,11578],{"class":229}," ExecMainStartTimestamp,MainPID",[220,11580,11581],{"class":233},"   # 查任意属性，脚本取 PID\u002F启动时间\n",[220,11583,11585,11587,11590,11593],{"class":222,"line":11584},20,[220,11586,500],{"class":226},[220,11588,11589],{"class":229}," list-timers",[220,11591,11592],{"class":266}," --all",[220,11594,11595],{"class":233},"     # 看 systemd 定时器（比 cron 更好管的一类任务）\n",[15,11597,11598,827,11601,11604,11605,11607,11608,11611,11612,11615],{},[19,11599,11600],{},"restart",[19,11602,11603],{},"reload"," 的取舍：",[19,11606,11603],{}," 让进程自己重读配置、连接不断（nginx、haproxy 支持），但",[27,11609,11610],{},"不是所有服务都实现","；配置改了却没生效，先 ",[19,11613,11614],{},"systemctl show app -p Environment,ExecStart"," 确认它拿到的到底是什么。",[46,11617,11619],{"id":11618},"_2-unit-文件的最小结构","2. unit 文件的最小结构",[15,11621,11622,11623,1848],{},"放在 ",[19,11624,11625],{},"\u002Fetc\u002Fsystemd\u002Fsystem\u002Fmyapp.service",[211,11627,11631],{"className":11628,"code":11629,"language":11630,"meta":216,"style":216},"language-ini shiki shiki-themes github-dark","[Unit]\nDescription=My App worker\nAfter=network-online.target postgresql.service\nWants=postgresql.service\n\n[Service]\nType=simple\nUser=deploy\nGroup=deploy\nWorkingDirectory=\u002Fsrv\u002Fapp\nEnvironmentFile=\u002Fsrv\u002Fapp\u002Fenv.list\nExecStart=\u002Fsrv\u002Fapp\u002Fvenv\u002Fbin\u002Fgunicorn -c gunicorn.conf.py app:app\nExecReload=\u002Fbin\u002Fkill -HUP $MAINPID\nRestart=on-failure\nRestartSec=3\nStandardOutput=journal\nLimitNOFILE=65535\n\n[Install]\nWantedBy=multi-user.target\n","ini",[19,11632,11633,11638,11643,11648,11653,11657,11662,11667,11672,11677,11682,11687,11692,11697,11702,11707,11712,11717,11721,11726],{"__ignoreMap":216},[220,11634,11635],{"class":222,"line":223},[220,11636,11637],{},"[Unit]\n",[220,11639,11640],{"class":222,"line":276},[220,11641,11642],{},"Description=My App worker\n",[220,11644,11645],{"class":222,"line":594},[220,11646,11647],{},"After=network-online.target postgresql.service\n",[220,11649,11650],{"class":222,"line":925},[220,11651,11652],{},"Wants=postgresql.service\n",[220,11654,11655],{"class":222,"line":931},[220,11656,3740],{"emptyLinePlaceholder":3739},[220,11658,11659],{"class":222,"line":3528},[220,11660,11661],{},"[Service]\n",[220,11663,11664],{"class":222,"line":3537},[220,11665,11666],{},"Type=simple\n",[220,11668,11669],{"class":222,"line":3546},[220,11670,11671],{},"User=deploy\n",[220,11673,11674],{"class":222,"line":3555},[220,11675,11676],{},"Group=deploy\n",[220,11678,11679],{"class":222,"line":3564},[220,11680,11681],{},"WorkingDirectory=\u002Fsrv\u002Fapp\n",[220,11683,11684],{"class":222,"line":3572},[220,11685,11686],{},"EnvironmentFile=\u002Fsrv\u002Fapp\u002Fenv.list\n",[220,11688,11689],{"class":222,"line":3580},[220,11690,11691],{},"ExecStart=\u002Fsrv\u002Fapp\u002Fvenv\u002Fbin\u002Fgunicorn -c gunicorn.conf.py app:app\n",[220,11693,11694],{"class":222,"line":3588},[220,11695,11696],{},"ExecReload=\u002Fbin\u002Fkill -HUP $MAINPID\n",[220,11698,11699],{"class":222,"line":7714},[220,11700,11701],{},"Restart=on-failure\n",[220,11703,11704],{"class":222,"line":7726},[220,11705,11706],{},"RestartSec=3\n",[220,11708,11709],{"class":222,"line":11527},[220,11710,11711],{},"StandardOutput=journal\n",[220,11713,11714],{"class":222,"line":11540},[220,11715,11716],{},"LimitNOFILE=65535\n",[220,11718,11719],{"class":222,"line":11553},[220,11720,3740],{"emptyLinePlaceholder":3739},[220,11722,11723],{"class":222,"line":11566},[220,11724,11725],{},"[Install]\n",[220,11727,11728],{"class":222,"line":11584},[220,11729,11730],{},"WantedBy=multi-user.target\n",[51,11732,11733,11745],{},[54,11734,11735],{},[57,11736,11737,11740,11742],{},[60,11738,11739],{},"字段",[60,11741,2692],{},[60,11743,11744],{},"常踩的点",[70,11746,11747,11783,11805,11835,11862,11881,11906,11922,11944,11960],{},[57,11748,11749,11754,11772],{},[75,11750,11751],{},[19,11752,11753],{},"Type",[75,11755,11756,11759,11760,11763,11764,11767,11768,11771],{},[19,11757,11758],{},"simple","（默认，ExecStart 即主进程）\u002F",[19,11761,11762],{},"forking","（传统守护进程）\u002F",[19,11765,11766],{},"oneshot","（跑完就退）\u002F",[19,11769,11770],{},"notify","（自己报就绪）",[75,11773,11774,11775,11777,11778,187,11780],{},"脚本型任务用 ",[19,11776,11758],{}," 会因为脚本秒退被判失败，改 ",[19,11779,11766],{},[19,11781,11782],{},"Type=exec",[57,11784,11785,11790,11793],{},[75,11786,11787],{},[19,11788,11789],{},"ExecStart",[75,11791,11792],{},"启动命令",[75,11794,11795,11798,11799,11801,11802],{},[27,11796,11797],{},"必须绝对路径","；不能用 shell 特性（管道、",[19,11800,2436],{},"），需要时 ",[19,11803,11804],{},"ExecStart=\u002Fbin\u002Fsh -c '…'",[57,11806,11807,11818,11821],{},[75,11808,11809,1073,11812,1073,11815],{},[19,11810,11811],{},"After=",[19,11813,11814],{},"Wants=",[19,11816,11817],{},"Requires=",[75,11819,11820],{},"顺序与依赖",[75,11822,11823,11826,11827,11830,11831,11834],{},[19,11824,11825],{},"Requires"," 强依赖（对方失败我也失败），",[19,11828,11829],{},"Wants"," 弱依赖；",[19,11832,11833],{},"After"," 只管顺序不产生依赖，两者要搭配写",[57,11836,11837,11841,11852],{},[75,11838,11839],{},[19,11840,11288],{},[75,11842,11843,662,11846,662,11849],{},[19,11844,11845],{},"no",[19,11847,11848],{},"on-failure",[19,11850,11851],{},"always",[75,11853,11854,11855,11858,11859],{},"配合 ",[19,11856,11857],{},"StartLimitBurst","，重启风暴会被限流并进入 ",[19,11860,11861],{},"failed",[57,11863,11864,11872,11875],{},[75,11865,11866,1073,11869],{},[19,11867,11868],{},"User=",[19,11870,11871],{},"Group=",[75,11873,11874],{},"运行身份",[75,11876,11877,11878,11880],{},"改了要 ",[19,11879,3828],{}," 数据目录（第五章）",[57,11882,11883,11891,11894],{},[75,11884,11885,1073,11888],{},[19,11886,11887],{},"Environment=",[19,11889,11890],{},"EnvironmentFile=",[75,11892,11893],{},"环境变量",[75,11895,11896,11897,11902,11903],{},"文件里",[27,11898,11899,11900],{},"不要写 ",[19,11901,471],{},"，一行 ",[19,11904,11905],{},"KEY=VALUE",[57,11907,11908,11913,11916],{},[75,11909,11910],{},[19,11911,11912],{},"WorkingDirectory=",[75,11914,11915],{},"工作目录",[75,11917,11918,11919,11921],{},"不写就是 ",[19,11920,662],{},"，相对路径全崩",[57,11923,11924,11930,11933],{},[75,11925,11926,1073,11928],{},[19,11927,5324],{},[19,11929,5327],{},[75,11931,11932],{},"沙箱加固",[75,11934,11935,11936,11939,11940,11943],{},"开了 ",[19,11937,11938],{},"ProtectSystem=strict"," 又没给 ",[19,11941,11942],{},"ReadWritePaths"," 就会「Permission denied」而权限位是对的",[57,11945,11946,11954,11957],{},[75,11947,11948,662,11951],{},[19,11949,11950],{},"TimeoutStartSec",[19,11952,11953],{},"TimeoutStopSec",[75,11955,11956],{},"超时",[75,11958,11959],{},"启动慢的服务要放宽，否则反复被杀",[57,11961,11962,11967,11970],{},[75,11963,11964],{},[19,11965,11966],{},"[Install] WantedBy=",[75,11968,11969],{},"enable 时挂到哪个 target",[75,11971,11972,11975,11976],{},[19,11973,11974],{},"multi-user.target","（服务器）\u002F",[19,11977,11978],{},"graphical.target",[15,11980,11981,11982,11985,11986,11989,11990,11993,11994,465,11997,616],{},"改配置的三种途径优先级：",[19,11983,11984],{},"systemctl edit 服务名","（生成 ",[19,11987,11988],{},"\u002Fetc\u002Fsystemd\u002Fsystem\u002F服务名.d\u002Foverride.conf","）> 自己写同名 drop-in 目录 > 直接改 ",[19,11991,11992],{},"\u002Flib\u002Fsystemd\u002Fsystem\u002Fxxx.service","（会被包升级覆盖）。改完",[27,11995,11996],{},"必须",[19,11998,11999],{},"systemctl daemon-reload",[46,12001,12003],{"id":12002},"_3-journalctl-查服务日志","3. journalctl —— 查服务日志",[211,12005,12007],{"className":213,"code":12006,"language":215,"meta":216,"style":216},"journalctl -u nginx                     # 某个单元的全部日志\njournalctl -u nginx -f                  # 实时跟踪（等价 tail -f，但含重启前的日志）\njournalctl -u nginx --since \"2026-10-04 09:00\" --until \"10:00\"   # 时间窗；也支持 \"1 hour ago\"\njournalctl -p err -u nginx              # 只看错误及以上（emerg\u002Falert\u002Fcrit\u002Ferr\u002Fwarning\u002Fnotice\u002Finfo\u002Fdebug）\njournalctl -b                           # 本次启动的全部日志\njournalctl -b -1                        # 上一次启动——查无故重启必备\njournalctl -xe                          # 末尾几行 + 每条的解释（x 更多细节，e 跳到末尾）\njournalctl -k -b                        # 内核日志（等价 dmesg，但可按时段过滤）\njournalctl _PID=28414                   # 按字段查（字段=值 直接写）\njournalctl -f -u app -u worker          # 多个单元一起跟\njournalctl -o json-pretty -u app        # 换输出格式（json、short-iso、cat）\njournalctl --disk-usage                 # journald 占了多少磁盘\njournalctl --vacuum-size=200M           # 主动收缩日志（也可 --vacuum-time=7d）\njournalctl --list-boots                 # 列出所有启动记录\n",[19,12008,12009,12020,12033,12056,12072,12081,12092,12102,12113,12126,12143,12159,12169,12179],{"__ignoreMap":216},[220,12010,12011,12013,12015,12017],{"class":222,"line":223},[220,12012,11328],{"class":226},[220,12014,4787],{"class":266},[220,12016,600],{"class":229},[220,12018,12019],{"class":233},"                     # 某个单元的全部日志\n",[220,12021,12022,12024,12026,12028,12030],{"class":222,"line":276},[220,12023,11328],{"class":226},[220,12025,4787],{"class":266},[220,12027,600],{"class":229},[220,12029,2573],{"class":266},[220,12031,12032],{"class":233},"                  # 实时跟踪（等价 tail -f，但含重启前的日志）\n",[220,12034,12035,12037,12039,12041,12044,12047,12050,12053],{"class":222,"line":594},[220,12036,11328],{"class":226},[220,12038,4787],{"class":266},[220,12040,600],{"class":229},[220,12042,12043],{"class":266}," --since",[220,12045,12046],{"class":229}," \"2026-10-04 09:00\"",[220,12048,12049],{"class":266}," --until",[220,12051,12052],{"class":229}," \"10:00\"",[220,12054,12055],{"class":233},"   # 时间窗；也支持 \"1 hour ago\"\n",[220,12057,12058,12060,12062,12065,12067,12069],{"class":222,"line":925},[220,12059,11328],{"class":226},[220,12061,1385],{"class":266},[220,12063,12064],{"class":229}," err",[220,12066,4787],{"class":266},[220,12068,600],{"class":229},[220,12070,12071],{"class":233},"              # 只看错误及以上（emerg\u002Falert\u002Fcrit\u002Ferr\u002Fwarning\u002Fnotice\u002Finfo\u002Fdebug）\n",[220,12073,12074,12076,12078],{"class":222,"line":931},[220,12075,11328],{"class":226},[220,12077,1685],{"class":266},[220,12079,12080],{"class":233},"                           # 本次启动的全部日志\n",[220,12082,12083,12085,12087,12089],{"class":222,"line":3528},[220,12084,11328],{"class":226},[220,12086,1685],{"class":266},[220,12088,1214],{"class":266},[220,12090,12091],{"class":233},"                        # 上一次启动——查无故重启必备\n",[220,12093,12094,12096,12099],{"class":222,"line":3537},[220,12095,11328],{"class":226},[220,12097,12098],{"class":266}," -xe",[220,12100,12101],{"class":233},"                          # 末尾几行 + 每条的解释（x 更多细节，e 跳到末尾）\n",[220,12103,12104,12106,12108,12110],{"class":222,"line":3546},[220,12105,11328],{"class":226},[220,12107,281],{"class":266},[220,12109,1685],{"class":266},[220,12111,12112],{"class":233},"                        # 内核日志（等价 dmesg，但可按时段过滤）\n",[220,12114,12115,12117,12120,12123],{"class":222,"line":3555},[220,12116,11328],{"class":226},[220,12118,12119],{"class":229}," _PID=",[220,12121,12122],{"class":266},"28414",[220,12124,12125],{"class":233},"                   # 按字段查（字段=值 直接写）\n",[220,12127,12128,12130,12132,12134,12136,12138,12140],{"class":222,"line":3564},[220,12129,11328],{"class":226},[220,12131,2573],{"class":266},[220,12133,4787],{"class":266},[220,12135,4404],{"class":229},[220,12137,4787],{"class":266},[220,12139,10319],{"class":229},[220,12141,12142],{"class":233},"          # 多个单元一起跟\n",[220,12144,12145,12147,12149,12152,12154,12156],{"class":222,"line":3572},[220,12146,11328],{"class":226},[220,12148,5606],{"class":266},[220,12150,12151],{"class":229}," json-pretty",[220,12153,4787],{"class":266},[220,12155,4404],{"class":229},[220,12157,12158],{"class":233},"        # 换输出格式（json、short-iso、cat）\n",[220,12160,12161,12163,12166],{"class":222,"line":3580},[220,12162,11328],{"class":226},[220,12164,12165],{"class":266}," --disk-usage",[220,12167,12168],{"class":233},"                 # journald 占了多少磁盘\n",[220,12170,12171,12173,12176],{"class":222,"line":3588},[220,12172,11328],{"class":226},[220,12174,12175],{"class":266}," --vacuum-size=200M",[220,12177,12178],{"class":233},"           # 主动收缩日志（也可 --vacuum-time=7d）\n",[220,12180,12181,12183,12186],{"class":222,"line":7714},[220,12182,11328],{"class":226},[220,12184,12185],{"class":266}," --list-boots",[220,12187,12188],{"class":233},"                 # 列出所有启动记录\n",[15,12190,12191,12192,12195,12196,12199,12200,12203,12204,12207,12208,6079,12211,12214,12215,12218,12219,12222,12223,12226],{},"日志为空的两种常见原因：服务把日志写到了自己的文件（unit 里 ",[19,12193,12194],{},"StandardOutput=file:"," 或代码里 ",[19,12197,12198],{},"logging.FileHandler","），以及权限不够——普通用户查全量日志需要 ",[19,12201,12202],{},"systemd-journal"," 组（",[19,12205,12206],{},"sudo usermod -aG systemd-journal deploy","）。持久化默认可能关闭：",[19,12209,12210],{},"\u002Fetc\u002Fsystemd\u002Fjournald.conf",[19,12212,12213],{},"Storage=persistent","，然后 ",[19,12216,12217],{},"systemctl restart systemd-journald","，否则 ",[19,12220,12221],{},"\u002Fvar\u002Flog\u002Fjournal"," 不存在、",[19,12224,12225],{},"-b -1"," 查不到上次开机的事。",[15,12228,12229,12230,12233,12234,34,12237,34,12240,12243],{},"journald 上限的四个参数（同一文件）：",[19,12231,12232],{},"SystemMaxUse=500M","（磁盘上限，最有用）、",[19,12235,12236],{},"SystemMaxFileSize=50M",[19,12238,12239],{},"MaxRetentionSec=30day",[19,12241,12242],{},"ForwardToSyslog=yes","（转给 rsyslog）。",[46,12245,12247],{"id":12246},"_4-crontab-定时任务","4. crontab —— 定时任务",[211,12249,12251],{"className":213,"code":12250,"language":215,"meta":216,"style":216},"crontab -l          # 看当前用户的任务\ncrontab -e          # 编辑（改完自动生效，无需重启 cron）\nsudo crontab -u deploy -e    # 读写别人的（root 权限）\ncrontab -r          # 删除全部任务 —— 危险，没有二次确认\n",[19,12252,12253,12263,12273,12289],{"__ignoreMap":216},[220,12254,12255,12258,12260],{"class":222,"line":223},[220,12256,12257],{"class":226},"crontab",[220,12259,3193],{"class":266},[220,12261,12262],{"class":233},"          # 看当前用户的任务\n",[220,12264,12265,12267,12270],{"class":222,"line":276},[220,12266,12257],{"class":226},[220,12268,12269],{"class":266}," -e",[220,12271,12272],{"class":233},"          # 编辑（改完自动生效，无需重启 cron）\n",[220,12274,12275,12277,12280,12282,12284,12286],{"class":222,"line":594},[220,12276,120],{"class":226},[220,12278,12279],{"class":229}," crontab",[220,12281,4787],{"class":266},[220,12283,4424],{"class":229},[220,12285,12269],{"class":266},[220,12287,12288],{"class":233},"    # 读写别人的（root 权限）\n",[220,12290,12291,12293,12295],{"class":222,"line":925},[220,12292,12257],{"class":226},[220,12294,7611],{"class":266},[220,12296,12297],{"class":233},"          # 删除全部任务 —— 危险，没有二次确认\n",[15,12299,12300],{},"字段顺序与示例：",[211,12302,12304],{"className":903,"code":12303,"language":905,"meta":216,"style":216},"分 时 日 月 周          命令\n 0 3 * * *  find \u002Fsrv\u002Fapp\u002Flogs -name '*.log' -mtime +7 -delete\n*\u002F5 * * * *  \u002Fsrv\u002Fapp\u002Fhealthcheck.sh >> \u002Fvar\u002Flog\u002Fhc.log 2>&1\n30 4 * * 1-5  \u002Fusr\u002Fbin\u002Fpg_dump appdb | gzip > \u002Fbackup\u002Fapp.sql.gz\n@reboot      \u002Fsrv\u002Fapp\u002Fstartup.sh\n",[19,12305,12306,12311,12316,12321,12326],{"__ignoreMap":216},[220,12307,12308],{"class":222,"line":223},[220,12309,12310],{},"分 时 日 月 周          命令\n",[220,12312,12313],{"class":222,"line":276},[220,12314,12315],{}," 0 3 * * *  find \u002Fsrv\u002Fapp\u002Flogs -name '*.log' -mtime +7 -delete\n",[220,12317,12318],{"class":222,"line":594},[220,12319,12320],{},"*\u002F5 * * * *  \u002Fsrv\u002Fapp\u002Fhealthcheck.sh >> \u002Fvar\u002Flog\u002Fhc.log 2>&1\n",[220,12322,12323],{"class":222,"line":925},[220,12324,12325],{},"30 4 * * 1-5  \u002Fusr\u002Fbin\u002Fpg_dump appdb | gzip > \u002Fbackup\u002Fapp.sql.gz\n",[220,12327,12328],{"class":222,"line":931},[220,12329,12330],{},"@reboot      \u002Fsrv\u002Fapp\u002Fstartup.sh\n",[51,12332,12333,12344],{},[54,12334,12335],{},[57,12336,12337,12339,12341],{},[60,12338,2033],{},[60,12340,68],{},[60,12342,12343],{},"易混点",[70,12345,12346,12359,12375,12392,12409],{},[57,12347,12348,12353,12356],{},[75,12349,12350],{},[19,12351,12352],{},"* * * * *",[75,12354,12355],{},"每分钟",[75,12357,12358],{},"五段依次是「分 时 日 月 周」，第 3 位是「几号」而不是小时，写反就永不执行",[57,12360,12361,12366,12369],{},[75,12362,12363],{},[19,12364,12365],{},"0 3 * * *",[75,12367,12368],{},"每天 03:00",[75,12370,12371,12372,12374],{},"「日」和「周」同时限制时是",[27,12373,6895],{},"关系（cron 特性，不是 bug）",[57,12376,12377,12382,12385],{},[75,12378,12379],{},[19,12380,12381],{},"*\u002F5",[75,12383,12384],{},"每 5 分钟",[75,12386,12387,12388,12391],{},"分钟从 0 起算，",[19,12389,12390],{},"*\u002F7"," 不是严格每 7 分钟",[57,12393,12394,12400,12403],{},[75,12395,12396,12399],{},[19,12397,12398],{},"1-5","（周）",[75,12401,12402],{},"周一到周五",[75,12404,12405,12408],{},[27,12406,12407],{},"0 和 7 都是周日","；有的实现只认 0",[57,12410,12411,12417,12423],{},[75,12412,12413,12416],{},[19,12414,12415],{},"23:59"," 式",[75,12418,12419,12420],{},"不存在，必须 ",[19,12421,12422],{},"59 23 * * *",[75,12424,12425],{},"顺序写反就是「59 分 23 时」→ 永远不跑",[15,12427,12428],{},"三个经典坑：",[1790,12430,12431,12451,12468],{},[813,12432,12433,12436,12437,12440,12441,12444,12445,1083,12448,616],{},[27,12434,12435],{},"PATH 不同","。cron 的环境只有 ",[19,12438,12439],{},"\u002Fusr\u002Fbin:\u002Fbin","，没有你的 ",[19,12442,12443],{},"~\u002F.bashrc"," 里的路径。写绝对路径，或在 crontab 顶部加 ",[19,12446,12447],{},"PATH=\u002Fusr\u002Flocal\u002Fbin:\u002Fusr\u002Fbin:\u002Fbin",[19,12449,12450],{},"MAILTO=\"\"",[813,12452,12453,12459,12460,12463,12464,12467],{},[27,12454,12455,12458],{},[19,12456,12457],{},"%"," 是特殊字符","（被当换行\u002F参数分隔）。crontab 里 ",[19,12461,12462],{},"date +%F"," 必须写成 ",[19,12465,12466],{},"date +\\%F","，或者干脆把命令放进脚本文件（强烈推荐）。",[813,12469,12470,616,12473,12475,12476,12479,12480,187,12483,616],{},[27,12471,12472],{},"注释与输出",[19,12474,116],{}," 行会被忽略；任务有输出时 cron 会试图用 ",[19,12477,12478],{},"sendmail"," 发信，没装 MTA 就表现为「静默失败」。所有行末尾加 ",[19,12481,12482],{},">> \u002Fvar\u002Flog\u002Fcron.out 2>&1",[19,12484,12450],{},[15,12486,12487,12488,12491,12492,12495,12496,12499,12500,1284,12503,12506,12507,12510,12511,12513,12514,12517],{},"排查有没有跑：",[19,12489,12490],{},"grep CRON \u002Fvar\u002Flog\u002Fsyslog","（Debian）或 ",[19,12493,12494],{},"journalctl -u cron","（RHEL 系与 systemd 环境）。",[19,12497,12498],{},"at"," 一次性任务的用法：",[19,12501,12502],{},"echo '\u002Fsrv\u002Fapp\u002Freport.sh' | at 02:00 tomorrow",[19,12504,12505],{},"atq"," 看队列、",[19,12508,12509],{},"atrm 3"," 取消；需要装 ",[19,12512,12498],{}," 且 ",[19,12515,12516],{},"atd"," 在跑。",[46,12519,12521],{"id":12520},"_5-timedatectl-时间与时区","5. timedatectl —— 时间与时区",[211,12523,12525],{"className":213,"code":12524,"language":215,"meta":216,"style":216},"timedatectl                                # 一站式看时间、时区、NTP 状态：排「日志时间对不上」先看这里\ntimedatectl set-timezone Asia\u002FShanghai     # 设时区；改完服务不一定立刻感知，Java 应用还要 -Duser.timezone\ntimedatectl set-ntp true                   # 开启 systemd-timesyncd 同步；NTPSynchronized=yes 才算同步上\ntimedatectl show-timesync --all            # 看用的哪台 NTP 服务器；内网要配 \u002Fetc\u002Fsystemd\u002Ftimesyncd.conf 的 Server=\ntimedatectl set-time \"2026-10-04 09:30:00\" # 手动设时间：必须先关 NTP！时间回拨会让证书校验、\n                                           # cron、数据库主从全部乱套\nchronyc sources                            # 用 chrony 时的对时源（RHEL 系默认 chrony，\nntpq -p                                    # timedatectl 显示 inactive 不代表没同步）\nhwclock -r                                 # 读硬件时钟——双系统时间错乱（UTC vs 本地）就在这里\n",[19,12526,12527,12535,12548,12561,12573,12586,12591,12602,12612],{"__ignoreMap":216},[220,12528,12529,12532],{"class":222,"line":223},[220,12530,12531],{"class":226},"timedatectl",[220,12533,12534],{"class":233},"                                # 一站式看时间、时区、NTP 状态：排「日志时间对不上」先看这里\n",[220,12536,12537,12539,12542,12545],{"class":222,"line":276},[220,12538,12531],{"class":226},[220,12540,12541],{"class":229}," set-timezone",[220,12543,12544],{"class":229}," Asia\u002FShanghai",[220,12546,12547],{"class":233},"     # 设时区；改完服务不一定立刻感知，Java 应用还要 -Duser.timezone\n",[220,12549,12550,12552,12555,12558],{"class":222,"line":594},[220,12551,12531],{"class":226},[220,12553,12554],{"class":229}," set-ntp",[220,12556,12557],{"class":266}," true",[220,12559,12560],{"class":233},"                   # 开启 systemd-timesyncd 同步；NTPSynchronized=yes 才算同步上\n",[220,12562,12563,12565,12568,12570],{"class":222,"line":925},[220,12564,12531],{"class":226},[220,12566,12567],{"class":229}," show-timesync",[220,12569,11592],{"class":266},[220,12571,12572],{"class":233},"            # 看用的哪台 NTP 服务器；内网要配 \u002Fetc\u002Fsystemd\u002Ftimesyncd.conf 的 Server=\n",[220,12574,12575,12577,12580,12583],{"class":222,"line":931},[220,12576,12531],{"class":226},[220,12578,12579],{"class":229}," set-time",[220,12581,12582],{"class":229}," \"2026-10-04 09:30:00\"",[220,12584,12585],{"class":233}," # 手动设时间：必须先关 NTP！时间回拨会让证书校验、\n",[220,12587,12588],{"class":222,"line":3528},[220,12589,12590],{"class":233},"                                           # cron、数据库主从全部乱套\n",[220,12592,12593,12596,12599],{"class":222,"line":3537},[220,12594,12595],{"class":226},"chronyc",[220,12597,12598],{"class":229}," sources",[220,12600,12601],{"class":233},"                            # 用 chrony 时的对时源（RHEL 系默认 chrony，\n",[220,12603,12604,12607,12609],{"class":222,"line":3546},[220,12605,12606],{"class":226},"ntpq",[220,12608,1385],{"class":266},[220,12610,12611],{"class":233},"                                    # timedatectl 显示 inactive 不代表没同步）\n",[220,12613,12614,12617,12619],{"class":222,"line":3555},[220,12615,12616],{"class":226},"hwclock",[220,12618,7611],{"class":266},[220,12620,12621],{"class":233},"                                 # 读硬件时钟——双系统时间错乱（UTC vs 本地）就在这里\n",[15,12623,12624,12625,12628,12629,12632,12633,616],{},"容器里的时间问题：容器共享宿主内核时钟，改容器时区只影响 ",[19,12626,12627],{},"TZ"," 环境变量（",[19,12630,12631],{},"TZ=Asia\u002FShanghai","），不要试图在容器里 ",[19,12634,12635],{},"set-time",[46,12637,808],{"id":12638},"踩坑提醒-10",[810,12640,12641,12657,12670,12680],{},[813,12642,12643,12644,12646,12647,12650,12651,689,12654,12656],{},"「改了 unit 没生效」= 忘了 ",[19,12645,11999],{},"；「改了 drop-in 还在用旧值」= ",[19,12648,12649],{},"systemctl cat"," 看合并后的真实结果，systemd 的覆盖语义是",[27,12652,12653],{},"空赋值",[19,12655,11887],{}," 空值 = 清空）。",[813,12658,12659,12662,12663,12666,12667,616],{},[19,12660,12661],{},"enable"," ≠ 启动、",[19,12664,12665],{},"disable"," ≠ 停止。部署脚本里统一 ",[19,12668,12669],{},"systemctl enable --now app",[813,12671,12672,12673,12676,12677,616],{},"crontab 里 ",[19,12674,12675],{},"@reboot"," 执行时网络可能还没就绪，依赖外部服务要重试或用 systemd 的 ",[19,12678,12679],{},"After=network-online.target",[813,12681,12682,12683,12685,12686,34,12688,12690],{},"日志时间戳乱掉的第二原因：应用自己设了 UTC 而系统是本时区（nginx access log 用本地、",[19,12684,11328],{}," 用本地但导出的 JSON 是 UTC）。核对一遍 ",[19,12687,7224],{},[19,12689,12531],{},"、应用配置三处。",[10,12692,12694],{"id":12693},"十二用户与组iduseraddusermodpasswd","十二、用户与组：id、useradd、usermod、passwd",[15,12696,12697,12698,34,12701,12704],{},"权限问题的一半其实是身份问题：服务用的是哪个用户、这个用户在不在组里、改了组为什么没生效。这一章把用户\u002F组的查询与增删命令，以及 ",[19,12699,12700],{},"\u002Fetc\u002Fpasswd",[19,12702,12703],{},"\u002Fetc\u002Fshadow"," 的字段逐个讲清。",[46,12706,12708],{"id":12707},"_1-idwhow-先看清现状","1. id、who、w —— 先看清现状",[211,12710,12712],{"className":213,"code":12711,"language":215,"meta":216,"style":216},"id                    # 当前用户的 uid\u002Fgid 与全部附属组：uid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo)\nid deploy             # 查别人（按名或按号 id 1001）——排查服务用户最常用的第一步\nwhoami                # 只有用户名；注意 sudo whoami 会显示 root——判断当前是不是特权态\nw                     # 谁在线 + 在跑什么（含 LOGIN@ JCPU TTY FROM IDLE WHAT），比 who 信息多\nwho                   # 谁登录了：deploy  pts\u002F0  2026-10-04 08:55 (10.0.0.12)\nlast deploy           # 历史登录（读 \u002Fvar\u002Flog\u002Fwtmp），只看某人；lastb 看失败尝试（要 root）\ngetent passwd         # 查所有账户（含 LDAP\u002FSSSD\u002FNIS）——脚本里用这个而不是读文件\ngetent group sudo     # 看某个组的成员：判断「他到底在不在组里」\ngroups deploy         # 只列组名，比 id 好读\nusers                 # 一行列出在线用户名\n",[19,12713,12714,12721,12730,12738,12745,12753,12763,12774,12786,12796],{"__ignoreMap":216},[220,12715,12716,12718],{"class":222,"line":223},[220,12717,10451],{"class":226},[220,12719,12720],{"class":233},"                    # 当前用户的 uid\u002Fgid 与全部附属组：uid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo)\n",[220,12722,12723,12725,12727],{"class":222,"line":276},[220,12724,10451],{"class":226},[220,12726,4424],{"class":229},[220,12728,12729],{"class":233},"             # 查别人（按名或按号 id 1001）——排查服务用户最常用的第一步\n",[220,12731,12732,12735],{"class":222,"line":594},[220,12733,12734],{"class":226},"whoami",[220,12736,12737],{"class":233},"                # 只有用户名；注意 sudo whoami 会显示 root——判断当前是不是特权态\n",[220,12739,12740,12742],{"class":222,"line":925},[220,12741,3910],{"class":226},[220,12743,12744],{"class":233},"                     # 谁在线 + 在跑什么（含 LOGIN@ JCPU TTY FROM IDLE WHAT），比 who 信息多\n",[220,12746,12747,12750],{"class":222,"line":931},[220,12748,12749],{"class":226},"who",[220,12751,12752],{"class":233},"                   # 谁登录了：deploy  pts\u002F0  2026-10-04 08:55 (10.0.0.12)\n",[220,12754,12755,12758,12760],{"class":222,"line":3528},[220,12756,12757],{"class":226},"last",[220,12759,4424],{"class":229},[220,12761,12762],{"class":233},"           # 历史登录（读 \u002Fvar\u002Flog\u002Fwtmp），只看某人；lastb 看失败尝试（要 root）\n",[220,12764,12765,12768,12771],{"class":222,"line":3537},[220,12766,12767],{"class":226},"getent",[220,12769,12770],{"class":229}," passwd",[220,12772,12773],{"class":233},"         # 查所有账户（含 LDAP\u002FSSSD\u002FNIS）——脚本里用这个而不是读文件\n",[220,12775,12776,12778,12781,12783],{"class":222,"line":3546},[220,12777,12767],{"class":226},[220,12779,12780],{"class":229}," group",[220,12782,8230],{"class":229},[220,12784,12785],{"class":233},"     # 看某个组的成员：判断「他到底在不在组里」\n",[220,12787,12788,12791,12793],{"class":222,"line":3555},[220,12789,12790],{"class":226},"groups",[220,12792,4424],{"class":229},[220,12794,12795],{"class":233},"         # 只列组名，比 id 好读\n",[220,12797,12798,12801],{"class":222,"line":3564},[220,12799,12800],{"class":226},"users",[220,12802,12803],{"class":233},"                 # 一行列出在线用户名\n",[46,12805,12807],{"id":12806},"_2-useradd-与-adduser-增删改用户","2. useradd 与 adduser —— 增删改用户",[15,12809,12810,12811,616],{},"先说清一件事：",[27,12812,12813,827,12816,12819],{},[19,12814,12815],{},"useradd",[19,12817,12818],{},"adduser"," 不是一回事",[51,12821,12822,12837],{},[54,12823,12824],{},[57,12825,12826,12828,12831,12834],{},[60,12827,5168],{},[60,12829,12830],{},"性质",[60,12832,12833],{},"行为",[60,12835,12836],{},"用在哪",[70,12838,12839,12856],{},[57,12840,12841,12845,12848,12853],{},[75,12842,12843],{},[19,12844,12815],{},[75,12846,12847],{},"底层二进制（各发行版都有）",[75,12849,12850],{},[27,12851,12852],{},"默认不建家目录、不设密码、不交互式",[75,12854,12855],{},"脚本、Dockerfile",[57,12857,12858,12862,12865,12871],{},[75,12859,12860],{},[19,12861,12818],{},[75,12863,12864],{},"Perl 友好脚本（Debian\u002FUbuntu 独有）",[75,12866,12867,12868],{},"交互式问全名\u002F密码，自动建家目录、拷 ",[19,12869,12870],{},"\u002Fetc\u002Fskel",[75,12872,12873],{},"手工建号",[211,12875,12877],{"className":213,"code":12876,"language":215,"meta":216,"style":216},"sudo useradd -m -s \u002Fbin\u002Fbash -G deploy,app deploy2\n# -m 建家目录、-s 给登录 shell、-G 附属组——RHEL 系手工建号只能这么写\nsudo adduser deploy                 # Debian\u002FUbuntu 交互式建号\nsudo useradd -u 1500 -e 2026-12-31 -c \"CI runner\" ci\n# -u 指定 uid（容器\u002F集群对齐必备）、-g 主组、-d 家目录路径、-e 账户过期日、-c 备注字段\nsudo userdel -r deploy              # 删用户并删家目录；不加 -r 会留下 \u002Fhome\u002Fdeploy（可能含密钥）；\n                                    # 删前先 ps -u deploy 确认没进程\n",[19,12878,12879,12902,12907,12919,12943,12948,12962],{"__ignoreMap":216},[220,12880,12881,12883,12886,12888,12890,12893,12896,12899],{"class":222,"line":223},[220,12882,120],{"class":226},[220,12884,12885],{"class":229}," useradd",[220,12887,5078],{"class":266},[220,12889,1978],{"class":266},[220,12891,12892],{"class":229}," \u002Fbin\u002Fbash",[220,12894,12895],{"class":266}," -G",[220,12897,12898],{"class":229}," deploy,app",[220,12900,12901],{"class":229}," deploy2\n",[220,12903,12904],{"class":222,"line":276},[220,12905,12906],{"class":233},"# -m 建家目录、-s 给登录 shell、-G 附属组——RHEL 系手工建号只能这么写\n",[220,12908,12909,12911,12914,12916],{"class":222,"line":594},[220,12910,120],{"class":226},[220,12912,12913],{"class":229}," adduser",[220,12915,4424],{"class":229},[220,12917,12918],{"class":233},"                 # Debian\u002FUbuntu 交互式建号\n",[220,12920,12921,12923,12925,12927,12930,12932,12935,12937,12940],{"class":222,"line":925},[220,12922,120],{"class":226},[220,12924,12885],{"class":229},[220,12926,4787],{"class":266},[220,12928,12929],{"class":266}," 1500",[220,12931,12269],{"class":266},[220,12933,12934],{"class":229}," 2026-12-31",[220,12936,2494],{"class":266},[220,12938,12939],{"class":229}," \"CI runner\"",[220,12941,12942],{"class":229}," ci\n",[220,12944,12945],{"class":222,"line":931},[220,12946,12947],{"class":233},"# -u 指定 uid（容器\u002F集群对齐必备）、-g 主组、-d 家目录路径、-e 账户过期日、-c 备注字段\n",[220,12949,12950,12952,12955,12957,12959],{"class":222,"line":3528},[220,12951,120],{"class":226},[220,12953,12954],{"class":229}," userdel",[220,12956,7611],{"class":266},[220,12958,4424],{"class":229},[220,12960,12961],{"class":233},"              # 删用户并删家目录；不加 -r 会留下 \u002Fhome\u002Fdeploy（可能含密钥）；\n",[220,12963,12964],{"class":222,"line":3537},[220,12965,12966],{"class":233},"                                    # 删前先 ps -u deploy 确认没进程\n",[46,12968,12970],{"id":12969},"_3-usermod-加组本章最贵的一课在这","3. usermod —— 加组（本章最贵的一课在这）",[211,12972,12974],{"className":213,"code":12973,"language":215,"meta":216,"style":216},"sudo usermod -aG docker deploy      # 加附属组：-a 是「追加」，必须写！\nsudo usermod -aG docker,app deploy  # 一次加多个组\nsudo usermod -L deploy              # 锁定账户（shadow 密码字段前加 !）；-U 解锁\nsudo usermod -l newname oldname     # 改名；家目录要另外 mv 并 chown\nsudo usermod -s \u002Fusr\u002Fsbin\u002Fnologin www   # 禁登录 shell——服务账户的标准做法\nsudo groupadd app                   # 建组；groupdel 删组\ngpasswd -a deploy app               # 老写法加组成员（等价 usermod -aG）；-d 移除\ngroupmems -g app -l                 # 列组成员；-a\u002F-d\u002F-p 加\u002F删\u002F清空\nnewgrp app                          # 当前会话临时切换有效组——不重新登录也能生效的补救，exit 退出\n",[19,12975,12976,12994,13010,13023,13040,13057,13069,13083,13098],{"__ignoreMap":216},[220,12977,12978,12980,12983,12986,12989,12991],{"class":222,"line":223},[220,12979,120],{"class":226},[220,12981,12982],{"class":229}," usermod",[220,12984,12985],{"class":266}," -aG",[220,12987,12988],{"class":229}," docker",[220,12990,4424],{"class":229},[220,12992,12993],{"class":233},"      # 加附属组：-a 是「追加」，必须写！\n",[220,12995,12996,12998,13000,13002,13005,13007],{"class":222,"line":276},[220,12997,120],{"class":226},[220,12999,12982],{"class":229},[220,13001,12985],{"class":266},[220,13003,13004],{"class":229}," docker,app",[220,13006,4424],{"class":229},[220,13008,13009],{"class":233},"  # 一次加多个组\n",[220,13011,13012,13014,13016,13018,13020],{"class":222,"line":594},[220,13013,120],{"class":226},[220,13015,12982],{"class":229},[220,13017,1492],{"class":266},[220,13019,4424],{"class":229},[220,13021,13022],{"class":233},"              # 锁定账户（shadow 密码字段前加 !）；-U 解锁\n",[220,13024,13025,13027,13029,13031,13034,13037],{"class":222,"line":925},[220,13026,120],{"class":226},[220,13028,12982],{"class":229},[220,13030,3193],{"class":266},[220,13032,13033],{"class":229}," newname",[220,13035,13036],{"class":229}," oldname",[220,13038,13039],{"class":233},"     # 改名；家目录要另外 mv 并 chown\n",[220,13041,13042,13044,13046,13048,13051,13054],{"class":222,"line":931},[220,13043,120],{"class":226},[220,13045,12982],{"class":229},[220,13047,1978],{"class":266},[220,13049,13050],{"class":229}," \u002Fusr\u002Fsbin\u002Fnologin",[220,13052,13053],{"class":229}," www",[220,13055,13056],{"class":233},"   # 禁登录 shell——服务账户的标准做法\n",[220,13058,13059,13061,13064,13066],{"class":222,"line":3528},[220,13060,120],{"class":226},[220,13062,13063],{"class":229}," groupadd",[220,13065,4404],{"class":229},[220,13067,13068],{"class":233},"                   # 建组；groupdel 删组\n",[220,13070,13071,13074,13076,13078,13080],{"class":222,"line":3537},[220,13072,13073],{"class":226},"gpasswd",[220,13075,551],{"class":266},[220,13077,4424],{"class":229},[220,13079,4404],{"class":229},[220,13081,13082],{"class":233},"               # 老写法加组成员（等价 usermod -aG）；-d 移除\n",[220,13084,13085,13088,13091,13093,13095],{"class":222,"line":3546},[220,13086,13087],{"class":226},"groupmems",[220,13089,13090],{"class":266}," -g",[220,13092,4404],{"class":229},[220,13094,3193],{"class":266},[220,13096,13097],{"class":233},"                 # 列组成员；-a\u002F-d\u002F-p 加\u002F删\u002F清空\n",[220,13099,13100,13103,13105],{"class":222,"line":3555},[220,13101,13102],{"class":226},"newgrp",[220,13104,4404],{"class":229},[220,13106,13107],{"class":233},"                          # 当前会话临时切换有效组——不重新登录也能生效的补救，exit 退出\n",[15,13109,13110,1848,13119,13122,13123,13125,13126,13129,13130,13132,13133,616],{},[27,13111,13112,6508,13115,13118],{},[19,13113,13114],{},"usermod -aG",[19,13116,13117],{},"-a"," 为什么这么重要",[19,13120,13121],{},"usermod -G docker deploy","（少了 ",[19,13124,13117],{},"）会把 deploy 的",[27,13127,13128],{},"所有附属组替换成只有 docker","——原本在 ",[19,13131,120],{}," 组里的用户当场失去 sudo 权限，若他正是唯一的管理员就只能靠 root 密码或单用户模式救。安全写法永远带上 ",[19,13134,13117],{},[15,13136,13137,13138,3324,13141,13143],{},"改完组后",[27,13139,13140],{},"该用户必须重新登录",[19,13142,13102],{},"）才生效，因为组成员身份在登录时被写进凭证，已有会话不会更新——这是「加了组还是 permission denied」的头号原因。",[46,13145,13147],{"id":13146},"_4-passwd-与-chage-管密码","4. passwd 与 chage —— 管密码",[211,13149,13151],{"className":213,"code":13150,"language":215,"meta":216,"style":216},"passwd deploy            # 改自己或指定用户密码（指定他人需 root）\npasswd -l deploy         # 锁定（密码前加 !）；passwd -u 解锁\npasswd -e deploy         # 让下次登录必须改密码\npasswd -d deploy         # 清空密码（配合 SSH 密钥场景，谨慎）\npasswd -S deploy         # 看状态：P 可用 \u002F L 锁定 \u002F NP 无密码 + 最近改密日期\nchage -l deploy          # 看过期详情；chage -M 90 -W 7 -I 14 设策略\n",[19,13152,13153,13162,13173,13184,13195,13206],{"__ignoreMap":216},[220,13154,13155,13157,13159],{"class":222,"line":223},[220,13156,4992],{"class":226},[220,13158,4424],{"class":229},[220,13160,13161],{"class":233},"            # 改自己或指定用户密码（指定他人需 root）\n",[220,13163,13164,13166,13168,13170],{"class":222,"line":276},[220,13165,4992],{"class":226},[220,13167,3193],{"class":266},[220,13169,4424],{"class":229},[220,13171,13172],{"class":233},"         # 锁定（密码前加 !）；passwd -u 解锁\n",[220,13174,13175,13177,13179,13181],{"class":222,"line":594},[220,13176,4992],{"class":226},[220,13178,12269],{"class":266},[220,13180,4424],{"class":229},[220,13182,13183],{"class":233},"         # 让下次登录必须改密码\n",[220,13185,13186,13188,13190,13192],{"class":222,"line":925},[220,13187,4992],{"class":226},[220,13189,1455],{"class":266},[220,13191,4424],{"class":229},[220,13193,13194],{"class":233},"         # 清空密码（配合 SSH 密钥场景，谨慎）\n",[220,13196,13197,13199,13201,13203],{"class":222,"line":931},[220,13198,4992],{"class":226},[220,13200,2894],{"class":266},[220,13202,4424],{"class":229},[220,13204,13205],{"class":233},"         # 看状态：P 可用 \u002F L 锁定 \u002F NP 无密码 + 最近改密日期\n",[220,13207,13208,13211,13213,13215],{"class":222,"line":3528},[220,13209,13210],{"class":226},"chage",[220,13212,3193],{"class":266},[220,13214,4424],{"class":229},[220,13216,13217],{"class":233},"          # 看过期详情；chage -M 90 -W 7 -I 14 设策略\n",[46,13219,13221],{"id":13220},"_5-etcpasswd-与-etcshadow-的字段","5. \u002Fetc\u002Fpasswd 与 \u002Fetc\u002Fshadow 的字段",[15,13223,13224,689,13226,13229,13230,13232],{},[19,13225,12700],{},[19,13227,13228],{},"getent passwd deploy"," 的原始内容，7 个字段用 ",[19,13231,3422],{}," 分隔）：",[211,13234,13236],{"className":903,"code":13235,"language":905,"meta":216,"style":216},"deploy:x:1000:1000:Deploy User:\u002Fhome\u002Fdeploy:\u002Fbin\u002Fbash\n",[19,13237,13238],{"__ignoreMap":216},[220,13239,13240],{"class":222,"line":223},[220,13241,13235],{},[51,13243,13244,13257],{},[54,13245,13246],{},[57,13247,13248,13250,13253,13255],{},[60,13249,943],{},[60,13251,13252],{},"值",[60,13254,68],{},[60,13256,2109],{},[70,13258,13259,13276,13293,13314,13332,13352,13369],{},[57,13260,13261,13263,13267,13270],{},[75,13262,1072],{},[75,13264,13265],{},[19,13266,88],{},[75,13268,13269],{},"用户名",[75,13271,13272,13273,13275],{},"登录与 ",[19,13274,3828],{}," 用",[57,13277,13278,13280,13284,13287],{},[75,13279,8025],{},[75,13281,13282],{},[19,13283,3558],{},[75,13285,13286],{},"密码占位符",[75,13288,13289,13290],{},"真正的哈希在 shadow；这里是空表示",[27,13291,13292],{},"无密码",[57,13294,13295,13297,13301,13304],{},[75,13296,1076],{},[75,13298,13299],{},[19,13300,5033],{},[75,13302,13303],{},"UID",[75,13305,13306,13307,1284,13310,13313],{},"0=root，系统账户 ",[19,13308,13309],{},"\u003C 1000",[19,13311,13312],{},"65534","=nobody",[57,13315,13316,13319,13323,13326],{},[75,13317,13318],{},"4",[75,13320,13321],{},[19,13322,5033],{},[75,13324,13325],{},"GID（主组号）",[75,13327,13328,13329],{},"指向 ",[19,13330,13331],{},"\u002Fetc\u002Fgroup",[57,13333,13334,13337,13342,13345],{},[75,13335,13336],{},"5",[75,13338,13339],{},[19,13340,13341],{},"Deploy User",[75,13343,13344],{},"GECOS 备注",[75,13346,13347,13348,13351],{},"全名\u002F电话，",[19,13349,13350],{},"finger"," 读这里",[57,13353,13354,13357,13361,13364],{},[75,13355,13356],{},"6",[75,13358,13359],{},[19,13360,190],{},[75,13362,13363],{},"家目录",[75,13365,13366,13368],{},[19,13367,96],{}," 的来源；不存在则登录报错",[57,13370,13371,13374,13379,13382],{},[75,13372,13373],{},"7",[75,13375,13376],{},[19,13377,13378],{},"\u002Fbin\u002Fbash",[75,13380,13381],{},"登录 shell",[75,13383,13384,34,13387,13390],{},[19,13385,13386],{},"\u002Fusr\u002Fsbin\u002Fnologin",[19,13388,13389],{},"\u002Fbin\u002Ffalse"," = 禁交互登录",[15,13392,13393,13395,13396,13398],{},[19,13394,12703],{},"（8 个字段，只有 root 与 shadow 组可读，权限 ",[19,13397,4575],{},"）：",[211,13400,13402],{"className":903,"code":13401,"language":905,"meta":216,"style":216},"deploy:$y$j9T$abc…:20550:0:90:7:14::\n",[19,13403,13404],{"__ignoreMap":216},[220,13405,13406],{"class":222,"line":223},[220,13407,13401],{},[51,13409,13410,13420],{},[54,13411,13412],{},[57,13413,13414,13416,13418],{},[60,13415,943],{},[60,13417,13252],{},[60,13419,68],{},[70,13421,13422,13432,13461,13480,13491,13507,13518,13530],{},[57,13423,13424,13426,13430],{},[75,13425,1072],{},[75,13427,13428],{},[19,13429,88],{},[75,13431,13269],{},[57,13433,13434,13436,13441],{},[75,13435,8025],{},[75,13437,13438],{},[19,13439,13440],{},"$y$j9T$…",[75,13442,13443,13444,13447,13448,13451,13452,13455,13456,187,13458,13460],{},"密码哈希；前缀 ",[19,13445,13446],{},"$y$","=yescrypt、",[19,13449,13450],{},"$6$","=SHA-512、",[19,13453,13454],{},"$2b$","=bcrypt；",[19,13457,3659],{},[19,13459,2436],{}," 开头 = 锁定；空 = 无密码可登录",[57,13462,13463,13465,13470],{},[75,13464,1076],{},[75,13466,13467],{},[19,13468,13469],{},"20550",[75,13471,13472,13473,689,13476,13479],{},"最后一次改密距 1970-01-01 的",[27,13474,13475],{},"天数",[19,13477,13478],{},"date -d \"1970-01-01 +20550 days\""," 换算）",[57,13481,13482,13484,13488],{},[75,13483,13318],{},[75,13485,13486],{},[19,13487,8018],{},[75,13489,13490],{},"改密后至少隔多少天才允许再改（防来回改历史密码）",[57,13492,13493,13495,13500],{},[75,13494,13336],{},[75,13496,13497],{},[19,13498,13499],{},"90",[75,13501,13502,13503,13506],{},"密码有效期天数（",[19,13504,13505],{},"99999"," = 永不过期）",[57,13508,13509,13511,13515],{},[75,13510,13356],{},[75,13512,13513],{},[19,13514,13373],{},[75,13516,13517],{},"过期前多少天开始警告",[57,13519,13520,13522,13527],{},[75,13521,13373],{},[75,13523,13524],{},[19,13525,13526],{},"14",[75,13528,13529],{},"过期后多少天宽限期，之后账户锁定",[57,13531,13532,13535,13537],{},[75,13533,13534],{},"8",[75,13536,9024],{},[75,13538,13539],{},"账户本身失效的天数（与密码无关）",[15,13541,13542,13544,13545,13547,13548,13554,13555,13558,13559,13562],{},[19,13543,13331],{}," 的 4 个字段：组名、",[19,13546,3558],{}," 占位（组密码基本不用）、GID、成员列表（逗号分隔）。注意：",[27,13549,13550,13551,13553],{},"这里列出的成员只是「附加组」，用户在 ",[19,13552,12700],{}," 里的 GID 主组不会出现在这里","，所以「",[19,13556,13557],{},"getent group deploy"," 里没有他」不代表他不属于 deploy 组。",[19,13560,13561],{},"\u002Fetc\u002Fgshadow"," 存组管理员与组密码，字段与 shadow 类似。",[15,13564,13565,13566,13569,13570,13573,13574,13577,13578,13581],{},"相关命令：",[19,13567,13568],{},"getent passwd 1000","（按 uid 反查）、",[19,13571,13572],{},"lastlog","（每人最后登录，读 ",[19,13575,13576],{},"\u002Fvar\u002Flog\u002Flastlog"," 稀疏文件）、",[19,13579,13580],{},"faillock --user deploy","（pam_faillock 的失败计数，RHEL 系）。",[46,13583,808],{"id":13584},"踩坑提醒-11",[810,13586,13587,13607,13626,13645],{},[813,13588,13589,13590,1284,13593,13595,13596,13599,13600,13603,13604,616],{},"新建用户忘了 ",[19,13591,13592],{},"-m",[19,13594,96],{}," 不存在导致后续所有「写家目录」的脚本失败：",[19,13597,13598],{},"useradd -m"," 或在 ",[19,13601,13602],{},"\u002Fetc\u002Fdefault\u002Fuseradd"," 里设 ",[19,13605,13606],{},"CREATE_HOME=yes",[813,13608,13609,13610,34,13612,13615,13616,1284,13618,13621,13622,13625],{},"服务账户（",[19,13611,8456],{},[19,13613,13614],{},"redis","）默认 shell 是 ",[19,13617,13386],{},[19,13619,13620],{},"su - nginx"," 必然失败；正确姿势 ",[19,13623,13624],{},"sudo -u nginx \u002Fbin\u002Fbash -l","（root 可以覆盖 shell）。",[813,13627,13628,13629,13631,13632,13634,13635,13637,13638,1073,13641,13644],{},"直接编辑 ",[19,13630,12700],{}," 用普通 ",[19,13633,3112],{}," 有并发风险（同时 ",[19,13636,12815],{}," 会覆盖），要改就 ",[19,13639,13640],{},"vipw",[19,13642,13643],{},"vigr","（带锁与语法检查）。",[813,13646,13647,13648,616],{},"UID 不一致是集群环境的隐形故障源：NFS 共享目录 + 各节点 UID 不同 → 权限完全乱。跨节点统一 ",[19,13649,13650],{},"useradd -u 1500",[10,13652,13654],{"id":13653},"十三网络ipssdigcurlssh","十三、网络：ip、ss、dig、curl、ssh",[15,13656,13657,13658,34,13661,34,13664,34,13667,13670],{},"网络排障的顺序基本固定：本机有没有 IP → 路由到不到 → DNS 解析对不对 → 端口有没有监听 → 防火墙让不让过 → 应用返回什么。",[19,13659,13660],{},"ip",[19,13662,13663],{},"ss",[19,13665,13666],{},"dig",[19,13668,13669],{},"curl"," 各管一段，本章按这个顺序给命令。",[861,13672],{"height":9857,"src":13673,"title":13674},"\u002Ftutorials\u002Flinux\u002Fdemos\u002F13-network.html","示例 13：ip\u002Fss\u002Fcurl 三连与端口连通性测试",[46,13676,13678],{"id":13677},"_1-ip-取代-ifconfig-与-netstat","1. ip —— 取代 ifconfig 与 netstat",[15,13680,13681,689,13684,34,13687,34,13690,13693,13694,689,13697,34,13699,13701,13702,13704],{},[19,13682,13683],{},"net-tools",[19,13685,13686],{},"ifconfig",[19,13688,13689],{},"netstat",[19,13691,13692],{},"route","）在新发行版里默认不装了，",[19,13695,13696],{},"iproute2",[19,13698,13660],{},[19,13700,13663],{},"）才是当前的标准，且能显示 ",[19,13703,13686],{}," 拿不到的信息（每个地址的前缀长度、路由表来源、队列规则、命名空间）。",[211,13706,13708],{"className":213,"code":13707,"language":215,"meta":216,"style":216},"ip a                        # 看所有接口与地址（addr 的缩写）\nip -br a                    # 一行一接口，最清爽\nip -4 addr show eth0        # 只看 IPv4、指定网卡\nip r                        # 看路由表（route 的缩写）\nip route get 8.8.8.8        # 问「去往这个地址走哪条路、用哪个网卡」——排障神器\nip l                        # 看链路状态；ip link set dev eth0 down 改状态（要 root）\nip neigh                    # ARP 表（= arp -a），排查同网段不通\nip -s link                  # 接口统计：RX errors 非 0 说明网卡\u002F驱动问题\nip route add default via 10.0.0.1    # 加默认网关——重启失效！永久配置要写 netplan\u002FNetworkManager\nip netns                    # 网络命名空间列表；ip netns exec foo ss -tulpn\n",[19,13709,13710,13719,13731,13749,13759,13775,13784,13794,13805,13826],{"__ignoreMap":216},[220,13711,13712,13714,13716],{"class":222,"line":223},[220,13713,13660],{"class":226},[220,13715,1672],{"class":229},[220,13717,13718],{"class":233},"                        # 看所有接口与地址（addr 的缩写）\n",[220,13720,13721,13723,13726,13728],{"class":222,"line":276},[220,13722,13660],{"class":226},[220,13724,13725],{"class":266}," -br",[220,13727,1672],{"class":229},[220,13729,13730],{"class":233},"                    # 一行一接口，最清爽\n",[220,13732,13733,13735,13738,13741,13743,13746],{"class":222,"line":594},[220,13734,13660],{"class":226},[220,13736,13737],{"class":266}," -4",[220,13739,13740],{"class":229}," addr",[220,13742,11571],{"class":229},[220,13744,13745],{"class":229}," eth0",[220,13747,13748],{"class":233},"        # 只看 IPv4、指定网卡\n",[220,13750,13751,13753,13756],{"class":222,"line":925},[220,13752,13660],{"class":226},[220,13754,13755],{"class":229}," r",[220,13757,13758],{"class":233},"                        # 看路由表（route 的缩写）\n",[220,13760,13761,13763,13766,13769,13772],{"class":222,"line":931},[220,13762,13660],{"class":226},[220,13764,13765],{"class":229}," route",[220,13767,13768],{"class":229}," get",[220,13770,13771],{"class":266}," 8.8.8.8",[220,13773,13774],{"class":233},"        # 问「去往这个地址走哪条路、用哪个网卡」——排障神器\n",[220,13776,13777,13779,13781],{"class":222,"line":3528},[220,13778,13660],{"class":226},[220,13780,5638],{"class":229},[220,13782,13783],{"class":233},"                        # 看链路状态；ip link set dev eth0 down 改状态（要 root）\n",[220,13785,13786,13788,13791],{"class":222,"line":3537},[220,13787,13660],{"class":226},[220,13789,13790],{"class":229}," neigh",[220,13792,13793],{"class":233},"                    # ARP 表（= arp -a），排查同网段不通\n",[220,13795,13796,13798,13800,13802],{"class":222,"line":3546},[220,13797,13660],{"class":226},[220,13799,1978],{"class":266},[220,13801,4376],{"class":229},[220,13803,13804],{"class":233},"                  # 接口统计：RX errors 非 0 说明网卡\u002F驱动问题\n",[220,13806,13807,13809,13811,13814,13817,13820,13823],{"class":222,"line":3555},[220,13808,13660],{"class":226},[220,13810,13765],{"class":229},[220,13812,13813],{"class":229}," add",[220,13815,13816],{"class":229}," default",[220,13818,13819],{"class":229}," via",[220,13821,13822],{"class":266}," 10.0.0.1",[220,13824,13825],{"class":233},"    # 加默认网关——重启失效！永久配置要写 netplan\u002FNetworkManager\n",[220,13827,13828,13830,13833],{"class":222,"line":3564},[220,13829,13660],{"class":226},[220,13831,13832],{"class":229}," netns",[220,13834,13835],{"class":233},"                    # 网络命名空间列表；ip netns exec foo ss -tulpn\n",[15,13837,13838,13841,13842,13845,13846,13849,13850,187,13853,13856,13857,662,13860,689,13863,13866],{},[19,13839,13840],{},"ip a"," 输出里要会看的三个东西：",[19,13843,13844],{},"inet 10.0.0.12\u002F24 brd …"," 是 IPv4 与掩码（",[19,13847,13848],{},"\u002F24"," = 255.255.255.0）、",[19,13851,13852],{},"state UP",[19,13854,13855],{},"NO-CARRIER"," 是链路是否插了线、",[19,13858,13859],{},"scope global",[19,13861,13862],{},"link",[19,13864,13865],{},"fe80::"," 是链路本地地址，不能直接当目标）。",[46,13868,13870],{"id":13869},"_2-ss-谁在监听哪个端口","2. ss —— 谁在监听哪个端口",[211,13872,13874],{"className":213,"code":13873,"language":215,"meta":216,"style":216},"ss -tulnp           # 日常万能式：TCP + UDP、监听中的、带进程名\nss -tuln            # 同上但无需 root（不显示进程列）\nss -tan state established       # 只看已建立的 TCP 连接\nss -tp state established '( dport = :443 )'   # 按端口过滤，能匹配到进程（netstat 做不到）\nss -s               # 各状态连接数汇总（一眼看 TIME_WAIT 堆积）\nss -i               # 拥塞控制、RTT 等 TCP 内部信息\nss -m               # 内存占用（内核缓冲）\nsudo ss -K dst 10.0.0.5        # 直接杀掉匹配的连接（需 root，4.9+ 内核）\n",[19,13875,13876,13886,13896,13912,13929,13938,13947,13956],{"__ignoreMap":216},[220,13877,13878,13880,13883],{"class":222,"line":223},[220,13879,13663],{"class":226},[220,13881,13882],{"class":266}," -tulnp",[220,13884,13885],{"class":233},"           # 日常万能式：TCP + UDP、监听中的、带进程名\n",[220,13887,13888,13890,13893],{"class":222,"line":276},[220,13889,13663],{"class":226},[220,13891,13892],{"class":266}," -tuln",[220,13894,13895],{"class":233},"            # 同上但无需 root（不显示进程列）\n",[220,13897,13898,13900,13903,13906,13909],{"class":222,"line":594},[220,13899,13663],{"class":226},[220,13901,13902],{"class":266}," -tan",[220,13904,13905],{"class":229}," state",[220,13907,13908],{"class":229}," established",[220,13910,13911],{"class":233},"       # 只看已建立的 TCP 连接\n",[220,13913,13914,13916,13919,13921,13923,13926],{"class":222,"line":925},[220,13915,13663],{"class":226},[220,13917,13918],{"class":266}," -tp",[220,13920,13905],{"class":229},[220,13922,13908],{"class":229},[220,13924,13925],{"class":229}," '( dport = :443 )'",[220,13927,13928],{"class":233},"   # 按端口过滤，能匹配到进程（netstat 做不到）\n",[220,13930,13931,13933,13935],{"class":222,"line":931},[220,13932,13663],{"class":226},[220,13934,1978],{"class":266},[220,13936,13937],{"class":233},"               # 各状态连接数汇总（一眼看 TIME_WAIT 堆积）\n",[220,13939,13940,13942,13944],{"class":222,"line":3528},[220,13941,13663],{"class":226},[220,13943,1593],{"class":266},[220,13945,13946],{"class":233},"               # 拥塞控制、RTT 等 TCP 内部信息\n",[220,13948,13949,13951,13953],{"class":222,"line":3537},[220,13950,13663],{"class":226},[220,13952,5078],{"class":266},[220,13954,13955],{"class":233},"               # 内存占用（内核缓冲）\n",[220,13957,13958,13960,13963,13966,13969,13972],{"class":222,"line":3546},[220,13959,120],{"class":226},[220,13961,13962],{"class":229}," ss",[220,13964,13965],{"class":266}," -K",[220,13967,13968],{"class":229}," dst",[220,13970,13971],{"class":266}," 10.0.0.5",[220,13973,13974],{"class":233},"        # 直接杀掉匹配的连接（需 root，4.9+ 内核）\n",[15,13976,13977,13978,13981,13982,13985,13986,13989,13990,13993,13994,13996,13997,13999,14000,14002,14003,14006,14007,14010,14011,14013,14014,14017],{},"字母含义：",[19,13979,13980],{},"-t"," TCP、",[19,13983,13984],{},"-u"," UDP、",[19,13987,13988],{},"-l"," listening、",[19,13991,13992],{},"-p"," 显示进程（需 root 才能看到别人的）、",[19,13995,2007],{}," 数字不做名称解析（更快，避免 DNS 卡住）、",[19,13998,13117],{}," 全部（含未监听）、",[19,14001,10372],{}," Unix 套接字、",[19,14004,14005],{},"-w"," raw、",[19,14008,14009],{},"-s"," 统计、",[19,14012,1858],{}," 内部信息、",[19,14015,14016],{},"-e"," 扩展（uid\u002Finode）。",[15,14019,9006,14020,14023],{},[19,14021,14022],{},"netstat -antp"," 的对照：",[51,14025,14026,14042],{},[54,14027,14028],{},[57,14029,14030,14033,14036,14039],{},[60,14031,14032],{},"需求",[60,14034,14035],{},"netstat（老）",[60,14037,14038],{},"ss（新）",[60,14040,14041],{},"ss 的优势",[70,14043,14044,14064,14085,14103,14121,14139],{},[57,14045,14046,14049,14054,14059],{},[75,14047,14048],{},"列监听端口 + 进程",[75,14050,14051],{},[19,14052,14053],{},"netstat -tulnp",[75,14055,14056],{},[19,14057,14058],{},"ss -tulnp",[75,14060,14061,14063],{},[19,14062,13663],{}," 直接从内核接口读，海量连接时快几十倍",[57,14065,14066,14069,14073,14078],{},[75,14067,14068],{},"全部 TCP 连接",[75,14070,14071],{},[19,14072,14022],{},[75,14074,14075],{},[19,14076,14077],{},"ss -tanp",[75,14079,14080,14081,14084],{},"支持 ",[19,14082,14083],{},"state"," 精确过滤",[57,14086,14087,14090,14095,14100],{},[75,14088,14089],{},"只看 TIME_WAIT",[75,14091,14092],{},[19,14093,14094],{},"netstat -ant | grep TIME_WAIT",[75,14096,14097],{},[19,14098,14099],{},"ss -tan state time-wait",[75,14101,14102],{},"不用 grep，不丢格式",[57,14104,14105,14108,14113,14118],{},[75,14106,14107],{},"Unix 域套接字",[75,14109,14110],{},[19,14111,14112],{},"netstat -xlp",[75,14114,14115],{},[19,14116,14117],{},"ss -xlp",[75,14119,14120],{},"输出更完整",[57,14122,14123,14126,14131,14136],{},[75,14124,14125],{},"路由表",[75,14127,14128],{},[19,14129,14130],{},"route -n",[75,14132,14133],{},[19,14134,14135],{},"ip r",[75,14137,14138],{},"支持策略路由显示",[57,14140,14141,14144,14149,14154],{},[75,14142,14143],{},"接口统计",[75,14145,14146],{},[19,14147,14148],{},"netstat -i",[75,14150,14151],{},[19,14152,14153],{},"ip -s link",[75,14155,14156],{},"含丢包\u002F错误细分",[15,14158,14159,6079,14161,662,14164,14167,14168,1284,14171,14173,14174,14176],{},[19,14160,13663],{},[19,14162,14163],{},"Recv-Q",[19,14165,14166],{},"Send-Q"," 在监听端口上的含义与连接上不同：监听行上它们是",[27,14169,14170],{},"已完成队列长度 \u002F backlog 上限",[19,14172,14163],{}," 接近 ",[19,14175,14166],{}," 说明应用 accept 不过来（连接被丢弃），是「服务偶发 502\u002F超时」的关键线索。",[46,14178,14180],{"id":14179},"_3-连通性诊断pingdignc","3. 连通性诊断：ping、dig、nc",[211,14182,14184],{"className":213,"code":14183,"language":215,"meta":216,"style":216},"ping -c 4 10.0.0.5          # 测 ICMP 可达与 RTT：-c 4 发 4 个包，-W 2 超时，-i 0.2 快速发\ntraceroute -n -w 2 host     # 逐跳路径：-n 不解析域名（否则慢到不能用）、-T -p 443 用 TCP 探测\ntracepath 8.8.8.8           # 无需 root 的替代，同时给出 PMTU\nmtr -rwnc 50 host           # traceroute + ping 合体：-r 报告模式、-w 宽、-c 50 发 50 包\ndig +short app.example.com  # DNS 查询首选：+short 只留答案、+trace 从根查起、+noall +answer 精简\ndig A host @1.1.1.1         # 指定 DNS 服务器对比\nnslookup app.example.com 8.8.8.8    # 简易 DNS（老工具）：输出信息少，脚本别用\nhost -t MX example.com      # 一行查 DNS；host -l example.com 测区域传送\ngetent hosts app.internal   # 走本机完整解析链（含 \u002Fetc\u002Fhosts、nsswitch）——排「dig 能解析但 curl 不行」\nnc -vz 10.0.0.5 5432        # 测端口通不通：-z 只探端口、-v 给结果（telnet 的替代，多数机器没装 telnet 了）\nnc -l 8080                  # 起临时 HTTP 靶机；nc -l 8080 > dump.bin 收文件（OpenBSD\u002FGNU 版参数不同）\nethtool -i eth0             # 网卡驱动；ethtool -s 看速率\u002F双工\u002F链路——物理层问题唯一入口\n",[19,14185,14186,14200,14217,14227,14242,14255,14270,14282,14298,14311,14327,14339],{"__ignoreMap":216},[220,14187,14188,14190,14192,14195,14197],{"class":222,"line":223},[220,14189,4995],{"class":226},[220,14191,2494],{"class":266},[220,14193,14194],{"class":266}," 4",[220,14196,13971],{"class":266},[220,14198,14199],{"class":233},"          # 测 ICMP 可达与 RTT：-c 4 发 4 个包，-W 2 超时，-i 0.2 快速发\n",[220,14201,14202,14205,14207,14209,14211,14214],{"class":222,"line":276},[220,14203,14204],{"class":226},"traceroute",[220,14206,1669],{"class":266},[220,14208,6691],{"class":266},[220,14210,1495],{"class":266},[220,14212,14213],{"class":229}," host",[220,14215,14216],{"class":233},"     # 逐跳路径：-n 不解析域名（否则慢到不能用）、-T -p 443 用 TCP 探测\n",[220,14218,14219,14222,14224],{"class":222,"line":594},[220,14220,14221],{"class":226},"tracepath",[220,14223,13771],{"class":266},[220,14225,14226],{"class":233},"           # 无需 root 的替代，同时给出 PMTU\n",[220,14228,14229,14232,14235,14237,14239],{"class":222,"line":925},[220,14230,14231],{"class":226},"mtr",[220,14233,14234],{"class":266}," -rwnc",[220,14236,2551],{"class":266},[220,14238,14213],{"class":229},[220,14240,14241],{"class":233},"           # traceroute + ping 合体：-r 报告模式、-w 宽、-c 50 发 50 包\n",[220,14243,14244,14246,14249,14252],{"class":222,"line":931},[220,14245,13666],{"class":226},[220,14247,14248],{"class":229}," +short",[220,14250,14251],{"class":229}," app.example.com",[220,14253,14254],{"class":233},"  # DNS 查询首选：+short 只留答案、+trace 从根查起、+noall +answer 精简\n",[220,14256,14257,14259,14262,14264,14267],{"class":222,"line":3528},[220,14258,13666],{"class":226},[220,14260,14261],{"class":229}," A",[220,14263,14213],{"class":229},[220,14265,14266],{"class":229}," @1.1.1.1",[220,14268,14269],{"class":233},"         # 指定 DNS 服务器对比\n",[220,14271,14272,14275,14277,14279],{"class":222,"line":3537},[220,14273,14274],{"class":226},"nslookup",[220,14276,14251],{"class":229},[220,14278,13771],{"class":266},[220,14280,14281],{"class":233},"    # 简易 DNS（老工具）：输出信息少，脚本别用\n",[220,14283,14284,14287,14289,14292,14295],{"class":222,"line":3546},[220,14285,14286],{"class":226},"host",[220,14288,8459],{"class":266},[220,14290,14291],{"class":229}," MX",[220,14293,14294],{"class":229}," example.com",[220,14296,14297],{"class":233},"      # 一行查 DNS；host -l example.com 测区域传送\n",[220,14299,14300,14302,14305,14308],{"class":222,"line":3555},[220,14301,12767],{"class":226},[220,14303,14304],{"class":229}," hosts",[220,14306,14307],{"class":229}," app.internal",[220,14309,14310],{"class":233},"   # 走本机完整解析链（含 \u002Fetc\u002Fhosts、nsswitch）——排「dig 能解析但 curl 不行」\n",[220,14312,14313,14316,14319,14321,14324],{"class":222,"line":3564},[220,14314,14315],{"class":226},"nc",[220,14317,14318],{"class":266}," -vz",[220,14320,13971],{"class":266},[220,14322,14323],{"class":266}," 5432",[220,14325,14326],{"class":233},"        # 测端口通不通：-z 只探端口、-v 给结果（telnet 的替代，多数机器没装 telnet 了）\n",[220,14328,14329,14331,14333,14336],{"class":222,"line":3572},[220,14330,14315],{"class":226},[220,14332,3193],{"class":266},[220,14334,14335],{"class":266}," 8080",[220,14337,14338],{"class":233},"                  # 起临时 HTTP 靶机；nc -l 8080 > dump.bin 收文件（OpenBSD\u002FGNU 版参数不同）\n",[220,14340,14341,14344,14346,14348],{"class":222,"line":3580},[220,14342,14343],{"class":226},"ethtool",[220,14345,1593],{"class":266},[220,14347,13745],{"class":229},[220,14349,14350],{"class":233},"             # 网卡驱动；ethtool -s 看速率\u002F双工\u002F链路——物理层问题唯一入口\n",[15,14352,14353,14354,418,14357,14359,14360,616],{},"注意两点：很多机房禁 ICMP，",[27,14355,14356],{},"ping 不通不代表服务挂",[19,14358,14231],{}," 中间跳丢包常是路由器限速的假象，",[27,14361,14362],{},"只有最后一跳持续丢才是真问题",[46,14364,14366],{"id":14365},"_4-curl-发请求与读-http","4. curl —— 发请求与读 HTTP",[211,14368,14370],{"className":213,"code":14369,"language":215,"meta":216,"style":216},"curl -I https:\u002F\u002Fapp.example.com       # 只看响应头（HEAD），验证 301\u002F403\u002F缓存头\ncurl -sSL -o page.html https:\u002F\u002F…      # 下载标准式：-s 静默、-S 但报错仍显示、-L 跟随重定向、-o 存文件\ncurl -s -w '%{http_code}\\n' -o \u002Fdev\u002Fnull http:\u002F\u002F127.0.0.1:8080\u002Fhealthz\n                                      # 健康检查一行式：只要状态码\ncurl -X POST -H 'Content-Type: application\u002Fjson' -d '{\"a\":1}' URL   # 发 JSON；-d @body.json 从文件读\ncurl -X POST --data-urlencode 'q=中文 词' URL    # 表单编码（-d 不做编码，中文必错）\ncurl -u user:pass URL                 # Basic 认证；--netrc 用 ~\u002F.netrc\ncurl --retry 3 --retry-delay 2 --retry-connrefused -sS URL   # 重试，部署脚本必备\ncurl --connect-timeout 3 --max-time 10 URL                   # 超时控制，否则默认可以挂很久\ncurl -k URL                           # 跳过证书校验——只用于临时验证，别写进脚本；正确做法 --cacert ca.pem\ncurl -x http:\u002F\u002F10.0.0.9:8888 URL      # 走代理\ncurl -v URL 2>&1 | head -40           # 看完整握手（TLS 版本、证书、请求\u002F响应头）\ncurl --resolve app.example.com:443:10.0.0.12 https:\u002F\u002Fapp.example.com\n                                      # 本地强行指定 IP：验证负载均衡后面的某台机器\ncurl -T file URL --user u:p           # 上传（PUT）\ncurl -s 'URL?page=1' | jq .total      # 配合 jq 取字段\n",[19,14371,14372,14384,14402,14420,14425,14451,14470,14484,14509,14528,14539,14553,14573,14586,14591,14611],{"__ignoreMap":216},[220,14373,14374,14376,14378,14381],{"class":222,"line":223},[220,14375,13669],{"class":226},[220,14377,1738],{"class":266},[220,14379,14380],{"class":229}," https:\u002F\u002Fapp.example.com",[220,14382,14383],{"class":233},"       # 只看响应头（HEAD），验证 301\u002F403\u002F缓存头\n",[220,14385,14386,14388,14391,14393,14396,14399],{"class":222,"line":276},[220,14387,13669],{"class":226},[220,14389,14390],{"class":266}," -sSL",[220,14392,5606],{"class":266},[220,14394,14395],{"class":229}," page.html",[220,14397,14398],{"class":229}," https:\u002F\u002F…",[220,14400,14401],{"class":233},"      # 下载标准式：-s 静默、-S 但报错仍显示、-L 跟随重定向、-o 存文件\n",[220,14403,14404,14406,14408,14410,14413,14415,14417],{"class":222,"line":594},[220,14405,13669],{"class":226},[220,14407,1978],{"class":266},[220,14409,6691],{"class":266},[220,14411,14412],{"class":229}," '%{http_code}\\n'",[220,14414,5606],{"class":266},[220,14416,8120],{"class":229},[220,14418,14419],{"class":229}," http:\u002F\u002F127.0.0.1:8080\u002Fhealthz\n",[220,14421,14422],{"class":222,"line":925},[220,14423,14424],{"class":233},"                                      # 健康检查一行式：只要状态码\n",[220,14426,14427,14429,14431,14434,14437,14440,14442,14445,14448],{"class":222,"line":931},[220,14428,13669],{"class":226},[220,14430,9349],{"class":266},[220,14432,14433],{"class":229}," POST",[220,14435,14436],{"class":266}," -H",[220,14438,14439],{"class":229}," 'Content-Type: application\u002Fjson'",[220,14441,1455],{"class":266},[220,14443,14444],{"class":229}," '{\"a\":1}'",[220,14446,14447],{"class":229}," URL",[220,14449,14450],{"class":233},"   # 发 JSON；-d @body.json 从文件读\n",[220,14452,14453,14455,14457,14459,14462,14465,14467],{"class":222,"line":3528},[220,14454,13669],{"class":226},[220,14456,9349],{"class":266},[220,14458,14433],{"class":229},[220,14460,14461],{"class":266}," --data-urlencode",[220,14463,14464],{"class":229}," 'q=中文 词'",[220,14466,14447],{"class":229},[220,14468,14469],{"class":233},"    # 表单编码（-d 不做编码，中文必错）\n",[220,14471,14472,14474,14476,14479,14481],{"class":222,"line":3537},[220,14473,13669],{"class":226},[220,14475,4787],{"class":266},[220,14477,14478],{"class":229}," user:pass",[220,14480,14447],{"class":229},[220,14482,14483],{"class":233},"                 # Basic 认证；--netrc 用 ~\u002F.netrc\n",[220,14485,14486,14488,14491,14493,14496,14498,14501,14504,14506],{"class":222,"line":3546},[220,14487,13669],{"class":226},[220,14489,14490],{"class":266}," --retry",[220,14492,5564],{"class":266},[220,14494,14495],{"class":266}," --retry-delay",[220,14497,1495],{"class":266},[220,14499,14500],{"class":266}," --retry-connrefused",[220,14502,14503],{"class":266}," -sS",[220,14505,14447],{"class":229},[220,14507,14508],{"class":233},"   # 重试，部署脚本必备\n",[220,14510,14511,14513,14516,14518,14521,14523,14525],{"class":222,"line":3555},[220,14512,13669],{"class":226},[220,14514,14515],{"class":266}," --connect-timeout",[220,14517,5564],{"class":266},[220,14519,14520],{"class":266}," --max-time",[220,14522,10952],{"class":266},[220,14524,14447],{"class":229},[220,14526,14527],{"class":233},"                   # 超时控制，否则默认可以挂很久\n",[220,14529,14530,14532,14534,14536],{"class":222,"line":3564},[220,14531,13669],{"class":226},[220,14533,281],{"class":266},[220,14535,14447],{"class":229},[220,14537,14538],{"class":233},"                           # 跳过证书校验——只用于临时验证，别写进脚本；正确做法 --cacert ca.pem\n",[220,14540,14541,14543,14545,14548,14550],{"class":222,"line":3572},[220,14542,13669],{"class":226},[220,14544,5108],{"class":266},[220,14546,14547],{"class":229}," http:\u002F\u002F10.0.0.9:8888",[220,14549,14447],{"class":229},[220,14551,14552],{"class":233},"      # 走代理\n",[220,14554,14555,14557,14559,14561,14563,14565,14567,14570],{"class":222,"line":3580},[220,14556,13669],{"class":226},[220,14558,574],{"class":266},[220,14560,14447],{"class":229},[220,14562,8080],{"class":2439},[220,14564,2626],{"class":2439},[220,14566,4431],{"class":226},[220,14568,14569],{"class":266}," -40",[220,14571,14572],{"class":233},"           # 看完整握手（TLS 版本、证书、请求\u002F响应头）\n",[220,14574,14575,14577,14580,14583],{"class":222,"line":3588},[220,14576,13669],{"class":226},[220,14578,14579],{"class":266}," --resolve",[220,14581,14582],{"class":229}," app.example.com:443:10.0.0.12",[220,14584,14585],{"class":229}," https:\u002F\u002Fapp.example.com\n",[220,14587,14588],{"class":222,"line":7714},[220,14589,14590],{"class":233},"                                      # 本地强行指定 IP：验证负载均衡后面的某台机器\n",[220,14592,14593,14595,14598,14600,14602,14605,14608],{"class":222,"line":7726},[220,14594,13669],{"class":226},[220,14596,14597],{"class":266}," -T",[220,14599,2376],{"class":229},[220,14601,14447],{"class":229},[220,14603,14604],{"class":266}," --user",[220,14606,14607],{"class":229}," u:p",[220,14609,14610],{"class":233},"           # 上传（PUT）\n",[220,14612,14613,14615,14617,14620,14622,14625,14628],{"class":222,"line":11527},[220,14614,13669],{"class":226},[220,14616,1978],{"class":266},[220,14618,14619],{"class":229}," 'URL?page=1'",[220,14621,2626],{"class":2439},[220,14623,14624],{"class":226}," jq",[220,14626,14627],{"class":229}," .total",[220,14629,14630],{"class":233},"      # 配合 jq 取字段\n",[15,14632,14633],{},"看耗时分布的一行格式串（可直接抄）：",[211,14635,14637],{"className":213,"code":14636,"language":215,"meta":216,"style":216},"curl -s -o \u002Fdev\u002Fnull -w 'code=%{http_code} dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total} size=%{size_download}\\n' https:\u002F\u002Fapp.example.com\n",[19,14638,14639],{"__ignoreMap":216},[220,14640,14641,14643,14645,14647,14649,14651,14654],{"class":222,"line":223},[220,14642,13669],{"class":226},[220,14644,1978],{"class":266},[220,14646,5606],{"class":266},[220,14648,8120],{"class":229},[220,14650,6691],{"class":266},[220,14652,14653],{"class":229}," 'code=%{http_code} dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total} size=%{size_download}\\n'",[220,14655,14585],{"class":229},[15,14657,14658,14660,14661,14663,14664,14666,14667,14670,14671,14674,14675,14678,14679,14681,14682,14685,14686,14689,14690,14693,14694,616,14696,14699,14700,14703],{},[19,14659,13669],{}," 的退出码有用：",[19,14662,13373],{}," 连不上、",[19,14665,13356],{}," DNS 解析失败、",[19,14668,14669],{},"28"," 超时、",[19,14672,14673],{},"35"," TLS 握手错、",[19,14676,14677],{},"60"," 证书问题——脚本里判 ",[19,14680,7924],{}," 比看输出可靠。",[19,14683,14684],{},"wget"," 的差异：递归下载（",[19,14687,14688],{},"wget -r -np -nH -k URL","）与自动断点续传（",[19,14691,14692],{},"-c","）更方便；单发请求与调试头信息一律 ",[19,14695,13669],{},[19,14697,14698],{},"aria2c -x 16 -s 16 URL"," 多连接下载大文件快很多，",[19,14701,14702],{},"aria2c --input-file=list.txt"," 批量。",[46,14705,14707],{"id":14706},"_5-sshscprsync","5. ssh、scp、rsync",[211,14709,14711],{"className":213,"code":14710,"language":215,"meta":216,"style":216},"ssh deploy@10.0.0.12                  # 登录，默认 22；ssh -v 看握手细节（排「为什么拒绝」）\nssh -p 2222 deploy@host               # 指定端口；配置里 Port 改了必须用 -p\nssh -i ~\u002F.ssh\u002Fid_ed25519_prod deploy@host   # 指定私钥；权限必须 600，否则报 UNPROTECTED PRIVATE KEY FILE\nssh -J bastion app                     # 跳板机（ProxyJump）；多级 -J bastion1,bastion2\nssh -L 5432:db.internal:5432 -fN deploy@jump\n                                       # 本地端口转发：把远端能到的内网服务映射到本机 5432，\n                                       # 之后 psql -h 127.0.0.1 -p 5432 直连；-N 不开 shell、-f 后台\nssh -R 8080:localhost:8080 deploy@jump # 远程端口转发：把本机服务暴露到跳板机（需 GatewayPorts=yes）\nssh -D 1080 deploy@jump                # SOCKS 代理：curl --socks5 127.0.0.1:1080 URL 配合用\nssh -o ServerAliveInterval=30 host     # 防中间设备断连；写进 ~\u002F.ssh\u002Fconfig 更省事\nssh-copy-id -i ~\u002F.ssh\u002Fid_ed25519.pub deploy@host   # 一键装公钥，仍需一次密码，之后免密\nscp app.tar.gz deploy@host:\u002Fsrv\u002Fapp\u002F       # 复制：注意 scp 语义像 cp；传目录要 -r；新版走 SFTP 协议\nrsync -avz --delete src\u002F host:\u002Fsrv\u002Fapp\u002F        # 增量同步\u002F备份——见下\nrsync -a --progress --partial big.iso host:\u002Fdata\u002F   # 断点续传大文件\n",[19,14712,14713,14723,14738,14752,14767,14782,14787,14792,14807,14822,14839,14854,14867,14885],{"__ignoreMap":216},[220,14714,14715,14717,14720],{"class":222,"line":223},[220,14716,5384],{"class":226},[220,14718,14719],{"class":229}," deploy@10.0.0.12",[220,14721,14722],{"class":233},"                  # 登录，默认 22；ssh -v 看握手细节（排「为什么拒绝」）\n",[220,14724,14725,14727,14729,14732,14735],{"class":222,"line":276},[220,14726,5384],{"class":226},[220,14728,1385],{"class":266},[220,14730,14731],{"class":266}," 2222",[220,14733,14734],{"class":229}," deploy@host",[220,14736,14737],{"class":233},"               # 指定端口；配置里 Port 改了必须用 -p\n",[220,14739,14740,14742,14744,14747,14749],{"class":222,"line":594},[220,14741,5384],{"class":226},[220,14743,1593],{"class":266},[220,14745,14746],{"class":229}," ~\u002F.ssh\u002Fid_ed25519_prod",[220,14748,14734],{"class":229},[220,14750,14751],{"class":233},"   # 指定私钥；权限必须 600，否则报 UNPROTECTED PRIVATE KEY FILE\n",[220,14753,14754,14756,14759,14762,14764],{"class":222,"line":925},[220,14755,5384],{"class":226},[220,14757,14758],{"class":266}," -J",[220,14760,14761],{"class":229}," bastion",[220,14763,4404],{"class":229},[220,14765,14766],{"class":233},"                     # 跳板机（ProxyJump）；多级 -J bastion1,bastion2\n",[220,14768,14769,14771,14773,14776,14779],{"class":222,"line":931},[220,14770,5384],{"class":226},[220,14772,1492],{"class":266},[220,14774,14775],{"class":229}," 5432:db.internal:5432",[220,14777,14778],{"class":266}," -fN",[220,14780,14781],{"class":229}," deploy@jump\n",[220,14783,14784],{"class":222,"line":3528},[220,14785,14786],{"class":233},"                                       # 本地端口转发：把远端能到的内网服务映射到本机 5432，\n",[220,14788,14789],{"class":222,"line":3537},[220,14790,14791],{"class":233},"                                       # 之后 psql -h 127.0.0.1 -p 5432 直连；-N 不开 shell、-f 后台\n",[220,14793,14794,14796,14798,14801,14804],{"class":222,"line":3546},[220,14795,5384],{"class":226},[220,14797,4219],{"class":266},[220,14799,14800],{"class":229}," 8080:localhost:8080",[220,14802,14803],{"class":229}," deploy@jump",[220,14805,14806],{"class":233}," # 远程端口转发：把本机服务暴露到跳板机（需 GatewayPorts=yes）\n",[220,14808,14809,14811,14814,14817,14819],{"class":222,"line":3555},[220,14810,5384],{"class":226},[220,14812,14813],{"class":266}," -D",[220,14815,14816],{"class":266}," 1080",[220,14818,14803],{"class":229},[220,14820,14821],{"class":233},"                # SOCKS 代理：curl --socks5 127.0.0.1:1080 URL 配合用\n",[220,14823,14824,14826,14828,14831,14834,14836],{"class":222,"line":3564},[220,14825,5384],{"class":226},[220,14827,5606],{"class":266},[220,14829,14830],{"class":229}," ServerAliveInterval=",[220,14832,14833],{"class":266},"30",[220,14835,14213],{"class":229},[220,14837,14838],{"class":233},"     # 防中间设备断连；写进 ~\u002F.ssh\u002Fconfig 更省事\n",[220,14840,14841,14844,14846,14849,14851],{"class":222,"line":3572},[220,14842,14843],{"class":226},"ssh-copy-id",[220,14845,1593],{"class":266},[220,14847,14848],{"class":229}," ~\u002F.ssh\u002Fid_ed25519.pub",[220,14850,14734],{"class":229},[220,14852,14853],{"class":233},"   # 一键装公钥，仍需一次密码，之后免密\n",[220,14855,14856,14859,14861,14864],{"class":222,"line":3580},[220,14857,14858],{"class":226},"scp",[220,14860,9199],{"class":229},[220,14862,14863],{"class":229}," deploy@host:\u002Fsrv\u002Fapp\u002F",[220,14865,14866],{"class":233},"       # 复制：注意 scp 语义像 cp；传目录要 -r；新版走 SFTP 协议\n",[220,14868,14869,14871,14874,14877,14879,14882],{"class":222,"line":3588},[220,14870,6470],{"class":226},[220,14872,14873],{"class":266}," -avz",[220,14875,14876],{"class":266}," --delete",[220,14878,1570],{"class":229},[220,14880,14881],{"class":229}," host:\u002Fsrv\u002Fapp\u002F",[220,14883,14884],{"class":233},"        # 增量同步\u002F备份——见下\n",[220,14886,14887,14889,14891,14894,14897,14899,14902],{"class":222,"line":7714},[220,14888,6470],{"class":226},[220,14890,551],{"class":266},[220,14892,14893],{"class":266}," --progress",[220,14895,14896],{"class":266}," --partial",[220,14898,1646],{"class":229},[220,14900,14901],{"class":229}," host:\u002Fdata\u002F",[220,14903,14904],{"class":233},"   # 断点续传大文件\n",[15,14906,14907,14910,14911,14913],{},[19,14908,14909],{},"~\u002F.ssh\u002Fconfig"," 是可维护性的关键（文件权限必须 ",[19,14912,4049],{},"），一份够用多年的样例：",[211,14915,14919],{"className":14916,"code":14917,"language":14918,"meta":216,"style":216},"language-ssh_config shiki shiki-themes github-dark","Host *\n  ServerAliveInterval 30\n  AddKeysToAgent yes\n  IdentityFile ~\u002F.ssh\u002Fid_ed25519\n\nHost prod-web\n  HostName 10.0.0.12\n  User deploy\n  IdentityFile ~\u002F.ssh\u002Fid_ed25519_prod\n  ProxyJump bastion\n\nHost db-tunnel\n  HostName bastion\n  User ops\n  LocalForward 5432 db.internal:5432\n","ssh_config",[19,14920,14921,14926,14931,14936,14941,14945,14950,14955,14960,14965,14970,14974,14979,14984,14989],{"__ignoreMap":216},[220,14922,14923],{"class":222,"line":223},[220,14924,14925],{},"Host *\n",[220,14927,14928],{"class":222,"line":276},[220,14929,14930],{},"  ServerAliveInterval 30\n",[220,14932,14933],{"class":222,"line":594},[220,14934,14935],{},"  AddKeysToAgent yes\n",[220,14937,14938],{"class":222,"line":925},[220,14939,14940],{},"  IdentityFile ~\u002F.ssh\u002Fid_ed25519\n",[220,14942,14943],{"class":222,"line":931},[220,14944,3740],{"emptyLinePlaceholder":3739},[220,14946,14947],{"class":222,"line":3528},[220,14948,14949],{},"Host prod-web\n",[220,14951,14952],{"class":222,"line":3537},[220,14953,14954],{},"  HostName 10.0.0.12\n",[220,14956,14957],{"class":222,"line":3546},[220,14958,14959],{},"  User deploy\n",[220,14961,14962],{"class":222,"line":3555},[220,14963,14964],{},"  IdentityFile ~\u002F.ssh\u002Fid_ed25519_prod\n",[220,14966,14967],{"class":222,"line":3564},[220,14968,14969],{},"  ProxyJump bastion\n",[220,14971,14972],{"class":222,"line":3572},[220,14973,3740],{"emptyLinePlaceholder":3739},[220,14975,14976],{"class":222,"line":3580},[220,14977,14978],{},"Host db-tunnel\n",[220,14980,14981],{"class":222,"line":3588},[220,14982,14983],{},"  HostName bastion\n",[220,14985,14986],{"class":222,"line":7714},[220,14987,14988],{},"  User ops\n",[220,14990,14991],{"class":222,"line":7726},[220,14992,14993],{},"  LocalForward 5432 db.internal:5432\n",[15,14995,14996,14997,15000,15001,15004,15005,15008],{},"之后 ",[19,14998,14999],{},"ssh prod-web"," 一条搞定跳板与密钥。",[19,15002,15003],{},"Host *"," 段的顺序",[27,15006,15007],{},"只在第一次匹配时生效","，所以具体 Host 要写在通配前面。",[15,15010,15011,15013,15014,15016,15017,15020,15021,15024,15025,827,15028,15031,15032,15035],{},[19,15012,6470],{}," 为什么更适合备份：只传差异（滚动校验）、可保留权限与时间戳（",[19,15015,13117],{}," = ",[19,15018,15019],{},"-rlptgoD","）、能删除目标多余文件（",[19,15022,15023],{},"--delete"," 镜像语义）、可断点续传、可通过 ssh 加密传输、支持 ",[19,15026,15027],{},"--exclude",[19,15029,15030],{},"--link-dest"," 做",[27,15033,15034],{},"硬链接式全量快照","（省空间又每个快照都是完整目录）。",[211,15037,15039],{"className":213,"code":15038,"language":215,"meta":216,"style":216},"rsync -avz --delete --exclude='node_modules\u002F' --exclude='.git\u002F' \u002Fsrv\u002Fapp\u002F deploy@backup:\u002Fbackup\u002Fapp-$(date +%F)\u002F\nrsync -a --delete --link-dest=..\u002Fapp-2026-10-03 \u002Fsrv\u002Fapp\u002F \u002Fbackup\u002Fapp-2026-10-04\u002F\n",[19,15040,15041,15077],{"__ignoreMap":216},[220,15042,15043,15045,15047,15049,15051,15054,15056,15059,15062,15065,15067,15069,15071,15074],{"class":222,"line":223},[220,15044,6470],{"class":226},[220,15046,14873],{"class":266},[220,15048,14876],{"class":266},[220,15050,9325],{"class":266},[220,15052,15053],{"class":229},"'node_modules\u002F'",[220,15055,9325],{"class":266},[220,15057,15058],{"class":229},"'.git\u002F'",[220,15060,15061],{"class":229}," \u002Fsrv\u002Fapp\u002F",[220,15063,15064],{"class":229}," deploy@backup:\u002Fbackup\u002Fapp-",[220,15066,7221],{"class":6987},[220,15068,7224],{"class":226},[220,15070,7227],{"class":229},[220,15072,15073],{"class":6987},")",[220,15075,15076],{"class":229},"\u002F\n",[220,15078,15079,15081,15083,15085,15088,15090],{"class":222,"line":276},[220,15080,6470],{"class":226},[220,15082,551],{"class":266},[220,15084,14876],{"class":266},[220,15086,15087],{"class":266}," --link-dest=..\u002Fapp-2026-10-03",[220,15089,15061],{"class":229},[220,15091,15092],{"class":229}," \u002Fbackup\u002Fapp-2026-10-04\u002F\n",[15,15094,15095,6557,15100,15103,15104,15106,15107,15110,15111,15114,15115,15118,15119,15121],{},[27,15096,15097,15099],{},[19,15098,15023],{}," 务必先干跑",[19,15101,15102],{},"-n --delete","（试运行）或 ",[19,15105,1858],{},"（显示每一项动作）。rsync 的路径语义坑：源 ",[19,15108,15109],{},"src\u002F","（带斜杠）= 同步",[27,15112,15113],{},"目录内容","，源 ",[19,15116,15117],{},"src","（无斜杠）= 在目标下建 ",[19,15120,15117],{}," 目录，这一条能造成整站目录结构错位。",[46,15123,15125],{"id":15124},"_6-防火墙与-etchosts","6. 防火墙与 \u002Fetc\u002Fhosts",[51,15127,15128,15141],{},[54,15129,15130],{},[57,15131,15132,15135,15138],{},[60,15133,15134],{},"后端",[60,15136,15137],{},"常见于",[60,15139,15140],{},"常用命令",[70,15142,15143,15163,15189],{},[57,15144,15145,15149,15151],{},[75,15146,15147],{},[19,15148,751],{},[75,15150,638],{},[75,15152,15153,15156,15157,418,15160],{},[19,15154,15155],{},"sudo firewall-cmd --permanent --add-port=80\u002Ftcp"," → ",[19,15158,15159],{},"sudo firewall-cmd --reload",[19,15161,15162],{},"sudo firewall-cmd --list-all",[57,15164,15165,15169,15172],{},[75,15166,15167],{},[19,15168,745],{},[75,15170,15171],{},"Ubuntu\u002FDebian 桌面与轻量服务器",[75,15173,15174,34,15177,34,15180,34,15183,689,15186,1062],{},[19,15175,15176],{},"sudo ufw allow 22\u002Ftcp",[19,15178,15179],{},"sudo ufw deny 3306",[19,15181,15182],{},"sudo ufw status verbose",[19,15184,15185],{},"sudo ufw enable",[27,15187,15188],{},"开之前务必先 allow ssh",[57,15190,15191,15197,15200],{},[75,15192,15193,1073,15195],{},[19,15194,756],{},[19,15196,759],{},[75,15198,15199],{},"手工管理或老环境",[75,15201,15202,34,15205,418,15208],{},[19,15203,15204],{},"sudo iptables -L -n -v --line-numbers",[19,15206,15207],{},"sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT",[19,15209,15210],{},"sudo nft list ruleset",[15,15212,15213,15214,8831,15217,15220,15221,15224,15225,15228,15229,15232,15233,15236,15237,15240],{},"三条通用提醒：",[19,15215,15216],{},"iptables -A",[27,15218,15219],{},"追加到链尾","（默认策略 DROP 时可能永远走不到），插入用 ",[19,15222,15223],{},"-I INPUT 1","；规则改完不保存重启就没了（",[19,15226,15227],{},"iptables-save > \u002Fetc\u002Fiptables\u002Frules.v4","，或 ",[19,15230,15231],{},"netfilter-persistent save","）；ufw\u002Ffirewalld 都在最上层，",[27,15234,15235],{},"用底层命令加的规则它们看不见","，别混用两套。Docker 会直接写 iptables 并在启动时插入自己的链，",[19,15238,15239],{},"ufw deny"," 拦不住已 expose 的容器端口——这是极常见的困惑来源。",[15,15242,15243,8831,15246,15249,15250,6508,15253,13398],{},[19,15244,15245],{},"\u002Fetc\u002Fhosts",[27,15247,15248],{},"本机静态解析","，优先级在 nsswitch 里通常高于 DNS（",[19,15251,15252],{},"\u002Fetc\u002Fnsswitch.conf",[19,15254,15255],{},"hosts: files dns",[211,15257,15259],{"className":213,"code":15258,"language":215,"meta":216,"style":216},"printf '10.0.0.12  app.internal\\n' | sudo tee -a \u002Fetc\u002Fhosts\n",[19,15260,15261],{"__ignoreMap":216},[220,15262,15263,15265,15268,15270,15272,15274,15276],{"class":222,"line":223},[220,15264,292],{"class":266},[220,15266,15267],{"class":229}," '10.0.0.12  app.internal\\n'",[220,15269,2626],{"class":2439},[220,15271,8230],{"class":226},[220,15273,8200],{"class":229},[220,15275,551],{"class":266},[220,15277,8237],{"class":229},[15,15279,15280,15281,15284,15285,15287],{},"用途：临时指向某台后端验证、屏蔽域名（指 ",[19,15282,15283],{},"127.0.0.1","）、容器里注入别名。坑：改了 hosts 但应用没生效，通常是应用做了自己的 DNS 缓存，或它走的是 ",[19,15286,13666],{}," 式直接查询；反之 hosts 会盖掉 DNS，测试完记得删。",[46,15289,808],{"id":15290},"踩坑提醒-12",[810,15292,15293,15311,15326,15362,15379],{},[813,15294,15295,15296,15298,15299,15302,15303,15306,15307,15310],{},"「",[19,15297,13663],{}," 显示监听在 ",[19,15300,15301],{},"127.0.0.1:8080","，外部怎么都连不上」——服务只绑了回环地址，改 ",[19,15304,15305],{},"0.0.0.0:8080","（或配置里的 ",[19,15308,15309],{},"bind","）。这一条比防火墙更常是根因。",[813,15312,15313,15315,15316,187,15319,15321,15322,15325],{},[19,15314,4995],{}," 不通不代表服务不可用（ICMP 被封），要用 ",[19,15317,15318],{},"nc -vz",[19,15320,13669],{}," 测",[27,15323,15324],{},"目标端口","才算数。",[813,15327,15328,15329,15332,15333,15336,15337,15340,15341,689,15344,15347,15348,15351,15352,15355,15356,6508,15359,1520],{},"排 DNS 问题按链路顺序：",[19,15330,15331],{},"getent hosts X","（含 hosts 文件）→ ",[19,15334,15335],{},"dig +short X @\u003C本机 resolv 里的服务器>"," → 换 ",[19,15338,15339],{},"@1.1.1.1"," 对比 → 查 ",[19,15342,15343],{},"\u002Fetc\u002Fresolv.conf",[19,15345,15346],{},"systemd-resolved"," 环境里它是指向 ",[19,15349,15350],{},"127.0.0.53"," 的软链接，",[27,15353,15354],{},"直接改会被覆盖","，正确改法是 ",[19,15357,15358],{},"\u002Fetc\u002Fsystemd\u002Fresolved.conf",[19,15360,15361],{},"DNS=",[813,15363,15364,15365,827,15367,15370,15371,662,15373,15375,15376,616],{},"容器里 ",[19,15366,15245],{},[19,15368,15369],{},"resolv.conf"," 是挂载文件，",[19,15372,4142],{},[19,15374,3828],{}," 会报 ",[19,15377,15378],{},"Device or resource busy",[813,15380,15381,15383],{},[19,15382,14231],{}," 中间跳的丢包率是路由器 ICMP 限速造成的假象，只看最后一跳与整体 RTT。",[10,15385,15387],{"id":15386},"十四磁盘dfdumountfsck","十四、磁盘：df、du、mount、fsck",[15,15389,15390,15392,15393,15396],{},[19,15391,5287],{}," 有两种完全不同的原因（块用满 vs inode 用满），",[19,15394,15395],{},"du"," 找不到大头时还有第三种（被删除但仍在打开的文件）。这一章把磁盘相关命令按「看容量 → 看谁占 → 看设备 → 挂载 → 修文件系统」的顺序排好。",[861,15398],{"height":3858,"src":15399,"title":15400},"\u002Ftutorials\u002Flinux\u002Fdemos\u002F14-disk.html","示例 14：df\u002Fdu 找空间大头与 inode 用满的排查",[46,15402,15404],{"id":15403},"_1-df-看文件系统容量","1. df —— 看文件系统容量",[211,15406,15408],{"className":213,"code":15407,"language":215,"meta":216,"style":216},"df -h             # 各文件系统使用率（人读大小）\ndf -hT            # 含文件系统类型\ndf -i             # inode 使用率——磁盘满的第二种原因\ndf -h \u002Fsrv\u002Fapp    # 只看这个路径所在分区\n",[19,15409,15410,15420,15430,15439],{"__ignoreMap":216},[220,15411,15412,15415,15417],{"class":222,"line":223},[220,15413,15414],{"class":226},"df",[220,15416,4373],{"class":266},[220,15418,15419],{"class":233},"             # 各文件系统使用率（人读大小）\n",[220,15421,15422,15424,15427],{"class":222,"line":276},[220,15423,15414],{"class":226},[220,15425,15426],{"class":266}," -hT",[220,15428,15429],{"class":233},"            # 含文件系统类型\n",[220,15431,15432,15434,15436],{"class":222,"line":594},[220,15433,15414],{"class":226},[220,15435,1593],{"class":266},[220,15437,15438],{"class":233},"             # inode 使用率——磁盘满的第二种原因\n",[220,15440,15441,15443,15445,15447],{"class":222,"line":925},[220,15442,15414],{"class":226},[220,15444,4373],{"class":266},[220,15446,894],{"class":229},[220,15448,15449],{"class":233},"    # 只看这个路径所在分区\n",[15,15451,15452,15455,15456,15459,15460,689,15463,15466,15467,15470,15471,34,15474,34,15477,15480,15481,15484,15485,616],{},[19,15453,15454],{},"df -h"," 各列：",[19,15457,15458],{},"Filesystem","（来源设备或 overlay）、",[19,15461,15462],{},"Size",[27,15464,15465],{},"总容量，不含文件系统预留","，所以比 ",[19,15468,15469],{},"lsblk"," 显示的小）、",[19,15472,15473],{},"Used",[19,15475,15476],{},"Avail",[19,15478,15479],{},"Use%","（注意它按 ",[19,15482,15483],{},"Used\u002F(Used+Avail)"," 算，root 预留的 5% 不计入，所以会出现「Avail 还剩 4.5G 但 Use% 已 100%」）、",[19,15486,15487],{},"Mounted on",[46,15489,15491],{"id":15490},"_2-du-看谁占了空间","2. du —— 看谁占了空间",[211,15493,15495],{"className":213,"code":15494,"language":215,"meta":216,"style":216},"du -sh \u002Fsrv\u002Fapp                                    # 单个目录总占用\ndu -h --max-depth=1 . | sort -hr | head -15        # 当前目录各子目录大小 Top 15\ndu -xh \u002F --max-depth=2 2>\u002Fdev\u002Fnull | sort -h | tail -20   # 全盘 Top 大目录（-x 不跨挂载点）\ndu -sh \u002Fvar\u002Flog\u002F* | sort -h | tail                 # 按日志类别看\nfind \u002F -xdev -type f -size +200M -printf '%s\\t%p\\n' | sort -rn | head   # 全盘 Top 大文件（第六章）\nsudo lsof +L1 | head                                # 已删除但仍被占用的空间\ndocker system df                                    # Docker 占用（docker system prune -a 清理）\n",[19,15496,15497,15509,15536,15568,15591,15624,15641],{"__ignoreMap":216},[220,15498,15499,15501,15504,15506],{"class":222,"line":223},[220,15500,15395],{"class":226},[220,15502,15503],{"class":266}," -sh",[220,15505,894],{"class":229},[220,15507,15508],{"class":233},"                                    # 单个目录总占用\n",[220,15510,15511,15513,15515,15518,15520,15522,15524,15527,15529,15531,15533],{"class":222,"line":276},[220,15512,15395],{"class":226},[220,15514,4373],{"class":266},[220,15516,15517],{"class":266}," --max-depth=1",[220,15519,5517],{"class":229},[220,15521,2626],{"class":2439},[220,15523,6127],{"class":226},[220,15525,15526],{"class":266}," -hr",[220,15528,2626],{"class":2439},[220,15530,4431],{"class":226},[220,15532,11006],{"class":266},[220,15534,15535],{"class":233},"        # 当前目录各子目录大小 Top 15\n",[220,15537,15538,15540,15543,15545,15548,15550,15552,15554,15556,15558,15560,15563,15565],{"class":222,"line":594},[220,15539,15395],{"class":226},[220,15541,15542],{"class":266}," -xh",[220,15544,3621],{"class":229},[220,15546,15547],{"class":266}," --max-depth=2",[220,15549,8063],{"class":2439},[220,15551,8684],{"class":229},[220,15553,2626],{"class":2439},[220,15555,6127],{"class":226},[220,15557,4373],{"class":266},[220,15559,2626],{"class":2439},[220,15561,15562],{"class":226}," tail",[220,15564,7850],{"class":266},[220,15566,15567],{"class":233},"   # 全盘 Top 大目录（-x 不跨挂载点）\n",[220,15569,15570,15572,15574,15576,15578,15580,15582,15584,15586,15588],{"class":222,"line":925},[220,15571,15395],{"class":226},[220,15573,15503],{"class":266},[220,15575,9593],{"class":229},[220,15577,2436],{"class":266},[220,15579,2626],{"class":2439},[220,15581,6127],{"class":226},[220,15583,4373],{"class":266},[220,15585,2626],{"class":2439},[220,15587,15562],{"class":226},[220,15589,15590],{"class":233},"                 # 按日志类别看\n",[220,15592,15593,15595,15597,15599,15601,15603,15605,15607,15609,15611,15613,15615,15617,15619,15621],{"class":222,"line":931},[220,15594,694],{"class":226},[220,15596,3621],{"class":229},[220,15598,5581],{"class":266},[220,15600,1775],{"class":266},[220,15602,1461],{"class":229},[220,15604,5650],{"class":266},[220,15606,6116],{"class":229},[220,15608,6119],{"class":266},[220,15610,6122],{"class":229},[220,15612,2626],{"class":2439},[220,15614,6127],{"class":226},[220,15616,6130],{"class":266},[220,15618,2626],{"class":2439},[220,15620,4431],{"class":226},[220,15622,15623],{"class":233},"   # 全盘 Top 大文件（第六章）\n",[220,15625,15626,15628,15631,15634,15636,15638],{"class":222,"line":3528},[220,15627,120],{"class":226},[220,15629,15630],{"class":229}," lsof",[220,15632,15633],{"class":229}," +L1",[220,15635,2626],{"class":2439},[220,15637,4431],{"class":226},[220,15639,15640],{"class":233},"                                # 已删除但仍被占用的空间\n",[220,15642,15643,15646,15649,15652],{"class":222,"line":3537},[220,15644,15645],{"class":226},"docker",[220,15647,15648],{"class":229}," system",[220,15650,15651],{"class":229}," df",[220,15653,15654],{"class":233},"                                    # Docker 占用（docker system prune -a 清理）\n",[15,15656,15657,827,15659,15661,15662,15664,15665,15667,15668,15670,15671,15673,15674,15676,15677,15680],{},[19,15658,15395],{},[19,15660,15414],{}," 数字不一致的三个正当理由：① 有被删除但进程仍打开的文件（",[19,15663,15395],{}," 看不见，",[19,15666,15414],{}," 算满）；② 隐藏挂载点覆盖目录（",[19,15669,15395],{}," 算的是覆盖后的）；③ 文件系统预留块（",[19,15672,15414],{}," 的 Size 不含）。加 ",[19,15675,10372],{}," 避免扫进别的文件系统导致统计跑偏；",[19,15678,15679],{},"--apparent-size"," 看表观大小（稀疏文件差异巨大）。",[46,15682,15684],{"id":15683},"_3-lsblkblkidfindmnt-看设备与挂载","3. lsblk、blkid、findmnt —— 看设备与挂载",[211,15686,15688],{"className":213,"code":15687,"language":215,"meta":216,"style":216},"lsblk -f              # 块设备树，含文件系统类型与 UUID\nlsblk -o NAME,SIZE,FSTYPE,MOUNTPOINTS    # 自选列\nsudo blkid \u002Fdev\u002Fvdb1  # 设备 UUID 与类型——写 \u002Fetc\u002Ffstab 时用 UUID 而不是设备名\nfindmnt -T \u002Fsrv\u002Fapp   # 问这个路径落在哪个挂载点、读写还是只读（ro 就是磁盘只读挂载）\nfindmnt --fstab       # 看 fstab 的解析结果\nfree -h               # 内存与 swap；available 才是可用值（第十七章）\nsudo swapon --show    # swap 分区\u002F文件；swapon \u002Fswapfile 启用、swapoff -a 关闭（容器里通常禁用）\n",[19,15689,15690,15699,15711,15724,15736,15746,15756],{"__ignoreMap":216},[220,15691,15692,15694,15696],{"class":222,"line":223},[220,15693,15469],{"class":226},[220,15695,2573],{"class":266},[220,15697,15698],{"class":233},"              # 块设备树，含文件系统类型与 UUID\n",[220,15700,15701,15703,15705,15708],{"class":222,"line":276},[220,15702,15469],{"class":226},[220,15704,5606],{"class":266},[220,15706,15707],{"class":229}," NAME,SIZE,FSTYPE,MOUNTPOINTS",[220,15709,15710],{"class":233},"    # 自选列\n",[220,15712,15713,15715,15718,15721],{"class":222,"line":594},[220,15714,120],{"class":226},[220,15716,15717],{"class":229}," blkid",[220,15719,15720],{"class":229}," \u002Fdev\u002Fvdb1",[220,15722,15723],{"class":233},"  # 设备 UUID 与类型——写 \u002Fetc\u002Ffstab 时用 UUID 而不是设备名\n",[220,15725,15726,15729,15731,15733],{"class":222,"line":925},[220,15727,15728],{"class":226},"findmnt",[220,15730,14597],{"class":266},[220,15732,894],{"class":229},[220,15734,15735],{"class":233},"   # 问这个路径落在哪个挂载点、读写还是只读（ro 就是磁盘只读挂载）\n",[220,15737,15738,15740,15743],{"class":222,"line":931},[220,15739,15728],{"class":226},[220,15741,15742],{"class":266}," --fstab",[220,15744,15745],{"class":233},"       # 看 fstab 的解析结果\n",[220,15747,15748,15751,15753],{"class":222,"line":3528},[220,15749,15750],{"class":226},"free",[220,15752,4373],{"class":266},[220,15754,15755],{"class":233},"               # 内存与 swap；available 才是可用值（第十七章）\n",[220,15757,15758,15760,15763,15766],{"class":222,"line":3537},[220,15759,120],{"class":226},[220,15761,15762],{"class":229}," swapon",[220,15764,15765],{"class":266}," --show",[220,15767,15768],{"class":233},"    # swap 分区\u002F文件；swapon \u002Fswapfile 启用、swapoff -a 关闭（容器里通常禁用）\n",[46,15770,15772],{"id":15771},"_4-inode-用满另一种磁盘满","4. inode 用满：另一种「磁盘满」",[15,15774,15775,15776,15778,15779,15781,15782,1284,15785,15788],{},"症状：",[19,15777,15454],{}," 显示还有 30% 空间，但写文件报 ",[19,15780,5287],{}," → 跑 ",[19,15783,15784],{},"df -i",[19,15786,15787],{},"IFuse%"," = 100% 就是 inode 用满。谁吃的：",[211,15790,15792],{"className":213,"code":15791,"language":215,"meta":216,"style":216},"for d in \u002Fvar\u002Fspool\u002F* \u002Fsrv\u002Fapp\u002Ftmp; do echo -n \"$d \"; find $d -type f | wc -l; done\nfind \u002F -xdev -type f | awk -F\u002F '{print $2\"\u002F\"$3}' | sort | uniq -c | sort -rn | head\nfind \u002Fsrv\u002Fapp\u002Ftmp -type f -mtime +1 -delete    # 清完 df -i 立刻回落\n",[19,15793,15794,15852,15894],{"__ignoreMap":216},[220,15795,15796,15799,15802,15805,15808,15811,15813,15816,15819,15821,15824,15827,15829,15831,15833,15836,15838,15840,15842,15845,15847,15849],{"class":222,"line":223},[220,15797,15798],{"class":2439},"for",[220,15800,15801],{"class":6987}," d ",[220,15803,15804],{"class":2439},"in",[220,15806,15807],{"class":229}," \u002Fvar\u002Fspool\u002F*",[220,15809,15810],{"class":229}," \u002Fsrv\u002Fapp\u002Ftmp",[220,15812,8550],{"class":6987},[220,15814,15815],{"class":2439},"do",[220,15817,15818],{"class":266}," echo",[220,15820,1669],{"class":266},[220,15822,15823],{"class":229}," \"",[220,15825,15826],{"class":6987},"$d",[220,15828,15823],{"class":229},[220,15830,8550],{"class":6987},[220,15832,694],{"class":226},[220,15834,15835],{"class":6987}," $d ",[220,15837,5445],{"class":266},[220,15839,1461],{"class":229},[220,15841,2626],{"class":2439},[220,15843,15844],{"class":226}," wc",[220,15846,3193],{"class":266},[220,15848,8550],{"class":6987},[220,15850,15851],{"class":2439},"done\n",[220,15853,15854,15856,15858,15860,15862,15864,15866,15868,15871,15874,15876,15878,15880,15882,15884,15886,15888,15890,15892],{"class":222,"line":276},[220,15855,694],{"class":226},[220,15857,3621],{"class":229},[220,15859,5581],{"class":266},[220,15861,1775],{"class":266},[220,15863,1461],{"class":229},[220,15865,2626],{"class":2439},[220,15867,7884],{"class":226},[220,15869,15870],{"class":266}," -F\u002F",[220,15872,15873],{"class":229}," '{print $2\"\u002F\"$3}'",[220,15875,2626],{"class":2439},[220,15877,6127],{"class":226},[220,15879,2626],{"class":2439},[220,15881,7774],{"class":226},[220,15883,2494],{"class":266},[220,15885,2626],{"class":2439},[220,15887,6127],{"class":226},[220,15889,6130],{"class":266},[220,15891,2626],{"class":2439},[220,15893,6135],{"class":226},[220,15895,15896,15898,15900,15902,15904,15906,15909,15911],{"class":222,"line":594},[220,15897,694],{"class":226},[220,15899,15810],{"class":229},[220,15901,1775],{"class":266},[220,15903,1461],{"class":229},[220,15905,5665],{"class":266},[220,15907,15908],{"class":229}," +1",[220,15910,5943],{"class":266},[220,15912,15913],{"class":233},"    # 清完 df -i 立刻回落\n",[15,15915,15916,15917,15920,15921,15924,15925,15928],{},"通常是海量小文件（session、缓存、mail queue、监控点）。根治靠重建分区时 ",[19,15918,15919],{},"mkfs -T ext4 -i 4096","（每 4096 字节一个 inode，默认 16384）或换 XFS\u002FBtrfs——ext4 的 inode 数量在格式化时定死，运行期不能扩；XFS 的 inode 随用随长（受 ",[19,15922,15923],{},"imaxpct"," 限制，可 ",[19,15926,15927],{},"xfs_growfs -m"," 调）。属于规划问题，需要备份重来。",[46,15930,15932],{"id":15931},"_5-分区与格式化危险区先读警告","5. 分区与格式化（危险区，先读警告）",[15,15934,15935,15936,15950,15951,15954,15955,15957,15958,827,15961,15963,15964,827,15967,15970],{},"危险边界必须先讲清：",[27,15937,15938,34,15940,662,15943,15946,15947,15949],{},[19,15939,37],{},[19,15941,15942],{},"fdisk",[19,15944,15945],{},"parted"," 的写操作、",[19,15948,40],{}," 都是不可逆的","。改分区表前一定 ",[19,15952,15953],{},"cp \u002Fetc\u002Ffstab ~\u002F"," 并做完整备份；",[19,15956,37],{}," 之前用 ",[19,15959,15960],{},"lsblk -f",[19,15962,15728],{}," 双重确认目标设备名（",[19,15965,15966],{},"\u002Fdev\u002Fvdb",[19,15968,15969],{},"\u002Fdev\u002Fvdb1"," 差一个数字就格式化了整块盘）。",[211,15972,15974],{"className":213,"code":15973,"language":215,"meta":216,"style":216},"sudo fdisk \u002Fdev\u002Fvdb     # MBR\u002F传统分区：n 新建、d 删除、p 打印、w 写入、q 弃改动\n                        # 只在 w 之前不生效；2TB 以上要用 GPT\nsudo parted -s \u002Fdev\u002Fvdb mklabel gpt                # 建 GPT 分区表\nsudo parted \u002Fdev\u002Fvdb mkpart data ext4 1MiB 100%    # 建分区——注意 parted 的 mkpart 立即写盘，\n                                                   # 没有「先看看」的机会；改完 partprobe 通知内核\nsudo mkfs.ext4 -L data -m 1 \u002Fdev\u002Fvdb1              # 建 ext4 文件系统——销毁数据！\n                                                   # -L 卷标；-m 1 把 root 预留降到 1%\nsudo mkfs.xfs -f \u002Fdev\u002Fvdb1                         # 建 XFS：-f 强制覆盖已有签名\nsudo wipefs -a \u002Fdev\u002Fvdb1                           # 清设备上的文件系统签名——重建盘前用，极其危险\n",[19,15975,15976,15989,15994,16014,16040,16045,16065,16070,16084],{"__ignoreMap":216},[220,15977,15978,15980,15983,15986],{"class":222,"line":223},[220,15979,120],{"class":226},[220,15981,15982],{"class":229}," fdisk",[220,15984,15985],{"class":229}," \u002Fdev\u002Fvdb",[220,15987,15988],{"class":233},"     # MBR\u002F传统分区：n 新建、d 删除、p 打印、w 写入、q 弃改动\n",[220,15990,15991],{"class":222,"line":276},[220,15992,15993],{"class":233},"                        # 只在 w 之前不生效；2TB 以上要用 GPT\n",[220,15995,15996,15998,16001,16003,16005,16008,16011],{"class":222,"line":594},[220,15997,120],{"class":226},[220,15999,16000],{"class":229}," parted",[220,16002,1978],{"class":266},[220,16004,15985],{"class":229},[220,16006,16007],{"class":229}," mklabel",[220,16009,16010],{"class":229}," gpt",[220,16012,16013],{"class":233},"                # 建 GPT 分区表\n",[220,16015,16016,16018,16020,16022,16025,16028,16031,16034,16037],{"class":222,"line":925},[220,16017,120],{"class":226},[220,16019,16000],{"class":229},[220,16021,15985],{"class":229},[220,16023,16024],{"class":229}," mkpart",[220,16026,16027],{"class":229}," data",[220,16029,16030],{"class":229}," ext4",[220,16032,16033],{"class":229}," 1MiB",[220,16035,16036],{"class":229}," 100%",[220,16038,16039],{"class":233},"    # 建分区——注意 parted 的 mkpart 立即写盘，\n",[220,16041,16042],{"class":222,"line":931},[220,16043,16044],{"class":233},"                                                   # 没有「先看看」的机会；改完 partprobe 通知内核\n",[220,16046,16047,16049,16052,16054,16056,16058,16060,16062],{"class":222,"line":3528},[220,16048,120],{"class":226},[220,16050,16051],{"class":229}," mkfs.ext4",[220,16053,1492],{"class":266},[220,16055,16027],{"class":229},[220,16057,5078],{"class":266},[220,16059,267],{"class":266},[220,16061,15720],{"class":229},[220,16063,16064],{"class":233},"              # 建 ext4 文件系统——销毁数据！\n",[220,16066,16067],{"class":222,"line":3537},[220,16068,16069],{"class":233},"                                                   # -L 卷标；-m 1 把 root 预留降到 1%\n",[220,16071,16072,16074,16077,16079,16081],{"class":222,"line":3546},[220,16073,120],{"class":226},[220,16075,16076],{"class":229}," mkfs.xfs",[220,16078,2573],{"class":266},[220,16080,15720],{"class":229},[220,16082,16083],{"class":233},"                         # 建 XFS：-f 强制覆盖已有签名\n",[220,16085,16086,16088,16091,16093,16095],{"class":222,"line":3555},[220,16087,120],{"class":226},[220,16089,16090],{"class":229}," wipefs",[220,16092,551],{"class":266},[220,16094,15720],{"class":229},[220,16096,16097],{"class":233},"                           # 清设备上的文件系统签名——重建盘前用，极其危险\n",[46,16099,16101],{"id":16100},"_6-mountumount-与-etcfstab","6. mount、umount 与 \u002Fetc\u002Ffstab",[211,16103,16105],{"className":213,"code":16104,"language":215,"meta":216,"style":216},"sudo mount \u002Fdev\u002Fvdb1 \u002Fdata                  # 挂载；挂载点原有内容会被「遮住」（不是丢失），卸载后回来\nsudo mount -o ro,noexec \u002Fdev\u002Fvdb1 \u002Fdata     # 只读 + 禁执行挂载\nsudo mount -o remount,rw \u002F                  # 重新挂载为读写\nsudo umount \u002Fdata                           # 卸载\nsudo umount -l \u002Fdata                        # 惰性卸载——报 target is busy 时先用\nsudo lsof +D \u002Fdata                          # lsof\u002Ffuser 找占用者，别习惯性 -l 掩盖问题\nsudo fuser -vm \u002Fdata\nsudo systemctl daemon-reload && sudo mount -a   # 改完 fstab 用 mount -a 验证：成功才说明重启不会翻车\nfindmnt --verify                            # 检查 \u002Fetc\u002Ffstab 语法——fstab 写错会导致启动进 emergency mode\n",[19,16106,16107,16122,16140,16156,16168,16181,16195,16208,16227],{"__ignoreMap":216},[220,16108,16109,16111,16114,16116,16119],{"class":222,"line":223},[220,16110,120],{"class":226},[220,16112,16113],{"class":229}," mount",[220,16115,15720],{"class":229},[220,16117,16118],{"class":229}," \u002Fdata",[220,16120,16121],{"class":233},"                  # 挂载；挂载点原有内容会被「遮住」（不是丢失），卸载后回来\n",[220,16123,16124,16126,16128,16130,16133,16135,16137],{"class":222,"line":276},[220,16125,120],{"class":226},[220,16127,16113],{"class":229},[220,16129,5606],{"class":266},[220,16131,16132],{"class":229}," ro,noexec",[220,16134,15720],{"class":229},[220,16136,16118],{"class":229},[220,16138,16139],{"class":233},"     # 只读 + 禁执行挂载\n",[220,16141,16142,16144,16146,16148,16151,16153],{"class":222,"line":594},[220,16143,120],{"class":226},[220,16145,16113],{"class":229},[220,16147,5606],{"class":266},[220,16149,16150],{"class":229}," remount,rw",[220,16152,3621],{"class":229},[220,16154,16155],{"class":233},"                  # 重新挂载为读写\n",[220,16157,16158,16160,16163,16165],{"class":222,"line":925},[220,16159,120],{"class":226},[220,16161,16162],{"class":229}," umount",[220,16164,16118],{"class":229},[220,16166,16167],{"class":233},"                           # 卸载\n",[220,16169,16170,16172,16174,16176,16178],{"class":222,"line":931},[220,16171,120],{"class":226},[220,16173,16162],{"class":229},[220,16175,3193],{"class":266},[220,16177,16118],{"class":229},[220,16179,16180],{"class":233},"                        # 惰性卸载——报 target is busy 时先用\n",[220,16182,16183,16185,16187,16190,16192],{"class":222,"line":3528},[220,16184,120],{"class":226},[220,16186,15630],{"class":229},[220,16188,16189],{"class":229}," +D",[220,16191,16118],{"class":229},[220,16193,16194],{"class":233},"                          # lsof\u002Ffuser 找占用者，别习惯性 -l 掩盖问题\n",[220,16196,16197,16199,16202,16205],{"class":222,"line":3537},[220,16198,120],{"class":226},[220,16200,16201],{"class":229}," fuser",[220,16203,16204],{"class":266}," -vm",[220,16206,16207],{"class":229}," \u002Fdata\n",[220,16209,16210,16212,16214,16216,16218,16220,16222,16224],{"class":222,"line":3546},[220,16211,120],{"class":226},[220,16213,8486],{"class":229},[220,16215,11481],{"class":229},[220,16217,6988],{"class":6987},[220,16219,120],{"class":226},[220,16221,16113],{"class":229},[220,16223,551],{"class":266},[220,16225,16226],{"class":233},"   # 改完 fstab 用 mount -a 验证：成功才说明重启不会翻车\n",[220,16228,16229,16231,16234],{"class":222,"line":3555},[220,16230,15728],{"class":226},[220,16232,16233],{"class":266}," --verify",[220,16235,16236],{"class":233},"                            # 检查 \u002Fetc\u002Ffstab 语法——fstab 写错会导致启动进 emergency mode\n",[15,16238,16239,16242],{},[19,16240,16241],{},"\u002Fetc\u002Ffstab"," 六个字段：",[211,16244,16246],{"className":903,"code":16245,"language":905,"meta":216,"style":216},"UUID=2f8b…  \u002Fdata  ext4  defaults,noatime,nofail  0  2\n设备或UUID   挂载点 类型   挂载选项                dump  fsck顺序\n",[19,16247,16248,16253],{"__ignoreMap":216},[220,16249,16250],{"class":222,"line":223},[220,16251,16252],{},"UUID=2f8b…  \u002Fdata  ext4  defaults,noatime,nofail  0  2\n",[220,16254,16255],{"class":222,"line":276},[220,16256,16257],{},"设备或UUID   挂载点 类型   挂载选项                dump  fsck顺序\n",[51,16259,16260,16271],{},[54,16261,16262],{},[57,16263,16264,16266,16269],{},[60,16265,11739],{},[60,16267,16268],{},"常用值",[60,16270,452],{},[70,16272,16273,16294,16313,16340,16351,16363],{},[57,16274,16275,16278,16288],{},[75,16276,16277],{},"1 设备",[75,16279,16280,34,16283,34,16286],{},[19,16281,16282],{},"UUID=",[19,16284,16285],{},"LABEL=",[19,16287,15969],{},[75,16289,16290,16293],{},[27,16291,16292],{},"一律用 UUID","：设备名会因内核探测顺序变化",[57,16295,16296,16299,16310],{},[75,16297,16298],{},"2 挂载点",[75,16300,16301,34,16304,34,16307],{},[19,16302,16303],{},"none",[19,16305,16306],{},"swap",[19,16308,16309],{},"\u002Fdata",[75,16311,16312],{},"目录必须已存在",[57,16314,16315,16318,16334],{},[75,16316,16317],{},"3 类型",[75,16319,16320,34,16323,34,16326,34,16329,34,16332],{},[19,16321,16322],{},"ext4",[19,16324,16325],{},"xfs",[19,16327,16328],{},"vfat",[19,16330,16331],{},"nfs",[19,16333,16306],{},[75,16335,16336,16339],{},[19,16337,16338],{},"auto"," 让 mount 猜",[57,16341,16342,16345,16348],{},[75,16343,16344],{},"4 选项",[75,16346,16347],{},"见下",[75,16349,16350],{},"逗号分隔，无空格",[57,16352,16353,16356,16360],{},[75,16354,16355],{},"5 dump",[75,16357,16358],{},[19,16359,8018],{},[75,16361,16362],{},"备份工具用，现代基本都填 0",[57,16364,16365,16368,16381],{},[75,16366,16367],{},"6 fsck 顺序",[75,16369,16370,16372,16373,16375,16376,16378,16379],{},[19,16371,662],{}," 填 ",[19,16374,1072],{},"，其他本地盘 ",[19,16377,8025],{},"，网络\u002Fswap 填 ",[19,16380,8018],{},[75,16382,16383],{},"别给 NFS 填非 0，会卡启动",[15,16385,16386,16387,689,16390,16393,16394,16397,16398,34,16401,662,16403,34,16406,16408,16409,16412,16413,16416,16417,16420,16421,16424],{},"关键挂载选项：",[19,16388,16389],{},"nofail",[27,16391,16392],{},"设备不在也继续启动","，云主机加数据盘必写，否则盘被摘机就起不来）、",[19,16395,16396],{},"noatime","（不记录访问时间，显著降低读 IO）、",[19,16399,16400],{},"nodiratime",[19,16402,5259],{},[19,16404,16405],{},"rw",[19,16407,5262],{},"（上传目录必备安全项）、",[19,16410,16411],{},"nosuid","（同上）、",[19,16414,16415],{},"compress","（zfs\u002Fbtrfs）、",[19,16418,16419],{},"_netdev","（NFS：等网络就绪再挂）、",[19,16422,16423],{},"x-systemd.automount","（按需挂载）。",[46,16426,16428],{"id":16427},"_7-no-space-left完整排查","7. 「No space left」完整排查",[51,16430,16431,16442],{},[54,16432,16433],{},[57,16434,16435,16438,16440],{},[60,16436,16437],{},"步骤",[60,16439,5168],{},[60,16441,5171],{},[70,16443,16444,16458,16470,16491,16502,16517],{},[57,16445,16446,16448,16452],{},[75,16447,1072],{},[75,16449,16450],{},[19,16451,15454],{},[75,16453,16454,16455,16457],{},"块用满 → 进 2；",[19,16456,15479],{}," 未满 → 进 3",[57,16459,16460,16462,16467],{},[75,16461,8025],{},[75,16463,16464],{},[19,16465,16466],{},"du -xh \u002F --max-depth=2 | sort -h | tail",[75,16468,16469],{},"找目录大头；日志\u002Fdocker\u002F临时文件三大来源",[57,16471,16472,16475,16480],{},[75,16473,16474],{},"2b",[75,16476,16477],{},[19,16478,16479],{},"sudo lsof +L1 | sort -k7 -n | tail",[75,16481,16482,16487,16488,16490],{},[27,16483,16484,16486],{},[19,16485,15395],{}," 找不到大头时必查","：已删除但句柄未关的文件，",[19,16489,15414],{}," 算它占用；重启对应进程即释放",[57,16492,16493,16495,16499],{},[75,16494,1076],{},[75,16496,16497],{},[19,16498,15784],{},[75,16500,16501],{},"inode 满 → 海量小文件，见第 4 节",[57,16503,16504,16506,16511],{},[75,16505,13318],{},[75,16507,16508],{},[19,16509,16510],{},"findmnt -T \u002F出错的路径",[75,16512,16513,16514,16516],{},"落在 ",[19,16515,5259],{}," 的挂载点上 → 是只读问题不是空间问题",[57,16518,16519,16521,16526],{},[75,16520,13336],{},[75,16522,16523],{},[19,16524,16525],{},"dmesg | tail -30",[75,16527,16528,1073,16531,16534],{},[19,16529,16530],{},"EXT4-fs error",[19,16532,16533],{},"Remounting filesystem read-only"," → 磁盘故障，见 fsck",[15,16536,16537,16540,16541,34,16544,16547,16548,16551,16552,16555],{},[19,16538,16539],{},"\u002Fvar\u002Flog"," 撑爆的标准处理：",[19,16542,16543],{},"journalctl --vacuum-size=200M",[19,16545,16546],{},"logrotate -f \u002Fetc\u002Flogrotate.conf"," 强制跑一次、清 docker 日志 ",[19,16549,16550],{},"truncate -s 0 \u002Fvar\u002Flib\u002Fdocker\u002Fcontainers\u002F*\u002F*-json.log","（治本是给 docker 配 ",[19,16553,16554],{},"log-opts"," 上限）。",[46,16557,16559],{"id":16558},"_8-fsck-为什么不能在挂载时跑","8. fsck —— 为什么不能在挂载时跑",[15,16561,16562,16565],{},[19,16563,16564],{},"fsck"," 直接改文件系统元数据。分区处于读写挂载时，内核正在并发写这些结构，检查器看到的是「正在变」的磁盘，会把正常状态判为不一致并「修复」成损坏——结果是文件系统当场报废。因此：",[51,16567,16568,16578],{},[54,16569,16570],{},[57,16571,16572,16575],{},[60,16573,16574],{},"情况",[60,16576,16577],{},"正确做法",[70,16579,16580,16603,16613,16624,16642],{},[57,16581,16582,16585],{},[75,16583,16584],{},"根分区\u002F已挂载分区要检查",[75,16586,16587,16590,16591,16594,16595,16598,16599,16602],{},[19,16588,16589],{},"sudo touch \u002Fforcefsck"," 后重启（老式）；或 ",[19,16592,16593],{},"shutdown -rF","；或改 fstab 第 6 字段并用 ",[19,16596,16597],{},"tune2fs -c 30 \u002Fdev\u002Fsda1"," 设次数触发；最稳是",[27,16600,16601],{},"救援模式\u002F单用户模式","或 Live CD",[57,16604,16605,16608],{},[75,16606,16607],{},"数据盘可下线",[75,16609,16610],{},[19,16611,16612],{},"sudo umount \u002Fdata && sudo fsck -fy \u002Fdev\u002Fvdb1 && sudo mount \u002Fdata",[57,16614,16615,16618],{},[75,16616,16617],{},"只想「检查不修」",[75,16619,16620,16623],{},[19,16621,16622],{},"sudo fsck -n \u002Fdev\u002Fsda1","（对挂载分区也不写，但结果不可靠，仅参考）",[57,16625,16626,16629],{},[75,16627,16628],{},"XFS",[75,16630,16631,16637,16638,16641],{},[27,16632,16633,16636],{},[19,16634,16635],{},"xfs_repair"," 同样必须卸载","；XFS 只支持离线修复（",[19,16639,16640],{},"xfs_check"," 已废弃）",[57,16643,16644,16647],{},[75,16645,16646],{},"看自动检查策略",[75,16648,16649],{},[19,16650,16651],{},"tune2fs -l \u002Fdev\u002Fsda1 | grep -E 'Maximum mount count|Check interval|Last check'",[15,16653,16654,16655,16658,16659,16661,16662,16665,16666,16668,16669,16672,16673,16676],{},"ext4 的 ",[19,16656,16657],{},"-y","（自动答 yes）适合无人值守，但会「修」掉本该保留的数据；重要盘用不带 ",[19,16660,16657],{}," 的交互模式或先 ",[19,16663,16664],{},"e2image"," 做元数据镜像。",[19,16667,16564],{}," 跑完找回的碎片在 ",[19,16670,16671],{},"\u002Flost+found","，文件名是 inode 号，可用 ",[19,16674,16675],{},"find \u002F -inum 123456"," 反查。",[46,16678,16680],{"id":16679},"_9-inotifywait-文件监控与那个著名的上限","9. inotifywait —— 文件监控与那个著名的上限",[211,16682,16684],{"className":213,"code":16683,"language":215,"meta":216,"style":216},"inotifywait -m -r -e modify,create,delete,move --format '%w %f %e' \u002Fsrv\u002Fapp\u002Fuploads\n# -m 持续监控、-r 递归、-e 指定事件、--format 输出好解析\ninotifywatch -t 60 \u002Fdata       # 统计一段时间内的事件计数\n",[19,16685,16686,16709,16714],{"__ignoreMap":216},[220,16687,16688,16691,16693,16695,16697,16700,16703,16706],{"class":222,"line":223},[220,16689,16690],{"class":226},"inotifywait",[220,16692,5078],{"class":266},[220,16694,7611],{"class":266},[220,16696,12269],{"class":266},[220,16698,16699],{"class":229}," modify,create,delete,move",[220,16701,16702],{"class":266}," --format",[220,16704,16705],{"class":229}," '%w %f %e'",[220,16707,16708],{"class":229}," \u002Fsrv\u002Fapp\u002Fuploads\n",[220,16710,16711],{"class":222,"line":276},[220,16712,16713],{"class":233},"# -m 持续监控、-r 递归、-e 指定事件、--format 输出好解析\n",[220,16715,16716,16719,16721,16724,16726],{"class":222,"line":594},[220,16717,16718],{"class":226},"inotifywatch",[220,16720,8459],{"class":266},[220,16722,16723],{"class":266}," 60",[220,16725,16118],{"class":229},[220,16727,16728],{"class":233},"       # 统计一段时间内的事件计数\n",[15,16730,16731,16732,16735,16736,16739,16740,16743,16744,1848],{},"来自 ",[19,16733,16734],{},"inotify-tools"," 包（Debian ",[19,16737,16738],{},"apt install inotify-tools","，RHEL 在 EPEL）。监控失效\u002F程序报 ",[19,16741,16742],{},"inotify_add_watch(...) no space"," 时，",[27,16745,16746],{},"不是磁盘满，是 watch 数超限",[211,16748,16750],{"className":213,"code":16749,"language":215,"meta":216,"style":216},"cat \u002Fproc\u002Fsys\u002Ffs\u002Finotify\u002Fmax_user_watches      # 默认常为 8192\necho fs.inotify.max_user_watches=524288 | sudo tee -a \u002Fetc\u002Fsysctl.d\u002F99-inotify.conf\nsudo sysctl --system\n",[19,16751,16752,16762,16783],{"__ignoreMap":216},[220,16753,16754,16756,16759],{"class":222,"line":223},[220,16755,2304],{"class":226},[220,16757,16758],{"class":229}," \u002Fproc\u002Fsys\u002Ffs\u002Finotify\u002Fmax_user_watches",[220,16760,16761],{"class":233},"      # 默认常为 8192\n",[220,16763,16764,16766,16769,16772,16774,16776,16778,16780],{"class":222,"line":276},[220,16765,468],{"class":266},[220,16767,16768],{"class":229}," fs.inotify.max_user_watches=",[220,16770,16771],{"class":266},"524288",[220,16773,2626],{"class":2439},[220,16775,8230],{"class":226},[220,16777,8200],{"class":229},[220,16779,551],{"class":266},[220,16781,16782],{"class":229}," \u002Fetc\u002Fsysctl.d\u002F99-inotify.conf\n",[220,16784,16785,16787,16790],{"class":222,"line":594},[220,16786,120],{"class":226},[220,16788,16789],{"class":229}," sysctl",[220,16791,16792],{"class":266}," --system\n",[15,16794,16795,16798,16799,16802,16803,16806],{},[19,16796,16797],{},"max_user_instances","（每个用户的 inotify 实例数）与 ",[19,16800,16801],{},"max_queued_events","（事件队列长度）也常在 CI\u002FIDE 环境需要调高。注意：",[27,16804,16805],{},"NFS\u002FFUSE 挂载点上的 inotify 不工作","（远程改动不会通知本机），跨机同步要用轮询或 rsync。",[46,16808,808],{"id":16809},"踩坑提醒-13",[810,16811,16812,16823,16828,16838,16844],{},[813,16813,16814,16815,16818,16819,16822],{},"删了大文件空间没回来：先想「有没有进程还开着它」（",[19,16816,16817],{},"lsof +L1","），重启进程或 ",[19,16820,16821],{},": > 文件"," 截断，而不是再删一遍。",[813,16824,16825,16827],{},[19,16826,33],{}," 删目录树时正在被进程写入的日志会导致磁盘占用反而上升（inode 与块都被打开句柄占住），标准清理顺序是先停服务再清。",[813,16829,16830,16833,16834,16837],{},[19,16831,16832],{},"mount"," 到非空目录会「隐藏」原内容，看起来数据丢了其实还在——",[19,16835,16836],{},"umount"," 就回来。",[813,16839,16840,16841,16843],{},"云主机加盘后忘了 ",[19,16842,16389],{},"，运维把盘摘掉机器直接起不来进 emergency mode。这是最常见的线上事故之一。",[813,16845,16846,16848,16849,16852,16853,16855,16856,616],{},[19,16847,40],{}," 用于写盘时（",[19,16850,16851],{},"dd if=iso of=\u002Fdev\u002Fsdb","）输错设备名就是毁盘，永远先用 ",[19,16854,15960],{}," 确认，再三检查 ",[19,16857,16858],{},"of=",[10,16860,16862],{"id":16861},"十五软件包管理aptdnfapk","十五、软件包管理：apt、dnf、apk",[15,16864,16865],{},"包管理器的差异是发行版之间最大的日常摩擦：同一种需求（查文件归属、装某个包、看谁依赖它）在 Debian、RHEL、Alpine 上命令完全不同。这一章按体系列命令，然后把「三个反向查询」单独讲透——它们才是排障时真正要找的东西。",[861,16867],{"height":3858,"src":16868,"title":16869},"\u002Ftutorials\u002Flinux\u002Fdemos\u002F15-packages.html","示例 15：apt \u002F dnf \u002F rpm \u002F dpkg 的查询与安装",[46,16871,16873],{"id":16872},"_1-apt-debian-ubuntu-系","1. apt —— Debian \u002F Ubuntu 系",[211,16875,16877],{"className":213,"code":16876,"language":215,"meta":216,"style":216},"sudo apt update                    # 刷新索引——只刷新列表，不升级！装任何东西之前\nsudo apt install -y nginx          # 安装；-y 给脚本用。apt 交互式优先；脚本里用 apt-get（apt 会警告接口不稳定）\nsudo apt upgrade                   # 升级全部（保守，不删包）\nsudo apt full-upgrade              # 升级并允许删包（旧名 dist-upgrade），大版本迁移用\nsudo apt remove apache2            # 删包但留配置；purge 连配置一起删，重装时配置会被读回来\nsudo apt autoremove --purge        # 清理不再需要的依赖——会带走你手动装的孤儿包，看清楚列表再 yes\napt search nginx                   # 搜包名与描述（子串不是正则）\napt show nginx                     # 版本、依赖、大小、源——装之前先看\napt list --installed               # 已装列表；可 apt list --installed 'libssl*'\napt policy nginx                   # 候选版本与来源仓库——排「为什么装到旧版本」的唯一正解（含 Pin 优先级）\napt-cache madison nginx            # 各仓库里可用的版本清单\nsudo apt-mark hold linux-image-generic   # 钉住版本不升级（unhold 解开）——生产锁内核的标准做法\nsudo apt -s upgrade                # -s 模拟：看会动什么，不实际改动——生产升级前先跑这个\nsudo apt -f install                # 修依赖：dpkg -i 装失败后的正确一步\n",[19,16878,16879,16892,16908,16920,16932,16947,16962,16974,16985,16998,17010,17023,17039,17052],{"__ignoreMap":216},[220,16880,16881,16883,16886,16889],{"class":222,"line":223},[220,16882,120],{"class":226},[220,16884,16885],{"class":229}," apt",[220,16887,16888],{"class":229}," update",[220,16890,16891],{"class":233},"                    # 刷新索引——只刷新列表，不升级！装任何东西之前\n",[220,16893,16894,16896,16898,16900,16903,16905],{"class":222,"line":276},[220,16895,120],{"class":226},[220,16897,16885],{"class":229},[220,16899,8193],{"class":229},[220,16901,16902],{"class":266}," -y",[220,16904,600],{"class":229},[220,16906,16907],{"class":233},"          # 安装；-y 给脚本用。apt 交互式优先；脚本里用 apt-get（apt 会警告接口不稳定）\n",[220,16909,16910,16912,16914,16917],{"class":222,"line":594},[220,16911,120],{"class":226},[220,16913,16885],{"class":229},[220,16915,16916],{"class":229}," upgrade",[220,16918,16919],{"class":233},"                   # 升级全部（保守，不删包）\n",[220,16921,16922,16924,16926,16929],{"class":222,"line":925},[220,16923,120],{"class":226},[220,16925,16885],{"class":229},[220,16927,16928],{"class":229}," full-upgrade",[220,16930,16931],{"class":233},"              # 升级并允许删包（旧名 dist-upgrade），大版本迁移用\n",[220,16933,16934,16936,16938,16941,16944],{"class":222,"line":931},[220,16935,120],{"class":226},[220,16937,16885],{"class":229},[220,16939,16940],{"class":229}," remove",[220,16942,16943],{"class":229}," apache2",[220,16945,16946],{"class":233},"            # 删包但留配置；purge 连配置一起删，重装时配置会被读回来\n",[220,16948,16949,16951,16953,16956,16959],{"class":222,"line":3528},[220,16950,120],{"class":226},[220,16952,16885],{"class":229},[220,16954,16955],{"class":229}," autoremove",[220,16957,16958],{"class":266}," --purge",[220,16960,16961],{"class":233},"        # 清理不再需要的依赖——会带走你手动装的孤儿包，看清楚列表再 yes\n",[220,16963,16964,16966,16969,16971],{"class":222,"line":3537},[220,16965,653],{"class":226},[220,16967,16968],{"class":229}," search",[220,16970,600],{"class":229},[220,16972,16973],{"class":233},"                   # 搜包名与描述（子串不是正则）\n",[220,16975,16976,16978,16980,16982],{"class":222,"line":3546},[220,16977,653],{"class":226},[220,16979,11571],{"class":229},[220,16981,600],{"class":229},[220,16983,16984],{"class":233},"                     # 版本、依赖、大小、源——装之前先看\n",[220,16986,16987,16989,16992,16995],{"class":222,"line":3555},[220,16988,653],{"class":226},[220,16990,16991],{"class":229}," list",[220,16993,16994],{"class":266}," --installed",[220,16996,16997],{"class":233},"               # 已装列表；可 apt list --installed 'libssl*'\n",[220,16999,17000,17002,17005,17007],{"class":222,"line":3564},[220,17001,653],{"class":226},[220,17003,17004],{"class":229}," policy",[220,17006,600],{"class":229},[220,17008,17009],{"class":233},"                   # 候选版本与来源仓库——排「为什么装到旧版本」的唯一正解（含 Pin 优先级）\n",[220,17011,17012,17015,17018,17020],{"class":222,"line":3572},[220,17013,17014],{"class":226},"apt-cache",[220,17016,17017],{"class":229}," madison",[220,17019,600],{"class":229},[220,17021,17022],{"class":233},"            # 各仓库里可用的版本清单\n",[220,17024,17025,17027,17030,17033,17036],{"class":222,"line":3580},[220,17026,120],{"class":226},[220,17028,17029],{"class":229}," apt-mark",[220,17031,17032],{"class":229}," hold",[220,17034,17035],{"class":229}," linux-image-generic",[220,17037,17038],{"class":233},"   # 钉住版本不升级（unhold 解开）——生产锁内核的标准做法\n",[220,17040,17041,17043,17045,17047,17049],{"class":222,"line":3588},[220,17042,120],{"class":226},[220,17044,16885],{"class":229},[220,17046,1978],{"class":266},[220,17048,16916],{"class":229},[220,17050,17051],{"class":233},"                # -s 模拟：看会动什么，不实际改动——生产升级前先跑这个\n",[220,17053,17054,17056,17058,17060,17062],{"class":222,"line":7714},[220,17055,120],{"class":226},[220,17057,16885],{"class":229},[220,17059,2573],{"class":266},[220,17061,8193],{"class":229},[220,17063,17064],{"class":233},"                # 修依赖：dpkg -i 装失败后的正确一步\n",[46,17066,17068],{"id":17067},"_2-dpkg-本地包与文件归属","2. dpkg —— 本地包与文件归属",[211,17070,17072],{"className":213,"code":17071,"language":215,"meta":216,"style":216},"sudo dpkg -i .\u002Fpkg.deb             # 装本地 .deb——不解析依赖！缺依赖时 sudo apt -f install 补\ndpkg -l | awk '$2 ~ \u002F^i\u002F {print $2}'    # 只列在用的；rc 状态 = 已删但有残留配置\ndpkg -L nginx                      # 这个包装了哪些文件——找配置\u002F二进制在哪\ndpkg -S \u002Fetc\u002Fnginx\u002Fnginx.conf      # 这个文件属于哪个包\nsudo dpkg-reconfigure tzdata       # 重跑配置问答（locales、keyboard-configuration 同理）\napt-file search bin\u002Fconvert        # 查「未装的包」里谁有这个文件（apt install apt-file + apt-file update）\n",[19,17073,17074,17089,17105,17116,17127,17140],{"__ignoreMap":216},[220,17075,17076,17078,17081,17083,17086],{"class":222,"line":223},[220,17077,120],{"class":226},[220,17079,17080],{"class":229}," dpkg",[220,17082,1593],{"class":266},[220,17084,17085],{"class":229}," .\u002Fpkg.deb",[220,17087,17088],{"class":233},"             # 装本地 .deb——不解析依赖！缺依赖时 sudo apt -f install 补\n",[220,17090,17091,17093,17095,17097,17099,17102],{"class":222,"line":276},[220,17092,656],{"class":226},[220,17094,3193],{"class":266},[220,17096,2626],{"class":2439},[220,17098,7884],{"class":226},[220,17100,17101],{"class":229}," '$2 ~ \u002F^i\u002F {print $2}'",[220,17103,17104],{"class":233},"    # 只列在用的；rc 状态 = 已删但有残留配置\n",[220,17106,17107,17109,17111,17113],{"class":222,"line":594},[220,17108,656],{"class":226},[220,17110,1492],{"class":266},[220,17112,600],{"class":229},[220,17114,17115],{"class":233},"                      # 这个包装了哪些文件——找配置\u002F二进制在哪\n",[220,17117,17118,17120,17122,17124],{"class":222,"line":925},[220,17119,656],{"class":226},[220,17121,2894],{"class":266},[220,17123,2344],{"class":229},[220,17125,17126],{"class":233},"      # 这个文件属于哪个包\n",[220,17128,17129,17131,17134,17137],{"class":222,"line":931},[220,17130,120],{"class":226},[220,17132,17133],{"class":229}," dpkg-reconfigure",[220,17135,17136],{"class":229}," tzdata",[220,17138,17139],{"class":233},"       # 重跑配置问答（locales、keyboard-configuration 同理）\n",[220,17141,17142,17145,17147,17150],{"class":222,"line":3528},[220,17143,17144],{"class":226},"apt-file",[220,17146,16968],{"class":229},[220,17148,17149],{"class":229}," bin\u002Fconvert",[220,17151,17152],{"class":233},"        # 查「未装的包」里谁有这个文件（apt install apt-file + apt-file update）\n",[46,17154,17156],{"id":17155},"_3-dnf-与-rpm-rhel-centos-rocky-fedora-系","3. dnf 与 rpm —— RHEL \u002F CentOS \u002F Rocky \u002F Fedora 系",[15,17158,17159,17161,17162,17164,17165,17167,17168,17170],{},[19,17160,661],{}," 是 ",[19,17163,665],{}," 的继任者，命令基本兼容（",[19,17166,665],{}," 现在通常是 ",[19,17169,661],{}," 的软链接）：",[211,17172,17174],{"className":213,"code":17173,"language":215,"meta":216,"style":216},"sudo dnf -y install nginx          # 安装；dnf remove pkg 卸载（dnf autoremove 清依赖）\ndnf update --security              # 只打安全补丁；update-minimal 同义\ndnf search log \u002F dnf info nginx    # 搜 \u002F 详情\ndnf list available 'python3*'      # 列可用包\ndnf provides '*\u002Fnginx.conf'        # 哪个包提供这个文件路径——排缺库必备，比 apt-file 方便\ndnf repoquery --installed --whatrequires glibc   # 谁依赖它（删之前看）\ndnf repoquery -l pkg               # 包装了哪些文件（等价 rpm -ql）\nsudo dnf history                   # 事务历史；dnf history info 12 看详情\nsudo dnf history undo 12           # 回滚——yum 做不到，dnf 的最大优势\ndnf module list php                # RHEL8+ 模块流：同一包多版本；dnf module enable php:8.2\nsudo dnf install dnf-plugins-core  # 拿到 dnf config-manager（--add-repo URL）、builddep\nrpm -q nginx                       # 是否已装；rpm -qa 'nginx*' 模糊查\nrpm -ql nginx                      # 包内文件列表；--scripts 连安装脚本一起看\nrpm -qf \u002Fetc\u002Fmy.cnf                # 文件属于哪个包（对应 dpkg -S）\nrpm -qpi pkg.rpm \u002F rpm -qpl pkg.rpm  # 查未安装的 rpm 的信息\u002F文件清单\nsudo rpm -ivh pkg.rpm              # 安装（i 安装 v 显示 h 进度条）；依赖不满足会拒绝，改用 dnf install .\u002Fpkg.rpm\nrpm -V openssh-server              # 校验文件是否被改过：S5（大小\u002F摘要不符）、M（模式变）、?（缺失）\nsudo rpm -e pkg                    # 卸载并做依赖检查；--nodeps 强删会留下坏系统，别用\n",[19,17175,17176,17192,17204,17225,17240,17253,17271,17285,17297,17314,17329,17343,17354,17366,17379,17402,17416,17428],{"__ignoreMap":216},[220,17177,17178,17180,17183,17185,17187,17189],{"class":222,"line":223},[220,17179,120],{"class":226},[220,17181,17182],{"class":229}," dnf",[220,17184,16902],{"class":266},[220,17186,8193],{"class":229},[220,17188,600],{"class":229},[220,17190,17191],{"class":233},"          # 安装；dnf remove pkg 卸载（dnf autoremove 清依赖）\n",[220,17193,17194,17196,17198,17201],{"class":222,"line":276},[220,17195,661],{"class":226},[220,17197,16888],{"class":229},[220,17199,17200],{"class":266}," --security",[220,17202,17203],{"class":233},"              # 只打安全补丁；update-minimal 同义\n",[220,17205,17206,17208,17210,17213,17215,17217,17220,17222],{"class":222,"line":594},[220,17207,661],{"class":226},[220,17209,16968],{"class":229},[220,17211,17212],{"class":229}," log",[220,17214,3621],{"class":229},[220,17216,17182],{"class":229},[220,17218,17219],{"class":229}," info",[220,17221,600],{"class":229},[220,17223,17224],{"class":233},"    # 搜 \u002F 详情\n",[220,17226,17227,17229,17231,17234,17237],{"class":222,"line":925},[220,17228,661],{"class":226},[220,17230,16991],{"class":229},[220,17232,17233],{"class":229}," available",[220,17235,17236],{"class":229}," 'python3*'",[220,17238,17239],{"class":233},"      # 列可用包\n",[220,17241,17242,17244,17247,17250],{"class":222,"line":931},[220,17243,661],{"class":226},[220,17245,17246],{"class":229}," provides",[220,17248,17249],{"class":229}," '*\u002Fnginx.conf'",[220,17251,17252],{"class":233},"        # 哪个包提供这个文件路径——排缺库必备，比 apt-file 方便\n",[220,17254,17255,17257,17260,17262,17265,17268],{"class":222,"line":3528},[220,17256,661],{"class":226},[220,17258,17259],{"class":229}," repoquery",[220,17261,16994],{"class":266},[220,17263,17264],{"class":266}," --whatrequires",[220,17266,17267],{"class":229}," glibc",[220,17269,17270],{"class":233},"   # 谁依赖它（删之前看）\n",[220,17272,17273,17275,17277,17279,17282],{"class":222,"line":3537},[220,17274,661],{"class":226},[220,17276,17259],{"class":229},[220,17278,3193],{"class":266},[220,17280,17281],{"class":229}," pkg",[220,17283,17284],{"class":233},"               # 包装了哪些文件（等价 rpm -ql）\n",[220,17286,17287,17289,17291,17294],{"class":222,"line":3546},[220,17288,120],{"class":226},[220,17290,17182],{"class":229},[220,17292,17293],{"class":229}," history",[220,17295,17296],{"class":233},"                   # 事务历史；dnf history info 12 看详情\n",[220,17298,17299,17301,17303,17305,17308,17311],{"class":222,"line":3555},[220,17300,120],{"class":226},[220,17302,17182],{"class":229},[220,17304,17293],{"class":229},[220,17306,17307],{"class":229}," undo",[220,17309,17310],{"class":266}," 12",[220,17312,17313],{"class":233},"           # 回滚——yum 做不到，dnf 的最大优势\n",[220,17315,17316,17318,17321,17323,17326],{"class":222,"line":3564},[220,17317,661],{"class":226},[220,17319,17320],{"class":229}," module",[220,17322,16991],{"class":229},[220,17324,17325],{"class":229}," php",[220,17327,17328],{"class":233},"                # RHEL8+ 模块流：同一包多版本；dnf module enable php:8.2\n",[220,17330,17331,17333,17335,17337,17340],{"class":222,"line":3572},[220,17332,120],{"class":226},[220,17334,17182],{"class":229},[220,17336,8193],{"class":229},[220,17338,17339],{"class":229}," dnf-plugins-core",[220,17341,17342],{"class":233},"  # 拿到 dnf config-manager（--add-repo URL）、builddep\n",[220,17344,17345,17347,17349,17351],{"class":222,"line":3580},[220,17346,668],{"class":226},[220,17348,6979],{"class":266},[220,17350,600],{"class":229},[220,17352,17353],{"class":233},"                       # 是否已装；rpm -qa 'nginx*' 模糊查\n",[220,17355,17356,17358,17361,17363],{"class":222,"line":3588},[220,17357,668],{"class":226},[220,17359,17360],{"class":266}," -ql",[220,17362,600],{"class":229},[220,17364,17365],{"class":233},"                      # 包内文件列表；--scripts 连安装脚本一起看\n",[220,17367,17368,17370,17373,17376],{"class":222,"line":7714},[220,17369,668],{"class":226},[220,17371,17372],{"class":266}," -qf",[220,17374,17375],{"class":229}," \u002Fetc\u002Fmy.cnf",[220,17377,17378],{"class":233},"                # 文件属于哪个包（对应 dpkg -S）\n",[220,17380,17381,17383,17386,17389,17391,17394,17397,17399],{"class":222,"line":7726},[220,17382,668],{"class":226},[220,17384,17385],{"class":266}," -qpi",[220,17387,17388],{"class":229}," pkg.rpm",[220,17390,3621],{"class":229},[220,17392,17393],{"class":229}," rpm",[220,17395,17396],{"class":266}," -qpl",[220,17398,17388],{"class":229},[220,17400,17401],{"class":233},"  # 查未安装的 rpm 的信息\u002F文件清单\n",[220,17403,17404,17406,17408,17411,17413],{"class":222,"line":11527},[220,17405,120],{"class":226},[220,17407,17393],{"class":229},[220,17409,17410],{"class":266}," -ivh",[220,17412,17388],{"class":229},[220,17414,17415],{"class":233},"              # 安装（i 安装 v 显示 h 进度条）；依赖不满足会拒绝，改用 dnf install .\u002Fpkg.rpm\n",[220,17417,17418,17420,17422,17425],{"class":222,"line":11540},[220,17419,668],{"class":226},[220,17421,7621],{"class":266},[220,17423,17424],{"class":229}," openssh-server",[220,17426,17427],{"class":233},"              # 校验文件是否被改过：S5（大小\u002F摘要不符）、M（模式变）、?（缺失）\n",[220,17429,17430,17432,17434,17436,17438],{"class":222,"line":11553},[220,17431,120],{"class":226},[220,17433,17393],{"class":229},[220,17435,12269],{"class":266},[220,17437,17281],{"class":229},[220,17439,17440],{"class":233},"                    # 卸载并做依赖检查；--nodeps 强删会留下坏系统，别用\n",[46,17442,17444],{"id":17443},"_4-apk-alpine容器里最常见","4. apk —— Alpine（容器里最常见）",[211,17446,17448],{"className":213,"code":17447,"language":215,"meta":216,"style":216},"apk add --no-cache curl bash       # 安装；--no-cache 免手动 update，镜像构建里标配\napk del .build-deps                # 卸载；多阶段构建里的假想依赖组\napk info -e curl                   # 存在则返回 0，脚本判断用\napk search -x 'curl*'              # 搜包；-x 精确名\napk policy busybox                 # 看版本与仓库来源\napk info -w \u002Fbin\u002Fsh                # 文件属于哪个包 → busybox\napk info -L pkg \u002F apk info -d pkg  # 包内文件 \u002F 包的依赖\napk audit                          # 检查被改动的受管文件——容器镜像审计\napk fix                            # 修复损坏安装\n",[19,17449,17450,17467,17480,17493,17507,17519,17533,17557,17567],{"__ignoreMap":216},[220,17451,17452,17454,17456,17459,17461,17464],{"class":222,"line":223},[220,17453,673],{"class":226},[220,17455,13813],{"class":229},[220,17457,17458],{"class":266}," --no-cache",[220,17460,588],{"class":229},[220,17462,17463],{"class":229}," bash",[220,17465,17466],{"class":233},"       # 安装；--no-cache 免手动 update，镜像构建里标配\n",[220,17468,17469,17471,17474,17477],{"class":222,"line":276},[220,17470,673],{"class":226},[220,17472,17473],{"class":229}," del",[220,17475,17476],{"class":229}," .build-deps",[220,17478,17479],{"class":233},"                # 卸载；多阶段构建里的假想依赖组\n",[220,17481,17482,17484,17486,17488,17490],{"class":222,"line":594},[220,17483,673],{"class":226},[220,17485,17219],{"class":229},[220,17487,12269],{"class":266},[220,17489,588],{"class":229},[220,17491,17492],{"class":233},"                   # 存在则返回 0，脚本判断用\n",[220,17494,17495,17497,17499,17501,17504],{"class":222,"line":925},[220,17496,673],{"class":226},[220,17498,16968],{"class":229},[220,17500,5108],{"class":266},[220,17502,17503],{"class":229}," 'curl*'",[220,17505,17506],{"class":233},"              # 搜包；-x 精确名\n",[220,17508,17509,17511,17513,17516],{"class":222,"line":931},[220,17510,673],{"class":226},[220,17512,17004],{"class":229},[220,17514,17515],{"class":229}," busybox",[220,17517,17518],{"class":233},"                 # 看版本与仓库来源\n",[220,17520,17521,17523,17525,17527,17530],{"class":222,"line":3528},[220,17522,673],{"class":226},[220,17524,17219],{"class":229},[220,17526,6691],{"class":266},[220,17528,17529],{"class":229}," \u002Fbin\u002Fsh",[220,17531,17532],{"class":233},"                # 文件属于哪个包 → busybox\n",[220,17534,17535,17537,17539,17541,17543,17545,17548,17550,17552,17554],{"class":222,"line":3537},[220,17536,673],{"class":226},[220,17538,17219],{"class":229},[220,17540,1492],{"class":266},[220,17542,17281],{"class":229},[220,17544,3621],{"class":229},[220,17546,17547],{"class":229}," apk",[220,17549,17219],{"class":229},[220,17551,1455],{"class":266},[220,17553,17281],{"class":229},[220,17555,17556],{"class":233},"  # 包内文件 \u002F 包的依赖\n",[220,17558,17559,17561,17564],{"class":222,"line":3546},[220,17560,673],{"class":226},[220,17562,17563],{"class":229}," audit",[220,17565,17566],{"class":233},"                          # 检查被改动的受管文件——容器镜像审计\n",[220,17568,17569,17571,17574],{"class":222,"line":3555},[220,17570,673],{"class":226},[220,17572,17573],{"class":229}," fix",[220,17575,17576],{"class":233},"                            # 修复损坏安装\n",[46,17578,17580],{"id":17579},"_5-三个反向查询跨体系对照","5. 三个反向查询：跨体系对照",[51,17582,17583,17597],{},[54,17584,17585],{},[57,17586,17587,17590,17592,17595],{},[60,17588,17589],{},"我要问的问题",[60,17591,635],{},[60,17593,17594],{},"RHEL 系",[60,17596,641],{},[70,17598,17599,17619,17639,17670,17691,17714],{},[57,17600,17601,17604,17609,17614],{},[75,17602,17603],{},"这个文件是谁装进来的",[75,17605,17606],{},[19,17607,17608],{},"dpkg -S \u002Fusr\u002Fbin\u002Fconvert",[75,17610,17611],{},[19,17612,17613],{},"rpm -qf \u002Fusr\u002Fbin\u002Fconvert",[75,17615,17616],{},[19,17617,17618],{},"apk info -w \u002Fusr\u002Fbin\u002Fconvert",[57,17620,17621,17624,17629,17634],{},[75,17622,17623],{},"这个包装了哪些文件",[75,17625,17626],{},[19,17627,17628],{},"dpkg -L imagemagick",[75,17630,17631],{},[19,17632,17633],{},"rpm -ql imagemagick",[75,17635,17636],{},[19,17637,17638],{},"apk info -L imagemagick",[57,17640,17641,17646,17658,17664],{},[75,17642,17643],{},[27,17644,17645],{},"某个命令在哪个包里（还没装）",[75,17647,17648,187,17651,17654,17655,1062],{},[19,17649,17650],{},"apt-file search bin\u002Fconvert",[19,17652,17653],{},"apt search","（或直接装 ",[19,17656,17657],{},"debscan",[75,17659,17660,17663],{},[19,17661,17662],{},"dnf provides '*\u002Fconvert'","（最方便，仓库索引里查）",[75,17665,17666,17669],{},[19,17667,17668],{},"apk search -x"," + 看包描述",[57,17671,17672,17675,17680,17685],{},[75,17673,17674],{},"谁依赖这个包",[75,17676,17677],{},[19,17678,17679],{},"apt-cache rdepends pkg",[75,17681,17682],{},[19,17683,17684],{},"dnf repoquery --whatrequires --installed pkg",[75,17686,17687,17690],{},[19,17688,17689],{},"apk info -t pkg","（依赖树反向）",[57,17692,17693,17696,17704,17709],{},[75,17694,17695],{},"这个包依赖什么",[75,17697,17698,1073,17701],{},[19,17699,17700],{},"apt-cache depends pkg",[19,17702,17703],{},"dpkg -s pkg",[75,17705,17706],{},[19,17707,17708],{},"dnf repoquery --requires pkg",[75,17710,17711],{},[19,17712,17713],{},"apk info -d pkg",[57,17715,17716,17719,17725,17731],{},[75,17717,17718],{},"文件有没有被改过",[75,17720,17721,17724],{},[19,17722,17723],{},"debsums","（要装）",[75,17726,17727,17730],{},[19,17728,17729],{},"rpm -V pkg","（内置）",[75,17732,17733],{},[19,17734,17735],{},"apk audit",[15,17737,17738,17739,17742,17743,17746,17747,17750,17751,17754],{},"「这个命令找不到，该装哪个包」在 Debian 上还有个内置提示器：装 ",[19,17740,17741],{},"command-not-found","（Ubuntu 默认有）后，输入 ",[19,17744,17745],{},"convert"," 会直接回 ",[19,17748,17749],{},"sudo apt install imagemagick","。RHEL 系同理由 ",[19,17752,17753],{},"dnf-plugins-core"," 提供。",[46,17756,17758],{"id":17757},"_6-源码安装","6. 源码安装",[211,17760,17762],{"className":213,"code":17761,"language":215,"meta":216,"style":216},"tar xzf app-1.9.3.tar.gz && cd app-1.9.3\n.\u002Fconfigure --prefix=\u002Fusr\u002Flocal --with-openssl     # ① 探测环境，生成 Makefile\nmake -j\"$(nproc)\"                                  # ② 编译\nsudo make install                                  # ③ 装到 \u002Fusr\u002Flocal\nsudo ldconfig                                      # ④ 刷新动态库缓存（装了 .so 才需要）\n",[19,17763,17764,17780,17794,17813,17825],{"__ignoreMap":216},[220,17765,17766,17768,17770,17773,17775,17777],{"class":222,"line":223},[220,17767,8945],{"class":226},[220,17769,9244],{"class":229},[220,17771,17772],{"class":229}," app-1.9.3.tar.gz",[220,17774,6988],{"class":6987},[220,17776,464],{"class":266},[220,17778,17779],{"class":229}," app-1.9.3\n",[220,17781,17782,17785,17788,17791],{"class":222,"line":276},[220,17783,17784],{"class":226},".\u002Fconfigure",[220,17786,17787],{"class":266}," --prefix=\u002Fusr\u002Flocal",[220,17789,17790],{"class":266}," --with-openssl",[220,17792,17793],{"class":233},"     # ① 探测环境，生成 Makefile\n",[220,17795,17796,17798,17801,17804,17807,17810],{"class":222,"line":594},[220,17797,8190],{"class":226},[220,17799,17800],{"class":266}," -j",[220,17802,17803],{"class":229},"\"$(",[220,17805,17806],{"class":226},"nproc",[220,17808,17809],{"class":229},")\"",[220,17811,17812],{"class":233},"                                  # ② 编译\n",[220,17814,17815,17817,17820,17822],{"class":222,"line":925},[220,17816,120],{"class":226},[220,17818,17819],{"class":229}," make",[220,17821,8193],{"class":229},[220,17823,17824],{"class":233},"                                  # ③ 装到 \u002Fusr\u002Flocal\n",[220,17826,17827,17829,17832],{"class":222,"line":931},[220,17828,120],{"class":226},[220,17830,17831],{"class":229}," ldconfig",[220,17833,17834],{"class":233},"                                      # ④ 刷新动态库缓存（装了 .so 才需要）\n",[51,17836,17837,17849],{},[54,17838,17839],{},[57,17840,17841,17843,17846],{},[60,17842,16437],{},[60,17844,17845],{},"常见失败",[60,17847,17848],{},"处理",[70,17850,17851,17890,17906,17929,17953],{},[57,17852,17853,17857,17863],{},[75,17854,17855],{},[19,17856,17784],{},[75,17858,17859,17862],{},[19,17860,17861],{},"no acceptable C compiler"," \u002F 缺依赖头文件",[75,17864,17865,17868,17869,17872,17873,34,17876,17879,17880,418,17883,1073,17886,17889],{},[19,17866,17867],{},"apt install build-essential","；缺的 ",[19,17870,17871],{},"-dev"," 包按报错补（",[19,17874,17875],{},"zlib1g-dev",[19,17877,17878],{},"libssl-dev","）；RHEL 系 ",[19,17881,17882],{},"dnf groupinstall \"Development Tools\"",[19,17884,17885],{},"dnf builddep pkg.src.rpm",[19,17887,17888],{},"apt build-dep pkg"," 一次装齐构建依赖",[57,17891,17892,17896,17899],{},[75,17893,17894],{},[19,17895,8190],{},[75,17897,17898],{},"内存不足被 OOM 杀掉",[75,17900,17901,17902,17905],{},"降到 ",[19,17903,17904],{},"make -j1","；或加 swap",[57,17907,17908,17913,17919],{},[75,17909,17910],{},[19,17911,17912],{},"make install",[75,17914,17915,17916],{},"没权限写 ",[19,17917,17918],{},"\u002Fusr\u002Flocal",[75,17920,2286,17921,17924,17925,17928],{},[19,17922,17923],{},"--prefix=$HOME\u002F.local","（之后要把 ",[19,17926,17927],{},"$HOME\u002F.local\u002Fbin"," 加 PATH）",[57,17930,17931,17934,17939],{},[75,17932,17933],{},"运行期",[75,17935,17936],{},[19,17937,17938],{},"error while loading shared libraries: libxxx.so.1",[75,17940,17941,17942,17945,17946,17949,17950],{},"库没在 ",[19,17943,17944],{},"\u002Fetc\u002Fld.so.conf"," 路径里，",[19,17947,17948],{},"echo \u002Fusr\u002Flocal\u002Flib | sudo tee \u002Fetc\u002Fld.so.conf.d\u002Flocal.conf && sudo ldconfig","；临时验证用 ",[19,17951,17952],{},"LD_LIBRARY_PATH",[57,17954,17955,17958,17966],{},[75,17956,17957],{},"卸载",[75,17959,17960],{},[27,17961,17962,17965],{},[19,17963,17964],{},"make uninstall"," 通常不存在",[75,17967,17968],{},"这正是源码安装的致命缺陷",[15,17970,17971,17974,17975,17977,17978,17981,17982,17985,17986,17989,17990,17993,17994,616],{},[19,17972,17973],{},"checkinstall"," 的价值：把 ",[19,17976,17912],{}," 的产物打包成一个",[27,17979,17980],{},"受包管理器记录","的 .deb\u002F.rpm，于是 ",[19,17983,17984],{},"dpkg -L"," 能查文件、",[19,17987,17988],{},"apt remove"," 能干净卸载。命令替换：",[19,17991,17992],{},"sudo checkinstall --pkgname=myapp --pkgversion=1.9.3 --default","。原则：",[27,17995,17996,17997,17999],{},"能用发行版包就不源码装；必须源码装就用 checkinstall，并只装到 ",[19,17998,17918],{},"（与系统包目录隔离，方便整体删除）",[46,18001,18003],{"id":18002},"_7-snap-flatpak-appimage","7. snap \u002F flatpak \u002F appimage",[51,18005,18006,18018],{},[54,18007,18008],{},[57,18009,18010,18013,18015],{},[60,18011,18012],{},"新型包格式",[60,18014,5168],{},[60,18016,18017],{},"一句话",[70,18019,18020,18049,18071,18086],{},[57,18021,18022,18028,18042],{},[75,18023,18024,18027],{},[19,18025,18026],{},"snap","（Ubuntu 主推）",[75,18029,18030,34,18033,34,18036,34,18039],{},[19,18031,18032],{},"snap install htop",[19,18034,18035],{},"snap list",[19,18037,18038],{},"snap changes",[19,18040,18041],{},"snap revert",[75,18043,18044,18045,18048],{},"自带依赖与沙箱，自动升级；冷启动慢、",[19,18046,18047],{},"\u002Fsnap"," 占空间大、镜像源在某些网络下很慢",[57,18050,18051,18057,18068],{},[75,18052,18053,18056],{},[19,18054,18055],{},"flatpak","（桌面应用）",[75,18058,18059,34,18062,34,18065],{},[19,18060,18061],{},"flatpak install flathub org.videolan.VLC",[19,18063,18064],{},"flatpak run …",[19,18066,18067],{},"flatpak update",[75,18069,18070],{},"面向桌面 GUI，服务器基本不用",[57,18072,18073,18078,18083],{},[75,18074,18075],{},[19,18076,18077],{},"appimage",[75,18079,18080],{},[19,18081,18082],{},".\u002FSomeApp.AppImage",[75,18084,18085],{},"单文件免安装，给一个可执行位就能跑",[57,18087,18088,18099,18102],{},[75,18089,18090,1073,18093,1073,18096],{},[19,18091,18092],{},"pipx",[19,18094,18095],{},"cargo install",[19,18097,18098],{},"npm i -g",[75,18100,18101],{},"语言级生态",[75,18103,18104],{},"与系统包管理器隔离，冲突少但更新不统一",[46,18106,18108],{"id":18107},"_8-容器化发行版为什么没有-systemd","8. 容器化发行版为什么没有 systemd",[15,18110,18111,18112,1848],{},"Docker\u002FOCI 镜像里的「Debian\u002FAlpine」只是一个根文件系统，",[27,18113,18114],{},"不是完整虚拟机",[51,18116,18117,18130],{},[54,18118,18119],{},[57,18120,18121,18124,18127],{},[60,18122,18123],{},"现象",[60,18125,18126],{},"原因",[60,18128,18129],{},"后果与替代",[70,18131,18132,18152,18165,18185,18196,18214],{},[57,18133,18134,18143,18146],{},[75,18135,18136,18137,18139,18140,1062],{},"没有 ",[19,18138,500],{},"（或报 ",[19,18141,18142],{},"System has not been booted with systemd",[75,18144,18145],{},"PID 1 是应用本身，没有 init 系统",[75,18147,2286,18148,18151],{},[19,18149,18150],{},"docker exec"," 起进程、镜像 entrypoint、s6\u002Fsupervisord 管多进程",[57,18153,18154,18159,18162],{},[75,18155,18156,18158],{},[19,18157,653],{}," 装完服务不自动启动",[75,18160,18161],{},"没有 systemd 触发 unit",[75,18163,18164],{},"手工在启动脚本里拉起，或改由 supervisor 管",[57,18166,18167,18172,18175],{},[75,18168,18169,18171],{},[19,18170,11328],{}," 无用武之地",[75,18173,18174],{},"journald 没跑",[75,18176,18177,18178,1073,18181,18184],{},"日志走 stdout\u002Fstderr，由 ",[19,18179,18180],{},"docker logs",[19,18182,18183],{},"kubectl logs"," 收",[57,18186,18187,18190,18193],{},[75,18188,18189],{},"cron 不工作",[75,18191,18192],{},"crond 没被启动",[75,18194,18195],{},"外部调度器（K8s CronJob）或镜像里显式起 crond",[57,18197,18198,18205,18208],{},[75,18199,18200,662,18202,18204],{},[19,18201,13660],{},[19,18203,13663],{}," 看不到宿主机全部信息",[75,18206,18207],{},"独立网络命名空间",[75,18209,18210,18213],{},[19,18211,18212],{},"nsenter"," 进命名空间，或在容器内只关心自己那套",[57,18215,18216,18219,18225],{},[75,18217,18218],{},"装包习惯差异",[75,18220,18221,18222],{},"镜像层要",[27,18223,18224],{},"越小越好",[75,18226,18227,34,18230,18233,18234,18237],{},[19,18228,18229],{},"apt install -y --no-install-recommends",[19,18231,18232],{},"apk add --no-cache","、装完 ",[19,18235,18236],{},"&& rm -rf \u002Fvar\u002Flib\u002Fapt\u002Flists\u002F*","；调试工具单独打 debug 镜像",[15,18239,18240,18241,34,18244,34,18247,18250,18251,1073,18254,18257],{},"排「容器里为什么这条命令没有」时，先想是不是精简镜像（",[19,18242,18243],{},"*-slim",[19,18245,18246],{},"alpine",[19,18248,18249],{},"distroless","）根本没带这个包，用 ",[19,18252,18253],{},"apk info",[19,18255,18256],{},"dpkg -l"," 核对，再决定是装包还是换镜像。",[46,18259,808],{"id":18260},"踩坑提醒-14",[810,18262,18263,18278,18293,18303],{},[813,18264,18265,827,18268,18271,18272,689,18275,18277],{},[19,18266,18267],{},"apt upgrade",[19,18269,18270],{},"apt full-upgrade"," 混用能引出依赖地狱；生产上先 ",[19,18273,18274],{},"apt -s upgrade",[19,18276,14009],{}," 模拟，不实际改动）看会动什么。",[813,18279,18280,18281,18284,18285,18288,18289,18292],{},"手动 ",[19,18282,18283],{},"dpkg -i"," 装失败后",[27,18286,18287],{},"不要","反复重试，",[19,18290,18291],{},"sudo apt -f install"," 才是修依赖的正确一步。",[813,18294,18295,18298,18299,18302],{},[19,18296,18297],{},"rpm -Va"," 输出巨多不等于系统被入侵：绝大多数是配置文件被管理员合法改动、日志文件被写入。看 ",[19,18300,18301],{},"S5","（摘要变了）且是二进制路径才值得警惕。",[813,18304,18305,18306,689,18309,18311,18312,18315,18316,34,18319,34,18322,18325],{},"升级 Python\u002FOpenSSL\u002Fglibc 这类系统库时",[27,18307,18308],{},"永远不要用源码覆盖",[19,18310,17912],{}," 到 ",[19,18313,18314],{},"\u002Fusr","），apt\u002Fdnf 自身依赖它们，覆盖会当场把包管理器变砖。要装新版本用 ",[19,18317,18318],{},"apt install python3.12",[19,18320,18321],{},"dnf module",[19,18323,18324],{},"pyenv","（装在用户目录）或容器。",[10,18327,18329],{"id":18328},"十六shell-本身变量引号与脚本的标配","十六、Shell 本身：变量、引号与脚本的标配",[15,18331,18332,18334,18335,18338],{},[19,18333,215],{}," 不是命令行集合，它是一门语言。脚本里 90% 的诡异行为（变量为空、单词被拆、退出码丢失）都出自三个地方：引号、展开、",[19,18336,18337],{},"set -e"," 的缺失。这一章把这几件事一次讲清。",[46,18340,18342],{"id":18341},"_1-引号三兄弟","1. 引号三兄弟",[51,18344,18345,18359],{},[54,18346,18347],{},[57,18348,18349,18351,18354,18357],{},[60,18350,2033],{},[60,18352,18353],{},"名字",[60,18355,18356],{},"会不会展开变量\u002F命令",[60,18358,443],{},[70,18360,18361,18383,18415],{},[57,18362,18363,18368,18371,18377],{},[75,18364,18365],{},[19,18366,18367],{},"'…'",[75,18369,18370],{},"单引号",[75,18372,18373,18376],{},[27,18374,18375],{},"完全不展开","，所见即字面",[75,18378,18379,18382],{},[19,18380,18381],{},"echo '$HOME $(date)'"," 原样输出",[57,18384,18385,18390,18393,18409],{},[75,18386,18387],{},[19,18388,18389],{},"\"…\"",[75,18391,18392],{},"双引号",[75,18394,18395,18396,34,18399,34,18402,18405,18406],{},"展开 ",[19,18397,18398],{},"$var",[19,18400,18401],{},"$(cmd)",[19,18403,18404],{},"`","，但",[27,18407,18408],{},"不拆分单词、不做 glob",[75,18410,18411,18414],{},[19,18412,18413],{},"echo \"$file\""," 带空格安全",[57,18416,18417,18422,18425,18428],{},[75,18418,18419],{},[19,18420,18421],{},"`…`",[75,18423,18424],{},"反引号命令替换",[75,18426,18427],{},"展开，但嵌套需转义，可读性差",[75,18429,18430,18431],{},"新代码一律换成 ",[19,18432,18433],{},"$(…)",[15,18435,18436,18437,689,18440,18443,18444,18447,18448,18451],{},"规则一句话：",[27,18438,18439],{},"变量永远包在双引号里",[19,18441,18442],{},"\"$x\"","），除非你明确想要单词拆分。判断是否会被拆，用 ",[19,18445,18446],{},"set -f","（临时禁 glob）或 ",[19,18449,18450],{},"printf '[%s]\\n' \"$x\""," 观察。",[15,18453,18454,827,18457,1848,18460,18463,18464,34,18467,34,18470,8157,18473,18476,18477,18480,18481,616],{},[19,18455,18456],{},"$'…'",[19,18458,18459],{},"$\"…\"",[19,18461,18462],{},"$'a\\nb'"," 支持 C 转义（",[19,18465,18466],{},"\\n",[19,18468,18469],{},"\\t",[19,18471,18472],{},"\\x41",[19,18474,18475],{},"$\"msg\""," 做本地化翻译（基本遇不到）。空数组\u002F未设变量在 ",[19,18478,18479],{},"set -u"," 下的安全写法是 ",[19,18482,18483],{},"${x:-}",[46,18485,18487],{"id":18486},"_2-变量与-export","2. 变量与 export",[211,18489,18491],{"className":213,"code":18490,"language":215,"meta":216,"style":216},"NAME=deploy                 # 赋值——等号两边不能有空格！NAME = deploy 会被当成执行命令\nreadonly PI=3.14            # 只读，再改会报错\nexport JAVA_HOME=\u002Fusr\u002Flib\u002Fjvm\u002Fjava-17   # 导出为环境变量（子进程可见）；只对之后的子进程生效\nexport -f fn                # 导出函数给子 bash；cron\u002Fsystemd 里不生效（不是 bash 子进程）\nenv                         # 打印当前全部环境；env -i bash 起一个干净环境（测试利器）\nprintenv PATH               # 取单个变量，比 echo $PATH 稳（未设时 echo 输出空行看不出来）\nunset NAME                  # 删变量；export -n NAME 只取消导出而保留本地值\ndeclare -p                  # 看变量的类型与属性，排查数组\u002F整数属性时用\n",[19,18492,18493,18506,18522,18537,18549,18557,18568,18579],{"__ignoreMap":216},[220,18494,18495,18498,18501,18503],{"class":222,"line":223},[220,18496,18497],{"class":6987},"NAME",[220,18499,18500],{"class":2439},"=",[220,18502,88],{"class":229},[220,18504,18505],{"class":233},"                 # 赋值——等号两边不能有空格！NAME = deploy 会被当成执行命令\n",[220,18507,18508,18511,18514,18516,18519],{"class":222,"line":276},[220,18509,18510],{"class":2439},"readonly",[220,18512,18513],{"class":6987}," PI",[220,18515,18500],{"class":2439},[220,18517,18518],{"class":266},"3.14",[220,18520,18521],{"class":233},"            # 只读，再改会报错\n",[220,18523,18524,18526,18529,18531,18534],{"class":222,"line":594},[220,18525,471],{"class":2439},[220,18527,18528],{"class":6987}," JAVA_HOME",[220,18530,18500],{"class":2439},[220,18532,18533],{"class":6987},"\u002Fusr\u002Flib\u002Fjvm\u002Fjava-17   ",[220,18535,18536],{"class":233},"# 导出为环境变量（子进程可见）；只对之后的子进程生效\n",[220,18538,18539,18541,18543,18546],{"class":222,"line":925},[220,18540,471],{"class":2439},[220,18542,2573],{"class":266},[220,18544,18545],{"class":6987}," fn                ",[220,18547,18548],{"class":233},"# 导出函数给子 bash；cron\u002Fsystemd 里不生效（不是 bash 子进程）\n",[220,18550,18551,18554],{"class":222,"line":931},[220,18552,18553],{"class":226},"env",[220,18555,18556],{"class":233},"                         # 打印当前全部环境；env -i bash 起一个干净环境（测试利器）\n",[220,18558,18559,18562,18565],{"class":222,"line":3528},[220,18560,18561],{"class":226},"printenv",[220,18563,18564],{"class":229}," PATH",[220,18566,18567],{"class":233},"               # 取单个变量，比 echo $PATH 稳（未设时 echo 输出空行看不出来）\n",[220,18569,18570,18573,18576],{"class":222,"line":3537},[220,18571,18572],{"class":266},"unset",[220,18574,18575],{"class":229}," NAME",[220,18577,18578],{"class":233},"                  # 删变量；export -n NAME 只取消导出而保留本地值\n",[220,18580,18581,18584,18586],{"class":222,"line":3546},[220,18582,18583],{"class":2439},"declare",[220,18585,1385],{"class":266},[220,18587,18588],{"class":233},"                  # 看变量的类型与属性，排查数组\u002F整数属性时用\n",[15,18590,18591],{},"变量展开的形式（脚本里避免 bug 的关键）：",[51,18593,18594,18602],{},[54,18595,18596],{},[57,18597,18598,18600],{},[60,18599,2033],{},[60,18601,68],{},[70,18603,18604,18614,18628,18644,18654],{},[57,18605,18606,18611],{},[75,18607,18608],{},[19,18609,18610],{},"${x:-默认}",[75,18612,18613],{},"x 未设或空则用默认（不改变 x）",[57,18615,18616,18621],{},[75,18617,18618],{},[19,18619,18620],{},"${x:=默认}",[75,18622,18623,18624,18627],{},"x 未设或空则",[27,18625,18626],{},"赋值","为默认（常用于「首次运行算一次」）",[57,18629,18630,18635],{},[75,18631,18632],{},[19,18633,18634],{},"${x:?消息}",[75,18636,18637,18638,1848,18641],{},"x 未设或空则报错退出——",[27,18639,18640],{},"破坏性命令前的保险",[19,18642,18643],{},"rm -rf \"${DIR:?}\u002F*\"",[57,18645,18646,18651],{},[75,18647,18648],{},[19,18649,18650],{},"${x:+替代}",[75,18652,18653],{},"x 有值才用替代值",[57,18655,18656,18670],{},[75,18657,18658,1073,18661,1073,18664,1073,18667],{},[19,18659,18660],{},"${#x}",[19,18662,18663],{},"${x:5:3}",[19,18665,18666],{},"${x%%.txt}",[19,18668,18669],{},"${x\u002F\u002Fa\u002Fb}",[75,18671,18672,18673,18675],{},"长度 \u002F 子串 \u002F 删后缀 \u002F 替换（比调用 ",[19,18674,697],{}," 快且无 fork）",[46,18677,18679],{"id":18678},"_3-path-怎么安全地加一段","3. PATH 怎么安全地加一段",[211,18681,18683],{"className":213,"code":18682,"language":215,"meta":216,"style":216},"export PATH=\"$HOME\u002F.local\u002Fbin:$PATH\"    # 日常写法；重复执行会把 PATH 越拉越长，脚本里别这样\n\ncase \":$PATH:\" in *\":$HOME\u002F.local\u002Fbin:\"*) ;; *) export PATH=\"$HOME\u002F.local\u002Fbin:$PATH\" ;; esac\n# 幂等写法：重复执行不累积\n",[19,18684,18685,18709,18713,18772],{"__ignoreMap":216},[220,18686,18687,18689,18691,18693,18696,18698,18701,18704,18706],{"class":222,"line":223},[220,18688,471],{"class":2439},[220,18690,18564],{"class":6987},[220,18692,18500],{"class":2439},[220,18694,18695],{"class":229},"\"",[220,18697,2091],{"class":6987},[220,18699,18700],{"class":229},"\u002F.local\u002Fbin:",[220,18702,18703],{"class":6987},"$PATH",[220,18705,18695],{"class":229},[220,18707,18708],{"class":233},"    # 日常写法；重复执行会把 PATH 越拉越长，脚本里别这样\n",[220,18710,18711],{"class":222,"line":276},[220,18712,3740],{"emptyLinePlaceholder":3739},[220,18714,18715,18718,18721,18723,18726,18729,18732,18735,18737,18740,18742,18744,18747,18750,18753,18755,18757,18759,18761,18763,18765,18767,18769],{"class":222,"line":594},[220,18716,18717],{"class":2439},"case",[220,18719,18720],{"class":229}," \":",[220,18722,18703],{"class":6987},[220,18724,18725],{"class":229},":\"",[220,18727,18728],{"class":2439}," in",[220,18730,18731],{"class":2439}," *",[220,18733,18734],{"class":229},"\":",[220,18736,2091],{"class":6987},[220,18738,18739],{"class":229},"\u002F.local\u002Fbin:\"",[220,18741,2436],{"class":2439},[220,18743,15073],{"class":2439},[220,18745,18746],{"class":6987}," ;; ",[220,18748,18749],{"class":2439},"*)",[220,18751,18752],{"class":2439}," export",[220,18754,18564],{"class":6987},[220,18756,18500],{"class":2439},[220,18758,18695],{"class":229},[220,18760,2091],{"class":6987},[220,18762,18700],{"class":229},[220,18764,18703],{"class":6987},[220,18766,18695],{"class":229},[220,18768,18746],{"class":6987},[220,18770,18771],{"class":2439},"esac\n",[220,18773,18774],{"class":222,"line":925},[220,18775,18776],{"class":233},"# 幂等写法：重复执行不累积\n",[15,18778,4622,18779,18781,18782,18785,18786,15228,18788,18791,18792,18794,18795,18797,18798,18800,18801,18804,18805,18807,18808,18811,18812,18815,18816,7960,18818,616],{},[19,18780,511],{}," 里出现",[27,18783,18784],{},"空段","（开头或结尾的 ",[19,18787,3422],{},[19,18789,18790],{},"PATH=:","）等价于把当前目录 ",[19,18793,1082],{}," 加进搜索路径——任何人能在 ",[19,18796,1058],{}," 放一个 ",[19,18799,348],{}," 骗你执行。检查：",[19,18802,18803],{},"echo \"$PATH\" | tr ':' '\\n' | nl","。sudo 会用 ",[19,18806,4705],{}," 覆盖你的 PATH（",[19,18809,18810],{},"sudo -V | grep secure_path"," 查看），所以 ",[19,18813,18814],{},"sudo mytool"," 找不到东西时要改 ",[19,18817,4846],{},[19,18819,471],{},[46,18821,18823],{"id":18822},"_4-启动文件什么时候被读","4. 启动文件什么时候被读",[51,18825,18826,18842],{},[54,18827,18828],{},[57,18829,18830,18833,18836,18839],{},[60,18831,18832],{},"文件",[60,18834,18835],{},"谁读它",[60,18837,18838],{},"何时",[60,18840,18841],{},"放什么",[70,18843,18844,18862,18901,18919,18938,18953],{},[57,18845,18846,18854,18856,18859],{},[75,18847,18848,15156,18851],{},[19,18849,18850],{},"\u002Fetc\u002Fprofile",[19,18852,18853],{},"\u002Fetc\u002Fprofile.d\u002F*.sh",[75,18855,13381],{},[75,18857,18858],{},"登录时一次",[75,18860,18861],{},"系统级环境（模块、语言、代理）",[57,18863,18864,18875,18877,18892],{},[75,18865,18866,1073,18869,1073,18872],{},[19,18867,18868],{},"~\u002F.bash_profile",[19,18870,18871],{},"~\u002F.profile",[19,18873,18874],{},"~\u002F.login",[75,18876,13381],{},[75,18878,18879,18882,18883,34,18886,34,18889,1062],{},[27,18880,18881],{},"找到第一个就不往后","（bash 只看 ",[19,18884,18885],{},".bash_profile",[19,18887,18888],{},".bash_login",[19,18890,18891],{},".profile",[75,18893,18894,18895,18897,18898],{},"只写 ",[19,18896,471],{},"，并 ",[19,18899,18900],{},"source ~\u002F.bashrc",[57,18902,18903,18907,18913,18916],{},[75,18904,18905],{},[19,18906,12443],{},[75,18908,18909,18912],{},[27,18910,18911],{},"非交互","但交互式的 shell（每个新终端窗口）",[75,18914,18915],{},"每次开 shell",[75,18917,18918],{},"alias、函数、提示符、补全",[57,18920,18921,18929,18932,18935],{},[75,18922,18923,1073,18926],{},[19,18924,18925],{},"\u002Fetc\u002Fbash.bashrc",[19,18927,18928],{},"\u002Fetc\u002Fbashrc",[75,18930,18931],{},"所有 bash",[75,18933,18934],{},"每次",[75,18936,18937],{},"系统级 alias",[57,18939,18940,18945,18947,18950],{},[75,18941,18942],{},[19,18943,18944],{},"~\u002F.bash_logout",[75,18946,13381],{},[75,18948,18949],{},"退出时",[75,18951,18952],{},"清理（清屏等）",[57,18954,18955,18960,18963,18966],{},[75,18956,18957],{},[19,18958,18959],{},"$BASH_ENV",[75,18961,18962],{},"非交互 shell（脚本）",[75,18964,18965],{},"脚本启动时",[75,18967,18968],{},"极少用，注意脚本环境",[15,18970,18971,18972,18975,18976,18979,18980,18983,18984,1284,18987,18989,18990,18993,18994,662,18996,18998,18999,616],{},"经典问题：",[19,18973,18974],{},".bashrc"," 里写了 alias 但 ",[19,18977,18978],{},"ssh host 'cmd'"," 不生效——因为 ",[19,18981,18982],{},"ssh host cmd"," 起的是",[27,18985,18986],{},"非交互 shell",[19,18988,18974],{}," 开头通常有 ",[19,18991,18992],{},"case $- in *i*) ;; *) return;; esac"," 直接返回。把要共享的东西放 ",[19,18995,18891],{},[19,18997,18974],{}," 的守卫之前，或用 ",[19,19000,19001],{},"BASH_ENV",[46,19003,19005],{"id":19004},"_5-alias函数与-history","5. alias、函数与 history",[211,19007,19009],{"className":213,"code":19008,"language":215,"meta":216,"style":216},"alias ll='ls -lh --color=auto'    # 定义别名；别名不做参数展开，需要参数就写函数\nunalias ll                        # 取消；\\ls 或 command ls 临时绕开别名；脚本里判断用 type -a\nmkcd() { mkdir -p \"$1\" && cd \"$1\"; }   # 函数：定义即执行副作用\n\nhistory                             # 历史列表；history 20 看最近 20 条；HISTSIZE\u002FHISTFILESIZE 控条数\nhistory -a                          # 立即落盘；多终端并发会互相覆盖，设 shopt -s histappend\n!!                                  # 重复上条整条；sudo !! 是忘加 sudo 的标准补救\n!$                                  # 上条的最后一个参数：mkdir a\u002Fb\u002Fc 然后 cd !$；!* 是全部参数\n!42                                 # 执行历史第 42 条；!grep 按开头字符串；!?conf? 最近含 conf 的\n!42:p                               # 只打印不执行，先确认再跑\nCtrl-R                              # 反向搜索——效率最高的一个键；配 fzf 后可模糊选\nhistory -c; history -w              # 清历史；注意 ~\u002F.bash_history 里的敏感命令\n",[19,19010,19011,19026,19036,19070,19074,19082,19091,19099,19108,19118,19128,19135],{"__ignoreMap":216},[220,19012,19013,19015,19018,19020,19023],{"class":222,"line":223},[220,19014,477],{"class":2439},[220,19016,19017],{"class":6987}," ll",[220,19019,18500],{"class":2439},[220,19021,19022],{"class":229},"'ls -lh --color=auto'",[220,19024,19025],{"class":233},"    # 定义别名；别名不做参数展开，需要参数就写函数\n",[220,19027,19028,19031,19033],{"class":222,"line":276},[220,19029,19030],{"class":266},"unalias",[220,19032,19017],{"class":229},[220,19034,19035],{"class":233},"                        # 取消；\\ls 或 command ls 临时绕开别名；脚本里判断用 type -a\n",[220,19037,19038,19041,19044,19046,19048,19050,19052,19054,19056,19058,19060,19062,19064,19067],{"class":222,"line":594},[220,19039,19040],{"class":226},"mkcd",[220,19042,19043],{"class":6987},"() { ",[220,19045,1372],{"class":226},[220,19047,1385],{"class":266},[220,19049,15823],{"class":229},[220,19051,7305],{"class":266},[220,19053,18695],{"class":229},[220,19055,6988],{"class":6987},[220,19057,464],{"class":266},[220,19059,15823],{"class":229},[220,19061,7305],{"class":266},[220,19063,18695],{"class":229},[220,19065,19066],{"class":6987},"; }   ",[220,19068,19069],{"class":233},"# 函数：定义即执行副作用\n",[220,19071,19072],{"class":222,"line":925},[220,19073,3740],{"emptyLinePlaceholder":3739},[220,19075,19076,19079],{"class":222,"line":931},[220,19077,19078],{"class":266},"history",[220,19080,19081],{"class":233},"                             # 历史列表；history 20 看最近 20 条；HISTSIZE\u002FHISTFILESIZE 控条数\n",[220,19083,19084,19086,19088],{"class":222,"line":3528},[220,19085,19078],{"class":266},[220,19087,551],{"class":266},[220,19089,19090],{"class":233},"                          # 立即落盘；多终端并发会互相覆盖，设 shopt -s histappend\n",[220,19092,19093,19096],{"class":222,"line":3537},[220,19094,19095],{"class":2439},"!!",[220,19097,19098],{"class":233},"                                  # 重复上条整条；sudo !! 是忘加 sudo 的标准补救\n",[220,19100,19101,19103,19105],{"class":222,"line":3546},[220,19102,3659],{"class":2439},[220,19104,99],{"class":226},[220,19106,19107],{"class":233},"                                  # 上条的最后一个参数：mkdir a\u002Fb\u002Fc 然后 cd !$；!* 是全部参数\n",[220,19109,19110,19112,19115],{"class":222,"line":3555},[220,19111,3659],{"class":2439},[220,19113,19114],{"class":226},"42",[220,19116,19117],{"class":233},"                                 # 执行历史第 42 条；!grep 按开头字符串；!?conf? 最近含 conf 的\n",[220,19119,19120,19122,19125],{"class":222,"line":3564},[220,19121,3659],{"class":2439},[220,19123,19124],{"class":226},"42:p",[220,19126,19127],{"class":233},"                               # 只打印不执行，先确认再跑\n",[220,19129,19130,19132],{"class":222,"line":3572},[220,19131,3375],{"class":226},[220,19133,19134],{"class":233},"                              # 反向搜索——效率最高的一个键；配 fzf 后可模糊选\n",[220,19136,19137,19139,19141,19143,19145,19147],{"class":222,"line":3580},[220,19138,19078],{"class":266},[220,19140,2494],{"class":266},[220,19142,8550],{"class":6987},[220,19144,19078],{"class":266},[220,19146,6691],{"class":266},[220,19148,19149],{"class":233},"              # 清历史；注意 ~\u002F.bash_history 里的敏感命令\n",[15,19151,19152],{},"位置参数与特殊变量：",[51,19154,19155,19167],{},[54,19156,19157],{},[57,19158,19159,19162,19164],{},[60,19160,19161],{},"变量",[60,19163,68],{},[60,19165,19166],{},"关键区别",[70,19168,19169,19184,19204,19220,19242,19262,19279,19294,19313,19326],{},[57,19170,19171,19175,19178],{},[75,19172,19173],{},[19,19174,7309],{},[75,19176,19177],{},"脚本名",[75,19179,2286,19180,19183],{},[19,19181,19182],{},"basename \"$0\""," 做 usage 提示",[57,19185,19186,19196,19199],{},[75,19187,19188,6474,19190,34,19193],{},[19,19189,7305],{},[19,19191,19192],{},"$9",[19,19194,19195],{},"${10}",[75,19197,19198],{},"第 N 个参数",[75,19200,19201],{},[27,19202,19203],{},"10 个以上必须加花括号",[57,19205,19206,19211,19214],{},[75,19207,19208],{},[19,19209,19210],{},"$#",[75,19212,19213],{},"参数个数",[75,19215,19216,19217],{},"判断数量：",[19,19218,19219],{},"[ $# -lt 2 ] && usage",[57,19221,19222,19227,19233],{},[75,19223,19224],{},[19,19225,19226],{},"\"$@\"",[75,19228,19229,19230],{},"所有参数，",[27,19231,19232],{},"各自保持为独立词",[75,19234,19235,19236,34,19239],{},"正确默认写法；",[19,19237,19238],{},"for f in \"$@\"",[19,19240,19241],{},"cmd \"$@\"",[57,19243,19244,19249,19259],{},[75,19245,19246],{},[19,19247,19248],{},"\"$*\"",[75,19250,19251,19252,19255,19256,19258],{},"所有参数拼成",[27,19253,19254],{},"一个","字符串（按 ",[19,19257,7305],{}," 分隔）",[75,19260,19261],{},"只在「要当成一整串」时用",[57,19263,19264,19269,19272],{},[75,19265,19266],{},[19,19267,19268],{},"$$",[75,19270,19271],{},"当前 shell PID",[75,19273,19274,19275,19278],{},"临时文件命名 ",[19,19276,19277],{},"f=$(mktemp)"," 更好",[57,19280,19281,19285,19288],{},[75,19282,19283],{},[19,19284,7924],{},[75,19286,19287],{},"上一条命令的退出码",[75,19289,19290,19291,19293],{},"0 成功；",[19,19292,18337],{}," 之后基本不用再手动查",[57,19295,19296,19301,19304],{},[75,19297,19298],{},[19,19299,19300],{},"$!",[75,19302,19303],{},"最后一个后台进程 PID",[75,19305,19306,19309,19310],{},[19,19307,19308],{},"cmd & pid=$!"," 之后 ",[19,19311,19312],{},"kill $pid",[57,19314,19315,19320,19323],{},[75,19316,19317],{},[19,19318,19319],{},"$_",[75,19321,19322],{},"上一条命令的最后一个参数",[75,19324,19325],{},"交互式很好用",[57,19327,19328,19336,19339],{},[75,19329,19330,1073,19333],{},[19,19331,19332],{},"$-",[19,19334,19335],{},"$BASHPID",[75,19337,19338],{},"当前选项标志 \u002F 子 shell 真实 PID",[75,19340,19341],{},"判断是否交互 shell",[46,19343,19345],{"id":19344},"_6-脚本的标配","6. 脚本的标配",[211,19347,19349],{"className":213,"code":19348,"language":215,"meta":216,"style":216},"#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\nIFS=$'\\n\\t'\n",[19,19350,19351,19356,19367],{"__ignoreMap":216},[220,19352,19353],{"class":222,"line":223},[220,19354,19355],{"class":233},"#!\u002Fusr\u002Fbin\u002Fenv bash\n",[220,19357,19358,19361,19364],{"class":222,"line":276},[220,19359,19360],{"class":266},"set",[220,19362,19363],{"class":266}," -euo",[220,19365,19366],{"class":229}," pipefail\n",[220,19368,19369,19372,19374,19377,19380],{"class":222,"line":594},[220,19370,19371],{"class":6987},"IFS",[220,19373,18500],{"class":2439},[220,19375,19376],{"class":229},"$'",[220,19378,19379],{"class":266},"\\n\\t",[220,19381,19382],{"class":229},"'\n",[51,19384,19385,19397],{},[54,19386,19387],{},[57,19388,19389,19392,19394],{},[60,19390,19391],{},"选项",[60,19393,2692],{},[60,19395,19396],{},"为什么是标配",[70,19398,19399,19419,19434,19451,19473,19486,19499],{},[57,19400,19401,19408,19411],{},[75,19402,19403,689,19405,1062],{},[19,19404,18337],{},[19,19406,19407],{},"-o errexit",[75,19409,19410],{},"命令失败立即退出",[75,19412,19413,19414,19416,19417,1062],{},"没有它，脚本会在错误状态上继续跑到底（",[19,19415,464],{}," 失败后继续 ",[19,19418,1701],{},[57,19420,19421,19428,19431],{},[75,19422,19423,689,19425,1062],{},[19,19424,18479],{},[19,19426,19427],{},"-o nounset",[75,19429,19430],{},"引用未定义变量即报错",[75,19432,19433],{},"抓拼写错误与「本该传参却忘了传」",[57,19435,19436,19441,19444],{},[75,19437,19438],{},[19,19439,19440],{},"set -o pipefail",[75,19442,19443],{},"管道里任一命令失败即失败",[75,19445,19446,19447,19450],{},"默认只看最后一个命令的退出码：",[19,19448,19449],{},"grep x file | wc -l"," 里 grep 失败被吞",[57,19452,19453,19461,19464],{},[75,19454,19455,689,19458,1062],{},[19,19456,19457],{},"set -x",[19,19459,19460],{},"-o xtrace",[75,19462,19463],{},"打印每条命令的实际展开",[75,19465,19466,19469,19470],{},[27,19467,19468],{},"调试首选","，等价临时 ",[19,19471,19472],{},"bash -x script.sh",[57,19474,19475,19480,19483],{},[75,19476,19477],{},[19,19478,19479],{},"set -v",[75,19481,19482],{},"打印读入的原始行",[75,19484,19485],{},"看解析问题",[57,19487,19488,19493,19496],{},[75,19489,19490],{},[19,19491,19492],{},"set +x",[75,19494,19495],{},"关掉上面的跟踪",[75,19497,19498],{},"遇到密码时记得关",[57,19500,19501,19506,19509],{},[75,19502,19503],{},[19,19504,19505],{},"IFS=$'\\n\\t'",[75,19507,19508],{},"限制字段分割符",[75,19510,19511],{},"防文件名里的空格被拆",[15,19513,19514,19516,19517,19520,19521,662,19523,19525,19526,19528,19529,19532,19533,19536],{},[19,19515,18337],{}," 的例外（很多人踩）：条件语句里的命令（",[19,19518,19519],{},"if cmd; then","）、",[19,19522,1831],{},[19,19524,8003],{}," 左侧、管道里非最后一个命令（无 ",[19,19527,7928],{}," 时）、以及被取反的 ",[19,19530,19531],{},"! cmd"," 都",[27,19534,19535],{},"不会","触发退出。函数返回值看最后一条命令。",[46,19538,19540,19541,19543],{"id":19539},"_7-与-以及数组","7. [[ ]] 与 ",[220,19542],{},"，以及数组",[51,19545,19546,19564],{},[54,19547,19548],{},[57,19549,19550,19552,19558],{},[60,19551,1874],{},[60,19553,19554,19557],{},[19,19555,19556],{},"[ ]","（POSIX test）",[60,19559,19560,19563],{},[19,19561,19562],{},"[[ ]]","（bash 关键字）",[70,19565,19566,19582,19599,19617,19635,19649],{},[57,19567,19568,19571,19574],{},[75,19569,19570],{},"词拆分\u002F glob",[75,19572,19573],{},"会（变量必须加引号）",[75,19575,19576,1284,19578,19581],{},[27,19577,19535],{},[19,19579,19580],{},"[[ $x == *.log ]]"," 安全",[57,19583,19584,19587,19590],{},[75,19585,19586],{},"正则匹配",[75,19588,19589],{},"无",[75,19591,19592,19595,19596,1062],{},[19,19593,19594],{},"[[ $f =~ ^[0-9]{3}$ ]]","（右侧",[27,19597,19598],{},"不要加引号",[57,19600,19601,19604,19612],{},[75,19602,19603],{},"逻辑组合",[75,19605,19606,19607,662,19609,19611],{},"只能用 ",[19,19608,13117],{},[19,19610,5481],{},"（有坑）",[75,19613,14080,19614,19616],{},[19,19615,1831],{}," 与逻辑或（双竖线），可嵌套",[57,19618,19619,19622,19630],{},[75,19620,19621],{},"字符串比较",[75,19623,19624,689,19626,19629],{},[19,19625,18500],{},[19,19627,19628],{},"=="," 也接受但非 POSIX）",[75,19631,19632,19634],{},[19,19633,19628],{}," 支持模式匹配",[57,19636,19637,19640,19646],{},[75,19638,19639],{},"未加引号的空变量",[75,19641,19642,19645],{},[19,19643,19644],{},"[: too many arguments"," 报错",[75,19647,19648],{},"正常处理",[57,19650,19651,19654,19657],{},[75,19652,19653],{},"可用性",[75,19655,19656],{},"所有 sh",[75,19658,19659,19660,19662],{},"bash\u002Fzsh\u002Fksh（",[19,19661,800],{}," 里不能用）",[15,19664,19665,19666,19678,19679,19681,19682,19684,19685,19688,19689,19691,19692,662,19694,34,19696,19698,19699,19702,19703,19706],{},"结论：",[27,19667,19668,19669,1284,19671,19674,19675,19677],{},"bash 脚本一律 ",[19,19670,19562],{},[19,19672,19673],{},"\u002Fbin\u002Fsh"," 脚本一律 ",[19,19676,19556],{}," 并给变量加引号","。文件测试通用：",[19,19680,14016],{}," 存在、",[19,19683,2600],{}," 普通文件、",[19,19686,19687],{},"-d"," 目录、",[19,19690,10372],{}," 可执行、",[19,19693,8886],{},[19,19695,14005],{},[19,19697,14009],{}," 非空、",[19,19700,19701],{},"-L"," 符号链接、",[19,19704,19705],{},"a -nt b"," 更新。",[15,19708,19709],{},"数组与关联数组：",[211,19711,19713],{"className":213,"code":19712,"language":215,"meta":216,"style":216},"files=(a.log b.log c.log)\necho \"${files[1]}\"           # b.log\necho \"${#files[@]}\"          # 3（元素数）\nprintf '%s\\n' \"${files[@]}\"  # 全部展开，各占一行\nfiles+=(d.log)               # 追加\nunset 'files[1]'             # 删元素（注意引号，否则 shell 先展开）\n\ndeclare -A seen              # 关联数组（bash 4+，Alpine 的 ash 不支持）\nseen[10.0.0.12]=42\nfor k in \"${!seen[@]}\"; do echo \"$k => ${seen[$k]}\"; done\n(( seen[10.0.0.12]++ ))      # 计数惯用式\n",[19,19714,19715,19736,19751,19773,19792,19810,19820,19824,19836,19844,19892],{"__ignoreMap":216},[220,19716,19717,19720,19722,19725,19728,19731,19734],{"class":222,"line":223},[220,19718,19719],{"class":6987},"files",[220,19721,18500],{"class":2439},[220,19723,19724],{"class":6987},"(",[220,19726,19727],{"class":229},"a.log",[220,19729,19730],{"class":229}," b.log",[220,19732,19733],{"class":229}," c.log",[220,19735,7230],{"class":6987},[220,19737,19738,19740,19743,19745,19748],{"class":222,"line":276},[220,19739,468],{"class":266},[220,19741,19742],{"class":229}," \"${",[220,19744,19719],{"class":6987},[220,19746,19747],{"class":229},"[1]}\"",[220,19749,19750],{"class":233},"           # b.log\n",[220,19752,19753,19755,19757,19759,19761,19764,19767,19770],{"class":222,"line":594},[220,19754,468],{"class":266},[220,19756,19742],{"class":229},[220,19758,116],{"class":2439},[220,19760,19719],{"class":6987},[220,19762,19763],{"class":229},"[",[220,19765,19766],{"class":2439},"@",[220,19768,19769],{"class":229},"]}\"",[220,19771,19772],{"class":233},"          # 3（元素数）\n",[220,19774,19775,19777,19779,19781,19783,19785,19787,19789],{"class":222,"line":925},[220,19776,292],{"class":266},[220,19778,3747],{"class":229},[220,19780,19742],{"class":229},[220,19782,19719],{"class":6987},[220,19784,19763],{"class":229},[220,19786,19766],{"class":2439},[220,19788,19769],{"class":229},[220,19790,19791],{"class":233},"  # 全部展开，各占一行\n",[220,19793,19794,19796,19799,19801,19804,19807],{"class":222,"line":931},[220,19795,19719],{"class":6987},[220,19797,19798],{"class":2439},"+=",[220,19800,19724],{"class":6987},[220,19802,19803],{"class":229},"d.log",[220,19805,19806],{"class":6987},")               ",[220,19808,19809],{"class":233},"# 追加\n",[220,19811,19812,19814,19817],{"class":222,"line":3528},[220,19813,18572],{"class":266},[220,19815,19816],{"class":229}," 'files[1]'",[220,19818,19819],{"class":233},"             # 删元素（注意引号，否则 shell 先展开）\n",[220,19821,19822],{"class":222,"line":3537},[220,19823,3740],{"emptyLinePlaceholder":3739},[220,19825,19826,19828,19830,19833],{"class":222,"line":3546},[220,19827,18583],{"class":2439},[220,19829,2396],{"class":266},[220,19831,19832],{"class":6987}," seen              ",[220,19834,19835],{"class":233},"# 关联数组（bash 4+，Alpine 的 ash 不支持）\n",[220,19837,19838,19841],{"class":222,"line":3555},[220,19839,19840],{"class":226},"seen[10.0.0.12]",[220,19842,19843],{"class":229},"=42\n",[220,19845,19846,19848,19851,19853,19855,19857,19860,19862,19864,19866,19868,19870,19872,19874,19877,19880,19882,19884,19886,19888,19890],{"class":222,"line":3564},[220,19847,15798],{"class":2439},[220,19849,19850],{"class":6987}," k ",[220,19852,15804],{"class":2439},[220,19854,19742],{"class":229},[220,19856,3659],{"class":2439},[220,19858,19859],{"class":6987},"seen",[220,19861,19763],{"class":229},[220,19863,19766],{"class":2439},[220,19865,19769],{"class":229},[220,19867,8550],{"class":6987},[220,19869,15815],{"class":2439},[220,19871,15818],{"class":266},[220,19873,15823],{"class":229},[220,19875,19876],{"class":6987},"$k",[220,19878,19879],{"class":229}," => ${",[220,19881,19859],{"class":6987},[220,19883,19763],{"class":229},[220,19885,19876],{"class":6987},[220,19887,19769],{"class":229},[220,19889,8550],{"class":6987},[220,19891,15851],{"class":2439},[220,19893,19894,19897,19900,19902,19904,19906,19908,19910,19913,19916,19919,19922],{"class":222,"line":3572},[220,19895,19896],{"class":6987},"(( seen[",[220,19898,19899],{"class":266},"10",[220,19901,1082],{"class":6987},[220,19903,8018],{"class":266},[220,19905,1082],{"class":6987},[220,19907,8018],{"class":266},[220,19909,1082],{"class":6987},[220,19911,19912],{"class":266},"12",[220,19914,19915],{"class":6987},"]",[220,19917,19918],{"class":2439},"++",[220,19920,19921],{"class":6987}," ))      ",[220,19923,19924],{"class":233},"# 计数惯用式\n",[46,19926,19928],{"id":19927},"_8-shebang执行方式与-read","8. shebang、执行方式与 read",[51,19930,19931,19939],{},[54,19932,19933],{},[57,19934,19935,19937],{},[60,19936,2033],{},[60,19938,68],{},[70,19940,19941,19950,19960,19972,19986],{},[57,19942,19943,19947],{},[75,19944,19945],{},[19,19946,804],{},[75,19948,19949],{},"固定用 bash（发行版路径一致时最简单）",[57,19951,19952,19957],{},[75,19953,19954],{},[19,19955,19956],{},"#!\u002Fusr\u002Fbin\u002Fenv bash",[75,19958,19959],{},"从 PATH 找 bash，跨系统更稳（Nix\u002F自编译场景必备）",[57,19961,19962,19966],{},[75,19963,19964],{},[19,19965,800],{},[75,19967,19968,19969,19971],{},"承诺只用 POSIX（此时不能 ",[19,19970,19562],{},"、不能用数组）",[57,19973,19974,19979],{},[75,19975,19976],{},[19,19977,19978],{},"#!\u002Fusr\u002Fbin\u002Fenv -S bash -eu",[75,19980,19981,19982,19985],{},"env 的 ",[19,19983,19984],{},"-S"," 把参数一起传（GNU coreutils 8.30+）",[57,19987,19988,19991],{},[75,19989,19990],{},"没有 shebang",[75,19992,19993,19994,19997],{},"由调用者决定；",[19,19995,19996],{},"sh script.sh"," 会按 POSIX 跑，bash 语法当场报错",[211,19999,20001],{"className":213,"code":20000,"language":215,"meta":216,"style":216},"chmod +x deploy.sh          # 加执行位（没有它 .\u002Fdeploy.sh 报 Permission denied）\nbash deploy.sh              # 显式用 bash 跑，不需要执行位\nsource deploy.sh            # 在当前 shell 执行（cd 与 export 会留下来）；. .\u002Fenv.sh 是 POSIX 写法\n.\u002Fdeploy.sh                 # 子 shell 执行（最常见：改了 PATH 却没生效，就是用了 .\u002F）\n\nread -p \"目标环境 [dev\u002Fprod]: \" env     # 提示 + 读一行到 env\nread -r -d '' big \u003C file                 # 读整个文件（-d '' 以 NUL 结束）\nread -s -p \"密码: \" pass \u003C \u002Fdev\u002Ftty      # -s 不回显；从 tty 读而非管道\nprintf '%s\\n' a b c | while IFS= read -r line; do …; done   # 逐行读的正确姿势\nread -t 5 -n 1 -p \"按任意键继续\" _       # 带超时与单字符\n",[19,20002,20003,20015,20024,20033,20041,20045,20061,20082,20104,20149],{"__ignoreMap":216},[220,20004,20005,20007,20010,20012],{"class":222,"line":223},[220,20006,4142],{"class":226},[220,20008,20009],{"class":229}," +x",[220,20011,4161],{"class":229},[220,20013,20014],{"class":233},"          # 加执行位（没有它 .\u002Fdeploy.sh 报 Permission denied）\n",[220,20016,20017,20019,20021],{"class":222,"line":276},[220,20018,215],{"class":226},[220,20020,4161],{"class":229},[220,20022,20023],{"class":233},"              # 显式用 bash 跑，不需要执行位\n",[220,20025,20026,20028,20030],{"class":222,"line":594},[220,20027,474],{"class":266},[220,20029,4161],{"class":229},[220,20031,20032],{"class":233},"            # 在当前 shell 执行（cd 与 export 会留下来）；. .\u002Fenv.sh 是 POSIX 写法\n",[220,20034,20035,20038],{"class":222,"line":925},[220,20036,20037],{"class":226},".\u002Fdeploy.sh",[220,20039,20040],{"class":233},"                 # 子 shell 执行（最常见：改了 PATH 却没生效，就是用了 .\u002F）\n",[220,20042,20043],{"class":222,"line":931},[220,20044,3740],{"emptyLinePlaceholder":3739},[220,20046,20047,20050,20052,20055,20058],{"class":222,"line":3528},[220,20048,20049],{"class":266},"read",[220,20051,1385],{"class":266},[220,20053,20054],{"class":229}," \"目标环境 [dev\u002Fprod]: \"",[220,20056,20057],{"class":229}," env",[220,20059,20060],{"class":233},"     # 提示 + 读一行到 env\n",[220,20062,20063,20065,20067,20069,20072,20075,20077,20079],{"class":222,"line":3537},[220,20064,20049],{"class":266},[220,20066,7611],{"class":266},[220,20068,1455],{"class":266},[220,20070,20071],{"class":229}," ''",[220,20073,20074],{"class":229}," big",[220,20076,7705],{"class":2439},[220,20078,2376],{"class":229},[220,20080,20081],{"class":233},"                 # 读整个文件（-d '' 以 NUL 结束）\n",[220,20083,20084,20086,20088,20090,20093,20096,20098,20101],{"class":222,"line":3546},[220,20085,20049],{"class":266},[220,20087,1978],{"class":266},[220,20089,1385],{"class":266},[220,20091,20092],{"class":229}," \"密码: \"",[220,20094,20095],{"class":229}," pass",[220,20097,7705],{"class":2439},[220,20099,20100],{"class":229}," \u002Fdev\u002Ftty",[220,20102,20103],{"class":233},"      # -s 不回显；从 tty 读而非管道\n",[220,20105,20106,20108,20110,20112,20114,20116,20118,20121,20124,20126,20129,20131,20134,20136,20138,20141,20143,20146],{"class":222,"line":3555},[220,20107,292],{"class":266},[220,20109,3747],{"class":229},[220,20111,1672],{"class":229},[220,20113,1675],{"class":229},[220,20115,8366],{"class":229},[220,20117,2626],{"class":2439},[220,20119,20120],{"class":2439}," while",[220,20122,20123],{"class":6987}," IFS",[220,20125,18500],{"class":2439},[220,20127,20128],{"class":266}," read",[220,20130,7611],{"class":266},[220,20132,20133],{"class":229}," line",[220,20135,8550],{"class":6987},[220,20137,15815],{"class":2439},[220,20139,20140],{"class":226}," …",[220,20142,8550],{"class":6987},[220,20144,20145],{"class":2439},"done",[220,20147,20148],{"class":233},"   # 逐行读的正确姿势\n",[220,20150,20151,20153,20155,20157,20159,20161,20163,20166,20169],{"class":222,"line":3564},[220,20152,20049],{"class":266},[220,20154,8459],{"class":266},[220,20156,11201],{"class":266},[220,20158,1669],{"class":266},[220,20160,267],{"class":266},[220,20162,1385],{"class":266},[220,20164,20165],{"class":229}," \"按任意键继续\"",[220,20167,20168],{"class":229}," _",[220,20170,20171],{"class":233},"       # 带超时与单字符\n",[15,20173,20174,20176,20177,20179,20180,20183,20184,20190],{},[19,20175,20049],{}," 的两个必备参数：",[19,20178,8886],{},"（否则反斜杠被吃掉）、配 ",[19,20181,20182],{},"IFS=","（否则行首尾空白被去）。逐行处理文件时",[27,20185,20186,20187],{},"不要用 ",[19,20188,20189],{},"for line in $(cat file)","——它会按空白拆且丢失空行。",[46,20192,808],{"id":20193},"踩坑提醒-15",[810,20195,20196,20212,20219,20228,20242,20254],{},[813,20197,20198,827,20200,662,20202,20204,20205,20208,20209,616],{},[19,20199,18337],{},[19,20201,8915],{},[19,20203,1831],{}," 组合时不生效是",[27,20206,20207],{},"设计","，不是 bug；关键步骤显式判返回值或加 ",[19,20210,20211],{},"|| exit 1",[813,20213,20214,827,20216,20218],{},[19,20215,19248],{},[19,20217,19226],{}," 写反了不会报错，只会把带空格的参数粘成一坨——这是脚本处理文件名出问题的第一来源。",[813,20220,20221,20222,20225,20226,616],{},"反引号里的 ",[19,20223,20224],{},"$( )"," 混用导致转义地狱：全文统一 ",[19,20227,8597],{},[813,20229,20230,20231,662,20233,20235,20236,20239,20240,616],{},"修改 ",[19,20232,511],{},[19,20234,464],{}," 之后在下一个命令里「没变化」，几乎一定是因为你用了 ",[19,20237,20238],{},".\u002Fscript.sh","（子 shell）而不是 ",[19,20241,474],{},[813,20243,20244,20246,20247,20249,20250,20253],{},[19,20245,18974],{}," 里写了 ",[19,20248,18337],{}," 会让交互 shell 在命令失败后",[27,20251,20252],{},"直接退出","——这是很危险的误配，选项只该写在脚本里。",[813,20255,2286,20256,20258,20259,20261,20262,616],{},[19,20257,468],{}," 输出以 ",[19,20260,960],{}," 开头的内容会被当参数：统一用 ",[19,20263,20264],{},"printf '%s\\n'",[10,20266,20268],{"id":20267},"十七性能与排障freeiostatstrace-与总速查表","十七、性能与排障：free、iostat、strace 与总速查表",[15,20270,20271],{},"机器慢、服务挂、磁盘满、连不上——排障的通用方法是「用一小组命令把每个资源维度各看一眼」，而不是凭猜测乱翻。这一章先给每个资源的观测命令，再给一张「现象 → 先跑哪几条」的映射表，最后收口成「想干一件事 → 用什么命令」的总速查表。",[861,20273],{"height":6608,"src":20274,"title":20275},"\u002Ftutorials\u002Flinux\u002Fdemos\u002F17-troubleshoot.html","示例 17：现象到命令的排障路径与总速查表",[46,20277,20279],{"id":20278},"_1-free-内存available-才是重点","1. free —— 内存：available 才是重点",[211,20281,20283],{"className":213,"code":20282,"language":215,"meta":216,"style":216},"free -h\n",[19,20284,20285],{"__ignoreMap":216},[220,20286,20287,20289],{"class":222,"line":223},[220,20288,15750],{"class":226},[220,20290,20291],{"class":266}," -h\n",[211,20293,20295],{"className":903,"code":20294,"language":905,"meta":216,"style":216},"               total        used        free      shared  buff\u002Fcache   available\nMem:           7.7Gi       3.1Gi       214Mi       18Mi       4.3Gi       4.2Gi\nSwap:          1.9Gi       128Mi       1.8Gi\n",[19,20296,20297,20302,20307],{"__ignoreMap":216},[220,20298,20299],{"class":222,"line":223},[220,20300,20301],{},"               total        used        free      shared  buff\u002Fcache   available\n",[220,20303,20304],{"class":222,"line":276},[220,20305,20306],{},"Mem:           7.7Gi       3.1Gi       214Mi       18Mi       4.3Gi       4.2Gi\n",[220,20308,20309],{"class":222,"line":594},[220,20310,20311],{},"Swap:          1.9Gi       128Mi       1.8Gi\n",[51,20313,20314,20325],{},[54,20315,20316],{},[57,20317,20318,20320,20322],{},[60,20319,9982],{},[60,20321,68],{},[60,20323,20324],{},"读法",[70,20326,20327,20340,20356,20371,20387,20402],{},[57,20328,20329,20334,20337],{},[75,20330,20331],{},[19,20332,20333],{},"total",[75,20335,20336],{},"物理内存总量",[75,20338,20339],{},"减去内核占用才是可用上限",[57,20341,20342,20347,20350],{},[75,20343,20344],{},[19,20345,20346],{},"used",[75,20348,20349],{},"应用真正占用",[75,20351,20352,20355],{},[19,20353,20354],{},"used = total - free - buff\u002Fcache","（不同版本算法略有差异）",[57,20357,20358,20362,20368],{},[75,20359,20360],{},[19,20361,15750],{},[75,20363,20364,20367],{},[27,20365,20366],{},"完全没被用过","的内存",[75,20369,20370],{},"小是正常的，Linux 会把空闲内存拿去做页缓存",[57,20372,20373,20378,20381],{},[75,20374,20375],{},[19,20376,20377],{},"buff\u002Fcache",[75,20379,20380],{},"块设备缓冲 + 页缓存",[75,20382,20383,20386],{},[27,20384,20385],{},"可随时回收","，看着大不是问题",[57,20388,20389,20394,20397],{},[75,20390,20391],{},[19,20392,20393],{},"available",[75,20395,20396],{},"估算「还能给新应用多少」（free + 可回收的 cache）",[75,20398,20399],{},[27,20400,20401],{},"判断内存够不够只看这一列",[57,20403,20404,20409,20416],{},[75,20405,20406],{},[19,20407,20408],{},"shared",[75,20410,20411,20412,20415],{},"tmpfs（含 ",[19,20413,20414],{},"\u002Fdev\u002Fshm","）占用",[75,20417,20418],{},"变大会真吃掉内存",[15,20420,20421,20422,20424,20425,465,20428,20431,20432,6508,20434,662,20436,20438,20439,20441,20442,20445,20446,20449,20450,20453],{},"「内存吃满」的判据：",[19,20423,20393],{}," 很小 ",[27,20426,20427],{},"且",[19,20429,20430],{},"Swap"," 开始进出（",[19,20433,11196],{},[19,20435,11236],{},[19,20437,11239],{}," 非 0）。只看 ",[19,20440,15750],{}," 那一列得出「内存不足」的结论是错的。查谁在用：",[19,20443,20444],{},"ps -eo pid,user,rss,cmd --sort=-rss | head","（RSS 单位 KB）、",[19,20447,20448],{},"smem -rk","（含 PSS，共享内存分摊更准）、",[19,20451,20452],{},"sudo systemd-cgtop memory","（按 cgroup 看，容器环境最准）。",[46,20455,20457],{"id":20456},"_2-uptime-负载要结合核数看","2. uptime —— 负载：要结合核数看",[211,20459,20461],{"className":213,"code":20460,"language":215,"meta":216,"style":216},"uptime    #  09:42:11 up 14 days,  3:29,  2 users,  load average: 1.85, 1.42, 0.98\nnproc     #  4\n",[19,20462,20463,20471],{"__ignoreMap":216},[220,20464,20465,20468],{"class":222,"line":223},[220,20466,20467],{"class":226},"uptime",[220,20469,20470],{"class":233},"    #  09:42:11 up 14 days,  3:29,  2 users,  load average: 1.85, 1.42, 0.98\n",[220,20472,20473,20475],{"class":222,"line":276},[220,20474,17806],{"class":226},[220,20476,20477],{"class":233},"     #  4\n",[51,20479,20480,20490],{},[54,20481,20482],{},[57,20483,20484,20487],{},[60,20485,20486],{},"观察",[60,20488,20489],{},"判读",[70,20491,20492,20503,20522,20537],{},[57,20493,20494,20497],{},[75,20495,20496],{},"1 \u002F 5 \u002F 15 分钟三个数",[75,20498,20499,20500],{},"短期抖动看第一个；持续升高看第三个；",[27,20501,20502],{},"15 分钟值高才是真有问题",[57,20504,20505,20508],{},[75,20506,20507],{},"与核数之比",[75,20509,20510,20513,20514,20517,20518,20521],{},[19,20511,20512],{},"load \u002F nproc"," ≈ 1 表示刚好排满；4 核上 ",[19,20515,20516],{},"load 8"," = 一半时间在排队；",[19,20519,20520],{},"load 1"," 在 4 核上算很闲",[57,20523,20524,20530],{},[75,20525,20526,20527,20529],{},"高 load 但 ",[19,20528,10031],{}," 不高",[75,20531,20532,20533,20536],{},"说明在等 ",[27,20534,20535],{},"D 状态","（磁盘\u002FNFS）——不是 CPU 不够，去查 IO",[57,20538,20539,20542],{},[75,20540,20541],{},"容器里的 load",[75,20543,20544,20545,20548,20549,20552,20553],{},"显示的是",[27,20546,20547],{},"宿主机","的，不能用它判断容器健康；看 ",[19,20550,20551],{},"docker stats"," 或 cgroup 的 ",[19,20554,20555],{},"cpu.stat",[15,20557,20558,20559,20561,20562,20564,20565,20567,20568,20570,20571,662,20573,20576,20577,19520,20579,20582,20583,662,20585,34,20587,20590,20591,20593,20594,20597,20598,616],{},"CPU 各时间的细分来源：",[19,20560,10443],{},"（应用代码）、",[19,20563,10447],{},"（内核，",[19,20566,10447],{}," 高常见于频繁上下文切换\u002F系统调用风暴）、",[19,20569,10457],{},"（等 IO）、",[19,20572,11236],{},[19,20574,20575],{},"hi","（软\u002F硬中断，网络包风暴会打高 ",[19,20578,11236],{},[19,20580,20581],{},"st","（被宿主机「偷走」的时间，云主机常见）。工具：",[19,20584,9850],{},[19,20586,10705],{},[19,20588,20589],{},"mpstat -P ALL 1","（每核分布）、",[19,20592,11263],{},"（每进程）、",[19,20595,20596],{},"perf top","（热点函数，需 kernel 权限）、cgroup 限额导致的 ",[19,20599,20600],{},"throttled_time",[46,20602,20604],{"id":20603},"_3-iostatsarvmstat-磁盘-io","3. iostat、sar、vmstat —— 磁盘 IO",[51,20606,20607,20619],{},[54,20608,20609],{},[57,20610,20611,20614,20616],{},[60,20612,20613],{},"工具",[60,20615,5168],{},[60,20617,20618],{},"关键列",[70,20620,20621,20655,20693,20719,20737,20749],{},[57,20622,20623,20629,20634],{},[75,20624,20625,20628],{},[19,20626,20627],{},"iostat","（sysstat 包）",[75,20630,20631],{},[19,20632,20633],{},"iostat -xz 1",[75,20635,20636,20639,20640,20643,20644,662,20647,20650,20651,20654],{},[19,20637,20638],{},"%util","（设备繁忙比例，接近 100 说明设备是瓶颈）、",[19,20641,20642],{},"await","（平均等待 ms，SSD 应 \u003C1，HDD \u003C10）、",[19,20645,20646],{},"r_await",[19,20648,20649],{},"w_await"," 分开看、",[19,20652,20653],{},"avgqu-sz","（队列深度）",[57,20656,20657,20662,20684],{},[75,20658,20659],{},[19,20660,20661],{},"sar",[75,20663,20664,20667,20668,20671,20672,20675,20676,20679,20680,20683],{},[19,20665,20666],{},"sar -d 1 3","（磁盘）、",[19,20669,20670],{},"sar -u 1","（CPU）、",[19,20673,20674],{},"sar -r 1","（内存）、",[19,20677,20678],{},"sar -n DEV 1","（网卡）、",[19,20681,20682],{},"sar -B 1","（换页）",[75,20685,20686,1848,20689,20692],{},[27,20687,20688],{},"能查历史",[19,20690,20691],{},"sar -u -f \u002Fvar\u002Flog\u002Fsa\u002Fsa04","，排「昨天 3 点发生了什么」的唯一实用工具",[57,20694,20695,20699,20704],{},[75,20696,20697],{},[19,20698,11196],{},[75,20700,20701],{},[19,20702,20703],{},"vmstat 1",[75,20705,20706,20708,20709,20711,20712,662,20714,20716,20717],{},[19,20707,3888],{},"（运行队列）、",[19,20710,972],{},"（IO 阻塞数，持续 >0 说明 IO 堵）、",[19,20713,11236],{},[19,20715,11239],{},"（swap 活动）、",[19,20718,10457],{},[57,20720,20721,20726,20731],{},[75,20722,20723],{},[19,20724,20725],{},"iotop",[75,20727,20728],{},[19,20729,20730],{},"sudo iotop -o",[75,20732,20733,20734,20736],{},"哪个进程在读写（",[19,20735,5481],{}," 只显示有 IO 的）",[57,20738,20739,20744,20747],{},[75,20740,20741],{},[19,20742,20743],{},"pidstat -d 1 -p PID",[75,20745,20746],{},"每进程读写 KB\u002Fs",[75,20748,9024],{},[57,20750,20751,20759,20762],{},[75,20752,20753,662,20756],{},[19,20754,20755],{},"blktrace",[19,20757,20758],{},"biosnoop",[75,20760,20761],{},"BCC\u002Fbpftrace",[75,20763,20764],{},"内核级追踪，深度排障",[15,20766,20767,20769,20770,20773,20774,20776,20777,20779,20780,20783,20784,187,20787,20790],{},[19,20768,20638],{}," 对 SSD\u002FNVMe 阵列",[27,20771,20772],{},"不再等于饱和","（它能并行），要看 ",[19,20775,20642],{}," 与队列深度。",[19,20778,20661],{}," 的历史数据需要 ",[19,20781,20782],{},"sysstat"," 服务开启（",[19,20785,20786],{},"cat \u002Fetc\u002Fsysstat",[19,20788,20789],{},"ENABLED=yes","，RHEL 默认开）。",[46,20792,20794],{"id":20793},"_4-dmesg-内核消息与-oom-killer","4. dmesg —— 内核消息与 OOM Killer",[211,20796,20798],{"className":213,"code":20797,"language":215,"meta":216,"style":216},"dmesg -T | tail -50                                  # 人类时间戳看最近内核消息\ndmesg -w                                             # 持续看，等价 journalctl -kf\ndmesg | grep -iE 'killed process|out of memory'      # 找 OOM：内存耗尽被杀的记录\njournalctl -k --since \"1 hour ago\" | grep -i oom     # 同上，走 journald\ndmesg | grep -iE 'error|I\u002FO|reset|fail'              # 硬件错：EXT4-fs error、nvme: controller down\n",[19,20799,20800,20817,20826,20843,20866],{"__ignoreMap":216},[220,20801,20802,20805,20807,20809,20811,20814],{"class":222,"line":223},[220,20803,20804],{"class":226},"dmesg",[220,20806,14597],{"class":266},[220,20808,2626],{"class":2439},[220,20810,15562],{"class":226},[220,20812,20813],{"class":266}," -50",[220,20815,20816],{"class":233},"                                  # 人类时间戳看最近内核消息\n",[220,20818,20819,20821,20823],{"class":222,"line":276},[220,20820,20804],{"class":226},[220,20822,6691],{"class":266},[220,20824,20825],{"class":233},"                                             # 持续看，等价 journalctl -kf\n",[220,20827,20828,20830,20832,20834,20837,20840],{"class":222,"line":594},[220,20829,20804],{"class":226},[220,20831,2626],{"class":2439},[220,20833,315],{"class":226},[220,20835,20836],{"class":266}," -iE",[220,20838,20839],{"class":229}," 'killed process|out of memory'",[220,20841,20842],{"class":233},"      # 找 OOM：内存耗尽被杀的记录\n",[220,20844,20845,20847,20849,20851,20854,20856,20858,20860,20863],{"class":222,"line":925},[220,20846,11328],{"class":226},[220,20848,281],{"class":266},[220,20850,12043],{"class":266},[220,20852,20853],{"class":229}," \"1 hour ago\"",[220,20855,2626],{"class":2439},[220,20857,315],{"class":226},[220,20859,1593],{"class":266},[220,20861,20862],{"class":229}," oom",[220,20864,20865],{"class":233},"     # 同上，走 journald\n",[220,20867,20868,20870,20872,20874,20876,20879],{"class":222,"line":931},[220,20869,20804],{"class":226},[220,20871,2626],{"class":2439},[220,20873,315],{"class":226},[220,20875,20836],{"class":266},[220,20877,20878],{"class":229}," 'error|I\u002FO|reset|fail'",[220,20880,20881],{"class":233},"              # 硬件错：EXT4-fs error、nvme: controller down\n",[15,20883,20884,20885,20888,20889,20892,20893,20896,20897,34,20900,6508,20903,616],{},"OOM 记录会写清是谁被杀：",[19,20886,20887],{},"Out of memory: Killed process 28414 (java) total-vm:…","。保护关键进程：",[19,20890,20891],{},"echo -1000 > \u002Fproc\u002F$(pgrep app)\u002Foom_score_adj","（范围 -1000~1000），systemd 里写 ",[19,20894,20895],{},"OOMScoreAdjust=","。容器被杀看 cgroup：",[19,20898,20899],{},"docker inspect --format '{{.State.OOMKilled}}' id",[19,20901,20902],{},"cat \u002Fsys\u002Ffs\u002Fcgroup\u002F...\u002Fmemory.events",[19,20904,20905],{},"oom_kill",[15,20907,20908,20909,20911,20912,20915,20916,20919,20920,20923],{},"OOM 之后要做的：确认是谁涨的（",[19,20910,9846],{}," 按 RSS + 应用日志）、给服务加 ",[19,20913,20914],{},"MemoryMax=","（systemd）或容器 ",[19,20917,20918],{},"--memory","，避免它把整机拖死；",[19,20921,20922],{},"vm.swappiness=10"," 让内核更愿意用 swap 而不是直接杀（不是根治）。",[46,20925,20927],{"id":20926},"_5-lsof-谁在用这个文件端口","5. lsof —— 谁在用这个文件\u002F端口",[211,20929,20931],{"className":213,"code":20930,"language":215,"meta":216,"style":216},"sudo lsof -i :8080              # 按端口找进程；-i TCP:8080、-i 4（仅 IPv4）、-sTCP:LISTEN 只看监听\nsudo lsof \u002Fsrv\u002Fapp\u002Fapp.log      # 按文件找进程——排「umount busy」「空间不回收」都用它\nsudo lsof +L1                   # 已删除但被占用：链接数 \u003C1 的文件，磁盘空间被它占着\nsudo lsof -u deploy             # 按用户：看某人开了什么\nsudo lsof -p 28414              # 按 PID 全量：列它的 fd、mmap、库、socket\nsudo lsof -i                    # 只看网络，与 ss 互补（能看到 socket 文件描述符号）\nlsof | wc -l                    # 排查 fd 泄漏：ls \u002Fproc\u002FPID\u002Ffd | wc -l 对比 ulimit -n\n",[19,20932,20933,20947,20959,20970,20983,20996,21007],{"__ignoreMap":216},[220,20934,20935,20937,20939,20941,20944],{"class":222,"line":223},[220,20936,120],{"class":226},[220,20938,15630],{"class":229},[220,20940,1593],{"class":266},[220,20942,20943],{"class":229}," :8080",[220,20945,20946],{"class":233},"              # 按端口找进程；-i TCP:8080、-i 4（仅 IPv4）、-sTCP:LISTEN 只看监听\n",[220,20948,20949,20951,20953,20956],{"class":222,"line":276},[220,20950,120],{"class":226},[220,20952,15630],{"class":229},[220,20954,20955],{"class":229}," \u002Fsrv\u002Fapp\u002Fapp.log",[220,20957,20958],{"class":233},"      # 按文件找进程——排「umount busy」「空间不回收」都用它\n",[220,20960,20961,20963,20965,20967],{"class":222,"line":594},[220,20962,120],{"class":226},[220,20964,15630],{"class":229},[220,20966,15633],{"class":229},[220,20968,20969],{"class":233},"                   # 已删除但被占用：链接数 \u003C1 的文件，磁盘空间被它占着\n",[220,20971,20972,20974,20976,20978,20980],{"class":222,"line":925},[220,20973,120],{"class":226},[220,20975,15630],{"class":229},[220,20977,4787],{"class":266},[220,20979,4424],{"class":229},[220,20981,20982],{"class":233},"             # 按用户：看某人开了什么\n",[220,20984,20985,20987,20989,20991,20993],{"class":222,"line":931},[220,20986,120],{"class":226},[220,20988,15630],{"class":229},[220,20990,1385],{"class":266},[220,20992,9903],{"class":266},[220,20994,20995],{"class":233},"              # 按 PID 全量：列它的 fd、mmap、库、socket\n",[220,20997,20998,21000,21002,21004],{"class":222,"line":3528},[220,20999,120],{"class":226},[220,21001,15630],{"class":229},[220,21003,1593],{"class":266},[220,21005,21006],{"class":233},"                    # 只看网络，与 ss 互补（能看到 socket 文件描述符号）\n",[220,21008,21009,21012,21014,21016,21018],{"class":222,"line":3537},[220,21010,21011],{"class":226},"lsof",[220,21013,2626],{"class":2439},[220,21015,15844],{"class":226},[220,21017,3193],{"class":266},[220,21019,21020],{"class":233},"                    # 排查 fd 泄漏：ls \u002Fproc\u002FPID\u002Ffd | wc -l 对比 ulimit -n\n",[15,21022,21023,21025,21026,662,21028,21031,21032,616,21034,21037,21038,21041],{},[19,21024,21011],{}," 慢（要扫所有进程的 fd），生产上先看 ",[19,21027,13663],{},[19,21029,21030],{},"\u002Fproc\u002FPID\u002Ffd","，需要精确定位再 ",[19,21033,21011],{},[19,21035,21036],{},"fuser -vm \u002Fdata"," 是它的轻量替代（",[19,21039,21040],{},"fuser -k \u002Fdata"," 直接杀占用者——慎用）。",[46,21043,21045],{"id":21044},"_6-strace-ltrace-卡在哪个系统调用","6. strace \u002F ltrace —— 卡在哪个系统调用",[211,21047,21049],{"className":213,"code":21048,"language":215,"meta":216,"style":216},"strace -f -tt .\u002Fapp                  # 从头跟踪：-f 含子进程、-tt 带时间戳\nstrace -p 28414 -f -s 256             # 附加到运行中的进程（-s 256 加长字符串打印）\nstrace -e trace=network,file -p PID   # 只看某类调用（-e trace=%file）\nstrace -c -p 28414                    # 只出统计：哪个调用耗时\u002F次数最多——排「慢」先看这个\nstrace -o \u002Ftmp\u002Fst.log -yy -ttt -f -p PID   # 落盘分析（-yy 给 socket 对端地址）\nstrace -e openat -f bash script.sh    # 「脚本到底在读哪个配置文件」的终极答案\nltrace -c -p PID                       # 跟踪库函数调用（看它调了哪个 printf\u002FSSL_read）\nperf trace                             # 更低开销的 strace（bpf 实现）\n",[19,21050,21051,21067,21085,21102,21115,21139,21158,21172],{"__ignoreMap":216},[220,21052,21053,21056,21058,21061,21064],{"class":222,"line":223},[220,21054,21055],{"class":226},"strace",[220,21057,2573],{"class":266},[220,21059,21060],{"class":266}," -tt",[220,21062,21063],{"class":229}," .\u002Fapp",[220,21065,21066],{"class":233},"                  # 从头跟踪：-f 含子进程、-tt 带时间戳\n",[220,21068,21069,21071,21073,21075,21077,21079,21082],{"class":222,"line":276},[220,21070,21055],{"class":226},[220,21072,1385],{"class":266},[220,21074,9903],{"class":266},[220,21076,2573],{"class":266},[220,21078,1978],{"class":266},[220,21080,21081],{"class":266}," 256",[220,21083,21084],{"class":233},"             # 附加到运行中的进程（-s 256 加长字符串打印）\n",[220,21086,21087,21089,21091,21094,21096,21099],{"class":222,"line":594},[220,21088,21055],{"class":226},[220,21090,12269],{"class":266},[220,21092,21093],{"class":229}," trace=network,file",[220,21095,1385],{"class":266},[220,21097,21098],{"class":229}," PID",[220,21100,21101],{"class":233},"   # 只看某类调用（-e trace=%file）\n",[220,21103,21104,21106,21108,21110,21112],{"class":222,"line":925},[220,21105,21055],{"class":226},[220,21107,2494],{"class":266},[220,21109,1385],{"class":266},[220,21111,9903],{"class":266},[220,21113,21114],{"class":233},"                    # 只出统计：哪个调用耗时\u002F次数最多——排「慢」先看这个\n",[220,21116,21117,21119,21121,21124,21127,21130,21132,21134,21136],{"class":222,"line":931},[220,21118,21055],{"class":226},[220,21120,5606],{"class":266},[220,21122,21123],{"class":229}," \u002Ftmp\u002Fst.log",[220,21125,21126],{"class":266}," -yy",[220,21128,21129],{"class":266}," -ttt",[220,21131,2573],{"class":266},[220,21133,1385],{"class":266},[220,21135,21098],{"class":229},[220,21137,21138],{"class":233},"   # 落盘分析（-yy 给 socket 对端地址）\n",[220,21140,21141,21143,21145,21148,21150,21152,21155],{"class":222,"line":3528},[220,21142,21055],{"class":226},[220,21144,12269],{"class":266},[220,21146,21147],{"class":229}," openat",[220,21149,2573],{"class":266},[220,21151,17463],{"class":229},[220,21153,21154],{"class":229}," script.sh",[220,21156,21157],{"class":233},"    # 「脚本到底在读哪个配置文件」的终极答案\n",[220,21159,21160,21163,21165,21167,21169],{"class":222,"line":3537},[220,21161,21162],{"class":226},"ltrace",[220,21164,2494],{"class":266},[220,21166,1385],{"class":266},[220,21168,21098],{"class":229},[220,21170,21171],{"class":233},"                       # 跟踪库函数调用（看它调了哪个 printf\u002FSSL_read）\n",[220,21173,21174,21177,21180],{"class":222,"line":3546},[220,21175,21176],{"class":226},"perf",[220,21178,21179],{"class":229}," trace",[220,21181,21182],{"class":233},"                             # 更低开销的 strace（bpf 实现）\n",[15,21184,21185,21187,21188,21190,21191,662,21194,662,21197,662,21200,21203,21204,21206],{},[19,21186,21055],{}," 会让目标进程明显变慢（每次系统调用都要停下与 ptrace 通信），生产环境用 ",[19,21189,14692],{}," 抽样或短时间附加，别长时间挂着。看 ",[19,21192,21193],{},"EACCES",[19,21195,21196],{},"ENOENT",[19,21198,21199],{},"EAGAIN",[19,21201,21202],{},"ETIMEDOUT"," 这几个错误码，通常一步到位定位问题。",[19,21205,21162],{}," 对静态编译的程序无效（没有库调用可跟踪）。",[46,21208,21210],{"id":21209},"_7-tcpdump-与实时流量","7. tcpdump 与实时流量",[211,21212,21214],{"className":213,"code":21213,"language":215,"meta":216,"style":216},"sudo tcpdump -i any -nn tcp port 8080               # 看某端口有没有流量\nsudo tcpdump -i eth0 -nn host 10.0.0.5 and port 5432  # 抓某主机与某服务的全部包\nsudo tcpdump -i any -w \u002Ftmp\u002Flost.pcap tcp port 443   # 抓下来给 Wireshark 分析（-W 5 -C 50 分片限大小）\nsudo tcpdump -nn 'tcp[tcpflags] & (tcp-syn) != 0 and dst port 443'   # 只看 SYN 无响应（被防火墙丢）\nsudo tcpdump -nn 'tcp[tcpflags] & tcp-rst != 0'      # 看谁在连不上时发 RST\nsudo tcpdump -nn -s0 -A port 53                      # 抓 DNS 问答\n",[19,21215,21216,21242,21268,21293,21307,21321],{"__ignoreMap":216},[220,21217,21218,21220,21223,21225,21228,21231,21234,21237,21239],{"class":222,"line":223},[220,21219,120],{"class":226},[220,21221,21222],{"class":229}," tcpdump",[220,21224,1593],{"class":266},[220,21226,21227],{"class":229}," any",[220,21229,21230],{"class":266}," -nn",[220,21232,21233],{"class":229}," tcp",[220,21235,21236],{"class":229}," port",[220,21238,14335],{"class":266},[220,21240,21241],{"class":233},"               # 看某端口有没有流量\n",[220,21243,21244,21246,21248,21250,21252,21254,21256,21258,21261,21263,21265],{"class":222,"line":276},[220,21245,120],{"class":226},[220,21247,21222],{"class":229},[220,21249,1593],{"class":266},[220,21251,13745],{"class":229},[220,21253,21230],{"class":266},[220,21255,14213],{"class":229},[220,21257,13971],{"class":266},[220,21259,21260],{"class":229}," and",[220,21262,21236],{"class":229},[220,21264,14323],{"class":266},[220,21266,21267],{"class":233},"  # 抓某主机与某服务的全部包\n",[220,21269,21270,21272,21274,21276,21278,21280,21283,21285,21287,21290],{"class":222,"line":594},[220,21271,120],{"class":226},[220,21273,21222],{"class":229},[220,21275,1593],{"class":266},[220,21277,21227],{"class":229},[220,21279,6691],{"class":266},[220,21281,21282],{"class":229}," \u002Ftmp\u002Flost.pcap",[220,21284,21233],{"class":229},[220,21286,21236],{"class":229},[220,21288,21289],{"class":266}," 443",[220,21291,21292],{"class":233},"   # 抓下来给 Wireshark 分析（-W 5 -C 50 分片限大小）\n",[220,21294,21295,21297,21299,21301,21304],{"class":222,"line":925},[220,21296,120],{"class":226},[220,21298,21222],{"class":229},[220,21300,21230],{"class":266},[220,21302,21303],{"class":229}," 'tcp[tcpflags] & (tcp-syn) != 0 and dst port 443'",[220,21305,21306],{"class":233},"   # 只看 SYN 无响应（被防火墙丢）\n",[220,21308,21309,21311,21313,21315,21318],{"class":222,"line":931},[220,21310,120],{"class":226},[220,21312,21222],{"class":229},[220,21314,21230],{"class":266},[220,21316,21317],{"class":229}," 'tcp[tcpflags] & tcp-rst != 0'",[220,21319,21320],{"class":233},"      # 看谁在连不上时发 RST\n",[220,21322,21323,21325,21327,21329,21332,21334,21336,21339],{"class":222,"line":3528},[220,21324,120],{"class":226},[220,21326,21222],{"class":229},[220,21328,21230],{"class":266},[220,21330,21331],{"class":266}," -s0",[220,21333,2396],{"class":266},[220,21335,21236],{"class":229},[220,21337,21338],{"class":266}," 53",[220,21340,21341],{"class":233},"                      # 抓 DNS 问答\n",[15,21343,21344,21345,21348,21349,21352,21353,21356,21357,21360,21361,662,21364,21367,21368,21370,21371,689,21374,21377,21378,21381,21382,21385,21386,21388,21389,21391],{},"参数要点：",[19,21346,21347],{},"-nn","（不解析端口\u002F主机名，避免自己造 DNS 流量并卡住）、",[19,21350,21351],{},"-i any","（跨网卡，容器\u002F桥接场景必备）、",[19,21354,21355],{},"-s 0","（抓完整包，老版本默认截 96 字节）、",[19,21358,21359],{},"-c 100","（限量防写爆磁盘）、",[19,21362,21363],{},"-A",[19,21365,21366],{},"-X","（看载荷，明文协议调试用）、",[19,21369,13988],{},"（行缓冲实时输出，配管道用）。看实时流量分布用 ",[19,21372,21373],{},"nethogs",[27,21375,21376],{},"按进程","看带宽，找「谁在下东西」最直接）或 ",[19,21379,21380],{},"iftop -P","（按连接看，",[19,21383,21384],{},"-i eth0","，界面里 ",[19,21387,15],{}," 显示累计、",[19,21390,972],{}," 切换源\u002F目的排序）。",[46,21393,21395],{"id":21394},"_8-现象-先跑哪几条命令","8. 现象 → 先跑哪几条命令",[51,21397,21398,21410],{},[54,21399,21400],{},[57,21401,21402,21404,21407],{},[60,21403,18123],{},[60,21405,21406],{},"第一批命令（照抄）",[60,21408,21409],{},"看到什么算定位",[70,21411,21412,21449,21482,21501,21531,21555,21576,21604,21637,21661,21687,21709,21731],{},[57,21413,21414,21417,21434],{},[75,21415,21416],{},"CPU 高 \u002F 变慢",[75,21418,21419,15156,21422,21424,21425,21427,21428,15156,21431],{},[19,21420,21421],{},"uptime; nproc",[19,21423,9850],{},"（按 ",[19,21426,10598],{},"）→ ",[19,21429,21430],{},"pidstat 1 5",[19,21432,21433],{},"strace -c -p PID",[75,21435,21436,21438,21439,21441,21442,21444,21445,21448],{},[19,21437,10443],{}," 高 = 应用算法\u002F循环；",[19,21440,10447],{}," 高 = 系统调用风暴；",[19,21443,10457],{}," 高 = 其实是 IO；",[19,21446,21447],{},"load"," 高但 CPU 空 = D 状态堆",[57,21450,21451,21454,21467],{},[75,21452,21453],{},"内存吃满 \u002F 被 OOM",[75,21455,21456,15156,21459,15156,21462,15156,21464],{},[19,21457,21458],{},"free -h",[19,21460,21461],{},"dmesg | grep -i kill",[19,21463,20444],{},[19,21465,21466],{},"cat \u002Fproc\u002Fmeminfo | head",[75,21468,21469,21471,21472,21475,21476,662,21479,21481],{},[19,21470,20393],{}," 小 + ",[19,21473,21474],{},"si\u002Fso"," 活动 = 真缺；",[19,21477,21478],{},"SReclaimable",[19,21480,20377],{}," 大 = 假象",[57,21483,21484,21487,21498],{},[75,21485,21486],{},"磁盘满",[75,21488,21489,15156,21491,15156,21493,15156,21495],{},[19,21490,15454],{},[19,21492,15784],{},[19,21494,16466],{},[19,21496,21497],{},"sudo lsof +L1",[75,21499,21500],{},"三者各对应：块满 \u002F inode 满 \u002F 已删除未释放",[57,21502,21503,21506,21516],{},[75,21504,21505],{},"磁盘 IO 高",[75,21507,21508,15156,21510,15156,21513],{},[19,21509,20633],{},[19,21511,21512],{},"iotop -o",[19,21514,21515],{},"pidstat -d 1",[75,21517,21518,21520,21521,21523,21524,21527,21528,21530],{},[19,21519,20638],{},"≈100 且 ",[19,21522,20642],{}," 大 = 设备瓶颈；",[19,21525,21526],{},"util"," 低但 ",[19,21529,20642],{}," 高 = 队列排队\u002F驱动",[57,21532,21533,21536,21552],{},[75,21534,21535],{},"网络不通",[75,21537,21538,15156,21540,15156,21543,15156,21546,15156,21549],{},[19,21539,13840],{},[19,21541,21542],{},"ip route get 目标IP",[19,21544,21545],{},"ping -c 3",[19,21547,21548],{},"nc -vz 目标 端口",[19,21550,21551],{},"curl -v",[75,21553,21554],{},"路由不通 \u002F ICMP 被封 \u002F TCP 连不上 \u002F TLS 或 HTTP 层错，四层各归各位",[57,21556,21557,21560,21570],{},[75,21558,21559],{},"端口没监听",[75,21561,21562,15156,21564,15156,21567],{},[19,21563,14058],{},[19,21565,21566],{},"ss -lntp | grep 8080",[19,21568,21569],{},"systemctl status 服务",[75,21571,21572,21573,21575],{},"根本没起 vs 只绑了 ",[19,21574,15283],{}," vs 被别的服务占了",[57,21577,21578,21581,21592],{},[75,21579,21580],{},"服务起不来",[75,21582,21583,15156,21586,15156,21589],{},[19,21584,21585],{},"systemctl status 名",[19,21587,21588],{},"journalctl -u 名 -n 50 --no-pager",[19,21590,21591],{},"systemctl cat 名",[75,21593,21594,21595,34,21598,34,21601,21603],{},"看 ",[19,21596,21597],{},"Main PID",[19,21599,21600],{},"Exit code",[19,21602,11789],{}," 路径是否存在与权限",[57,21605,21606,21609,21627],{},[75,21607,21608],{},"进程杀不掉",[75,21610,21611,15156,21614,15156,21617,21620,21621,21623,21624],{},[19,21612,21613],{},"ps -o pid,stat,user,cmd -p PID",[19,21615,21616],{},"sudo kill 15",[19,21618,21619],{},"sudo kill -9"," → 若 ",[19,21622,10209],{}," 状态：",[19,21625,21626],{},"cat \u002Fproc\u002FPID\u002Fwchan",[75,21628,21629,21631,21632,21634,21635],{},[19,21630,10209],{}," = 等内核 IO（杀不动，修底层）；",[19,21633,9842],{}," = 僵尸（父进程回收）；systemd 拉起 = 用 ",[19,21636,11292],{},[57,21638,21639,21642,21655],{},[75,21640,21641],{},"时间不对",[75,21643,21644,15156,21646,662,21649,15156,21652],{},[19,21645,12531],{},[19,21647,21648],{},"chronyc sources",[19,21650,21651],{},"ntpq -p",[19,21653,21654],{},"hwclock -r",[75,21656,21657,21660],{},[19,21658,21659],{},"NTPSynchronized=no","、时区错、双系统 UTC\u002F本地冲突",[57,21662,21663,21666,21681],{},[75,21664,21665],{},"权限被拒",[75,21667,21668,15156,21671,15156,21673,15156,21676,662,21678],{},[19,21669,21670],{},"sudo -u appuser 原命令",[19,21672,5410],{},[19,21674,21675],{},"id appuser",[19,21677,5238],{},[19,21679,21680],{},"ausearch -m avc",[75,21682,21683,21684,21686],{},"父目录缺 ",[19,21685,3558],{},"、组变更后没重登、SELinux 上下文",[57,21688,21689,21692,21706],{},[75,21690,21691],{},"文件找不到\u002F版本乱",[75,21693,21694,15156,21697,3324,21700,21427,21703],{},[19,21695,21696],{},"command -v X",[19,21698,21699],{},"dpkg -S $(command -v X)",[19,21701,21702],{},"rpm -qf",[19,21704,21705],{},"update-alternatives --list X",[75,21707,21708],{},"多版本共存时看谁在前",[57,21710,21711,21714,21728],{},[75,21712,21713],{},"登录不上 SSH",[75,21715,21716,15156,21719,15156,21722,15156,21725],{},[19,21717,21718],{},"ssh -v",[19,21720,21721],{},"journalctl -u ssh -n 50",[19,21723,21724],{},"sudo ss -tlnp | grep 22",[19,21726,21727],{},"sudo tail -50 \u002Fvar\u002Flog\u002Fauth.log",[75,21729,21730],{},"端口、配置、认证方式、faillock 各查一段",[57,21732,21733,21736,21744],{},[75,21734,21735],{},"某个应用行为怪",[75,21737,21738,15156,21741],{},[19,21739,21740],{},"strace -f -e trace=file,network .\u002Fapp",[19,21742,21743],{},"lsof -p PID",[75,21745,21746],{},"读错配置、连错地址、写到只读目录",[46,21748,21750],{"id":21749},"_9-总速查表我想干一件事-用什么命令","9. 总速查表：我想干一件事 → 用什么命令",[51,21752,21753,21762],{},[54,21754,21755],{},[57,21756,21757,21760],{},[60,21758,21759],{},"我想",[60,21761,5168],{},[70,21763,21764,21778,21788,21798,21810,21826,21839,21852,21862,21872,21884,21896,21910,21920,21936,21949,21962,21972,21982,21992,22005,22018,22032,22045,22055,22069,22088,22102,22116,22132,22145,22167,22176,22186,22198,22211,22224,22238,22253],{},[57,21765,21766,21769],{},[75,21767,21768],{},"看我在哪、有什么",[75,21770,21771,34,21773,34,21775],{},[19,21772,1265],{},[19,21774,2324],{},[19,21776,21777],{},"tree -L 2",[57,21779,21780,21783],{},[75,21781,21782],{},"找文件（按名字）",[75,21784,21785],{},[19,21786,21787],{},"find \u002F -xdev -maxdepth 4 -name '*.log' -mtime -7",[57,21789,21790,21793],{},[75,21791,21792],{},"找文件（按内容）",[75,21794,21795],{},[19,21796,21797],{},"grep -rl '关键词' \u002Fetc",[57,21799,21800,21803],{},[75,21801,21802],{},"找命令属于哪个包",[75,21804,21805,1073,21807],{},[19,21806,21699],{},[19,21808,21809],{},"rpm -qf $(command -v X)",[57,21811,21812,21815],{},[75,21813,21814],{},"看文件内容",[75,21816,21817,34,21820,34,21823],{},[19,21818,21819],{},"less -N file",[19,21821,21822],{},"head -n 200",[19,21824,21825],{},"tail -F file",[57,21827,21828,21831],{},[75,21829,21830],{},"看不可见字符\u002F换行",[75,21832,21833,34,21836],{},[19,21834,21835],{},"cat -A file",[19,21837,21838],{},"od -c -N 64 file",[57,21840,21841,21844],{},[75,21842,21843],{},"批量替换配置",[75,21845,21846,3234,21849],{},[19,21847,21848],{},"sed -i.bak 's\u002F^旧\u002F新\u002F' file",[19,21850,21851],{},"diff file.bak file",[57,21853,21854,21857],{},[75,21855,21856],{},"按列统计",[75,21858,21859],{},[19,21860,21861],{},"awk '{print $1}' log | sort | uniq -c | sort -rn | head",[57,21863,21864,21867],{},[75,21865,21866],{},"看谁占空间",[75,21868,21869],{},[19,21870,21871],{},"du -h --max-depth=1 . | sort -h | tail -15",[57,21873,21874,21877],{},[75,21875,21876],{},"看磁盘\u002Finode",[75,21878,21879,34,21882],{},[19,21880,21881],{},"df -hT",[19,21883,15784],{},[57,21885,21886,21889],{},[75,21887,21888],{},"看挂载与读写状态",[75,21890,21891,34,21894],{},[19,21892,21893],{},"findmnt -T \u002F路径",[19,21895,15960],{},[57,21897,21898,21901],{},[75,21899,21900],{},"看进程",[75,21902,21903,34,21906,34,21908],{},[19,21904,21905],{},"ps aux --sort=-%cpu | head",[19,21907,9850],{},[19,21909,10705],{},[57,21911,21912,21915],{},[75,21913,21914],{},"按名字找进程",[75,21916,21917],{},[19,21918,21919],{},"pgrep -af '关键词'",[57,21921,21922,21925],{},[75,21923,21924],{},"停服务 \u002F 停进程",[75,21926,21927,418,21930,21932,21933],{},[19,21928,21929],{},"systemctl stop app",[19,21931,10844],{}," → 10 秒后 ",[19,21934,21935],{},"kill -9 28414",[57,21937,21938,21941],{},[75,21939,21940],{},"看服务日志",[75,21942,21943,34,21946],{},[19,21944,21945],{},"journalctl -u app -f",[19,21947,21948],{},"journalctl -u app --since \"10 min ago\" -p err",[57,21950,21951,21954],{},[75,21952,21953],{},"改服务配置",[75,21955,21956,15156,21959],{},[19,21957,21958],{},"systemctl edit app",[19,21960,21961],{},"systemctl daemon-reload && systemctl restart app",[57,21963,21964,21967],{},[75,21965,21966],{},"看谁在监听端口",[75,21968,21969],{},[19,21970,21971],{},"sudo ss -tulnp",[57,21973,21974,21977],{},[75,21975,21976],{},"测端口通不通",[75,21978,21979],{},[19,21980,21981],{},"nc -vz 10.0.0.5 5432",[57,21983,21984,21987],{},[75,21985,21986],{},"测接口健康",[75,21988,21989],{},[19,21990,21991],{},"curl -sS -o \u002Fdev\u002Fnull -w '%{http_code} %{time_total}\\n' URL",[57,21993,21994,21997],{},[75,21995,21996],{},"验证 DNS",[75,21998,21999,34,22002],{},[19,22000,22001],{},"getent hosts 域名",[19,22003,22004],{},"dig +short 域名 @1.1.1.1",[57,22006,22007,22010],{},[75,22008,22009],{},"走跳板连内网",[75,22011,22012,34,22015],{},[19,22013,22014],{},"ssh -J bastion app",[19,22016,22017],{},"ssh -L 5432:db:5432 bastion",[57,22019,22020,22023],{},[75,22021,22022],{},"备份\u002F同步目录",[75,22024,22025,22028,22029,22031],{},[19,22026,22027],{},"rsync -avz --delete src\u002F host:\u002Fbackup\u002F","（先 ",[19,22030,2007],{}," 干跑）",[57,22033,22034,22037],{},[75,22035,22036],{},"打包 \u002F 解包",[75,22038,22039,34,22042],{},[19,22040,22041],{},"tar czf app.tar.gz 目录",[19,22043,22044],{},"tar xzf app.tar.gz -C 目标",[57,22046,22047,22050],{},[75,22048,22049],{},"不解压搜日志",[75,22051,22052],{},[19,22053,22054],{},"zgrep -h 'ERROR' \u002Fvar\u002Flog\u002F*.gz",[57,22056,22057,22060],{},[75,22058,22059],{},"加一条定时任务",[75,22061,22062,22065,22066,22068],{},[19,22063,22064],{},"crontab -e","（命令写绝对路径、输出重定向、",[19,22067,12457],{}," 转义）",[57,22070,22071,22074],{},[75,22072,22073],{},"建用户\u002F加组",[75,22075,22076,34,22079,689,22082,22087],{},[19,22077,22078],{},"sudo useradd -m -s \u002Fbin\u002Fbash 名字",[19,22080,22081],{},"sudo usermod -aG docker 名字",[27,22083,22084,22085],{},"必须 ",[19,22086,13117],{},"，然后重新登录）",[57,22089,22090,22093],{},[75,22091,22092],{},"看自己身份",[75,22094,22095,34,22097,34,22099],{},[19,22096,10451],{},[19,22098,12790],{},[19,22100,22101],{},"sudo -l",[57,22103,22104,22107],{},[75,22105,22106],{},"修权限",[75,22108,22109,34,22112,34,22114],{},[19,22110,22111],{},"chown -R app:app \u002Fsrv\u002Fapp",[19,22113,4232],{},[19,22115,5410],{},[57,22117,22118,22121],{},[75,22119,22120],{},"装\u002F卸包",[75,22122,22123,1073,22126,1073,22129],{},[19,22124,22125],{},"apt install -y pkg",[19,22127,22128],{},"dnf -y install pkg",[19,22130,22131],{},"apk add --no-cache pkg",[57,22133,22134,22137],{},[75,22135,22136],{},"查包版本候选",[75,22138,22139,1073,22142],{},[19,22140,22141],{},"apt policy pkg",[19,22143,22144],{},"dnf list pkg",[57,22146,22147,22150],{},[75,22148,22149],{},"看资源总览",[75,22151,22152,34,22154,34,22156,34,22158,34,22160,662,22163,22166],{},[19,22153,21458],{},[19,22155,20467],{},[19,22157,20633],{},[19,22159,20703],{},[19,22161,22162],{},"vgs",[19,22164,22165],{},"lvs","（LVM）",[57,22168,22169,22172],{},[75,22170,22171],{},"查历史性能",[75,22173,22174],{},[19,22175,20691],{},[57,22177,22178,22181],{},[75,22179,22180],{},"抓包",[75,22182,22183],{},[19,22184,22185],{},"sudo tcpdump -i any -nn -c 200 tcp port 443",[57,22187,22188,22191],{},[75,22189,22190],{},"定位「卡在哪个调用」",[75,22192,22193,34,22195],{},[19,22194,21433],{},[19,22196,22197],{},"strace -f -e trace=file .\u002Fapp",[57,22199,22200,22203],{},[75,22201,22202],{},"看内核报错",[75,22204,22205,34,22208],{},[19,22206,22207],{},"dmesg -T | tail -50",[19,22209,22210],{},"journalctl -k -b --since \"1 hour ago\"",[57,22212,22213,22216],{},[75,22214,22215],{},"跑个脚本看细节",[75,22217,22218,22220,22221],{},[19,22219,19472],{},"、脚本头 ",[19,22222,22223],{},"set -euo pipefail",[57,22225,22226,22229],{},[75,22227,22228],{},"临时改环境变量",[75,22230,22231,34,22234,22237],{},[19,22232,22233],{},"export PATH=\"$HOME\u002F.local\u002Fbin:$PATH\"",[19,22235,22236],{},"env -i bash"," 起干净环境",[57,22239,22240,22243],{},[75,22241,22242],{},"查\u002F改时间与时区",[75,22244,22245,34,22247,34,22250],{},[19,22246,12531],{},[19,22248,22249],{},"timedatectl set-timezone Asia\u002FShanghai",[19,22251,22252],{},"timedatectl set-ntp true",[57,22254,22255,22258],{},[75,22256,22257],{},"安全地删一批文件",[75,22259,22260,22263,22264,22266,22267],{},[19,22261,22262],{},"find dir -name '*.tmp' -mtime +3 -print"," 确认后再把 ",[19,22265,5452],{}," 换 ",[19,22268,5458],{},[46,22270,22271],{"id":22271},"一句话记住",[15,22273,22274,22275,22278,22279,1083,22281,22284],{},"排障永远按资源维度各看一眼：",[27,22276,22277],{},"CPU（uptime\u002Ftop）→ 内存（free\u002Fdmesg）→ 磁盘容量（df -h \u002F df -i \u002F du）→ 磁盘 IO（iostat）→ 网络（ip\u002Fss\u002Fcurl）→ 服务（systemctl\u002Fjournalctl）→ 权限（namei\u002Fid）","。七条命令走完还定位不到，才需要 ",[19,22280,21055],{},[19,22282,22283],{},"tcpdump"," 这种深度工具。本手册第十七张表就是这张检查单的落地版本。",[22286,22287,22288],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}",{"title":216,"searchDepth":276,"depth":276,"links":22290},[22291,22299,22313,22322,22331,22342,22350,22358,22367,22375,22384,22392,22400,22409,22421,22432,22444],{"id":12,"depth":276,"text":13,"children":22292},[22293,22294,22295,22296,22297,22298],{"id":48,"depth":594,"text":49},{"id":197,"depth":594,"text":198},{"id":332,"depth":594,"text":333},{"id":427,"depth":594,"text":428},{"id":619,"depth":594,"text":620},{"id":808,"depth":594,"text":808},{"id":848,"depth":276,"text":849,"children":22300},[22301,22302,22303,22304,22305,22306,22307,22308,22309,22310,22311,22312],{"id":868,"depth":594,"text":869},{"id":1249,"depth":594,"text":1250},{"id":1291,"depth":594,"text":1292},{"id":1356,"depth":594,"text":1357},{"id":1426,"depth":594,"text":1427},{"id":1473,"depth":594,"text":1474},{"id":1523,"depth":594,"text":1524},{"id":1615,"depth":594,"text":1616},{"id":1695,"depth":594,"text":1696},{"id":1861,"depth":594,"text":1862},{"id":2023,"depth":594,"text":2024},{"id":2234,"depth":594,"text":808},{"id":2297,"depth":276,"text":2298,"children":22314},[22315,22316,22317,22318,22319,22320,22321],{"id":2312,"depth":594,"text":2313},{"id":2460,"depth":594,"text":2461},{"id":2530,"depth":594,"text":2531},{"id":2651,"depth":594,"text":2652},{"id":2902,"depth":594,"text":2903},{"id":2935,"depth":594,"text":2936},{"id":3051,"depth":594,"text":808},{"id":3097,"depth":276,"text":3098,"children":22323},[22324,22325,22326,22327,22328,22329,22330],{"id":3116,"depth":594,"text":3117},{"id":3381,"depth":594,"text":3382},{"id":3480,"depth":594,"text":3481},{"id":3597,"depth":594,"text":3598},{"id":3642,"depth":594,"text":3643},{"id":3697,"depth":594,"text":3698},{"id":3783,"depth":594,"text":808},{"id":3845,"depth":276,"text":3846,"children":22332},[22333,22334,22335,22336,22337,22338,22339,22340,22341],{"id":3863,"depth":594,"text":3864},{"id":4126,"depth":594,"text":4127},{"id":4313,"depth":594,"text":4314},{"id":4473,"depth":594,"text":4474},{"id":4642,"depth":594,"text":4643},{"id":4940,"depth":594,"text":4941},{"id":5050,"depth":594,"text":5051},{"id":5155,"depth":594,"text":5156},{"id":5358,"depth":594,"text":808},{"id":5418,"depth":276,"text":5419,"children":22343},[22344,22345,22346,22347,22348,22349],{"id":5465,"depth":594,"text":5466},{"id":5763,"depth":594,"text":5764},{"id":6148,"depth":594,"text":6149},{"id":6240,"depth":594,"text":6241},{"id":6363,"depth":594,"text":6364},{"id":6501,"depth":594,"text":808},{"id":6568,"depth":276,"text":6569,"children":22351},[22352,22353,22354,22355,22356,22357],{"id":6613,"depth":594,"text":6614},{"id":7019,"depth":594,"text":7020},{"id":7296,"depth":594,"text":7297},{"id":7571,"depth":594,"text":7572},{"id":7740,"depth":594,"text":7741},{"id":7901,"depth":594,"text":808},{"id":7982,"depth":276,"text":7983,"children":22359},[22360,22361,22362,22363,22364,22365,22366],{"id":8011,"depth":594,"text":8012},{"id":8174,"depth":594,"text":8175},{"id":8260,"depth":594,"text":8261},{"id":8445,"depth":594,"text":8446},{"id":8603,"depth":594,"text":8604},{"id":8735,"depth":594,"text":8736},{"id":8849,"depth":594,"text":808},{"id":8929,"depth":276,"text":8930,"children":22368},[22369,22370,22371,22372,22373,22374],{"id":8975,"depth":594,"text":8976},{"id":9168,"depth":594,"text":9169},{"id":9393,"depth":594,"text":9394},{"id":9505,"depth":594,"text":9506},{"id":9626,"depth":594,"text":9627},{"id":9760,"depth":594,"text":808},{"id":9832,"depth":276,"text":9833,"children":22376},[22377,22378,22379,22380,22381,22382,22383],{"id":9862,"depth":594,"text":9863},{"id":10287,"depth":594,"text":10288},{"id":10379,"depth":594,"text":10380},{"id":10736,"depth":594,"text":10737},{"id":11072,"depth":594,"text":11073},{"id":11147,"depth":594,"text":11148},{"id":11246,"depth":594,"text":808},{"id":11318,"depth":276,"text":11319,"children":22385},[22386,22387,22388,22389,22390,22391],{"id":11337,"depth":594,"text":11338},{"id":11618,"depth":594,"text":11619},{"id":12002,"depth":594,"text":12003},{"id":12246,"depth":594,"text":12247},{"id":12520,"depth":594,"text":12521},{"id":12638,"depth":594,"text":808},{"id":12693,"depth":276,"text":12694,"children":22393},[22394,22395,22396,22397,22398,22399],{"id":12707,"depth":594,"text":12708},{"id":12806,"depth":594,"text":12807},{"id":12969,"depth":594,"text":12970},{"id":13146,"depth":594,"text":13147},{"id":13220,"depth":594,"text":13221},{"id":13584,"depth":594,"text":808},{"id":13653,"depth":276,"text":13654,"children":22401},[22402,22403,22404,22405,22406,22407,22408],{"id":13677,"depth":594,"text":13678},{"id":13869,"depth":594,"text":13870},{"id":14179,"depth":594,"text":14180},{"id":14365,"depth":594,"text":14366},{"id":14706,"depth":594,"text":14707},{"id":15124,"depth":594,"text":15125},{"id":15290,"depth":594,"text":808},{"id":15386,"depth":276,"text":15387,"children":22410},[22411,22412,22413,22414,22415,22416,22417,22418,22419,22420],{"id":15403,"depth":594,"text":15404},{"id":15490,"depth":594,"text":15491},{"id":15683,"depth":594,"text":15684},{"id":15771,"depth":594,"text":15772},{"id":15931,"depth":594,"text":15932},{"id":16100,"depth":594,"text":16101},{"id":16427,"depth":594,"text":16428},{"id":16558,"depth":594,"text":16559},{"id":16679,"depth":594,"text":16680},{"id":16809,"depth":594,"text":808},{"id":16861,"depth":276,"text":16862,"children":22422},[22423,22424,22425,22426,22427,22428,22429,22430,22431],{"id":16872,"depth":594,"text":16873},{"id":17067,"depth":594,"text":17068},{"id":17155,"depth":594,"text":17156},{"id":17443,"depth":594,"text":17444},{"id":17579,"depth":594,"text":17580},{"id":17757,"depth":594,"text":17758},{"id":18002,"depth":594,"text":18003},{"id":18107,"depth":594,"text":18108},{"id":18260,"depth":594,"text":808},{"id":18328,"depth":276,"text":18329,"children":22433},[22434,22435,22436,22437,22438,22439,22440,22442,22443],{"id":18341,"depth":594,"text":18342},{"id":18486,"depth":594,"text":18487},{"id":18678,"depth":594,"text":18679},{"id":18822,"depth":594,"text":18823},{"id":19004,"depth":594,"text":19005},{"id":19344,"depth":594,"text":19345},{"id":19539,"depth":594,"text":22441},"7. [[ ]] 与 ，以及数组",{"id":19927,"depth":594,"text":19928},{"id":20193,"depth":594,"text":808},{"id":20267,"depth":276,"text":20268,"children":22445},[22446,22447,22448,22449,22450,22451,22452,22453,22454,22455],{"id":20278,"depth":594,"text":20279},{"id":20456,"depth":594,"text":20457},{"id":20603,"depth":594,"text":20604},{"id":20793,"depth":594,"text":20794},{"id":20926,"depth":594,"text":20927},{"id":21044,"depth":594,"text":21045},{"id":21209,"depth":594,"text":21210},{"id":21394,"depth":594,"text":21395},{"id":21749,"depth":594,"text":21750},{"id":22271,"depth":594,"text":22271},"linux","\u002Fimg\u002Flinux-cover.svg","2026-10-04","Linux 日常运维与开发常用命令手册：文件、权限、查找、文本处理、进程、服务、网络、磁盘、包管理、Shell 与排障，17 章按场景速查，配可交互的模拟终端示例。","md",{},"\u002Ftutorials\u002Flinux",{"title":5,"description":22459},{"loc":22462},"tutorials\u002Flinux","1LIWdJpxoVBXK9yM6RY4BBGy-0YaeXT25FlVF588ews",[22468,22470,22474,22478,22482,22486,22490,22494,22498,22502,22506,22510,22514,22518,22522,22526,22530,22534,22538,22542,22546,22550,22554,22558,22562,22566,22570,22574,22578,22582,22586,22590,22594,22598,22602,22606,22610,22614,22618,22622,22626,22630,22634,22638,22642,22646,22650,22654,22658,22662,22666,22670,22674,22678,22682,22686,22690,22694,22698,22702,22706,22710,22714,22718,22722,22726,22730,22734,22738,22742,22746,22750,22754,22758,22762,22766,22770,22774,22778,22782,22786,22790,22794,22798,22802,22806,22810,22814,22818,22822,22826,22830,22834,22838,22842,22846,22850,22854,22858,22862,22866,22870,22874,22878,22882,22886,22890,22894,22898,22902,22906,22910,22914,22918,22922,22926,22930,22934,22937,22941,22945,22949,22953,22957,22961,22965,22969,22973,22977,22981,22985,22989,22993,22997,23001,23005,23009,23013,23016,23020,23024,23028,23032,23036,23040,23044,23048,23052],{"id":22462,"title":5,"titles":22469,"content":22459,"level":223},[],{"id":22471,"title":13,"titles":22472,"content":22473,"level":276},"\u002Ftutorials\u002Flinux#一开始敲命令之前终端与帮助系统",[5],"在 Linux 中，学会「查命令」比背命令更重要。打开终端准备敲命令之前，有几个「不查不好意思问」的基础问题：眼前这行提示符是什么意思、忘了参数去哪查、为什么同一个 sh 在不同机器上行为不一样。这一章把这几件事说清楚，后面 16 章就能直接跳着查。 本手册的定位是速查：每个命令一小节，先给最常见的可直接复制的写法，再解释参数，最后补一句注意事项。遇到破坏性命令（rm -rf、mkfs、dd、kill -9）会单独标出风险。",{"id":22475,"title":49,"titles":22476,"content":22477,"level":594},"\u002Ftutorials\u002Flinux#_1-先看提示符你是谁在哪什么身份",[5,13],"观察点 长这样 含义 普通用户提示符 deploy@server:~$ 用户 deploy、主机 server、当前目录 ~、$ 表示非 root root 提示符 root@server:\u002Fsrv\u002Fapp# 结尾是 #，此刻敲的命令不再需要 sudo 只有 bash-4.4$ 容器里常见 $PS1 没配置，通常是精简镜像，功能可能缺 续行提示符 > 引号或括号没闭合，Ctrl-C 放弃当前输入 光标处 ^（vi 里） 光标在 ^ 那个字符上，不在它后面 本手册示例统一假设：用户 deploy、主机 server、工作目录 \u002Fsrv\u002Fapp 或 \u002Fhome\u002Fdeploy、Shell 是 bash。行首的 $ 是提示符的一部分，复制到终端时不要把它带上。",{"id":22479,"title":198,"titles":22480,"content":22481,"level":594},"\u002Ftutorials\u002Flinux#_2-man-查看命令手册页",[5,13],"man 是 manual（手册）的缩写，是 Linux 最权威的查命令方式。 示例 1：查看 ls 的手册页 man ls        # 打开 ls 的手册页 打开后：q 退出、\u002F关键词 搜索、n 跳到下一个匹配、空格翻页。 示例 2：指定章节编号 man 1 printf  # 1=用户命令，5=文件格式，8=管理命令\nman -k printf # 按关键词反查相关命令（等价于 apropos printf） 同一个名字在不同章节可能有多个手册页（比如 printf 既是命令又是 C 函数），man -a printf 可以一次看完所有同名章节。 示例 3：只要一行说明 whatis grep   # 只输出一行描述：grep pattern lines matched\napropos firewall  # 反过来：按关键词找命令名，能列出 rsync、dump 之类",{"id":22483,"title":333,"titles":22484,"content":22485,"level":594},"\u002Ftutorials\u002Flinux#_3-help-与-tldr-更快的两种查法",[5,13],"示例 1：选项速查 ls --help        # 一屏列出全部选项，日常最常用，够用了\nls --version     # 看命令版本与所属包，GNU 核心工具会写 ls (GNU coreutils) 9.x 示例 2：只看真实示例 tldr ls          # 需要装 tldr 包；只给真实用法示例，比 man 快十倍 示例 3：超长的 GNU 手册 info coreutils 'ls invocation'   # 层级导航：u 上级、n\u002Fp 前后节点 实用结论：查「某个参数是否存在、拼写对不对」用 --help 就够了；要看退出码、示例、相关文件用 man；--help 只讲参数，不讲「我该用哪个命令」，后者靠 apropos 和搜索引擎。",{"id":22487,"title":428,"titles":22488,"content":22489,"level":594},"\u002Ftutorials\u002Flinux#_4-typewhichcommand-这条命令到底从哪来",[5,13],"Linux 的「命令」其实分三种类型，搞清类型能解释一大半「找不到命令」和「行为不对」： 类型 例子 type 的输出 说明 Shell 内建命令 cd echo export source alias cd is a shell builtin 由 Shell 进程自己实现，改不了当前目录这类事的只能做成内建 外部命令（可执行文件） ls grep systemctl grep is \u002Fusr\u002Fbin\u002Fgrep 在 PATH 里找文件，fork 子进程执行 函数 \u002F 别名 自定义 ll()、alias rm='rm -i' ll is a function 只在当前 Shell 生效，脚本里看不到 示例 1：排查「为什么我的 ls 带颜色」 type -a ls    # 把别名\u002F函数\u002F内建\u002F文件全部来源列出来 示例 2：脚本里的标准写法 command -v python3   # 只返回可执行路径；if ! command -v python3 >\u002Fdev\u002Fnull; then …; fi\nwhich curl           # 只在 PATH 找文件（外部命令），看不到内建与别名，脚本别用它\nwhereis nginx        # 顺带给出二进制、源码、man 页位置，装完不知道手册在哪时用 ls 这类命令既是外部命令又常被别名覆盖，交互式排查首选 type -a，写脚本判断用 command -v。",{"id":22491,"title":620,"titles":22492,"content":22493,"level":594},"\u002Ftutorials\u002Flinux#_5-发行版差异会体现在哪",[5,13],"同一句命令在不同发行版上可能失效，差异集中在四个地方： 方面 Debian\u002FUbuntu RHEL\u002FRocky\u002FFedora Alpine 包管理 apt、dpkg dnf\u002Fyum、rpm apk C 库与命令风格 GNU coreutils GNU coreutils BusyBox（ls、find、sed 参数是精简版） sudo 默认可用 默认可用 默认没有，需 apk add sudo 服务管理 systemctl（容器内常缺） systemctl 多用 OpenRC 或 supervisord 防火墙后端 ufw 常见 firewalld 常见 iptables\u002Fnftables 裸用 Shell 默认 bash bash ash（sh 指向 BusyBox ash） head -n 这类基础参数四处一致；差异大的是 sed -i（BusyBox 要求 -i '' 或干脆不支持同时给后缀）、date -d（BusyBox 只支持少数格式）、find -printf（BusyBox 无）。写跨发行版的脚本时，第一行用 #!\u002Fbin\u002Fsh 就别用 bash 语法，用 #!\u002Fbin\u002Fbash 就要确认机器上有 bash。",{"id":22495,"title":808,"titles":22496,"content":22497,"level":594},"\u002Ftutorials\u002Flinux#踩坑提醒",[5,13],"man 页在容器里经常是空的（没装 man 和手册包）：apt install man-db manpages，或临时靠 --help 与 tldr。 帮助文本分「BSD 版」与「GNU 版」两套，macOS 的 sed -i、date -j 与 Linux 不通用，别把网上抄的 macOS 写法放到服务器上。 --help 只讲参数，不讲「我该用哪个命令」。后者靠 apropos 和搜索引擎。",{"id":22499,"title":849,"titles":22500,"content":22501,"level":276},"\u002Ftutorials\u002Flinux#二文件与目录lscdcpmvrmln",[5],"日常一半的终端时间花在「这个目录里有什么」「把这个文件挪到那里」。这一章的命令都写成可以直接复制粘贴的形式，重点在于看清 ls -l 的每个字段、理解复制与移动的差别，以及明白 rm -rf 为什么不能随手敲。",{"id":22503,"title":869,"titles":22504,"content":22505,"level":594},"\u002Ftutorials\u002Flinux#_1-ls-列出目录内容",[5,849],"ls 是 Linux 中使用频率最高的命令之一，用于列出目录中的文件。 示例 1：列出详细信息（最常用） ls -lh \u002Fsrv\u002Fapp    # -l 长格式列出，-h 文件大小以 K\u002FM\u002FG 人读格式显示 输出长这样，十个字符的权限串是本章唯一需要「背」的东西： $ ls -l \u002Fsrv\u002Fapp\n-rw-r--r-- 1 deploy deploy  1.2K Oct  4 09:12 app.py\ndrwxr-xr-x 3 deploy deploy  4.0K Oct  4 09:20 static\nlrwxrwxrwx 1 deploy deploy    11 Oct  4 09:21 current -> releases\u002Fv7\n-rwsr-xr-x 1 root   root   144.0K Mar 14  2024 helper 位置 内容 含义 第 1 字符 - d l c b s p 类型：普通文件 \u002F 目录 \u002F 链接 \u002F 字符设备 \u002F 块设备 \u002F socket \u002F 管道 第 2-4 rwx 属主（第三列的 deploy）的权限 第 5-7 r-- 属组（第四列）的权限 第 8-10 r-- 其他人的权限 第 2\u002F5\u002F8 位 s 或 S setuid（属主位）\u002F setgid（属组位）；大写 S 表示「有标记但没有对应 x」 最后一位 t 或 T sticky bit，常见于 \u002Ftmp（drwxrwxrwt） 第 2 列 1 \u002F 3 硬链接数；目录至少 2（. 和 ..），所以看到 1 说明不是目录 第 6 列 1.2K \u002F 4.0K 文件字节数；目录显示的是它自己的 inode 表大小，不含内容 第 7 列 Oct 4 09:12 mtime（内容最后改动时间）；ls -lu 看 atime，ls -lc 看 ctime（元数据变更） 示例 2：显示隐藏文件 ls -la    # -a 包含以 . 开头的隐藏文件；不加时不显示 示例 3：按不同方式排序 ls -lt           # -t 按修改时间，最新在前\nls -lS           # -S 按大小\nls -lr           # -r 反向排序\nls -lX           # -X 按扩展名 示例 4：方便脚本与管道 ls -1                              # -1 单列输出，方便接管道\nls -Q                              # -Q 每个名字加引号，含空格的文件名安全\nls --time-style=long-iso           # 日期变成 2026-10-04 09:12，脚本里更好解析 注意：ls 无参数时隐藏文件不显示；目录显示的大小不含内容，看总占用要用 du -sh。",{"id":22507,"title":1250,"titles":22508,"content":22509,"level":594},"\u002Ftutorials\u002Flinux#_2-pwd-打印当前目录",[5,849],"示例 1：我在哪 pwd         # 打印当前工作目录的绝对路径\npwd -P      # -P 解析掉所有符号链接，给出真实路径 Shell 有个同名内建的 pwd，pwd -P 走的是外部命令版本。",{"id":22511,"title":1292,"titles":22512,"content":22513,"level":594},"\u002Ftutorials\u002Flinux#_3-cd-切换目录",[5,849],"示例 1：几种常用跳法 cd \u002Fsrv\u002Fapp    # 绝对路径\ncd ~           # 回用户主目录（cd 不带参数效果相同）\ncd ..          # 上一层\ncd -           # 回到上一个待过的目录 注意：cd 是内建命令，只对当前 Shell 生效——在子 shell（比如脚本、( … )）里 cd 改不了父目录。",{"id":22515,"title":1357,"titles":22516,"content":22517,"level":594},"\u002Ftutorials\u002Flinux#_4-mkdir-与-rmdir-建目录删空目录",[5,849],"示例 1：创建多级目录 mkdir build          # 建单层目录，父目录不存在会失败\nmkdir -p a\u002Fb\u002Fc       # -p 逐级创建，已存在也不报错\nmkdir -p logs\u002F{2026..2028}   # 花括号展开，一次建三年目录 示例 2：删除目录 rmdir build          # 只能删空目录，非空就报错——比 rm -r 安全得多",{"id":22519,"title":1427,"titles":22520,"content":22521,"level":594},"\u002Ftutorials\u002Flinux#_5-touch-创建空文件或更新时间戳",[5,849],"示例 1：两个用途 touch app.log                    # 文件不存在就建空的；存在就更新 mtime\ntouch -d \"2026-10-01\" f          # -d 指定时间戳 部署时常用 touch「戳一下」源文件触发增量构建。",{"id":22523,"title":1474,"titles":22524,"content":22525,"level":594},"\u002Ftutorials\u002Flinux#_6-tree-树状展示目录",[5,849],"示例 1：限制深度 tree -L 2                  # -L 2 只显示两层，目录巨大时防止刷屏\ntree -aI '__pycache__'     # -a 含隐藏文件，-I 用正则排除 注意：tree 可能未安装（apt install tree）。",{"id":22527,"title":1524,"titles":22528,"content":22529,"level":594},"\u002Ftutorials\u002Flinux#_7-cp-复制",[5,849],"复制 = 原件保留、再拷贝一份。最常用的就两种写法： 示例 1：只复制内容 cp app.py \u002Ftmp\u002Fapp.py    # 目标权限按 umask 重算，属主变成执行者；生产备份别用这个 示例 2：完整备份（保留属性） cp -a src\u002F backup\u002F       # -a = -rp --preserve=links,timestamps：递归 + 保权限\u002F时间戳\u002F链接 示例 3：防覆盖 cp -i a.txt \u002Ftmp\u002F        # -i 目标已存在时先问；脚本里建议加，或直接先判断存在 跨机复制目录不要直接用 cp，用 rsync -a src\u002F host:\u002Fsrv\u002Fapp\u002F（见第十三章），它保留属性且可断点续传。",{"id":22531,"title":1616,"titles":22532,"content":22533,"level":594},"\u002Ftutorials\u002Flinux#_8-mv-移动与改名",[5,849],"示例 1：改名 \u002F 移动 mv a.txt old\u002Fa_2026-10-04.txt   # 同分区改名是改目录项，瞬时完成\nmv big.iso \u002Fmnt\u002Fusb\u002F            # 跨分区是先复制再删，慢且占双份空间 示例 2：控制覆盖行为 mv -n a b    # -n 目标存在就不覆盖\nmv -b a b    # -b 覆盖前先把目标备份一份",{"id":22535,"title":1696,"titles":22536,"content":22537,"level":594},"\u002Ftutorials\u002Flinux#_9-rm-删除危险命令先读三条铁律",[5,849],"rm 删除是不可逆的：它直接 unlink inode，文件系统层面没有回收站；debugfs\u002Fphotorec 抢救成功率低且需要立刻停止写入。 示例 1：安全地删 rm -i notes.txt      # -i 每个文件都确认\nrm -I build\u002F         # -I 只在删超过 3 个文件或递归时问一次，日常推荐\nrm -rf build\u002F        # -r 递归、-f 强制 —— 不可逆，谨慎 示例 2：删除前先看一眼范围 find build -type f -print    # 确认范围再动手，比事后找回容易；确定无误后把 -print 换成 -delete rm -rf 的三条铁律： 路径必须先补全再回车。rm -rf $DIR\u002F 在 $DIR 为空时变成 rm -rf \u002F（新版 coreutils 会拒绝，但 rm -rf \u002Fhome\u002Fdeploy\u002F 不会）。安全写法：rm -rf \"${DIR:?变量未设置}\u002F\"*。 不要在 cd 失败的目录里执行。脚本里写 cd \u002Fsrv\u002Fapp && rm -rf cache 用 &&，别用 ;——cd 失败时 ; 后面的命令会在当前目录执行。 保护参数：--preserve-root（默认开启，防 rm -rf \u002F）；手生时永远先加 -i。",{"id":22539,"title":1862,"titles":22540,"content":22541,"level":594},"\u002Ftutorials\u002Flinux#_10-ln-软链接与硬链接",[5,849],"Linux 的链接分两种，用法和性质完全不同： 维度 软链接（symbolic） 硬链接（hard） 创建 ln -s \u002Fsrv\u002Fapp\u002Freleases\u002Fv7 current ln big.txt big.bak 本质 独立 inode，存的是路径字符串 同一 inode 的第二个目录项 目标删了 变成悬空链接，ls -l 显示红色 内容仍在，链接数减 1；归零才真删数据 跨文件系统 可以（存路径） 不可以 指向目录 可以 一般禁止（root 也不建议） 权限位 lrwxrwxrwx（固定，实际权限看目标） 与目标共享权限 示例 1：发布目录的经典用法 ln -s \u002Fsrv\u002Fapp\u002Freleases\u002Fv7 current    # -s 建软链接，「版本目录 + current 指针」的发布方式\nln -sfn \u002Fsrv\u002Fapp\u002Freleases\u002Fv8 current  # 切换版本：-f 覆盖已存在的同名链接，-n 把它当普通文件处理 注意：-n 不能省——否则 ln -s 新目标 current 会把新链接建到 current\u002F 目录里面去，这是最经典的坑。",{"id":22543,"title":2024,"titles":22544,"content":22545,"level":594},"\u002Ftutorials\u002Flinux#_11-路径的三种写法与通配符",[5,849],"写法 例子 展开时机 绝对路径 \u002Fsrv\u002Fapp\u002Fapp.py 永远有效，脚本里首选 相对路径 .\u002Fstatic\u002Flogo.png、..\u002F..\u002Flogs\u002Fapp.log 依赖当前目录，cd 后就变了 主目录 ~\u002Fdownloads\u002Fa.zip（~ 在最前才生效） Shell 展开为 $HOME 通配符（glob）与花括号展开： 模式 匹配 备注 *.log 所有以 .log 结尾 没有匹配时 bash 默认原样保留该字符串（导致奇怪报错）；shopt -s nullglob 让它展开为空 app?.py 单字符占位 app12.py 不匹配 [0-9]* 字符集开头 [!0-9]* 或 [^0-9]* 取反 **\u002F*.js 递归 需要 shopt -s globstar extglob {a,b}.conf a.conf b.conf 花括号展开发生在通配之前，适合批量建文件 file_{1..5}.tmp 5 个名字 mkdir -p logs\u002F{2026..2028} 一次建三年目录 空格与特殊字符的文件名永远用引号包：cp \"my report 2026.pdf\" \u002Ftmp\u002F。确认目录里有带换行的怪文件时用 ls -Q 或 find . -print0。",{"id":22547,"title":808,"titles":22548,"content":22549,"level":594},"\u002Ftutorials\u002Flinux#踩坑提醒-1",[5,849],"ls -l 看到目录大小 4.0K 不代表里面只有 4KB，看总占用用 du -sh 目录（第十四章）。 cp 到已存在的同名文件会静默覆盖，脚本里加 -i 或先判断存在；mv 同样，-n 表示不覆盖，-b 覆盖前备份。 别名陷阱：很多发行版给 root 配了 alias rm='rm -i'，交互式很爽，脚本里 rm 卡在确认上。脚本开头 unalias -a 2>\u002Fdev\u002Fnull 或直接写 \u002Fbin\u002Frm。 用 ls | xargs rm 处理带空格的文件名会出大事，正确做法是 find … -print0 | xargs -0 rm --（第六章、第八章）。",{"id":22551,"title":2298,"titles":22552,"content":22553,"level":276},"\u002Ftutorials\u002Flinux#三查看文件内容catheadtaillessfile",[5],"看日志、看配置、看二进制里嵌的字符串——目的不同，工具差别很大。选错工具的后果通常是：cat 一个 8GB 日志把终端刷爆，或者用 less 打开后不知道怎么退出。",{"id":22555,"title":2313,"titles":22556,"content":22557,"level":594},"\u002Ftutorials\u002Flinux#_1-cat-一次性输出整个文件",[5,2298],"cat 适合短文件；长文件请用 less，超大文件先看体积（ls -lh 或 wc -c）。 示例 1：直接查看 cat \u002Fetc\u002Fnginx\u002Fnginx.conf    # 整个文件输出到终端 示例 2：带行号 cat -n app.py    # -n 每行前面加行号（空行也编号）\nnl file          # 类似但默认只给非空行编号，nl -ba 则全部编号 示例 3：查看不可见字符（查 Windows 换行必备） cat -A Makefile    # 行尾显示 $、Tab 显示 ^I、CR 显示 ^M 看到 ^M$ 就说明文件是 CRLF 行尾，用 sed -i 's\u002F\\r$\u002F\u002F' file 或 dos2unix file 处理。 示例 4：拼接文件 cat part_* > full.bin    # 顺序按参数顺序（glob 展开按名字排序）\ntac access.log           # tac 按行倒序输出，看倒序日志很方便",{"id":22559,"title":2461,"titles":22560,"content":22561,"level":594},"\u002Ftutorials\u002Flinux#_2-head-只看开头",[5,2298],"示例 1：前 N 行 head -n 200 big.csv    # 前 200 行；-n 指定行数，默认 10 行\nhead -c 512 file       # -c 按字节取 示例 2：去掉末尾几行 head -n -5 data.txt    # 输出除最后 5 行以外的全部内容（GNU 扩展，BusyBox 不支持）",{"id":22563,"title":2531,"titles":22564,"content":22565,"level":594},"\u002Ftutorials\u002Flinux#_3-tail-只看末尾与实时跟踪",[5,2298],"示例 1：最后 N 行 tail -n 50 app.log     # 默认 10 行 示例 2：实时跟踪日志（最常用的排障动作） tail -f -n 200 app.log      # -f 跟踪追加内容，Ctrl-C 退出\ntail -F \u002Fvar\u002Flog\u002Fsyslog     # -F 在文件被 logrotate 轮转后会重新打开，生产环境用大写 F -f 在日志轮转后会静默停止，这是「tail 看着没动静其实服务在写新文件」的常见原因。 示例 3：跟踪 + 过滤 tail -f app.log | grep --line-buffered ERROR 不加 --line-buffered 会因为管道缓冲看起来「卡住了」。看多文件时 tail -f 会在两个文件间轮流转屏，容易看漏；逐个终端各开一个，或用 multitail。",{"id":22567,"title":2652,"titles":22568,"content":22569,"level":594},"\u002Ftutorials\u002Flinux#_4-less-分页阅读",[5,2298],"less 是看长文件的标准答案（名字来自「LESS is more」）；more 只能往前翻，除了「机器上只装了它」之外没有理由选它。 示例 1：打开文件 less file    # 打开后按 q 退出——新手最常见的卡住点 键 作用 键 作用 Space \u002F b 下\u002F上一页 g \u002F g 跳到第一行 Enter \u002F y 下\u002F上一行 G 跳到最后一行 \u002F关键词 向下搜索 ?关键词 向上搜索 n \u002F N 下一个 \u002F 上一个匹配 &关键词 只显示匹配行（过滤视图） d \u002F u 半页下\u002F上 q 退出 F 进入跟踪模式（类似 tail -f） v 用 $EDITOR 打开当前文件 -N（启动参数） 显示行号 Ctrl-G 查看当前位置信息 示例 2：常用启动写法 less -N file        # 显示行号\nless +G file        # 打开即停在底部\nless +F app.log     # 打开即跟踪，像 tail -f，按 Ctrl-C 回到翻页\nless -S file        # 长行不折行，用左右箭头横向滚",{"id":22571,"title":2903,"titles":22572,"content":22573,"level":594},"\u002Ftutorials\u002Flinux#_5-file-判断文件的真实类型",[5,2298],"示例 1：不看扩展名看文件头 file img.png app.so    # 输出如：PNG image data, 800 x 600, 8-bit\u002Fcolor RGB file 判断的是文件头（magic number），因此「扩展名 jpg 实际是 png」这种问题只有它能发现。",{"id":22575,"title":2936,"titles":22576,"content":22577,"level":594},"\u002Ftutorials\u002Flinux#_6-odhexdumpxxdstrings-看二进制",[5,2298],"示例 1：字节转储 od -c -N 64 file          # 八进制\u002F字符转储，\\n、\\0 直接可见；-N 64 只看前 64 字节\nhexdump -C -n 128 file    # 经典十六进制视图：左偏移、中 16 字节、右 ASCII\nxxd file                  # 同上；xxd -r hex.txt bin 还能把 hex 文本反向写回文件 示例 2：抽出可打印字符串 strings -n 6 app.so                        # 默认最短 4 个字符，-n 6 减少噪音\nstrings -a \u002Fusr\u002Fbin\u002Fnginx | grep -E '^https?:\u002F\u002F'   # 找嵌在二进制里的路径、报错文本、版本号 压缩包不用先解压也能看：zcat app.log.gz、zless app.log.gz（第九章详述）。",{"id":22579,"title":808,"titles":22580,"content":22581,"level":594},"\u002Ftutorials\u002Flinux#踩坑提醒-2",[5,2298],"看完日志不记得怎么退出 less：按 q。卡在任何分页程序里，第一反应都是 q，不行再 Ctrl-C。 cat 大文件不会「只看前几行」，它会全部刷到终端，几 GB 的日志能让终端卡几十秒——先用 wc -c 或 ls -lh 确认体积。 复制出来的配置「看着一样但跑不通」，九成是行尾 CRLF：cat -A 看到 ^M$ 就用 sed -i 's\u002F\\r$\u002F\u002F' file 或 dos2unix file 处理。",{"id":22583,"title":3098,"titles":22584,"content":22585,"level":276},"\u002Ftutorials\u002Flinux#四最小编辑器集nano-与够用的-vim",[5],"服务器上手第一道坎往往不是命令而是编辑器：改一行配置，进了 vi 出不来，或者不知道怎么保存。这一章只讲两个编辑器——nano（新手，键位写在屏幕底部）和 vim（服务器上一定会遇到）——目标是「能改一行、能存盘、能退出」。",{"id":22587,"title":3117,"titles":22588,"content":22589,"level":594},"\u002Ftutorials\u002Flinux#_1-nano-键位就写在屏幕底部",[5,3098],"nano file 打开后，屏幕底部的 ^ 代表 Ctrl，照着按就行。 示例 1：最常用的四个键 nano \u002Fetc\u002Fnginx\u002Fnginx.conf   # 打开文件\n# Ctrl-O 然后 Enter —— 保存（写入当前文件名）\n# Ctrl-X —— 退出（有改动会问是否保存）\n# Ctrl-W —— 搜索（Where Is） 示例 2：带参数打开 nano +42 \u002Fetc\u002Fnginx\u002Fnginx.conf   # 打开即跳到 42 行\nnano -l file                     # 保存时自动补末尾换行 想永久显示行号，在 ~\u002F.nanorc 写 set linenumbers。完整按键表： 按键 作用 按键 作用 Ctrl-O 然后 Enter 写入（保存）当前文件名 Ctrl-X 退出（有改动会问是否保存） Ctrl-W 搜索（Where Is） Alt-W 重复上一次搜索 Ctrl-K \u002F Ctrl-U 剪切一行 \u002F 粘贴回来 Ctrl-^ 替换（Replace） Ctrl-A \u002F Ctrl-E 行首 \u002F 行尾 Alt-A \u002F Alt-E 设标记起点 \u002F 终点（选块） Ctrl-Shift-6（或 Alt-6） 复制选中块 Alt-Shift-3 删除选中块 Alt-\u002F 撤销 Alt-E（重复） 重做（Ctrl-6 旧版） Ctrl-T 打开拼写检查 Ctrl-R 把另一个文件插进来",{"id":22591,"title":3382,"titles":22592,"content":22593,"level":594},"\u002Ftutorials\u002Flinux#_2-vim-先分清三种模式",[5,3098],"vim 的行为取决于当前模式，模式之间靠这几个键切换： 模式 进入 能干什么 屏幕底部特征 普通模式（Normal） 启动即在此；Esc 回到 移动、删除、复制粘贴、: 命令 无提示 插入模式（Insert） i a o 打字 -- INSERT -- 命令行模式 普通模式按 : \u002F ? 存盘、退出、搜索、替换 光标跳到最底行 屏幕底部出现 -- INSERT -- 就说明你正在打字而不是在下命令——这是所有 vim 卡住的根源。",{"id":22595,"title":3481,"titles":22596,"content":22597,"level":594},"\u002Ftutorials\u002Flinux#_3-vim-够用的十二个键位",[5,3098],"按「进得去、改得了、出得来」排序： i          # 在光标前进入插入模式（开始打字）\na          # 在光标后插入\no          # 在下方开一个新行并插入（加新配置行必备）\nEsc        # 回到普通模式——打完字的第一件事\n:w         # 保存（write）\n:q         # 退出（有未保存改动会拒绝）\n:q!        # 强制退出并丢弃改动——「怎么退出 vim」的正确答案\n:wq        # 保存并退出（也可以按 ZZ）\nx          # 普通模式下删除光标处一个字符\ndd         # 剪切一行（复制用 yy，粘贴用 p）\nu          # 撤销（重做 Ctrl-R）\n\u002F关键词    # 向下搜索，按 n 跳下一个（? 向上）\ngg         # 跳到文件第一行（G 跳最后一行）",{"id":22599,"title":3598,"titles":22600,"content":22601,"level":594},"\u002Ftutorials\u002Flinux#_4-进阶但极常用的四个改配置真的会用到",[5,3098],":%s\u002Fold\u002Fnew\u002Fg      # 全文替换；加 c 变 :%s\u002Fold\u002Fnew\u002Fgc 逐个确认\n:10,20s\u002F^\u002F# \u002F      # 只在 10–20 行行首加注释（范围替换）\n:30                # 跳到 30 行；:10,30d 删除 10–30 行\nv                  # 可视模式（Ctrl-V 是列块模式，批量给多行加前缀）",{"id":22603,"title":3643,"titles":22604,"content":22605,"level":594},"\u002Ftutorials\u002Flinux#_5-怎么退出-vim这个梗的正确解法",[5,3098],"网上「如何从 vim 退出」的笑话，真实成因是：用户按了 i 进入插入模式，改完直接敲 :q!，结果 : q ! 被当成普通文本写进了文件。解法固定三步： 连按 Esc 两三次（保证回到普通模式）； 输入 :q! 然后 Enter（不想保留改动）或 :wq（保留）； 如果底部显示 E37\u002FE1734 之类的报错，说明还有未保存内容或输入被当命令解析，先 u 撤销刚才误打的字符再 :q!。",{"id":22607,"title":3698,"titles":22608,"content":22609,"level":594},"\u002Ftutorials\u002Flinux#_6-只有-vi-时的应急改行",[5,3098],"精简镜像里 vim 可能不存在（只有硬链接的 vi，且是 vi-compatible 模式），改一行配置有两种不动编辑器的办法： sed -i.bak 's\u002F^# *listen_port.*\u002Flisten_port 9080;\u002F' \u002Fetc\u002Fapp\u002Fapp.conf\n# -i.bak 原地修改并先留 .bak 备份；s 命令末尾的分号是给 nginx 的语法，别多别少\n\nprintf '%s\\n' 'user deploy;' 'worker_processes auto;' > \u002Fetc\u002Fapp\u002Fapp.conf.new\n# 小配置文件可以直接重建 改完先验语法再重载：nginx -t && systemctl reload nginx。真要用 vi 应急，键位与 vim 完全一致（i、Esc、:wq），只是缺多级撤销和语法高亮。",{"id":22611,"title":808,"titles":22612,"content":22613,"level":594},"\u002Ftutorials\u002Flinux#踩坑提醒-3",[5,3098],":w 报 E45: 'readonly' option is set：文件不可写。别退出去 sudo vim 重来，直接 :w !sudo tee % 就能以 root 写回当前缓冲。 :q 拒绝退出报 E37，别急着 :q! 丢掉半小时改动，先 :w!；确实不要了再 :q!。 vim 保存后文件属主变成 root（用 sudo vim 改的），导致应用写不了日志——要么 chown 回去，要么改用 :w !sudo tee %。 编辑器里「粘贴的代码乱七八糟缩进」：vim 需要 :set paste（或新版 :set pastetoggle=\u003CF3>），否则会自动缩进把每行叠歪。",{"id":22615,"title":3846,"titles":22616,"content":22617,"level":276},"\u002Ftutorials\u002Flinux#五权限与属主chmodchownsudo",[5],"Permission denied 是 Linux 里出现频率最高的错误，而它的成因至少有六种：文件权限、目录缺 x、属主不对、SELinux、capabilities、进程用的不是你以为的用户。这一章先把「读权限位」练熟，再给一套安全的修改顺序。",{"id":22619,"title":3864,"titles":22620,"content":22621,"level":594},"\u002Ftutorials\u002Flinux#_1-先学会读权限位",[5,3846],"字符位 对文件 对目录 r 能读内容（cat） 能列出名字（ls 目录本身） w 能改内容（写同一文件） 能在里面创建\u002F删除文件（真正的删除权看目录，不看文件！） x 能执行（配合 r 才能跑脚本） 能进入（cd）并访问其中的已知文件名 关键结论：目录的 x 比文件本身的权限更决定性。chmod 644 app.sh 之后脚本不能跑是缺文件 x；而 .\u002Fapp.sh 报 Permission denied 有时是它的父目录缺 x——没有 x 就无法解析路径，哪怕文件是 777。典型事故：家目录 chmod 700 ~ 之后，别人 cat \u002Fhome\u002Fdeploy\u002Fx.txt 必失败，即使 x.txt 是 644。 数字法：r=4、w=2、x=1，每组三位相加： 数字 含义 常见用途 644 rw-r--r-- 普通文件默认（配置、图片、代码） 755 rwxr-xr-x 可执行文件、脚本、目录默认 600 rw------- 私钥、含密码的配置 700 rwx------ .ssh 目录、只给自己看的目录 664 rw-rw-r-- 团队协作目录里的文件（配合组权限） 775 rwxrwxr-x 组内共享的目录 777 rwxrwxrwx 不要用，见下",{"id":22623,"title":4127,"titles":22624,"content":22625,"level":594},"\u002Ftutorials\u002Flinux#_2-chmod-修改权限",[5,3846],"示例 1：数字法（整套设置） chmod 644 app.conf     # 属主可读写，属组和其他人只读\nchmod 755 deploy.sh    # 属主可执行，其他人可读可执行 示例 2：符号法（改单个位，安全且不牵连其他位，推荐） chmod u+x deploy.sh       # u=属主 +执行位\nchmod g+w app.sh          # g=属组 加写权限\nchmod o-rwx private.conf  # o=其他人 去掉全部权限；a=全部人\nchmod -R a+rX \u002Fsrv\u002Fapp    # 大写 X：只给目录或已是可执行文件加 x —— 修目录树的最佳写法 chmod -R a+rX \u002Fsrv\u002Fapp 是「让所有人能读，但只给目录和已有的可执行文件加执行位」的标准解法；写成 chmod -R 777 会把配置文件也变可执行，还会打开目录写权限。 示例 3：特殊位 chmod u+s helper    # setuid：执行时以文件属主（root）身份，如 passwd\nchmod g+s dir       # setgid：目录里新建文件继承目录的组，团队共享目录用\nchmod o+t dir       # sticky：目录里只有属主能删自己文件，如 \u002Ftmp chmod 777 的真实风险：任何本地用户都能改你的代码和脚本（配合 cron 或 web 目录可直接提权），多数服务（sshd、nginx）还会因为权限过宽拒绝启动。正确替代顺序：先 chown -R appuser:appgroup 找对属主，再 chmod -R a+rX（或 755\u002F775）给最小权限。setuid 文件如果被写权限打开，等于本机任何用户能拿到 root，排查：find \u002F -xdev -perm -4000 -type f 2>\u002Fdev\u002Fnull（顺便看看有没有不认识的）。",{"id":22627,"title":4314,"titles":22628,"content":22629,"level":594},"\u002Ftutorials\u002Flinux#_3-chown-与-chgrp-修改属主属组",[5,3846],"示例 1：改属主、改属组 chown deploy:app app.py        # 同时改属主和属组；普通用户不能把文件送给别人，只有 root 能\nchgrp -R www-data logs         # 只改属组；比 chown 温和，不需要 root 也能改自己组的\nchown -R app:app \u002Fsrv\u002Fapp      # -R 递归整棵目录树\nchown -h link newtarget        # -h 改软链接本身，而不是它指向的文件 示例 2：大范围改动的安全网 chown -R --from=root app \u002Fsrv\u002Fapp       # --from 只改当前属主是 root 的文件，避免误改新加的文件\nfind \u002Fsrv\u002Fapp ! -user deploy -print | head   # 递归前先用它跑一遍确认范围 改属主时的三个雷： chown -R 在目录树里遇到别人正在写的数据库文件会打断服务——先停服务再改。 递归前先跑一遍确认范围（上面的 find）。 \u002Fsrv、\u002Fvar\u002Fwww 这类由包管理器维护的目录，chown -R root:root 之后可能连 apt 都跑不动。",{"id":22631,"title":4474,"titles":22632,"content":22633,"level":594},"\u002Ftutorials\u002Flinux#_4-umask-新文件的默认权限",[5,3846],"umask 是「新建文件时要屏蔽掉的位」。文件最多 666，目录最多 777，然后按位与 ~umask。 umask 新文件 新目录 场景 022 644 755 默认（系统用户、多数发行版） 002 664 775 团队协作组（配合共同属组） 077 600 700 安全敏感环境 027 640 750 常见服务器加固基线 示例 1：查看与设置 umask        # 查看，输出如 0002\numask -S     # 符号形式（u=rwx,g=rx,o=），好读\numask 027    # 只对当前 Shell 及其子进程生效，写进 ~\u002F.bashrc 才持久 坑：umask 只影响新建文件，不改动已存在的文件；用 mkdir 建了一半才发现 umask 不对，用 chmod -R a+rX 补。systemd 服务可以单独设 UMask=0027（在 unit 里），与登录 Shell 的 umask 无关——很多人卡在这里。",{"id":22635,"title":4643,"titles":22636,"content":22637,"level":594},"\u002Ftutorials\u002Flinux#_5-sudo-与-su-换用户的方式不同",[5,3846],"维度 sudo cmd sudo -i \u002F su - su cmd 需要谁的密码 通常自己的密码 sudo -i 自己；su - 目标用户（root） 目标用户 环境 保留大部分（secure_path） 完整登录环境 不完整（su - 才完整） 审计 每条命令写进 \u002Fvar\u002Flog\u002Fauth.log 或 journal 记会话 只记会话 可精细授权 能（只允许某些命令） 等价于给整个 shell 否 典型用途 跑一条特权命令 长期在 root 下操作 容器里 root 切普通用户 示例 1：常用写法 sudo !!                            # 上一条命令忘了加 sudo，重跑\nsudo -u appuser .\u002Fworker.sh        # 以某个服务用户身份跑（比给它 root 权限安全）\nsudo -i                            # 拿到 root 的登录 shell（读 \u002Froot\u002F.profile）\nsudo -l                            # 列出我当前被允许做什么（排查「为什么这个 sudo 不行」）\nsudo -v                            # 只刷新凭据缓存，测试密码对不对\nsudo --preserve-env=PATH cmd       # 需要保留自己的 PATH 时（默认 sudo 会重置） 示例 2：改 sudoers 的正确流程 \u002Fetc\u002Fsudoers 永远不要直接 vim 改——语法错了会把所有 sudo 权限锁死。 sudo visudo -c                        # 先检查现有语法\nsudo visudo                           # 用 visudo 打开（编辑完保存前自动校验）\nsudo visudo -f \u002Fetc\u002Fsudoers.d\u002Fdeploy  # 最好：单独放一个 drop-in 文件 drop-in 里写权限要最小化： deploy ALL=(ALL) NOPASSWD: \u002Fbin\u002Fsystemctl restart nginx, \u002Fbin\u002Fsystemctl status nginx visudo 默认用 vi；想换 EDITOR=nano sudo -E visudo（-E 保留环境变量，否则换不了）。sudoers 目录里最后加载的文件生效，\u002Fetc\u002Fsudoers.d\u002F 里的覆盖主文件，命名别带 . 和 ~（会被忽略，README 那种文件写权限进去会炸）。",{"id":22639,"title":4941,"titles":22640,"content":22641,"level":594},"\u002Ftutorials\u002Flinux#_6-特殊权限位一览",[5,3846],"位 数字 表现 作用 常见例子 setuid 4000 属主执行位是 s（rwsr-xr-x） 执行时以文件属主身份（root） passwd、ping（旧式） setgid 2000 属组执行位是 s（rwxr-sr-x） 文件：执行时以属组身份；目录：新建文件继承目录的组 团队共享目录 chmod g+s \u002Fsrv\u002Fshared sticky 1000 其他人执行位是 t（drwxrwxrwt） 目录里只有属主能删自己的文件 \u002Ftmp",{"id":22643,"title":5051,"titles":22644,"content":22645,"level":594},"\u002Ftutorials\u002Flinux#_7-setfacl-getfacl-既要给第三个人读又不要给其他人",[5,3846],"普通权限只有三档，遇到「给某个组加读写、其他人不动」就需要 ACL： getfacl \u002Fsrv\u002Fapp\u002Fdata                          # 查看（含默认 ACL）\nsetfacl -m u:backup:rx \u002Fsrv\u002Fapp\u002Fdata          # -m 加一条：给用户 backup 读+执行\nsetfacl -m d:g:deploy:rwX \u002Fsrv\u002Fshared         # d: 默认 ACL，新建文件自动继承\nsetfacl -x u:backup \u002Fsrv\u002Fapp\u002Fdata             # -x 删一条\nsetfacl -R -b \u002Fsrv\u002Fapp                        # -b 递归清空所有 ACL ls -l 末尾出现 +（如 -rw-rw-r--+）就说明有 ACL。坑：文件系统必须挂载带 acl 选项（现代 ext4\u002Fxfs 默认支持），NFS 上还要服务端配合；容器里 setfacl 常因缺 acl 包报错。",{"id":22647,"title":5156,"titles":22648,"content":22649,"level":594},"\u002Ftutorials\u002Flinux#_8-为什么-permission-denied排查清单",[5,3846],"检查顺序 命令 判断 1. 谁在执行这条操作 ps -o user= -p $(pgrep -f worker) nginx 是 www-data，你以为是 deploy 2. 文件本身与所有父目录的权限 namei -l \u002Fsrv\u002Fapp\u002Flogs\u002Fapp.log 一眼看全链路，父目录缺 x 立刻现形 3. 属主属组是否符合预期 stat -c '%U %G %a' file 部署脚本用 root 建的文件，进程写不了 4. 是否 SELinux\u002FAppArmor 拦的 sudo ausearch -m avc -ts recent；getenforce 临时验证：sudo setenforce 0（测完必须 setenforce 1 恢复） 5. 文件系统挂载是否 ro 或 noexec findmnt -T \u002Fsrv\u002Fapp 看到 ro 就是磁盘只读挂载（第十四章） 6. 磁盘\u002Finode 满（报的是别的错但很像权限） df -h; df -i No space left on device vs Read-only file system 7. 目录缺 x、文件缺 r 的具体组合 sudo -u appuser cat \u002Fsrv\u002Fapp\u002Fx 用真实身份复现最快 8. 服务被沙箱限制 systemctl cat myapp 看 ProtectSystem=、ReadWritePaths= systemd 加固参数导致写不了 SELinux 一句话说明：getenforce 返回 Enforcing 时，即使权限位全对也可能被安全上下文拒绝，典型表现是 nginx 读得了 \u002Fsrv\u002Fapp 里 644 的文件却报 403。正确修法不是关掉 SELinux，而是 ls -Z 看上下文、semanage fcontext + restorecon 打标签。RHEL 系默认开，Debian\u002FUbuntu 默认没开。",{"id":22651,"title":808,"titles":22652,"content":22653,"level":594},"\u002Ftutorials\u002Flinux#踩坑提醒-4",[5,3846],"改完组权限发现没生效：组成员变更不会作用于已登录的会话，重新登录或 newgrp 组名（第十二章）。 chmod -R 755 把 ~\u002F.ssh 也改了，结果 ssh 立刻连不上：authorized_keys 要 600、.ssh 要 700。 目录给了 w 没给 x（-wx）是极少见的组合：能创建\u002F删除文件但不能列出内容，排查时容易误判，一般不这么配。 权限问题一律用 namei -l 全路径 起手，比一层层 ls -ld 快且不会漏。",{"id":22655,"title":5419,"titles":22656,"content":22657,"level":276},"\u002Ftutorials\u002Flinux#六查找文件findlocategrep-r",[5],"「这个日志到底在哪」「谁把配置塞到系统里的」「按名字找命令」——三类需求对应 find、locate、type。find 参数多但有固定套路：先缩小范围（路径 + -maxdepth），再定条件（-name\u002F-type\u002F-mtime），最后才加动作（-print\u002F-exec\u002F-delete）。",{"id":22659,"title":5466,"titles":22660,"content":22661,"level":594},"\u002Ftutorials\u002Flinux#_1-find-按名字找",[5,5419],"find 的基本格式是 find 起点 条件… 动作…，条件之间默认是「与」，! 取反，-o 或（注意 -o 要用括号分组）。 示例 1：按文件名查 find \u002Fsrv\u002Fapp -name '*.log'     # -name 按 glob 匹配文件名；模式必须加引号，否则先被 Shell 展开就废了\nfind . -iname '*.conf'          # -iname 忽略大小写\nfind \u002Fetc -path '\u002Fetc\u002Fnginx\u002F*'  # -path 匹配全路径（glob）；-regex 是全路径正则（GNU） 示例 2：先缩小范围（好习惯） find \u002F -maxdepth 3 -name 'id_rsa'    # -maxdepth 限定层数，要放在条件最前面；放后面会先全扫一遍再截\nfind \u002F -xdev -name '*.env'           # -xdev 不跨文件系统，避免扫进 \u002Fproc、\u002Fmnt、备份盘\nfind . -path .\u002F.git -prune -o -name '*.js' -print   # -prune 跳过子树，比 -not -path 快很多 示例 3：按类型 \u002F 大小 \u002F 时间 \u002F 属主 \u002F 权限 find \u002Fvar -type l                      # -type：f 文件 d 目录 l 链接 s socket b\u002Fc 设备 p 管道\nfind . -size +100M                     # -size 按大小：+\u002F- 号，c 字节、k KiB、M MiB\nfind logs -mtime +7                    # -mtime 按修改天数：-7 七天以内，+7 超过七天，7 正好第七天（按 24h 计，不是日历天！）\nfind . -mmin -10                       # -mmin 按分钟：最近 10 分钟改过，排查「刚刚谁在写文件」很好用\nfind \u002Fetc -newer \u002Fetc\u002Fhostname         # -newer 比某个文件新，比 -mtime 精确；配合 touch -d 造时间锚点\nfind \u002Fhome -user deploy                # -user\u002F-group 按属主；-uid 1001 用于用户已被删除的情况\nfind \u002F -xdev -perm -4000 -type f       # -perm：-4000 至少含这些位（找 setuid），644 完全等于\nfind . -type d -empty                  # -empty 空文件\u002F空目录，清理部署留下的空目录 想精确到「最近 7 天」，最直观的是 -newermt \"2026-09-27\"（强烈推荐），不受 24 小时窗口影响。",{"id":22663,"title":5764,"titles":22664,"content":22665,"level":594},"\u002Ftutorials\u002Flinux#_2-find-的动作-print-exec-delete",[5,5419],"示例 1：先看范围，再动手 find build -type f -print              # 每个结果一行\nfind . -name '*.tmp' -print0           # -print0 用 \\0 分隔，交给 xargs -0（第八章），处理怪文件名安全 示例 2：对结果执行命令 find . -name '*.tmp' -exec rm -f {} \\;         # -exec … \\; 每个文件 fork 一次；{} 替换为路径\nfind . -name '*.tmp' -exec chmod 644 {} +      # -exec … + 把多个路径塞进一次调用，删\u002Fchmod 大量文件时快得多\nfind . -name '*.jpg' -exec convert {} {}.png \\; # 文件少、命令重时用逐个执行\nfind . -name '*.tmp' -ok rm {} \\;              # -ok 每个都问一次，手生时兜底 示例 3：直接删 find logs -name '*.log' -mtime +7 -delete    # -delete 内置删除，不 fork，隐含 -depth（先删文件后删目录） -delete 与 -exec rm {} + 的差别值得记牢： 维度 -delete -exec rm {} + 速度 快（无 fork，find 自己 unlink） 每个批次 fork 一次 rm 能否跑外部命令 不能 能（-exec sh -c '…' 任意组合） 隐含行为 自动 -depth，先删文件后删目录 需自己保证顺序 对目录 只能删空目录（非空报 Directory not empty） rm -r 能连子树一起删 误删风险 条件写错就当场删完 同样当场删，但可以换成 -print 先看 安全建议 永远先跑一遍去掉 -delete 的版本 同上；-exec rm -i 可加确认 危险的是 -delete 与 -o 组合时优先级误解：find . -name a -o -name b -delete 里 -delete 只作用于 -name b，要用括号 \\( -name a -o -name b \\) -delete。 示例 4：格式化输出做报表 find \u002F -xdev -type f -size +200M -printf '%s\\t%p\\n' | sort -rn | head\n# -printf 常用转义：%p 全路径、%f 文件名、%h 所在目录、%s 字节、%m 八进制权限、\n# %u\u002F%g 属主\u002F组、%TY-%Tm-%Td 日期、%i inode 号（找硬链接）——这是「谁占了空间」的组合拳",{"id":22667,"title":6149,"titles":22668,"content":22669,"level":594},"\u002Ftutorials\u002Flinux#_3-locate-靠数据库快但不实时",[5,5419],"示例 1：秒查全盘 locate nginx.conf        # 从数据库查，结果可能包含已删除的文件\nlocate -i 'id_rsa*'      # -i 忽略大小写 + glob；默认按子串匹配，-r '^\u002Fetc\u002F' 用正则\nlocate -b '\\.conf'       # -b 只按文件名基名匹配（不被当成路径一部分），少了误报\nsudo updatedb            # 刚建的文件查不到？手动重建数据库 数据库由 \u002Fetc\u002Fupdatedb.conf 决定（PRUNEFS、PRUNEPATHS），NFS\u002FFUSE\u002F容器卷通常被排除，所以「locate 找不到但 find 能找到」多半是这个原因。没装时：Debian apt install mlocate、RHEL 系 dnf install mlocate（新版是 plocate）、Alpine 一般直接用 find。",{"id":22671,"title":6241,"titles":22672,"content":22673,"level":594},"\u002Ftutorials\u002Flinux#_4-grep-r-按内容找文件",[5,5419],"示例 1：哪些文件里有某个词 grep -rl 'bind_port' \u002Fetc                  # -r 递归、-l 只列文件名\ngrep -rl --include='*.conf' 'listen' \u002Fetc  # 限制文件类型\ngrep -rL 'strict' conf.d                   # -L 反向：输出没有匹配的文件名\ngrep -rn 'bind_port' \u002Fetc                  # -n 显示行号，直接看匹配在哪一行\ngrep -rlZ 'A' dir | xargs -0 grep -l 'B'   # 找两个词都在的文件，注意 -Z\u002F-0 配对 装了 ripgrep 的话 rg -l bind_port 默认尊重 .gitignore 且快一个数量级，开发机上推荐；服务器上不一定有，别写进部署脚本。",{"id":22675,"title":6364,"titles":22676,"content":22677,"level":594},"\u002Ftutorials\u002Flinux#_5-查命令在哪which-whereis-type",[5,5419],"命令 会找到 例子 局限 which python3 PATH 里的可执行文件 \u002Fusr\u002Fbin\u002Fpython3 看不到别名、函数、内建；不是 POSIX 标准 type -a python3 全部来源（别名\u002F函数\u002F内建\u002F文件） 三条一起列出 交互式排查首选 command -v python3 只输出路径（脚本标准写法） \u002Fusr\u002Fbin\u002Fpython3 内建会输出名字本身 whereis nginx 二进制 + 源码 + man 页 \u002Fusr\u002Fsbin\u002Fnginx \u002Fusr\u002Fshare\u002Fman\u002Fman8\u002Fnginx.8.gz 按固定目录猜，不看 PATH apropos backup 帮助文本里含关键词的命令名 rsync、dump… 需要 man-db 索引存在 readlink -f $(command -v python3) 剥开所有软链接到真实文件 \u002Fusr\u002Fbin\u002Fpython3.12 排查 alternatives 时常用",{"id":22679,"title":808,"titles":22680,"content":22681,"level":594},"\u002Ftutorials\u002Flinux#踩坑提醒-5",[5,5419],"find 的 -name 忘加引号：find \u002F -name *.log 会被当前目录里的 *.log 先展开，结果扫的是无关名字。 -mtime -7 与「最近 7 天」的直觉不完全一致（按 24 小时窗口而非日历天），要精确到分钟用 -mmin -10080 或 -newermt \"2026-09-27\"（后者最直观，强烈推荐）。 find 输出直接喂给 xargs 会被空格\u002F换行坑到，一律 -print0 配 xargs -0。 在 \u002F 上跑 find 会大量报 Permission denied：加 2>\u002Fdev\u002Fnull 静音，或用 -xdev 限制在当前文件系统。",{"id":22683,"title":6569,"titles":22684,"content":22685,"level":276},"\u002Ftutorials\u002Flinux#七文本处理三剑客grepsedawk",[5],"日志分析、配置批改、数据统计，靠的就是 grep（找）、sed（改行）、awk（按列算）。再加五个小工具 sort、uniq、cut、tr、wc，能解决 95% 的「文本加工」需求。awk 与 sed 的分工一句话：改文本用 sed，算数据用 awk。",{"id":22687,"title":6614,"titles":22688,"content":22689,"level":594},"\u002Ftutorials\u002Flinux#_1-grep-查找匹配的行",[5,6569],"grep 基本格式：grep [选项] 模式 文件…。 示例 1：基础查找 grep 'ERROR' app.log         # 输出包含 ERROR 的行\ngrep -i error app.log        # -i 忽略大小写\ngrep -n 'listen' nginx.conf  # -n 显示行号\ngrep -c 'ERROR' app.log      # -c 只输出匹配的行数\ngrep -w 'id' data.txt        # -w 整词匹配，不会命中 width\ngrep -v '^#' app.conf        # -v 反选（排除），常用它去注释\ngrep -F '1.2.3' version.txt  # -F 按字面字符串匹配（不当正则），最快最安全\ngrep -m 1 'Start' big.log    # -m 1 匹配到第一条就停，大文件里找开头很有用 示例 2：正则匹配 grep -E 'ERROR|FATAL' app.log                  # -E 扩展正则，或直接用 |（等价 egrep，但别再用 egrep）\ngrep -oE '[0-9]{1,3}(\\.[0-9]{1,3}){3}' access.log  # -o 只输出匹配到的部分——抠 IP\ngrep '\\broot\\b' pass.conf                      # \\b 词边界（GNU）\ngrep '[[:space:]]$' conf                       # 找行尾空格（[[:digit:]] 类写法不受 locale 影响） 正则元字符记忆表（ERE，即 -E 模式）： 写法 含义 例子 . * + ? 任意字符 \u002F 0 或多 \u002F 1 或多 \u002F 0 或 1 ^GET .* 200$ [0-9]、[[:digit:]] 字符类（后者不受 locale 影响） grep '[[:space:]]$' conf 找行尾空格 {n,m} 出现次数区间 grep -E '[0-9]{1,3}(\\.[0-9]{1,3}){3}' 粗查 IP | 或 -E 'warn|error' ^ $ 行首 \u002F 行尾 grep -c '^$' 数空行 \\b 词边界（GNU） grep '\\broot\\b' 示例 3：上下文与多文件 grep -C 3 'Traceback' app.log    # -C 3 上下各 3 行；-A 只有后、-B 只有前\ngrep -rl 'TODO' src              # -l 只输出文件名\ngrep -q 'pattern' f && echo 有    # -q 不输出只给状态码，脚本判断「有没有搜到」专用\ntail -f app.log | grep --line-buffered ERROR   # 实时管道必备 --line-buffered",{"id":22691,"title":7020,"titles":22692,"content":22693,"level":594},"\u002Ftutorials\u002Flinux#_2-sed-按行替换与删除",[5,6569],"sed 把输入按行读进「模式空间」，执行脚本后输出。核心是 s\u002F找\u002F换\u002F标志。 示例 1：替换 sed 's\u002Fold\u002Fnew\u002F' file         # 每行第一个匹配替换；只输出到终端，不改文件\nsed 's\u002Fold\u002Fnew\u002Fg' file        # g 每行全部替换——不加 g 是常见遗漏\nsed 's\\|\u002Fa\u002Fb\\|\u002Fa\u002Fc\\|g' file   # 换分隔符避开路径里的 \u002F，也可用 #、@\nsed 's\u002Fx\u002Fy\u002F2' file            # 只替换每行第 2 个匹配\nsed -E 's\u002F([0-9]+)\u002F[\\1]\u002Fg'    # -E 扩展正则；() 分组、\\1 回引用、& 代表整段匹配 注意 BRE（默认）与 ERE（-E）的区别：BRE 里 + ? {} 要写成 \\+ \\? \\{，-E 后反过来不转义。 示例 2：取行与删行 sed -n '10,20p' file      # -n 关掉默认输出 + p 只打印 10–20 行\nsed '5d' file             # d 删第 5 行；'5,9d' 删区间\nsed '\u002F^$\u002Fd' file          # 删空行（匹配用 \u002F正则\u002F 代替行号）\nsed '\u002Fpattern\u002Fd' file     # 删含 pattern 的行；想删它的下一行用 pattern\u002F{n;d}\nsed -n '\u002FERROR\u002F,+3p' file # 匹配行及其后 3 行（GNU 扩展 +N） 示例 3：原地修改（先留备份！） cp \u002Fetc\u002Fssh\u002Fsshd_config \u002Fetc\u002Fssh\u002Fsshd_config.$(date +%F)\nsed -i 's\u002F^# *PasswordAuthentication yes\u002FPasswordAuthentication no\u002F' \u002Fetc\u002Fssh\u002Fsshd_config\ndiff \u002Fetc\u002Fssh\u002Fsshd_config.$(date +%F) \u002Fetc\u002Fssh\u002Fsshd_config -i 表示 in-place 原地修改，写成 -i.bak 会自动留一份备份。差异提醒：BusyBox 不支持 -i 带后缀（写成 -i -e）；macOS 必须给 -i ''。-i 会重建文件（新 inode），所以正在被 tail -F 跟的日志、以硬链接方式共享的文件、被进程按 inode 持有的文件都会受影响；nginx 之类的配置文件改完必须 reload 才生效。安全习惯：先不带 -i 跑一遍确认输出，再动手。",{"id":22695,"title":7297,"titles":22696,"content":22697,"level":594},"\u002Ftutorials\u002Flinux#_3-awk-按列算数据",[5,6569],"awk 默认以空白分列，$1 是第一列、$0 是整行、NF 是列数、NR 是行号；结构是 '条件 {动作}'。 示例 1：取列 awk '{print $1, $9}' access.log      # 打印第 1 列（客户端 IP）和第 9 列（状态码）\nawk '{print $NF}' file               # $NF 是最后一列（列数不定时必备）\nawk -F'\"' '{print $6}' access.log    # -F 用 \" 分列，第 6 段常是 URL\nawk -F'\\t' '$5==\"WARN\"' logs.tsv     # 制表符分隔 + 精确匹配某列 示例 2：按条件过滤 awk '$9 == 500' access.log           # 只输出状态码 500 的行\nawk '$3 > 1000 {print $1}' stat.txt  # 数值条件（字符串自动当数） 示例 3：聚合统计（awk 最经典的用法） awk '{a[$1]++} END {for (k in a) print a[k], k}' access.log | sort -rn | head\n# 按第 1 列计数、输出「次数 名字」再排序——Top N 统计的万能骨架\n\nawk 'NR>1 {sum += $2} END {print sum\u002F(NR-1)}' data.txt   # 跳过表头求平均\nawk -F, 'END {print NR}' data.csv                        # 数行数（比 wc -l 稳，能处理无末尾换行）\nawk 'count[$7]++ > 0 {print $7} END {exit}' access.log   # 找出第一个重复出现的 URL\nawk 'BEGIN {OFS=\",\"} {print $1,$2}'                      # OFS 控制输出分隔符 内置变量与函数速记：FS\u002FOFS 输入输出分隔符、ORS 输出记录分隔符（设 \"\" 可拼接行）、FILENAME、FNR（当前文件的行号，多文件时与 NR 不同）、length()、split()、sub()\u002Fgsub()、sprintf()、toupper()、match() + RSTART\u002FRLENGTH。",{"id":22699,"title":7572,"titles":22700,"content":22701,"level":594},"\u002Ftutorials\u002Flinux#_4-sortuniqcuttrwcpaste-五个过滤器",[5,6569],"sort file                # 默认字典序；受 locale 影响，脚本里加 LC_ALL=C 保证按字节稳定\nsort -n                  # 按数字排（否则 10 排在 9 前面）\nsort -h                  # 按人读大小排：2K 1G 10M 能正确排\nsort -r                  # 降序\nsort -V                  # 版本号感知：v1.10 排在 v1.9 后\nsort -t: -k3,3n \u002Fetc\u002Fpasswd    # -t 指定分隔符；-k3,3n 按第 3 列数字排。注意 ,3 不能省——\n                               # 只写 -k3 会从第 3 列一直排到行尾，结果和预期不符\nsort -u                  # 去重（需已排序，等价 sort | uniq）\nsort -m                  # 合并多个已排序文件\n\nuniq -c file             # 合并「相邻」重复行并计数——所以前面必须先 sort，否则看起来完全没去重\ncut -d: -f1 \u002Fetc\u002Fpasswd  # -d 分隔符 + -f 取列；-f1-3 区间、--complement 取反列；只支持单字符分隔符\ntr -d '\\r' \u003C win.txt     # tr 逐字符替换\u002F删除：删 CR、tr ' ' '\\n' 一行变多行、tr a-z A-Z 转大写\nwc -l file               # 行数；-w 词数、-c 字节数；多文件会额外给 total 行\npaste a.txt b.txt        # 按列并文件；-d, 指定分隔、-s 转置行列",{"id":22703,"title":7741,"titles":22704,"content":22705,"level":594},"\u002Ftutorials\u002Flinux#_5-三个日志统计的一行命令照抄可用",[5,6569],"场景：\u002Fsrv\u002Fapp\u002Flogs\u002Faccess.log，nginx combined 格式（IP - - [时间] \"方法 路径 协议\" 状态 字节 \"referer\" \"ua\"）。 awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10    # 1) 哪个 IP 请求最多（Top 10）\nawk '{print $9}' access.log | grep -v '^2' | sort | uniq -c | sort -rn  # 2) 状态码分布（只看非 2xx）\nawk '{print $NF, $7}' access.log | sort -rn | head -20                 # 3) 最慢的 20 个请求（假设 $NF 是耗时） 三条命令的结构都是「取列 → 归并计数 → 排序 → 截断」，学会一条就等于学会一片。想按小时聚合访问量，用 -o 抠出时间里的小时： grep -oE '\\[[^] ]+ [0-9]{2}' access.log | awk '{print $2}' | sort | uniq -c",{"id":22707,"title":808,"titles":22708,"content":22709,"level":594},"\u002Ftutorials\u002Flinux#踩坑提醒-6",[5,6569],"grep 在 sh 脚本里用 grep -E，别再用废弃的 egrep（新版会打 warning）。 管道里最后一个命令的退出码才是 $?（第八章讲 pipefail）。判断「有没有搜到」用 grep -q：它不输出只给状态码，if grep -q 'pattern' f; then。 sed -i 无备份直接改生产配置出事后无从回滚；养成 sed -i.bak 或先 cp 的习惯，改完 diff 一眼。 awk 里字符串比较要写 $5 == \"WARN\" 而不是 $5 = \"WARN\"——一个等号是赋值，永远为真，这是 awk 最隐蔽的错误。 uniq 忘 sort 会得到「看起来完全没去重」的结果；反之 sort -u 会静默改变顺序语义（等价 sort | uniq）。",{"id":22711,"title":7983,"titles":22712,"content":22713,"level":276},"\u002Ftutorials\u002Flinux#八管道重定向与命令组合",[5],"Shell 的表达能力几乎全在这一章：把 stdout 接给下一个命令（|），把输出\u002F错误写到别处（>、2>），把几条命令串成逻辑（&&、||）。搞不清这三个，脚本就只能写成猜谜。",{"id":22715,"title":8012,"titles":22716,"content":22717,"level":594},"\u002Ftutorials\u002Flinux#_1-重定向-三个流与-2",[5,7983],"每个进程有三个标准流：0 stdin、1 stdout、2 stderr。 cmd > out.txt            # stdout 覆盖写文件\ncmd >> out.txt           # 追加——日志文件必备，> 会把历史冲掉\ncmd 2> err.txt           # stderr 单独写文件，只看错误\ncmd > out.txt 2>&1       # stderr 并入同一个 stdout 再一起进文件。经典写法，顺序不能反！\ncmd 2>&1 > out.txt       # 反例：stderr 去了终端，stdout 才进文件（按从左到右解析）\ncmd &> out.txt           # 两者都进文件（bash 简写）；POSIX sh 里没有，讲可移植时别用\ncmd > \u002Fdev\u002Fnull 2>&1     # 全部丢进黑洞，让 cron 安静运行\ncmd \u003C in.txt             # stdin 来自文件\ncmd 1>&2                 # stdout 改道 stderr，让日志统一被 2> 捕获 >| 强制覆盖（即使开了 set -o noclobber）；&> 与 >| 都是 bash 扩展。判断命令失败用 $?：0 成功、非 0 失败，具体数字看 man 的 EXIT STATUS 章节。",{"id":22719,"title":8175,"titles":22720,"content":22721,"level":594},"\u002Ftutorials\u002Flinux#_2-tee-既上屏又落盘",[5,7983],"示例 1：边看边存 make install 2>&1 | tee -a \u002Fvar\u002Flog\u002Fdeploy.log     # -a 追加；不加就是覆盖 示例 2：绕过「>> 需要提权」的问题 echo \"10.0.0.5 internal\" | sudo tee -a \u002Fetc\u002Fhosts sudo some-cmd > \u002Froot\u002Fsecret.txt 的重定向是当前 Shell（无特权）执行的，所以会报 Permission denied——正确写法就是用 | sudo tee file。tee \u002Fdev\u002Fstderr 可把内容转错误流。",{"id":22723,"title":8261,"titles":22724,"content":22725,"level":594},"\u002Ftutorials\u002Flinux#_3-xargs-把参数交给命令",[5,7983],"find . -name '*.tmp' -print0 | xargs -0 rm -f --\n# 标准拼接：-0 处理空格\u002F换行（与 -print0 配对使用）；-- 防文件名被当参数（-rf 之类）\n\nseq 1 9 | xargs -n 3 cmd      # -n 3 每次最多传 3 个参数\nfind . -name '*.jpg' -print0 | xargs -0 -P 8 -n 1 convert   # -P 8 并发 8 个进程，适合下载\u002F压缩\necho a b c | xargs -I{} sh -c 'tar czf {}.tgz {}'           # -I{} 把参数放在指定位置，每行起一个 shell\nfind . -name '*.tmp' -print0 | xargs -0 -r rm --            # -r 空输入不执行（GNU 特性），避免 cmd 空跑\nxargs --show-limits           # 查 ARG_MAX；超限时报 Argument list too long xargs 与 -exec … + 的取舍：两者都能批量；-exec 更直白、不用考虑管道，xargs 更灵活（能在中间做 grep\u002Fsort）。只要文件名可能带空格或换行，就必须用 -print0\u002Fxargs -0 这一对，或者 find -exec。",{"id":22727,"title":8446,"titles":22728,"content":22729,"level":594},"\u002Ftutorials\u002Flinux#_4-与子-shell",[5,7983],"nginx -t && systemctl reload nginx             # && 前者成功才跑后者——改完配置先验证\nsystemctl is-active app || systemctl restart app   # || 前者失败才跑后者\ncd \u002Ftmp ; make                                 # ; 无条件依次执行（脚本里危险，见踩坑）\nlong_task &                                    # & 后台执行；关终端会收到 SIGHUP\n( cd \u002Ftmp && make )                            # 子 shell：里面的 cd 不影响当前目录\n{ cmd1; cmd2; } > log                          # 分组但同 shell（变量能保留）；{ 后有空格、要带 ;\necho $(date +%F)                               # 命令替换：$() 可嵌套，新代码一律用它\ntime make -j4                                  # 统计耗时（bash 内建的 time 更准，-p 给 POSIX 格式） $() 与反引号功能基本相同，但反引号内部的反斜杠和嵌套容易出错，新代码一律 $()。",{"id":22731,"title":8604,"titles":22732,"content":22733,"level":594},"\u002Ftutorials\u002Flinux#_5-here-doc-here-string",[5,7983],"cat > \u002Ftmp\u002Frun.sh \u003C\u003C'EOF'     # here doc：把多行喂给命令的 stdin\n#!\u002Fbin\u002Fbash\nset -euo pipefail\ncd \u002Fsrv\u002Fapp && make deploy\nEOF\n\nwc -c \u003C\u003C\u003C \"hello world\"       # here string：单行输入，等价 echo \"abc\" | wc -c\n\nsudo tee \u002Fetc\u002Fprofile.d\u002Fjava.sh >\u002Fdev\u002Fnull \u003C\u003C'EOF'    # \u003C\u003C'EOF' 原样不展开变量\nexport JAVA_HOME=\u002Fusr\u002Flib\u002Fjvm\u002Fjava-17\nexport PATH=\"$JAVA_HOME\u002Fbin:$PATH\"\nEOF \u003C\u003CEOF（不带引号）会对内容做变量展开，\u003C\u003C'EOF' 原样保留——写含 $HOME 的配置文件时用后者。\u003C\u003C-EOF 允许脚本里用 Tab 缩进（只剥 Tab，不剥空格）。here doc 结束的标记必须独占一行、顶格、无尾随空格，否则 Shell 会继续等输入（表现为卡在 >）。",{"id":22735,"title":8736,"titles":22736,"content":22737,"level":594},"\u002Ftutorials\u002Flinux#_6-后台任务与脱离终端",[5,7983],"long_task &                                  # 直接后台；关掉终端就收到 SIGHUP 会死\nnohup long_task > run.log 2>&1 &             # 标准做法：nohup 忽略 SIGHUP\njobs -l                                      # 看后台列表：[1]  28414 Running …\nfg %1                                        # 把 1 号作业调回前台（bg %1 继续后台）；%1 是作业号不是 PID\ndisown -h %1                                 # 已经跑着的任务从当前 Shell 解绑的补救\ntmux new -d -s build 'make deploy'           # 更好的方案：可重连，服务器场景首选 Ctrl-Z 是挂起（SIGTSTP）不是退出，进程还在占内存；忘记处理会留一堆 Stopped 进程，fg 回来或 kill %1 收掉。",{"id":22739,"title":808,"titles":22740,"content":22741,"level":594},"\u002Ftutorials\u002Flinux#踩坑提醒-7",[5,7983],"「明明加了 2>&1 还是没抓到错误」：顺序写反了。> 与 2>&1 按从左到右解析，cmd 2>&1 > f 里 stderr 指向的是终端的那个 1。 rm -rf dir && echo done 与 rm -rf dir; echo done 在脚本里的差别可能决定你是否覆盖了错误目录（第二章）。 xargs 不加 -r 时，空输入也会执行一次命令——find … | xargs rm 在极端情况下等价于 rm 无参数（报错），但换成 xargs chmod 777 就会拿当前目录开刀。 变量赋值不要留空格：NAME=deploy 正确，NAME = deploy 会被当成「执行名为 NAME 的命令」。 在管道里 if\u002Fwhile read 循环会跑在子 shell，循环里改的变量出来后消失（bash 4.2+ 可用 lastpipe，或把管道换成重定向 done \u003C file）。",{"id":22743,"title":8930,"titles":22744,"content":22745,"level":276},"\u002Ftutorials\u002Flinux#九压缩与归档targzipzip",[5],".tar.gz、.zip、.xz 三种东西经常被混为一谈：tar 负责打包（把多个文件合成一个，不改内容），gzip\u002Fxz 负责压缩（单流算法）。tar 的 -z\u002F-j\u002F-J 只是顺手帮你调了一下压缩器。",{"id":22747,"title":8976,"titles":22748,"content":22749,"level":594},"\u002Ftutorials\u002Flinux#_1-tar-的字母到底管什么",[5,8930],"字母 含义 单独用 c create 创建归档 与 x 互斥 x extract 解包 — t table of contents 列内容 不解包先看清单 r repend 追加（不能压缩） 少用 u update 只追加更新的文件 类似增量备份 f 归档file 名，必须紧跟文件名 -f - 表示 stdin\u002Fstdout v verbose 打印过程 看进度用 z 走 gzip（.tar.gz\u002F.tgz） 速度快、体积一般 j 走 bzip2（.tar.bz2） 慢、体积小 J 走 xz（.tar.xz） 最慢、最小，发行版源码包常用 a 按后缀自动选压缩器（--auto-compress） GNU tar 1.15+ 创建时可用",{"id":22751,"title":9169,"titles":22752,"content":22753,"level":594},"\u002Ftutorials\u002Flinux#_2-tar-常用写法",[5,8930],"tar cf app.tar \u002Fsrv\u002Fapp         # 打包不压缩：c 创建、f 后跟归档名\ntar czf app.tar.gz \u002Fsrv\u002Fapp     # 打包 + gzip；验证用 tar tzf app.tar.gz\ntar cJf app.tar.xz \u002Fsrv\u002Fapp     # 打包 + xz：最小、最慢\ntar caf app.tar.xz \u002Fsrv\u002Fapp     # a 按后缀自动选压缩器，不容易打错\n\ntar xzf app.tar.gz -C \u002Fsrv\u002Fapp  # 解包到指定目录：x 解、-C 目标目录（目录需已存在，-C 不会自动建）\ntar tzf app.tar.gz | head       # 解包前先看清单——必须的一步！没有顶层目录的归档（tarbomb）\n                                # 会把一堆文件直接散在当前目录；新版 tar 可用 --one-top-level 自动套一层\ntar xzf app.tar.gz etc\u002Fnginx\u002Fnginx.conf   # 只解一个文件，路径按 tar tzf 里看到的原样写\ntar xzf app.tar.gz -C \u002Ftmp --wildcards 'etc\u002Fnginx\u002F*'   # 只取归档里的某个目录\n\ntar czf app.tgz --exclude='.\u002Fnode_modules' --exclude='*.log' .   # 排除若干：--exclude 必须在创建之前给出\ntar czf app.tgz -X \u002Fsrv\u002Fexclude.txt .                             # 排除清单来自文件，一行一个 glob\nsudo tar xzf app.tar.gz -C \u002F      # 保留权限解包：不加 sudo 时属主会变执行者\ntar czhf a.tgz dir                # -h 把软链接指向的内容打进去（默认只存链接本身）",{"id":22755,"title":9394,"titles":22756,"content":22757,"level":594},"\u002Ftutorials\u002Flinux#_3-tar-与管道目录到目录的流式搬运",[5,8930],"tar cf - -C \u002Fsrv\u002Fapp . | tar xf - -C \u002Fsrv\u002Fapp-backup    # 目录到目录流式复制，保留权限，不落地中间文件\ntar czf - -C \u002Fsrv app | ssh deploy@backup 'cat > \u002Fbackup\u002Fapp.tgz'   # 跨机实时打包传过去\nssh deploy@server 'tar czf - \u002Fsrv\u002Fapp' | tar xzf - -C \u002Flocal\u002Fbackup  # 反过来，从远端拉一个目录 tar cf - 里的 - 是 stdout，xf - 里的 - 是 stdin。这种写法的价值：不产生临时文件、能跨过空间不足的目标盘、管道里还能插一层 gzip。",{"id":22759,"title":9506,"titles":22760,"content":22761,"level":594},"\u002Ftutorials\u002Flinux#_4-gzipbzip2xzzstd-单文件压缩器",[5,8930],"gzip -9 app.log        # 压缩并替换原文件：-1 最快、-9 最小（默认 6）；-k 保留原文件\ngunzip app.log.gz      # 解压（等价 gzip -d）；解压后 .gz 消失\nbzip2 -9 file          # 更高压缩、更慢，大文件慢到不可用\nxz -9e file            # 当前通用最优：-9e 额外努力；解压很快\nzstd -19 file          # 现代首选（快 + 小），服务器未必装，Alpine 仓库有\nzcat app.log.gz        # 不解压直接看\nzgrep -h 'ERROR' \u002Fvar\u002Flog\u002F*.gz   # 不解压直接搜——查历史日志必备，-h 去掉文件名前缀\nzless app.log.gz       # 分页看压缩包（less 配了 LESSOPEN 也能直接开 .gz） zgrep 处理多个 .gz 时按文件名顺序扫，配合 ls -r 可以从最新往回找，找完立刻停。",{"id":22763,"title":9627,"titles":22764,"content":22765,"level":594},"\u002Ftutorials\u002Flinux#_5-zip-unzip",[5,8930],"zip -r site.zip dist\u002F          # 压缩目录：-r 递归；-9 最高压缩、-q 安静\nzip -ru site.zip dist\u002F         # -u 只加更新的文件（zip 支持增量更新归档）\nzip -erP 'pass' site.zip dist\u002F # 加密码：传统 ZipCrypto 很弱，敏感数据别用 zip\nunzip -l site.zip              # -l 只列内容不解包\nunzip site.zip -d \u002Fsrv\u002Fapp     # -d 解压到目录；目录不存在会创建（与 tar -C 相反）\nunzip -o site.zip              # -o 全覆盖 \u002F -n 不覆盖——默认会逐个问，脚本里必须指定\nzipinfo -1 site.zip            # 比 unzip -l 灵活：-h 头部、-s 大小列表\nzip -FF broken.zip --out fixed.zip   # 传输截断时抢救 tar 与 zip 的选择：Linux 服务器之间用 tar.gz\u002Fxz（保留权限、时间戳、软链接），要给 Windows 或走浏览器下载用 zip。zip 不保存 Unix 权限位，解压后脚本全部丢执行位。",{"id":22767,"title":808,"titles":22768,"content":22769,"level":594},"\u002Ftutorials\u002Flinux#踩坑提醒-8",[5,8930],"tar 忘记 -f 后跟文件名（如 tar -czf app）会把 - 当成文件，或报 Cowardly refusing to create an empty archive；f 永远放参数最后。 打包整个 \u002F 会卡在 \u002Fproc、\u002Fsys 上：加 --exclude={\u002Fproc,\u002Fsys,\u002Fdev,\u002Frun}，或直接用专门的备份工具。 压缩前先看体积：du -sh；对一个正在被写入的日志压缩会得到两份都不完整的文件，先 cp 快照或用 logrotate 的 copytruncate。 gzip -9 与 -6 的体积差常在 1–3%，时间差却是几倍，别为了「更小」把部署脚本拖慢。 .tar.gz 想追加文件是不可能的（压缩流不能追加），需要重打包——这是很多人踩过的设计限制。",{"id":22771,"title":9833,"titles":22772,"content":22773,"level":276},"\u002Ftutorials\u002Flinux#十进程pstopkill",[5],"「服务没起来」的第一现场永远是进程列表：它在不在、属主是谁、CPU 吃了多少、状态是 S 还是 Z。这一章把 ps 的每一列、top 的每个数字、信号的优先级讲清楚，然后给出杀进程的正确顺序。",{"id":22775,"title":9863,"titles":22776,"content":22777,"level":594},"\u002Ftutorials\u002Flinux#_1-ps-抓一份进程快照",[5,9833],"ps 有两种参数风格，效果接近： ps aux     # BSD 风格：USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND\nps -ef     # POSIX\u002FUNIX 风格：UID PID PPID C STIME TTY TIME CMD，带父进程号\nps -fp 28414                    # 看单个 PID 的详情（含父进程）\nps -u deploy -o pid,cmd         # 只看某个用户起了什么\nps aux --sort=-rss | head       # 按内存排序，找吃内存的大户\nps -eo pid,ppid,user,%cpu,rss,etime,cmd --sort=-%cpu    # 自定义列（最推荐，脚本里稳）\npstree -ap 28414                # 进程树看父子关系（-a 带命令行） ps aux 各列含义： 列 含义 读法与坑 USER 属主用户 服务真实用户常是 www-data\u002Fnginx，不是你 PID 进程号 唯一，kill 用它 %CPU 自进程启动以来的平均 CPU 占比 单核满载 = 100；8 核全用可到 800；多核机器上超 100 完全正常 %MEM RSS 占物理内存百分比 与 RSS 一致，别拿它当「真实占用」 VSZ 虚拟地址空间大小（KB） 包含未实际分配的、映射的、共享的，JVM\u002F数据库常见几个 G 而 RSS 很小 RSS 常驻物理内存（KB） 实际占用看这一列；共享库会被多个进程重复计 TTY 控制终端 ? 表示无终端（服务\u002F后台进程） STAT 状态码 见下表，排障关键 START 启动时刻 显示日期表示不是今天起的 TIME 累计 CPU 时间（不是墙上时间） 跑了三天但 TIME 只有 5 分钟 = 一直在等 IO COMMAND 命令行 ps 只显示一次快照，top 会跟着改 STAT 常见组合： 码 含义 说明 R 运行\u002F可运行 在 CPU 上或在队列里 S 可中断睡眠 绝大多数正常服务的状态 D 不可中断睡眠 通常在等磁盘\u002FNFS；kill -9 对它无效，这是「进程杀不掉」的头号原因 T 停止 被 Ctrl-Z 或 SIGSTOP 挂起 Z 僵尸（defunct） 已退出但父进程没 wait；无法再被杀，只能让父进程回收 \u003C \u002F N 高优先级 \u002F 低优先级 nice 值的标记 l \u002F s \u002F + 多线程 \u002F 会话首领 \u002F 前台进程组 s 常见于服务进程",{"id":22779,"title":10288,"titles":22780,"content":22781,"level":594},"\u002Ftutorials\u002Flinux#_2-pgrep-与-pidof-找-pid",[5,9833],"pgrep -f 'gunicorn.*app'     # -f 按正则匹配整条命令行；不加 -f 只看程序名（15 字符截断）\npgrep -u deploy -f worker    # 叠加用户限定，更精确\npgrep -l -f nginx            # -l 同时打印进程名，快速核对是不是自己要的\npidof nginx                  # 按可执行文件名精确列出所有 PID：28414 28415\npidstat -p 28414 1           # 持续采样某进程的 CPU\u002FIO（sysstat 包），排「这个进程到底在忙什么」 用 pgrep -f 时的自杀风险：pkill -f 'tail -f app.log' 可能匹配到执行这条命令的 shell 本身。稳妥写法是加 -x（精确名）或用 pgrep 先列出确认。",{"id":22783,"title":10380,"titles":22784,"content":22785,"level":594},"\u002Ftutorials\u002Flinux#_3-top-实时视图",[5,9833],"top 的头部五区域与关键读数： 区域 样例 怎么读 第 1 行 top - 09:24:31 up 14 days, 3:12, 2 users, load average: 0.32, 0.41, 0.38 当前时间、运行时长、登录数、1\u002F5\u002F15 分钟平均负载 第 2 行 Tasks: 183 total, 1 running, 182 sleeping, 0 stopped, 0 zombie zombie 非 0 说明有父进程不回收 第 3 行 %Cpu(s): 12.3 us, 3.1 sy, 0.0 ni, 83.9 id, 0.4 wa, 0.0 hi, 0.3 si us 用户、sy 内核、id 空闲、wa 高等待说明磁盘瓶颈 第 4-5 行 MiB Mem: 7861.4 total, 214.3 free, 3210.5 used, 4436.6 buff\u002Fcache \u002F MiB Swap: 2048.0 total, 1900.0 free 看可用内存要算 free + buff\u002Fcache（第十七章细讲） 进程表 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND VIRT 虚拟、RES 常驻、SHR 共享；真正吃的是 RES 示例 1：常用启动方式 top -p 28414          # 只看某个进程\ntop -u deploy         # 只看某个用户\ntop -b -n 1 > snap.txt  # 批处理模式抓一次快照，脚本必备 示例 2：交互键（进入 top 之后按） 键 作用 键 作用 q 退出 1 展开每个 CPU 核心 P \u002F M 按 CPU \u002F 内存排序 T（小写 t） 切换头部显示 c 显示完整命令行 u \u002F U 只看某用户 f 选择显示哪些列 k 对某 PID 发信号（会二次确认） r renice 某 PID G 切换到某个 cpuset H 显示线程 g 保存\u002F切换视野组 空格 立即刷新 o\u002FR 改变刷新间隔 \u002F 反向排序 htop（apt install htop）好在：彩色树状视图、鼠标可点、F4 过滤、F9 送信号、F6 排序、Ctrl-L 重绘。%CPU 超 100 的解释：一个 8 线程的进程把 2 个核跑满会显示 200%，多核机器上 top 的 100% 指的是「一个核」，不是总机器的 1\u002F8；判断是否真忙要看展开后的每核利用率（按 1）。",{"id":22787,"title":10737,"titles":22788,"content":22789,"level":594},"\u002Ftutorials\u002Flinux#_4-kill-与信号-正确顺序三步别跳",[5,9833],"信号 编号 默认行为 能不能被捕获 用法 HUP 1 挂断 \u002F 让服务重读配置 能 kill -HUP 28414（老式重载，nginx 仍支持） INT 2 中断（等同 Ctrl-C） 能 交互场景 QUIT 3 退出并 dump core（Ctrl-\\） 能 有些服务用它输出栈 TERM 15 请求终止（默认） 能 kill 28414 就是它 KILL 9 立刻杀死，内核回收 不能 最后手段，见下 STOP 19（x86 Linux） 强制暂停，不可捕获 不能 冻结进程做调试；停止状态要 CONT 恢复 CONT 18 恢复被 STOP 的进程 — kill -CONT 28414 USR1\u002FUSR2 10\u002F12 自定义 能 各服务含义不同（看服务文档） kill 28414                  # ① SIGTERM（15）：给程序机会关连接、刷缓冲、清 pid 文件\nsleep 10; ps -p 28414       # ② 等 10 秒确认，没退出再说\nkill -9 28414               # ③ 仍不掉才 SIGKILL：内核硬拆，不做清理 kill -9 的代价：不执行信号处理函数 → 临时文件\u002F锁文件\u002Fpid 文件残留、半写的数据库文件、子进程变孤儿、共享内存段没释放。很多「服务重启后卡在启动中」其实是 -9 留下的锁没清。对 D 状态进程 -9 也无效——它在等内核 IO，只能等 IO 完成或修好底层（磁盘\u002F网络存储）。 其他常用发法 kill -15 28414              # 显式指定信号，等价 kill 28414\nkill -TERM -28414           # 负号 = 整个进程组，清理派生一堆子进程的任务\nkill %1                     # 按当前 Shell 的作业号\nkill -0 28414               # 不发送，只测进程在不在且有没有权限——脚本里的标准存活检查\nkillall nginx               # 按名字（精确匹配，默认区分大小写）；killall -u deploy vim\npkill -TERM -f 'gunicorn app'   # 按命令行正则，先温和来一遍；确认杀不掉再上 -9",{"id":22791,"title":11073,"titles":22792,"content":22793,"level":594},"\u002Ftutorials\u002Flinux#_5-nicereniceionice-调优先级",[5,9833],"nice -n 10 .\u002Fbatch.sh           # 以低优先级启动：-20（最高）到 19（最低），0 是默认\nrenice -n 10 -p 28414           # 运行中调优先级；调高需要 root；-u deploy 批量改某用户所有进程\nionice -c 3 -p 28414            # 调 IO 优先级（-c3 空闲级），备份任务必备，cfq\u002Fbfq 调度器下才有效\nchrt --deadline 0 1000000 cmd   # 实时调度类——一般业务别碰，会把机器拖死",{"id":22795,"title":11148,"titles":22796,"content":22797,"level":594},"\u002Ftutorials\u002Flinux#_6-僵尸与孤儿",[5,9833],"ps -eo pid,ppid,stat,cmd | awk '$3 ~ \u002FZ\u002F'          # 找僵尸进程（\u003Cdefunct> 的那些）\nps -eo pid,ppid,cmd | awk '$2 == 1'                # 看被 init 收养的孤儿\nvmstat 1 5                                         # 顺带一眼看全局 僵尸进程不占 CPU 和内存，只占一个 PID 表项，但会持续消耗父进程的资源并可能填满 PID。正确处理不是杀僵尸（杀不掉），而是：让父进程 wait（修代码里的信号\u002F子进程回收）、或重启父进程；如果父进程是 init\u002Fsystemd 且仍在回收，僵尸会自己消失。孤儿进程是被过继给 PID 1 的活着的孩子，本身无害。 vmstat 1 5 的四列：r（运行队列长度，持续大于核数说明 CPU 不够）、b（阻塞在 IO 的进程数）、si\u002Fso（swap 进出，非 0 说明内存紧张）、wa（IO 等待百分比）。这几列在第十七章排障时是主力。",{"id":22799,"title":808,"titles":22800,"content":22801,"level":594},"\u002Ftutorials\u002Flinux#踩坑提醒-9",[5,9833],"ps aux 的 %CPU 是平均值，抓瞬时值用 top 或 pidstat 1；「ps 显示 3% 但机器很卡」通常是采样时机问题。 VSZ 大而 RSS 小不代表泄漏：JVM、mmap 大文件、malloc 池都会虚高，判断泄漏看 RSS 的长期趋势。 别对 systemd 托管的服务用 kill -9：systemd 会按 Restart= 立刻拉起来，看起来像「杀不死」。正确做法是 systemctl stop（第十一章）。 pkill -f 加 sudo 时容易误杀自己的 SSH 会话（匹配到了命令行里的字符串）。执行前 pgrep 同一条件先看结果。 僵尸 + 高负载的组合，根因常常是磁盘 D 状态进程堆积，先用 ps -eo stat,cmd | grep '^D' 确认。",{"id":22803,"title":11319,"titles":22804,"content":22805,"level":276},"\u002Ftutorials\u002Flinux#十一服务与定时任务systemctljournalctlcrontab",[5],"现代 Linux 的服务由 systemd 管，日志由 journald 收，定时任务由 cron 跑。这一章覆盖三件套，重点在 systemctl 的正确用法与 journalctl 的过滤参数——它们决定了你能不能在三分钟内定位「服务为什么起不来」。",{"id":22807,"title":11338,"titles":22808,"content":22809,"level":594},"\u002Ftutorials\u002Flinux#_1-systemctl-管理服务",[5,11319],"systemctl status nginx          # 看状态、最近日志、主 PID：绿=active、红=failed、黄=activating\nsystemctl start nginx           # 启动；不改变开机是否自启\nsystemctl stop nginx            # 停止：发 ExecStop（无则 SIGTERM），超时 90s 后 SIGKILL\nsystemctl restart nginx         # 重启，有中断；能 reload 就别 restart\nsystemctl reload nginx          # 重读配置不断服务；服务必须支持 ExecReload，否则报错\nsystemctl reload-or-restart nginx   # 支持就 reload，否则 restart——部署脚本里的标准写法\nsystemctl enable nginx          # 设开机自启：只建符号链接，不会启动；加 --now 顺便起\nsystemctl disable nginx         # 取消自启：不会停正在运行的服务；加 --now 一起停\nsystemctl enable --now app      # 部署时统一这么写\nsystemctl is-active app         # 查是否运行（--quiet 只要状态码，可进 if）\nsystemctl is-enabled nginx      # 查是否自启：enabled\u002Fdisabled\u002Fstatic\u002Fmasked\nsystemctl daemon-reload         # 改了 unit 文件后必须执行！不 reload 则新配置不生效\nsystemctl list-units --type=service --state=failed    # 只看失败的单元\nsystemctl list-unit-files --type=service              # 列所有可 enable 的单元（看开机状态）\nsystemctl cat nginx             # 打印 unit 文件内容（含 drop-in 合并结果）\nsystemctl edit nginx            # 建 drop-in 覆盖（推荐，不动原文件），保存后自动 daemon-reload\nsystemctl mask nginx            # 彻底禁死（连手动 start 都不行）；unmask 恢复\nsystemctl list-dependencies nginx    # 依赖树：「谁还没起来导致我起不来」\nsystemctl show nginx -p ExecMainStartTimestamp,MainPID   # 查任意属性，脚本取 PID\u002F启动时间\nsystemctl list-timers --all     # 看 systemd 定时器（比 cron 更好管的一类任务） restart 与 reload 的取舍：reload 让进程自己重读配置、连接不断（nginx、haproxy 支持），但不是所有服务都实现；配置改了却没生效，先 systemctl show app -p Environment,ExecStart 确认它拿到的到底是什么。",{"id":22811,"title":11619,"titles":22812,"content":22813,"level":594},"\u002Ftutorials\u002Flinux#_2-unit-文件的最小结构",[5,11319],"放在 \u002Fetc\u002Fsystemd\u002Fsystem\u002Fmyapp.service： [Unit]\nDescription=My App worker\nAfter=network-online.target postgresql.service\nWants=postgresql.service\n\n[Service]\nType=simple\nUser=deploy\nGroup=deploy\nWorkingDirectory=\u002Fsrv\u002Fapp\nEnvironmentFile=\u002Fsrv\u002Fapp\u002Fenv.list\nExecStart=\u002Fsrv\u002Fapp\u002Fvenv\u002Fbin\u002Fgunicorn -c gunicorn.conf.py app:app\nExecReload=\u002Fbin\u002Fkill -HUP $MAINPID\nRestart=on-failure\nRestartSec=3\nStandardOutput=journal\nLimitNOFILE=65535\n\n[Install]\nWantedBy=multi-user.target 字段 作用 常踩的点 Type simple（默认，ExecStart 即主进程）\u002Fforking（传统守护进程）\u002Foneshot（跑完就退）\u002Fnotify（自己报就绪） 脚本型任务用 simple 会因为脚本秒退被判失败，改 oneshot 或 Type=exec ExecStart 启动命令 必须绝对路径；不能用 shell 特性（管道、*），需要时 ExecStart=\u002Fbin\u002Fsh -c '…' After= \u002F Wants= \u002F Requires= 顺序与依赖 Requires 强依赖（对方失败我也失败），Wants 弱依赖；After 只管顺序不产生依赖，两者要搭配写 Restart= no\u002Fon-failure\u002Falways 配合 StartLimitBurst，重启风暴会被限流并进入 failed User= \u002F Group= 运行身份 改了要 chown 数据目录（第五章） Environment= \u002F EnvironmentFile= 环境变量 文件里不要写 export，一行 KEY=VALUE WorkingDirectory= 工作目录 不写就是 \u002F，相对路径全崩 ProtectSystem= \u002F ReadWritePaths= 沙箱加固 开了 ProtectSystem=strict 又没给 ReadWritePaths 就会「Permission denied」而权限位是对的 TimeoutStartSec\u002FTimeoutStopSec 超时 启动慢的服务要放宽，否则反复被杀 [Install] WantedBy= enable 时挂到哪个 target multi-user.target（服务器）\u002Fgraphical.target 改配置的三种途径优先级：systemctl edit 服务名（生成 \u002Fetc\u002Fsystemd\u002Fsystem\u002F服务名.d\u002Foverride.conf）> 自己写同名 drop-in 目录 > 直接改 \u002Flib\u002Fsystemd\u002Fsystem\u002Fxxx.service（会被包升级覆盖）。改完必须 systemctl daemon-reload。",{"id":22815,"title":12003,"titles":22816,"content":22817,"level":594},"\u002Ftutorials\u002Flinux#_3-journalctl-查服务日志",[5,11319],"journalctl -u nginx                     # 某个单元的全部日志\njournalctl -u nginx -f                  # 实时跟踪（等价 tail -f，但含重启前的日志）\njournalctl -u nginx --since \"2026-10-04 09:00\" --until \"10:00\"   # 时间窗；也支持 \"1 hour ago\"\njournalctl -p err -u nginx              # 只看错误及以上（emerg\u002Falert\u002Fcrit\u002Ferr\u002Fwarning\u002Fnotice\u002Finfo\u002Fdebug）\njournalctl -b                           # 本次启动的全部日志\njournalctl -b -1                        # 上一次启动——查无故重启必备\njournalctl -xe                          # 末尾几行 + 每条的解释（x 更多细节，e 跳到末尾）\njournalctl -k -b                        # 内核日志（等价 dmesg，但可按时段过滤）\njournalctl _PID=28414                   # 按字段查（字段=值 直接写）\njournalctl -f -u app -u worker          # 多个单元一起跟\njournalctl -o json-pretty -u app        # 换输出格式（json、short-iso、cat）\njournalctl --disk-usage                 # journald 占了多少磁盘\njournalctl --vacuum-size=200M           # 主动收缩日志（也可 --vacuum-time=7d）\njournalctl --list-boots                 # 列出所有启动记录 日志为空的两种常见原因：服务把日志写到了自己的文件（unit 里 StandardOutput=file: 或代码里 logging.FileHandler），以及权限不够——普通用户查全量日志需要 systemd-journal 组（sudo usermod -aG systemd-journal deploy）。持久化默认可能关闭：\u002Fetc\u002Fsystemd\u002Fjournald.conf 里 Storage=persistent，然后 systemctl restart systemd-journald，否则 \u002Fvar\u002Flog\u002Fjournal 不存在、-b -1 查不到上次开机的事。 journald 上限的四个参数（同一文件）：SystemMaxUse=500M（磁盘上限，最有用）、SystemMaxFileSize=50M、MaxRetentionSec=30day、ForwardToSyslog=yes（转给 rsyslog）。",{"id":22819,"title":12247,"titles":22820,"content":22821,"level":594},"\u002Ftutorials\u002Flinux#_4-crontab-定时任务",[5,11319],"crontab -l          # 看当前用户的任务\ncrontab -e          # 编辑（改完自动生效，无需重启 cron）\nsudo crontab -u deploy -e    # 读写别人的（root 权限）\ncrontab -r          # 删除全部任务 —— 危险，没有二次确认 字段顺序与示例： 分 时 日 月 周          命令\n 0 3 * * *  find \u002Fsrv\u002Fapp\u002Flogs -name '*.log' -mtime +7 -delete\n*\u002F5 * * * *  \u002Fsrv\u002Fapp\u002Fhealthcheck.sh >> \u002Fvar\u002Flog\u002Fhc.log 2>&1\n30 4 * * 1-5  \u002Fusr\u002Fbin\u002Fpg_dump appdb | gzip > \u002Fbackup\u002Fapp.sql.gz\n@reboot      \u002Fsrv\u002Fapp\u002Fstartup.sh 写法 含义 易混点 * * * * * 每分钟 五段依次是「分 时 日 月 周」，第 3 位是「几号」而不是小时，写反就永不执行 0 3 * * * 每天 03:00 「日」和「周」同时限制时是或关系（cron 特性，不是 bug） *\u002F5 每 5 分钟 分钟从 0 起算，*\u002F7 不是严格每 7 分钟 1-5（周） 周一到周五 0 和 7 都是周日；有的实现只认 0 23:59 式 不存在，必须 59 23 * * * 顺序写反就是「59 分 23 时」→ 永远不跑 三个经典坑： PATH 不同。cron 的环境只有 \u002Fusr\u002Fbin:\u002Fbin，没有你的 ~\u002F.bashrc 里的路径。写绝对路径，或在 crontab 顶部加 PATH=\u002Fusr\u002Flocal\u002Fbin:\u002Fusr\u002Fbin:\u002Fbin 和 MAILTO=\"\"。 % 是特殊字符（被当换行\u002F参数分隔）。crontab 里 date +%F 必须写成 date +\\%F，或者干脆把命令放进脚本文件（强烈推荐）。 注释与输出。# 行会被忽略；任务有输出时 cron 会试图用 sendmail 发信，没装 MTA 就表现为「静默失败」。所有行末尾加 >> \u002Fvar\u002Flog\u002Fcron.out 2>&1 或 MAILTO=\"\"。 排查有没有跑：grep CRON \u002Fvar\u002Flog\u002Fsyslog（Debian）或 journalctl -u cron（RHEL 系与 systemd 环境）。at 一次性任务的用法：echo '\u002Fsrv\u002Fapp\u002Freport.sh' | at 02:00 tomorrow，atq 看队列、atrm 3 取消；需要装 at 且 atd 在跑。",{"id":22823,"title":12521,"titles":22824,"content":22825,"level":594},"\u002Ftutorials\u002Flinux#_5-timedatectl-时间与时区",[5,11319],"timedatectl                                # 一站式看时间、时区、NTP 状态：排「日志时间对不上」先看这里\ntimedatectl set-timezone Asia\u002FShanghai     # 设时区；改完服务不一定立刻感知，Java 应用还要 -Duser.timezone\ntimedatectl set-ntp true                   # 开启 systemd-timesyncd 同步；NTPSynchronized=yes 才算同步上\ntimedatectl show-timesync --all            # 看用的哪台 NTP 服务器；内网要配 \u002Fetc\u002Fsystemd\u002Ftimesyncd.conf 的 Server=\ntimedatectl set-time \"2026-10-04 09:30:00\" # 手动设时间：必须先关 NTP！时间回拨会让证书校验、\n                                           # cron、数据库主从全部乱套\nchronyc sources                            # 用 chrony 时的对时源（RHEL 系默认 chrony，\nntpq -p                                    # timedatectl 显示 inactive 不代表没同步）\nhwclock -r                                 # 读硬件时钟——双系统时间错乱（UTC vs 本地）就在这里 容器里的时间问题：容器共享宿主内核时钟，改容器时区只影响 TZ 环境变量（TZ=Asia\u002FShanghai），不要试图在容器里 set-time。",{"id":22827,"title":808,"titles":22828,"content":22829,"level":594},"\u002Ftutorials\u002Flinux#踩坑提醒-10",[5,11319],"「改了 unit 没生效」= 忘了 systemctl daemon-reload；「改了 drop-in 还在用旧值」= systemctl cat 看合并后的真实结果，systemd 的覆盖语义是空赋值（Environment= 空值 = 清空）。 enable ≠ 启动、disable ≠ 停止。部署脚本里统一 systemctl enable --now app。 crontab 里 @reboot 执行时网络可能还没就绪，依赖外部服务要重试或用 systemd 的 After=network-online.target。 日志时间戳乱掉的第二原因：应用自己设了 UTC 而系统是本时区（nginx access log 用本地、journalctl 用本地但导出的 JSON 是 UTC）。核对一遍 date、timedatectl、应用配置三处。",{"id":22831,"title":12694,"titles":22832,"content":22833,"level":276},"\u002Ftutorials\u002Flinux#十二用户与组iduseraddusermodpasswd",[5],"权限问题的一半其实是身份问题：服务用的是哪个用户、这个用户在不在组里、改了组为什么没生效。这一章把用户\u002F组的查询与增删命令，以及 \u002Fetc\u002Fpasswd、\u002Fetc\u002Fshadow 的字段逐个讲清。",{"id":22835,"title":12708,"titles":22836,"content":22837,"level":594},"\u002Ftutorials\u002Flinux#_1-idwhow-先看清现状",[5,12694],"id                    # 当前用户的 uid\u002Fgid 与全部附属组：uid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo)\nid deploy             # 查别人（按名或按号 id 1001）——排查服务用户最常用的第一步\nwhoami                # 只有用户名；注意 sudo whoami 会显示 root——判断当前是不是特权态\nw                     # 谁在线 + 在跑什么（含 LOGIN@ JCPU TTY FROM IDLE WHAT），比 who 信息多\nwho                   # 谁登录了：deploy  pts\u002F0  2026-10-04 08:55 (10.0.0.12)\nlast deploy           # 历史登录（读 \u002Fvar\u002Flog\u002Fwtmp），只看某人；lastb 看失败尝试（要 root）\ngetent passwd         # 查所有账户（含 LDAP\u002FSSSD\u002FNIS）——脚本里用这个而不是读文件\ngetent group sudo     # 看某个组的成员：判断「他到底在不在组里」\ngroups deploy         # 只列组名，比 id 好读\nusers                 # 一行列出在线用户名",{"id":22839,"title":12807,"titles":22840,"content":22841,"level":594},"\u002Ftutorials\u002Flinux#_2-useradd-与-adduser-增删改用户",[5,12694],"先说清一件事：useradd 与 adduser 不是一回事。 命令 性质 行为 用在哪 useradd 底层二进制（各发行版都有） 默认不建家目录、不设密码、不交互式 脚本、Dockerfile adduser Perl 友好脚本（Debian\u002FUbuntu 独有） 交互式问全名\u002F密码，自动建家目录、拷 \u002Fetc\u002Fskel 手工建号 sudo useradd -m -s \u002Fbin\u002Fbash -G deploy,app deploy2\n# -m 建家目录、-s 给登录 shell、-G 附属组——RHEL 系手工建号只能这么写\nsudo adduser deploy                 # Debian\u002FUbuntu 交互式建号\nsudo useradd -u 1500 -e 2026-12-31 -c \"CI runner\" ci\n# -u 指定 uid（容器\u002F集群对齐必备）、-g 主组、-d 家目录路径、-e 账户过期日、-c 备注字段\nsudo userdel -r deploy              # 删用户并删家目录；不加 -r 会留下 \u002Fhome\u002Fdeploy（可能含密钥）；\n                                    # 删前先 ps -u deploy 确认没进程",{"id":22843,"title":12970,"titles":22844,"content":22845,"level":594},"\u002Ftutorials\u002Flinux#_3-usermod-加组本章最贵的一课在这",[5,12694],"sudo usermod -aG docker deploy      # 加附属组：-a 是「追加」，必须写！\nsudo usermod -aG docker,app deploy  # 一次加多个组\nsudo usermod -L deploy              # 锁定账户（shadow 密码字段前加 !）；-U 解锁\nsudo usermod -l newname oldname     # 改名；家目录要另外 mv 并 chown\nsudo usermod -s \u002Fusr\u002Fsbin\u002Fnologin www   # 禁登录 shell——服务账户的标准做法\nsudo groupadd app                   # 建组；groupdel 删组\ngpasswd -a deploy app               # 老写法加组成员（等价 usermod -aG）；-d 移除\ngroupmems -g app -l                 # 列组成员；-a\u002F-d\u002F-p 加\u002F删\u002F清空\nnewgrp app                          # 当前会话临时切换有效组——不重新登录也能生效的补救，exit 退出 usermod -aG 的 -a 为什么这么重要：usermod -G docker deploy（少了 -a）会把 deploy 的所有附属组替换成只有 docker——原本在 sudo 组里的用户当场失去 sudo 权限，若他正是唯一的管理员就只能靠 root 密码或单用户模式救。安全写法永远带上 -a。 改完组后该用户必须重新登录（或 newgrp）才生效，因为组成员身份在登录时被写进凭证，已有会话不会更新——这是「加了组还是 permission denied」的头号原因。",{"id":22847,"title":13147,"titles":22848,"content":22849,"level":594},"\u002Ftutorials\u002Flinux#_4-passwd-与-chage-管密码",[5,12694],"passwd deploy            # 改自己或指定用户密码（指定他人需 root）\npasswd -l deploy         # 锁定（密码前加 !）；passwd -u 解锁\npasswd -e deploy         # 让下次登录必须改密码\npasswd -d deploy         # 清空密码（配合 SSH 密钥场景，谨慎）\npasswd -S deploy         # 看状态：P 可用 \u002F L 锁定 \u002F NP 无密码 + 最近改密日期\nchage -l deploy          # 看过期详情；chage -M 90 -W 7 -I 14 设策略",{"id":22851,"title":13221,"titles":22852,"content":22853,"level":594},"\u002Ftutorials\u002Flinux#_5-etcpasswd-与-etcshadow-的字段",[5,12694],"\u002Fetc\u002Fpasswd（getent passwd deploy 的原始内容，7 个字段用 : 分隔）： deploy:x:1000:1000:Deploy User:\u002Fhome\u002Fdeploy:\u002Fbin\u002Fbash 位置 值 含义 备注 1 deploy 用户名 登录与 chown 用 2 x 密码占位符 真正的哈希在 shadow；这里是空表示无密码 3 1000 UID 0=root，系统账户 \u003C 1000，65534=nobody 4 1000 GID（主组号） 指向 \u002Fetc\u002Fgroup 5 Deploy User GECOS 备注 全名\u002F电话，finger 读这里 6 \u002Fhome\u002Fdeploy 家目录 ~ 的来源；不存在则登录报错 7 \u002Fbin\u002Fbash 登录 shell \u002Fusr\u002Fsbin\u002Fnologin、\u002Fbin\u002Ffalse = 禁交互登录 \u002Fetc\u002Fshadow（8 个字段，只有 root 与 shadow 组可读，权限 640）： deploy:$y$j9T$abc…:20550:0:90:7:14:: 位置 值 含义 1 deploy 用户名 2 $y$j9T$… 密码哈希；前缀 $y$=yescrypt、$6$=SHA-512、$2b$=bcrypt；! 或 * 开头 = 锁定；空 = 无密码可登录 3 20550 最后一次改密距 1970-01-01 的天数（date -d \"1970-01-01 +20550 days\" 换算） 4 0 改密后至少隔多少天才允许再改（防来回改历史密码） 5 90 密码有效期天数（99999 = 永不过期） 6 7 过期前多少天开始警告 7 14 过期后多少天宽限期，之后账户锁定 8 — 账户本身失效的天数（与密码无关） \u002Fetc\u002Fgroup 的 4 个字段：组名、x 占位（组密码基本不用）、GID、成员列表（逗号分隔）。注意：这里列出的成员只是「附加组」，用户在 \u002Fetc\u002Fpasswd 里的 GID 主组不会出现在这里，所以「getent group deploy 里没有他」不代表他不属于 deploy 组。\u002Fetc\u002Fgshadow 存组管理员与组密码，字段与 shadow 类似。 相关命令：getent passwd 1000（按 uid 反查）、lastlog（每人最后登录，读 \u002Fvar\u002Flog\u002Flastlog 稀疏文件）、faillock --user deploy（pam_faillock 的失败计数，RHEL 系）。",{"id":22855,"title":808,"titles":22856,"content":22857,"level":594},"\u002Ftutorials\u002Flinux#踩坑提醒-11",[5,12694],"新建用户忘了 -m，~ 不存在导致后续所有「写家目录」的脚本失败：useradd -m 或在 \u002Fetc\u002Fdefault\u002Fuseradd 里设 CREATE_HOME=yes。 服务账户（nginx、redis）默认 shell 是 \u002Fusr\u002Fsbin\u002Fnologin，su - nginx 必然失败；正确姿势 sudo -u nginx \u002Fbin\u002Fbash -l（root 可以覆盖 shell）。 直接编辑 \u002Fetc\u002Fpasswd 用普通 vim 有并发风险（同时 useradd 会覆盖），要改就 vipw \u002F vigr（带锁与语法检查）。 UID 不一致是集群环境的隐形故障源：NFS 共享目录 + 各节点 UID 不同 → 权限完全乱。跨节点统一 useradd -u 1500。",{"id":22859,"title":13654,"titles":22860,"content":22861,"level":276},"\u002Ftutorials\u002Flinux#十三网络ipssdigcurlssh",[5],"网络排障的顺序基本固定：本机有没有 IP → 路由到不到 → DNS 解析对不对 → 端口有没有监听 → 防火墙让不让过 → 应用返回什么。ip、ss、dig、curl 各管一段，本章按这个顺序给命令。",{"id":22863,"title":13678,"titles":22864,"content":22865,"level":594},"\u002Ftutorials\u002Flinux#_1-ip-取代-ifconfig-与-netstat",[5,13654],"net-tools（ifconfig、netstat、route）在新发行版里默认不装了，iproute2（ip、ss）才是当前的标准，且能显示 ifconfig 拿不到的信息（每个地址的前缀长度、路由表来源、队列规则、命名空间）。 ip a                        # 看所有接口与地址（addr 的缩写）\nip -br a                    # 一行一接口，最清爽\nip -4 addr show eth0        # 只看 IPv4、指定网卡\nip r                        # 看路由表（route 的缩写）\nip route get 8.8.8.8        # 问「去往这个地址走哪条路、用哪个网卡」——排障神器\nip l                        # 看链路状态；ip link set dev eth0 down 改状态（要 root）\nip neigh                    # ARP 表（= arp -a），排查同网段不通\nip -s link                  # 接口统计：RX errors 非 0 说明网卡\u002F驱动问题\nip route add default via 10.0.0.1    # 加默认网关——重启失效！永久配置要写 netplan\u002FNetworkManager\nip netns                    # 网络命名空间列表；ip netns exec foo ss -tulpn ip a 输出里要会看的三个东西：inet 10.0.0.12\u002F24 brd … 是 IPv4 与掩码（\u002F24 = 255.255.255.0）、state UP 或 NO-CARRIER 是链路是否插了线、scope global\u002Flink（fe80:: 是链路本地地址，不能直接当目标）。",{"id":22867,"title":13870,"titles":22868,"content":22869,"level":594},"\u002Ftutorials\u002Flinux#_2-ss-谁在监听哪个端口",[5,13654],"ss -tulnp           # 日常万能式：TCP + UDP、监听中的、带进程名\nss -tuln            # 同上但无需 root（不显示进程列）\nss -tan state established       # 只看已建立的 TCP 连接\nss -tp state established '( dport = :443 )'   # 按端口过滤，能匹配到进程（netstat 做不到）\nss -s               # 各状态连接数汇总（一眼看 TIME_WAIT 堆积）\nss -i               # 拥塞控制、RTT 等 TCP 内部信息\nss -m               # 内存占用（内核缓冲）\nsudo ss -K dst 10.0.0.5        # 直接杀掉匹配的连接（需 root，4.9+ 内核） 字母含义：-t TCP、-u UDP、-l listening、-p 显示进程（需 root 才能看到别人的）、-n 数字不做名称解析（更快，避免 DNS 卡住）、-a 全部（含未监听）、-x Unix 套接字、-w raw、-s 统计、-i 内部信息、-e 扩展（uid\u002Finode）。 与 netstat -antp 的对照： 需求 netstat（老） ss（新） ss 的优势 列监听端口 + 进程 netstat -tulnp ss -tulnp ss 直接从内核接口读，海量连接时快几十倍 全部 TCP 连接 netstat -antp ss -tanp 支持 state 精确过滤 只看 TIME_WAIT netstat -ant | grep TIME_WAIT ss -tan state time-wait 不用 grep，不丢格式 Unix 域套接字 netstat -xlp ss -xlp 输出更完整 路由表 route -n ip r 支持策略路由显示 接口统计 netstat -i ip -s link 含丢包\u002F错误细分 ss 里 Recv-Q\u002FSend-Q 在监听端口上的含义与连接上不同：监听行上它们是已完成队列长度 \u002F backlog 上限，Recv-Q 接近 Send-Q 说明应用 accept 不过来（连接被丢弃），是「服务偶发 502\u002F超时」的关键线索。",{"id":22871,"title":14180,"titles":22872,"content":22873,"level":594},"\u002Ftutorials\u002Flinux#_3-连通性诊断pingdignc",[5,13654],"ping -c 4 10.0.0.5          # 测 ICMP 可达与 RTT：-c 4 发 4 个包，-W 2 超时，-i 0.2 快速发\ntraceroute -n -w 2 host     # 逐跳路径：-n 不解析域名（否则慢到不能用）、-T -p 443 用 TCP 探测\ntracepath 8.8.8.8           # 无需 root 的替代，同时给出 PMTU\nmtr -rwnc 50 host           # traceroute + ping 合体：-r 报告模式、-w 宽、-c 50 发 50 包\ndig +short app.example.com  # DNS 查询首选：+short 只留答案、+trace 从根查起、+noall +answer 精简\ndig A host @1.1.1.1         # 指定 DNS 服务器对比\nnslookup app.example.com 8.8.8.8    # 简易 DNS（老工具）：输出信息少，脚本别用\nhost -t MX example.com      # 一行查 DNS；host -l example.com 测区域传送\ngetent hosts app.internal   # 走本机完整解析链（含 \u002Fetc\u002Fhosts、nsswitch）——排「dig 能解析但 curl 不行」\nnc -vz 10.0.0.5 5432        # 测端口通不通：-z 只探端口、-v 给结果（telnet 的替代，多数机器没装 telnet 了）\nnc -l 8080                  # 起临时 HTTP 靶机；nc -l 8080 > dump.bin 收文件（OpenBSD\u002FGNU 版参数不同）\nethtool -i eth0             # 网卡驱动；ethtool -s 看速率\u002F双工\u002F链路——物理层问题唯一入口 注意两点：很多机房禁 ICMP，ping 不通不代表服务挂；mtr 中间跳丢包常是路由器限速的假象，只有最后一跳持续丢才是真问题。",{"id":22875,"title":14366,"titles":22876,"content":22877,"level":594},"\u002Ftutorials\u002Flinux#_4-curl-发请求与读-http",[5,13654],"curl -I https:\u002F\u002Fapp.example.com       # 只看响应头（HEAD），验证 301\u002F403\u002F缓存头\ncurl -sSL -o page.html https:\u002F\u002F…      # 下载标准式：-s 静默、-S 但报错仍显示、-L 跟随重定向、-o 存文件\ncurl -s -w '%{http_code}\\n' -o \u002Fdev\u002Fnull http:\u002F\u002F127.0.0.1:8080\u002Fhealthz\n                                      # 健康检查一行式：只要状态码\ncurl -X POST -H 'Content-Type: application\u002Fjson' -d '{\"a\":1}' URL   # 发 JSON；-d @body.json 从文件读\ncurl -X POST --data-urlencode 'q=中文 词' URL    # 表单编码（-d 不做编码，中文必错）\ncurl -u user:pass URL                 # Basic 认证；--netrc 用 ~\u002F.netrc\ncurl --retry 3 --retry-delay 2 --retry-connrefused -sS URL   # 重试，部署脚本必备\ncurl --connect-timeout 3 --max-time 10 URL                   # 超时控制，否则默认可以挂很久\ncurl -k URL                           # 跳过证书校验——只用于临时验证，别写进脚本；正确做法 --cacert ca.pem\ncurl -x http:\u002F\u002F10.0.0.9:8888 URL      # 走代理\ncurl -v URL 2>&1 | head -40           # 看完整握手（TLS 版本、证书、请求\u002F响应头）\ncurl --resolve app.example.com:443:10.0.0.12 https:\u002F\u002Fapp.example.com\n                                      # 本地强行指定 IP：验证负载均衡后面的某台机器\ncurl -T file URL --user u:p           # 上传（PUT）\ncurl -s 'URL?page=1' | jq .total      # 配合 jq 取字段 看耗时分布的一行格式串（可直接抄）： curl -s -o \u002Fdev\u002Fnull -w 'code=%{http_code} dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total} size=%{size_download}\\n' https:\u002F\u002Fapp.example.com curl 的退出码有用：7 连不上、6 DNS 解析失败、28 超时、35 TLS 握手错、60 证书问题——脚本里判 $? 比看输出可靠。wget 的差异：递归下载（wget -r -np -nH -k URL）与自动断点续传（-c）更方便；单发请求与调试头信息一律 curl。aria2c -x 16 -s 16 URL 多连接下载大文件快很多，aria2c --input-file=list.txt 批量。",{"id":22879,"title":14707,"titles":22880,"content":22881,"level":594},"\u002Ftutorials\u002Flinux#_5-sshscprsync",[5,13654],"ssh deploy@10.0.0.12                  # 登录，默认 22；ssh -v 看握手细节（排「为什么拒绝」）\nssh -p 2222 deploy@host               # 指定端口；配置里 Port 改了必须用 -p\nssh -i ~\u002F.ssh\u002Fid_ed25519_prod deploy@host   # 指定私钥；权限必须 600，否则报 UNPROTECTED PRIVATE KEY FILE\nssh -J bastion app                     # 跳板机（ProxyJump）；多级 -J bastion1,bastion2\nssh -L 5432:db.internal:5432 -fN deploy@jump\n                                       # 本地端口转发：把远端能到的内网服务映射到本机 5432，\n                                       # 之后 psql -h 127.0.0.1 -p 5432 直连；-N 不开 shell、-f 后台\nssh -R 8080:localhost:8080 deploy@jump # 远程端口转发：把本机服务暴露到跳板机（需 GatewayPorts=yes）\nssh -D 1080 deploy@jump                # SOCKS 代理：curl --socks5 127.0.0.1:1080 URL 配合用\nssh -o ServerAliveInterval=30 host     # 防中间设备断连；写进 ~\u002F.ssh\u002Fconfig 更省事\nssh-copy-id -i ~\u002F.ssh\u002Fid_ed25519.pub deploy@host   # 一键装公钥，仍需一次密码，之后免密\nscp app.tar.gz deploy@host:\u002Fsrv\u002Fapp\u002F       # 复制：注意 scp 语义像 cp；传目录要 -r；新版走 SFTP 协议\nrsync -avz --delete src\u002F host:\u002Fsrv\u002Fapp\u002F        # 增量同步\u002F备份——见下\nrsync -a --progress --partial big.iso host:\u002Fdata\u002F   # 断点续传大文件 ~\u002F.ssh\u002Fconfig 是可维护性的关键（文件权限必须 600），一份够用多年的样例： Host *\n  ServerAliveInterval 30\n  AddKeysToAgent yes\n  IdentityFile ~\u002F.ssh\u002Fid_ed25519\n\nHost prod-web\n  HostName 10.0.0.12\n  User deploy\n  IdentityFile ~\u002F.ssh\u002Fid_ed25519_prod\n  ProxyJump bastion\n\nHost db-tunnel\n  HostName bastion\n  User ops\n  LocalForward 5432 db.internal:5432 之后 ssh prod-web 一条搞定跳板与密钥。Host * 段的顺序只在第一次匹配时生效，所以具体 Host 要写在通配前面。 rsync 为什么更适合备份：只传差异（滚动校验）、可保留权限与时间戳（-a = -rlptgoD）、能删除目标多余文件（--delete 镜像语义）、可断点续传、可通过 ssh 加密传输、支持 --exclude 与 --link-dest 做硬链接式全量快照（省空间又每个快照都是完整目录）。 rsync -avz --delete --exclude='node_modules\u002F' --exclude='.git\u002F' \u002Fsrv\u002Fapp\u002F deploy@backup:\u002Fbackup\u002Fapp-$(date +%F)\u002F\nrsync -a --delete --link-dest=..\u002Fapp-2026-10-03 \u002Fsrv\u002Fapp\u002F \u002Fbackup\u002Fapp-2026-10-04\u002F --delete 务必先干跑：加 -n --delete（试运行）或 -i（显示每一项动作）。rsync 的路径语义坑：源 src\u002F（带斜杠）= 同步目录内容，源 src（无斜杠）= 在目标下建 src 目录，这一条能造成整站目录结构错位。",{"id":22883,"title":15125,"titles":22884,"content":22885,"level":594},"\u002Ftutorials\u002Flinux#_6-防火墙与-etchosts",[5,13654],"后端 常见于 常用命令 firewalld RHEL\u002FRocky\u002FFedora sudo firewall-cmd --permanent --add-port=80\u002Ftcp → sudo firewall-cmd --reload；sudo firewall-cmd --list-all ufw Ubuntu\u002FDebian 桌面与轻量服务器 sudo ufw allow 22\u002Ftcp、sudo ufw deny 3306、sudo ufw status verbose、sudo ufw enable（开之前务必先 allow ssh） iptables \u002F nftables 手工管理或老环境 sudo iptables -L -n -v --line-numbers、sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT；sudo nft list ruleset 三条通用提醒：iptables -A 是追加到链尾（默认策略 DROP 时可能永远走不到），插入用 -I INPUT 1；规则改完不保存重启就没了（iptables-save > \u002Fetc\u002Fiptables\u002Frules.v4，或 netfilter-persistent save）；ufw\u002Ffirewalld 都在最上层，用底层命令加的规则它们看不见，别混用两套。Docker 会直接写 iptables 并在启动时插入自己的链，ufw deny 拦不住已 expose 的容器端口——这是极常见的困惑来源。 \u002Fetc\u002Fhosts 是本机静态解析，优先级在 nsswitch 里通常高于 DNS（\u002Fetc\u002Fnsswitch.conf 的 hosts: files dns）： printf '10.0.0.12  app.internal\\n' | sudo tee -a \u002Fetc\u002Fhosts 用途：临时指向某台后端验证、屏蔽域名（指 127.0.0.1）、容器里注入别名。坑：改了 hosts 但应用没生效，通常是应用做了自己的 DNS 缓存，或它走的是 dig 式直接查询；反之 hosts 会盖掉 DNS，测试完记得删。",{"id":22887,"title":808,"titles":22888,"content":22889,"level":594},"\u002Ftutorials\u002Flinux#踩坑提醒-12",[5,13654],"「ss 显示监听在 127.0.0.1:8080，外部怎么都连不上」——服务只绑了回环地址，改 0.0.0.0:8080（或配置里的 bind）。这一条比防火墙更常是根因。 ping 不通不代表服务不可用（ICMP 被封），要用 nc -vz 或 curl 测目标端口才算数。 排 DNS 问题按链路顺序：getent hosts X（含 hosts 文件）→ dig +short X @\u003C本机 resolv 里的服务器> → 换 @1.1.1.1 对比 → 查 \u002Fetc\u002Fresolv.conf（systemd-resolved 环境里它是指向 127.0.0.53 的软链接，直接改会被覆盖，正确改法是 \u002Fetc\u002Fsystemd\u002Fresolved.conf 的 DNS=）。 容器里 \u002Fetc\u002Fhosts 与 resolv.conf 是挂载文件，chmod\u002Fchown 会报 Device or resource busy。 mtr 中间跳的丢包率是路由器 ICMP 限速造成的假象，只看最后一跳与整体 RTT。",{"id":22891,"title":15387,"titles":22892,"content":22893,"level":276},"\u002Ftutorials\u002Flinux#十四磁盘dfdumountfsck",[5],"No space left on device 有两种完全不同的原因（块用满 vs inode 用满），du 找不到大头时还有第三种（被删除但仍在打开的文件）。这一章把磁盘相关命令按「看容量 → 看谁占 → 看设备 → 挂载 → 修文件系统」的顺序排好。",{"id":22895,"title":15404,"titles":22896,"content":22897,"level":594},"\u002Ftutorials\u002Flinux#_1-df-看文件系统容量",[5,15387],"df -h             # 各文件系统使用率（人读大小）\ndf -hT            # 含文件系统类型\ndf -i             # inode 使用率——磁盘满的第二种原因\ndf -h \u002Fsrv\u002Fapp    # 只看这个路径所在分区 df -h 各列：Filesystem（来源设备或 overlay）、Size（总容量，不含文件系统预留，所以比 lsblk 显示的小）、Used、Avail、Use%（注意它按 Used\u002F(Used+Avail) 算，root 预留的 5% 不计入，所以会出现「Avail 还剩 4.5G 但 Use% 已 100%」）、Mounted on。",{"id":22899,"title":15491,"titles":22900,"content":22901,"level":594},"\u002Ftutorials\u002Flinux#_2-du-看谁占了空间",[5,15387],"du -sh \u002Fsrv\u002Fapp                                    # 单个目录总占用\ndu -h --max-depth=1 . | sort -hr | head -15        # 当前目录各子目录大小 Top 15\ndu -xh \u002F --max-depth=2 2>\u002Fdev\u002Fnull | sort -h | tail -20   # 全盘 Top 大目录（-x 不跨挂载点）\ndu -sh \u002Fvar\u002Flog\u002F* | sort -h | tail                 # 按日志类别看\nfind \u002F -xdev -type f -size +200M -printf '%s\\t%p\\n' | sort -rn | head   # 全盘 Top 大文件（第六章）\nsudo lsof +L1 | head                                # 已删除但仍被占用的空间\ndocker system df                                    # Docker 占用（docker system prune -a 清理） du 与 df 数字不一致的三个正当理由：① 有被删除但进程仍打开的文件（du 看不见，df 算满）；② 隐藏挂载点覆盖目录（du 算的是覆盖后的）；③ 文件系统预留块（df 的 Size 不含）。加 -x 避免扫进别的文件系统导致统计跑偏；--apparent-size 看表观大小（稀疏文件差异巨大）。",{"id":22903,"title":15684,"titles":22904,"content":22905,"level":594},"\u002Ftutorials\u002Flinux#_3-lsblkblkidfindmnt-看设备与挂载",[5,15387],"lsblk -f              # 块设备树，含文件系统类型与 UUID\nlsblk -o NAME,SIZE,FSTYPE,MOUNTPOINTS    # 自选列\nsudo blkid \u002Fdev\u002Fvdb1  # 设备 UUID 与类型——写 \u002Fetc\u002Ffstab 时用 UUID 而不是设备名\nfindmnt -T \u002Fsrv\u002Fapp   # 问这个路径落在哪个挂载点、读写还是只读（ro 就是磁盘只读挂载）\nfindmnt --fstab       # 看 fstab 的解析结果\nfree -h               # 内存与 swap；available 才是可用值（第十七章）\nsudo swapon --show    # swap 分区\u002F文件；swapon \u002Fswapfile 启用、swapoff -a 关闭（容器里通常禁用）",{"id":22907,"title":15772,"titles":22908,"content":22909,"level":594},"\u002Ftutorials\u002Flinux#_4-inode-用满另一种磁盘满",[5,15387],"症状：df -h 显示还有 30% 空间，但写文件报 No space left on device → 跑 df -i，IFuse% = 100% 就是 inode 用满。谁吃的： for d in \u002Fvar\u002Fspool\u002F* \u002Fsrv\u002Fapp\u002Ftmp; do echo -n \"$d \"; find $d -type f | wc -l; done\nfind \u002F -xdev -type f | awk -F\u002F '{print $2\"\u002F\"$3}' | sort | uniq -c | sort -rn | head\nfind \u002Fsrv\u002Fapp\u002Ftmp -type f -mtime +1 -delete    # 清完 df -i 立刻回落 通常是海量小文件（session、缓存、mail queue、监控点）。根治靠重建分区时 mkfs -T ext4 -i 4096（每 4096 字节一个 inode，默认 16384）或换 XFS\u002FBtrfs——ext4 的 inode 数量在格式化时定死，运行期不能扩；XFS 的 inode 随用随长（受 imaxpct 限制，可 xfs_growfs -m 调）。属于规划问题，需要备份重来。",{"id":22911,"title":15932,"titles":22912,"content":22913,"level":594},"\u002Ftutorials\u002Flinux#_5-分区与格式化危险区先读警告",[5,15387],"危险边界必须先讲清：mkfs、fdisk\u002Fparted 的写操作、dd 都是不可逆的。改分区表前一定 cp \u002Fetc\u002Ffstab ~\u002F 并做完整备份；mkfs 之前用 lsblk -f 与 findmnt 双重确认目标设备名（\u002Fdev\u002Fvdb 与 \u002Fdev\u002Fvdb1 差一个数字就格式化了整块盘）。 sudo fdisk \u002Fdev\u002Fvdb     # MBR\u002F传统分区：n 新建、d 删除、p 打印、w 写入、q 弃改动\n                        # 只在 w 之前不生效；2TB 以上要用 GPT\nsudo parted -s \u002Fdev\u002Fvdb mklabel gpt                # 建 GPT 分区表\nsudo parted \u002Fdev\u002Fvdb mkpart data ext4 1MiB 100%    # 建分区——注意 parted 的 mkpart 立即写盘，\n                                                   # 没有「先看看」的机会；改完 partprobe 通知内核\nsudo mkfs.ext4 -L data -m 1 \u002Fdev\u002Fvdb1              # 建 ext4 文件系统——销毁数据！\n                                                   # -L 卷标；-m 1 把 root 预留降到 1%\nsudo mkfs.xfs -f \u002Fdev\u002Fvdb1                         # 建 XFS：-f 强制覆盖已有签名\nsudo wipefs -a \u002Fdev\u002Fvdb1                           # 清设备上的文件系统签名——重建盘前用，极其危险",{"id":22915,"title":16101,"titles":22916,"content":22917,"level":594},"\u002Ftutorials\u002Flinux#_6-mountumount-与-etcfstab",[5,15387],"sudo mount \u002Fdev\u002Fvdb1 \u002Fdata                  # 挂载；挂载点原有内容会被「遮住」（不是丢失），卸载后回来\nsudo mount -o ro,noexec \u002Fdev\u002Fvdb1 \u002Fdata     # 只读 + 禁执行挂载\nsudo mount -o remount,rw \u002F                  # 重新挂载为读写\nsudo umount \u002Fdata                           # 卸载\nsudo umount -l \u002Fdata                        # 惰性卸载——报 target is busy 时先用\nsudo lsof +D \u002Fdata                          # lsof\u002Ffuser 找占用者，别习惯性 -l 掩盖问题\nsudo fuser -vm \u002Fdata\nsudo systemctl daemon-reload && sudo mount -a   # 改完 fstab 用 mount -a 验证：成功才说明重启不会翻车\nfindmnt --verify                            # 检查 \u002Fetc\u002Ffstab 语法——fstab 写错会导致启动进 emergency mode \u002Fetc\u002Ffstab 六个字段： UUID=2f8b…  \u002Fdata  ext4  defaults,noatime,nofail  0  2\n设备或UUID   挂载点 类型   挂载选项                dump  fsck顺序 字段 常用值 说明 1 设备 UUID=、LABEL=、\u002Fdev\u002Fvdb1 一律用 UUID：设备名会因内核探测顺序变化 2 挂载点 none、swap、\u002Fdata 目录必须已存在 3 类型 ext4、xfs、vfat、nfs、swap auto 让 mount 猜 4 选项 见下 逗号分隔，无空格 5 dump 0 备份工具用，现代基本都填 0 6 fsck 顺序 \u002F 填 1，其他本地盘 2，网络\u002Fswap 填 0 别给 NFS 填非 0，会卡启动 关键挂载选项：nofail（设备不在也继续启动，云主机加数据盘必写，否则盘被摘机就起不来）、noatime（不记录访问时间，显著降低读 IO）、nodiratime、ro\u002Frw、noexec（上传目录必备安全项）、nosuid（同上）、compress（zfs\u002Fbtrfs）、_netdev（NFS：等网络就绪再挂）、x-systemd.automount（按需挂载）。",{"id":22919,"title":16428,"titles":22920,"content":22921,"level":594},"\u002Ftutorials\u002Flinux#_7-no-space-left完整排查",[5,15387],"步骤 命令 判断 1 df -h 块用满 → 进 2；Use% 未满 → 进 3 2 du -xh \u002F --max-depth=2 | sort -h | tail 找目录大头；日志\u002Fdocker\u002F临时文件三大来源 2b sudo lsof +L1 | sort -k7 -n | tail du 找不到大头时必查：已删除但句柄未关的文件，df 算它占用；重启对应进程即释放 3 df -i inode 满 → 海量小文件，见第 4 节 4 findmnt -T \u002F出错的路径 落在 ro 的挂载点上 → 是只读问题不是空间问题 5 dmesg | tail -30 EXT4-fs error \u002F Remounting filesystem read-only → 磁盘故障，见 fsck \u002Fvar\u002Flog 撑爆的标准处理：journalctl --vacuum-size=200M、logrotate -f \u002Fetc\u002Flogrotate.conf 强制跑一次、清 docker 日志 truncate -s 0 \u002Fvar\u002Flib\u002Fdocker\u002Fcontainers\u002F*\u002F*-json.log（治本是给 docker 配 log-opts 上限）。",{"id":22923,"title":16559,"titles":22924,"content":22925,"level":594},"\u002Ftutorials\u002Flinux#_8-fsck-为什么不能在挂载时跑",[5,15387],"fsck 直接改文件系统元数据。分区处于读写挂载时，内核正在并发写这些结构，检查器看到的是「正在变」的磁盘，会把正常状态判为不一致并「修复」成损坏——结果是文件系统当场报废。因此： 情况 正确做法 根分区\u002F已挂载分区要检查 sudo touch \u002Fforcefsck 后重启（老式）；或 shutdown -rF；或改 fstab 第 6 字段并用 tune2fs -c 30 \u002Fdev\u002Fsda1 设次数触发；最稳是救援模式\u002F单用户模式或 Live CD 数据盘可下线 sudo umount \u002Fdata && sudo fsck -fy \u002Fdev\u002Fvdb1 && sudo mount \u002Fdata 只想「检查不修」 sudo fsck -n \u002Fdev\u002Fsda1（对挂载分区也不写，但结果不可靠，仅参考） XFS xfs_repair 同样必须卸载；XFS 只支持离线修复（xfs_check 已废弃） 看自动检查策略 tune2fs -l \u002Fdev\u002Fsda1 | grep -E 'Maximum mount count|Check interval|Last check' ext4 的 -y（自动答 yes）适合无人值守，但会「修」掉本该保留的数据；重要盘用不带 -y 的交互模式或先 e2image 做元数据镜像。fsck 跑完找回的碎片在 \u002Flost+found，文件名是 inode 号，可用 find \u002F -inum 123456 反查。",{"id":22927,"title":16680,"titles":22928,"content":22929,"level":594},"\u002Ftutorials\u002Flinux#_9-inotifywait-文件监控与那个著名的上限",[5,15387],"inotifywait -m -r -e modify,create,delete,move --format '%w %f %e' \u002Fsrv\u002Fapp\u002Fuploads\n# -m 持续监控、-r 递归、-e 指定事件、--format 输出好解析\ninotifywatch -t 60 \u002Fdata       # 统计一段时间内的事件计数 来自 inotify-tools 包（Debian apt install inotify-tools，RHEL 在 EPEL）。监控失效\u002F程序报 inotify_add_watch(...) no space 时，不是磁盘满，是 watch 数超限： cat \u002Fproc\u002Fsys\u002Ffs\u002Finotify\u002Fmax_user_watches      # 默认常为 8192\necho fs.inotify.max_user_watches=524288 | sudo tee -a \u002Fetc\u002Fsysctl.d\u002F99-inotify.conf\nsudo sysctl --system max_user_instances（每个用户的 inotify 实例数）与 max_queued_events（事件队列长度）也常在 CI\u002FIDE 环境需要调高。注意：NFS\u002FFUSE 挂载点上的 inotify 不工作（远程改动不会通知本机），跨机同步要用轮询或 rsync。",{"id":22931,"title":808,"titles":22932,"content":22933,"level":594},"\u002Ftutorials\u002Flinux#踩坑提醒-13",[5,15387],"删了大文件空间没回来：先想「有没有进程还开着它」（lsof +L1），重启进程或 : > 文件 截断，而不是再删一遍。 rm -rf 删目录树时正在被进程写入的日志会导致磁盘占用反而上升（inode 与块都被打开句柄占住），标准清理顺序是先停服务再清。 mount 到非空目录会「隐藏」原内容，看起来数据丢了其实还在——umount 就回来。 云主机加盘后忘了 nofail，运维把盘摘掉机器直接起不来进 emergency mode。这是最常见的线上事故之一。 dd 用于写盘时（dd if=iso of=\u002Fdev\u002Fsdb）输错设备名就是毁盘，永远先用 lsblk -f 确认，再三检查 of=。",{"id":22935,"title":16862,"titles":22936,"content":16865,"level":276},"\u002Ftutorials\u002Flinux#十五软件包管理aptdnfapk",[5],{"id":22938,"title":16873,"titles":22939,"content":22940,"level":594},"\u002Ftutorials\u002Flinux#_1-apt-debian-ubuntu-系",[5,16862],"sudo apt update                    # 刷新索引——只刷新列表，不升级！装任何东西之前\nsudo apt install -y nginx          # 安装；-y 给脚本用。apt 交互式优先；脚本里用 apt-get（apt 会警告接口不稳定）\nsudo apt upgrade                   # 升级全部（保守，不删包）\nsudo apt full-upgrade              # 升级并允许删包（旧名 dist-upgrade），大版本迁移用\nsudo apt remove apache2            # 删包但留配置；purge 连配置一起删，重装时配置会被读回来\nsudo apt autoremove --purge        # 清理不再需要的依赖——会带走你手动装的孤儿包，看清楚列表再 yes\napt search nginx                   # 搜包名与描述（子串不是正则）\napt show nginx                     # 版本、依赖、大小、源——装之前先看\napt list --installed               # 已装列表；可 apt list --installed 'libssl*'\napt policy nginx                   # 候选版本与来源仓库——排「为什么装到旧版本」的唯一正解（含 Pin 优先级）\napt-cache madison nginx            # 各仓库里可用的版本清单\nsudo apt-mark hold linux-image-generic   # 钉住版本不升级（unhold 解开）——生产锁内核的标准做法\nsudo apt -s upgrade                # -s 模拟：看会动什么，不实际改动——生产升级前先跑这个\nsudo apt -f install                # 修依赖：dpkg -i 装失败后的正确一步",{"id":22942,"title":17068,"titles":22943,"content":22944,"level":594},"\u002Ftutorials\u002Flinux#_2-dpkg-本地包与文件归属",[5,16862],"sudo dpkg -i .\u002Fpkg.deb             # 装本地 .deb——不解析依赖！缺依赖时 sudo apt -f install 补\ndpkg -l | awk '$2 ~ \u002F^i\u002F {print $2}'    # 只列在用的；rc 状态 = 已删但有残留配置\ndpkg -L nginx                      # 这个包装了哪些文件——找配置\u002F二进制在哪\ndpkg -S \u002Fetc\u002Fnginx\u002Fnginx.conf      # 这个文件属于哪个包\nsudo dpkg-reconfigure tzdata       # 重跑配置问答（locales、keyboard-configuration 同理）\napt-file search bin\u002Fconvert        # 查「未装的包」里谁有这个文件（apt install apt-file + apt-file update）",{"id":22946,"title":17156,"titles":22947,"content":22948,"level":594},"\u002Ftutorials\u002Flinux#_3-dnf-与-rpm-rhel-centos-rocky-fedora-系",[5,16862],"dnf 是 yum 的继任者，命令基本兼容（yum 现在通常是 dnf 的软链接）： sudo dnf -y install nginx          # 安装；dnf remove pkg 卸载（dnf autoremove 清依赖）\ndnf update --security              # 只打安全补丁；update-minimal 同义\ndnf search log \u002F dnf info nginx    # 搜 \u002F 详情\ndnf list available 'python3*'      # 列可用包\ndnf provides '*\u002Fnginx.conf'        # 哪个包提供这个文件路径——排缺库必备，比 apt-file 方便\ndnf repoquery --installed --whatrequires glibc   # 谁依赖它（删之前看）\ndnf repoquery -l pkg               # 包装了哪些文件（等价 rpm -ql）\nsudo dnf history                   # 事务历史；dnf history info 12 看详情\nsudo dnf history undo 12           # 回滚——yum 做不到，dnf 的最大优势\ndnf module list php                # RHEL8+ 模块流：同一包多版本；dnf module enable php:8.2\nsudo dnf install dnf-plugins-core  # 拿到 dnf config-manager（--add-repo URL）、builddep\nrpm -q nginx                       # 是否已装；rpm -qa 'nginx*' 模糊查\nrpm -ql nginx                      # 包内文件列表；--scripts 连安装脚本一起看\nrpm -qf \u002Fetc\u002Fmy.cnf                # 文件属于哪个包（对应 dpkg -S）\nrpm -qpi pkg.rpm \u002F rpm -qpl pkg.rpm  # 查未安装的 rpm 的信息\u002F文件清单\nsudo rpm -ivh pkg.rpm              # 安装（i 安装 v 显示 h 进度条）；依赖不满足会拒绝，改用 dnf install .\u002Fpkg.rpm\nrpm -V openssh-server              # 校验文件是否被改过：S5（大小\u002F摘要不符）、M（模式变）、?（缺失）\nsudo rpm -e pkg                    # 卸载并做依赖检查；--nodeps 强删会留下坏系统，别用",{"id":22950,"title":17444,"titles":22951,"content":22952,"level":594},"\u002Ftutorials\u002Flinux#_4-apk-alpine容器里最常见",[5,16862],"apk add --no-cache curl bash       # 安装；--no-cache 免手动 update，镜像构建里标配\napk del .build-deps                # 卸载；多阶段构建里的假想依赖组\napk info -e curl                   # 存在则返回 0，脚本判断用\napk search -x 'curl*'              # 搜包；-x 精确名\napk policy busybox                 # 看版本与仓库来源\napk info -w \u002Fbin\u002Fsh                # 文件属于哪个包 → busybox\napk info -L pkg \u002F apk info -d pkg  # 包内文件 \u002F 包的依赖\napk audit                          # 检查被改动的受管文件——容器镜像审计\napk fix                            # 修复损坏安装",{"id":22954,"title":17580,"titles":22955,"content":22956,"level":594},"\u002Ftutorials\u002Flinux#_5-三个反向查询跨体系对照",[5,16862],"我要问的问题 Debian\u002FUbuntu RHEL 系 Alpine 这个文件是谁装进来的 dpkg -S \u002Fusr\u002Fbin\u002Fconvert rpm -qf \u002Fusr\u002Fbin\u002Fconvert apk info -w \u002Fusr\u002Fbin\u002Fconvert 这个包装了哪些文件 dpkg -L imagemagick rpm -ql imagemagick apk info -L imagemagick 某个命令在哪个包里（还没装） apt-file search bin\u002Fconvert 或 apt search（或直接装 debscan） dnf provides '*\u002Fconvert'（最方便，仓库索引里查） apk search -x + 看包描述 谁依赖这个包 apt-cache rdepends pkg dnf repoquery --whatrequires --installed pkg apk info -t pkg（依赖树反向） 这个包依赖什么 apt-cache depends pkg \u002F dpkg -s pkg dnf repoquery --requires pkg apk info -d pkg 文件有没有被改过 debsums（要装） rpm -V pkg（内置） apk audit 「这个命令找不到，该装哪个包」在 Debian 上还有个内置提示器：装 command-not-found（Ubuntu 默认有）后，输入 convert 会直接回 sudo apt install imagemagick。RHEL 系同理由 dnf-plugins-core 提供。",{"id":22958,"title":17758,"titles":22959,"content":22960,"level":594},"\u002Ftutorials\u002Flinux#_6-源码安装",[5,16862],"tar xzf app-1.9.3.tar.gz && cd app-1.9.3\n.\u002Fconfigure --prefix=\u002Fusr\u002Flocal --with-openssl     # ① 探测环境，生成 Makefile\nmake -j\"$(nproc)\"                                  # ② 编译\nsudo make install                                  # ③ 装到 \u002Fusr\u002Flocal\nsudo ldconfig                                      # ④ 刷新动态库缓存（装了 .so 才需要） 步骤 常见失败 处理 .\u002Fconfigure no acceptable C compiler \u002F 缺依赖头文件 apt install build-essential；缺的 -dev 包按报错补（zlib1g-dev、libssl-dev）；RHEL 系 dnf groupinstall \"Development Tools\"；dnf builddep pkg.src.rpm \u002F apt build-dep pkg 一次装齐构建依赖 make 内存不足被 OOM 杀掉 降到 make -j1；或加 swap make install 没权限写 \u002Fusr\u002Flocal 用 --prefix=$HOME\u002F.local（之后要把 $HOME\u002F.local\u002Fbin 加 PATH） 运行期 error while loading shared libraries: libxxx.so.1 库没在 \u002Fetc\u002Fld.so.conf 路径里，echo \u002Fusr\u002Flocal\u002Flib | sudo tee \u002Fetc\u002Fld.so.conf.d\u002Flocal.conf && sudo ldconfig；临时验证用 LD_LIBRARY_PATH 卸载 make uninstall 通常不存在 这正是源码安装的致命缺陷 checkinstall 的价值：把 make install 的产物打包成一个受包管理器记录的 .deb\u002F.rpm，于是 dpkg -L 能查文件、apt remove 能干净卸载。命令替换：sudo checkinstall --pkgname=myapp --pkgversion=1.9.3 --default。原则：能用发行版包就不源码装；必须源码装就用 checkinstall，并只装到 \u002Fusr\u002Flocal（与系统包目录隔离，方便整体删除）。",{"id":22962,"title":18003,"titles":22963,"content":22964,"level":594},"\u002Ftutorials\u002Flinux#_7-snap-flatpak-appimage",[5,16862],"新型包格式 命令 一句话 snap（Ubuntu 主推） snap install htop、snap list、snap changes、snap revert 自带依赖与沙箱，自动升级；冷启动慢、\u002Fsnap 占空间大、镜像源在某些网络下很慢 flatpak（桌面应用） flatpak install flathub org.videolan.VLC、flatpak run …、flatpak update 面向桌面 GUI，服务器基本不用 appimage .\u002FSomeApp.AppImage 单文件免安装，给一个可执行位就能跑 pipx \u002F cargo install \u002F npm i -g 语言级生态 与系统包管理器隔离，冲突少但更新不统一",{"id":22966,"title":18108,"titles":22967,"content":22968,"level":594},"\u002Ftutorials\u002Flinux#_8-容器化发行版为什么没有-systemd",[5,16862],"Docker\u002FOCI 镜像里的「Debian\u002FAlpine」只是一个根文件系统，不是完整虚拟机： 现象 原因 后果与替代 没有 systemctl（或报 System has not been booted with systemd） PID 1 是应用本身，没有 init 系统 用 docker exec 起进程、镜像 entrypoint、s6\u002Fsupervisord 管多进程 apt 装完服务不自动启动 没有 systemd 触发 unit 手工在启动脚本里拉起，或改由 supervisor 管 journalctl 无用武之地 journald 没跑 日志走 stdout\u002Fstderr，由 docker logs \u002F kubectl logs 收 cron 不工作 crond 没被启动 外部调度器（K8s CronJob）或镜像里显式起 crond ip\u002Fss 看不到宿主机全部信息 独立网络命名空间 nsenter 进命名空间，或在容器内只关心自己那套 装包习惯差异 镜像层要越小越好 apt install -y --no-install-recommends、apk add --no-cache、装完 && rm -rf \u002Fvar\u002Flib\u002Fapt\u002Flists\u002F*；调试工具单独打 debug 镜像 排「容器里为什么这条命令没有」时，先想是不是精简镜像（*-slim、alpine、distroless）根本没带这个包，用 apk info \u002F dpkg -l 核对，再决定是装包还是换镜像。",{"id":22970,"title":808,"titles":22971,"content":22972,"level":594},"\u002Ftutorials\u002Flinux#踩坑提醒-14",[5,16862],"apt upgrade 与 apt full-upgrade 混用能引出依赖地狱；生产上先 apt -s upgrade（-s 模拟，不实际改动）看会动什么。 手动 dpkg -i 装失败后不要反复重试，sudo apt -f install 才是修依赖的正确一步。 rpm -Va 输出巨多不等于系统被入侵：绝大多数是配置文件被管理员合法改动、日志文件被写入。看 S5（摘要变了）且是二进制路径才值得警惕。 升级 Python\u002FOpenSSL\u002Fglibc 这类系统库时永远不要用源码覆盖（make install 到 \u002Fusr），apt\u002Fdnf 自身依赖它们，覆盖会当场把包管理器变砖。要装新版本用 apt install python3.12、dnf module、pyenv（装在用户目录）或容器。",{"id":22974,"title":18329,"titles":22975,"content":22976,"level":276},"\u002Ftutorials\u002Flinux#十六shell-本身变量引号与脚本的标配",[5],"bash 不是命令行集合，它是一门语言。脚本里 90% 的诡异行为（变量为空、单词被拆、退出码丢失）都出自三个地方：引号、展开、set -e 的缺失。这一章把这几件事一次讲清。",{"id":22978,"title":18342,"titles":22979,"content":22980,"level":594},"\u002Ftutorials\u002Flinux#_1-引号三兄弟",[5,18329],"写法 名字 会不会展开变量\u002F命令 例子 '…' 单引号 完全不展开，所见即字面 echo '$HOME $(date)' 原样输出 \"…\" 双引号 展开 $var、$(cmd)、`，但不拆分单词、不做 glob echo \"$file\" 带空格安全 `…` 反引号命令替换 展开，但嵌套需转义，可读性差 新代码一律换成 $(…) 规则一句话：变量永远包在双引号里（\"$x\"），除非你明确想要单词拆分。判断是否会被拆，用 set -f（临时禁 glob）或 printf '[%s]\\n' \"$x\" 观察。 $'…' 与 $\"…\"：$'a\\nb' 支持 C 转义（\\n、\\t、\\x41）；$\"msg\" 做本地化翻译（基本遇不到）。空数组\u002F未设变量在 set -u 下的安全写法是 ${x:-}。",{"id":22982,"title":18487,"titles":22983,"content":22984,"level":594},"\u002Ftutorials\u002Flinux#_2-变量与-export",[5,18329],"NAME=deploy                 # 赋值——等号两边不能有空格！NAME = deploy 会被当成执行命令\nreadonly PI=3.14            # 只读，再改会报错\nexport JAVA_HOME=\u002Fusr\u002Flib\u002Fjvm\u002Fjava-17   # 导出为环境变量（子进程可见）；只对之后的子进程生效\nexport -f fn                # 导出函数给子 bash；cron\u002Fsystemd 里不生效（不是 bash 子进程）\nenv                         # 打印当前全部环境；env -i bash 起一个干净环境（测试利器）\nprintenv PATH               # 取单个变量，比 echo $PATH 稳（未设时 echo 输出空行看不出来）\nunset NAME                  # 删变量；export -n NAME 只取消导出而保留本地值\ndeclare -p                  # 看变量的类型与属性，排查数组\u002F整数属性时用 变量展开的形式（脚本里避免 bug 的关键）： 写法 含义 ${x:-默认} x 未设或空则用默认（不改变 x） ${x:=默认} x 未设或空则赋值为默认（常用于「首次运行算一次」） ${x:?消息} x 未设或空则报错退出——破坏性命令前的保险：rm -rf \"${DIR:?}\u002F*\" ${x:+替代} x 有值才用替代值 ${#x} \u002F ${x:5:3} \u002F ${x%%.txt} \u002F ${x\u002F\u002Fa\u002Fb} 长度 \u002F 子串 \u002F 删后缀 \u002F 替换（比调用 sed 快且无 fork）",{"id":22986,"title":18679,"titles":22987,"content":22988,"level":594},"\u002Ftutorials\u002Flinux#_3-path-怎么安全地加一段",[5,18329],"export PATH=\"$HOME\u002F.local\u002Fbin:$PATH\"    # 日常写法；重复执行会把 PATH 越拉越长，脚本里别这样\n\ncase \":$PATH:\" in *\":$HOME\u002F.local\u002Fbin:\"*) ;; *) export PATH=\"$HOME\u002F.local\u002Fbin:$PATH\" ;; esac\n# 幂等写法：重复执行不累积 坑：PATH 里出现空段（开头或结尾的 :，或 PATH=:）等价于把当前目录 . 加进搜索路径——任何人能在 \u002Ftmp 放一个 ls 骗你执行。检查：echo \"$PATH\" | tr ':' '\\n' | nl。sudo 会用 secure_path 覆盖你的 PATH（sudo -V | grep secure_path 查看），所以 sudo mytool 找不到东西时要改 \u002Fetc\u002Fsudoers 而不是 export。",{"id":22990,"title":18823,"titles":22991,"content":22992,"level":594},"\u002Ftutorials\u002Flinux#_4-启动文件什么时候被读",[5,18329],"文件 谁读它 何时 放什么 \u002Fetc\u002Fprofile → \u002Fetc\u002Fprofile.d\u002F*.sh 登录 shell 登录时一次 系统级环境（模块、语言、代理） ~\u002F.bash_profile \u002F ~\u002F.profile \u002F ~\u002F.login 登录 shell 找到第一个就不往后（bash 只看 .bash_profile、.bash_login、.profile） 只写 export，并 source ~\u002F.bashrc ~\u002F.bashrc 非交互但交互式的 shell（每个新终端窗口） 每次开 shell alias、函数、提示符、补全 \u002Fetc\u002Fbash.bashrc \u002F \u002Fetc\u002Fbashrc 所有 bash 每次 系统级 alias ~\u002F.bash_logout 登录 shell 退出时 清理（清屏等） $BASH_ENV 非交互 shell（脚本） 脚本启动时 极少用，注意脚本环境 经典问题：.bashrc 里写了 alias 但 ssh host 'cmd' 不生效——因为 ssh host cmd 起的是非交互 shell，.bashrc 开头通常有 case $- in *i*) ;; *) return;; esac 直接返回。把要共享的东西放 .profile\u002F.bashrc 的守卫之前，或用 BASH_ENV。",{"id":22994,"title":19005,"titles":22995,"content":22996,"level":594},"\u002Ftutorials\u002Flinux#_5-alias函数与-history",[5,18329],"alias ll='ls -lh --color=auto'    # 定义别名；别名不做参数展开，需要参数就写函数\nunalias ll                        # 取消；\\ls 或 command ls 临时绕开别名；脚本里判断用 type -a\nmkcd() { mkdir -p \"$1\" && cd \"$1\"; }   # 函数：定义即执行副作用\n\nhistory                             # 历史列表；history 20 看最近 20 条；HISTSIZE\u002FHISTFILESIZE 控条数\nhistory -a                          # 立即落盘；多终端并发会互相覆盖，设 shopt -s histappend\n!!                                  # 重复上条整条；sudo !! 是忘加 sudo 的标准补救\n!$                                  # 上条的最后一个参数：mkdir a\u002Fb\u002Fc 然后 cd !$；!* 是全部参数\n!42                                 # 执行历史第 42 条；!grep 按开头字符串；!?conf? 最近含 conf 的\n!42:p                               # 只打印不执行，先确认再跑\nCtrl-R                              # 反向搜索——效率最高的一个键；配 fzf 后可模糊选\nhistory -c; history -w              # 清历史；注意 ~\u002F.bash_history 里的敏感命令 位置参数与特殊变量： 变量 含义 关键区别 $0 脚本名 用 basename \"$0\" 做 usage 提示 $1…$9、${10} 第 N 个参数 10 个以上必须加花括号 $# 参数个数 判断数量：[ $# -lt 2 ] && usage \"$@\" 所有参数，各自保持为独立词 正确默认写法；for f in \"$@\"、cmd \"$@\" \"$*\" 所有参数拼成一个字符串（按 $1 分隔） 只在「要当成一整串」时用 $$ 当前 shell PID 临时文件命名 f=$(mktemp) 更好 $? 上一条命令的退出码 0 成功；set -e 之后基本不用再手动查 $! 最后一个后台进程 PID cmd & pid=$! 之后 kill $pid $_ 上一条命令的最后一个参数 交互式很好用 $- \u002F $BASHPID 当前选项标志 \u002F 子 shell 真实 PID 判断是否交互 shell",{"id":22998,"title":19345,"titles":22999,"content":23000,"level":594},"\u002Ftutorials\u002Flinux#_6-脚本的标配",[5,18329],"#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\nIFS=$'\\n\\t' 选项 作用 为什么是标配 set -e（-o errexit） 命令失败立即退出 没有它，脚本会在错误状态上继续跑到底（cd 失败后继续 rm） set -u（-o nounset） 引用未定义变量即报错 抓拼写错误与「本该传参却忘了传」 set -o pipefail 管道里任一命令失败即失败 默认只看最后一个命令的退出码：grep x file | wc -l 里 grep 失败被吞 set -x（-o xtrace） 打印每条命令的实际展开 调试首选，等价临时 bash -x script.sh set -v 打印读入的原始行 看解析问题 set +x 关掉上面的跟踪 遇到密码时记得关 IFS=$'\\n\\t' 限制字段分割符 防文件名里的空格被拆 set -e 的例外（很多人踩）：条件语句里的命令（if cmd; then）、&&\u002F|| 左侧、管道里非最后一个命令（无 pipefail 时）、以及被取反的 ! cmd 都不会触发退出。函数返回值看最后一条命令。",{"id":23002,"title":22441,"titles":23003,"content":23004,"level":594},"\u002Ftutorials\u002Flinux#_7-与-以及数组",[5,18329],"维度 [ ]（POSIX test） [[ ]]（bash 关键字） 词拆分\u002F glob 会（变量必须加引号） 不会，[[ $x == *.log ]] 安全 正则匹配 无 [[ $f =~ ^[0-9]{3}$ ]]（右侧不要加引号） 逻辑组合 只能用 -a\u002F-o（有坑） 支持 && 与逻辑或（双竖线），可嵌套 字符串比较 =（== 也接受但非 POSIX） == 支持模式匹配 未加引号的空变量 [: too many arguments 报错 正常处理 可用性 所有 sh bash\u002Fzsh\u002Fksh（#!\u002Fbin\u002Fsh 里不能用） 结论：bash 脚本一律 [[ ]]，\u002Fbin\u002Fsh 脚本一律 [ ] 并给变量加引号。文件测试通用：-e 存在、-f 普通文件、-d 目录、-x 可执行、-r\u002F-w、-s 非空、-L 符号链接、a -nt b 更新。 数组与关联数组： files=(a.log b.log c.log)\necho \"${files[1]}\"           # b.log\necho \"${#files[@]}\"          # 3（元素数）\nprintf '%s\\n' \"${files[@]}\"  # 全部展开，各占一行\nfiles+=(d.log)               # 追加\nunset 'files[1]'             # 删元素（注意引号，否则 shell 先展开）\n\ndeclare -A seen              # 关联数组（bash 4+，Alpine 的 ash 不支持）\nseen[10.0.0.12]=42\nfor k in \"${!seen[@]}\"; do echo \"$k => ${seen[$k]}\"; done\n(( seen[10.0.0.12]++ ))      # 计数惯用式",{"id":23006,"title":19928,"titles":23007,"content":23008,"level":594},"\u002Ftutorials\u002Flinux#_8-shebang执行方式与-read",[5,18329],"写法 含义 #!\u002Fbin\u002Fbash 固定用 bash（发行版路径一致时最简单） #!\u002Fusr\u002Fbin\u002Fenv bash 从 PATH 找 bash，跨系统更稳（Nix\u002F自编译场景必备） #!\u002Fbin\u002Fsh 承诺只用 POSIX（此时不能 [[ ]]、不能用数组） #!\u002Fusr\u002Fbin\u002Fenv -S bash -eu env 的 -S 把参数一起传（GNU coreutils 8.30+） 没有 shebang 由调用者决定；sh script.sh 会按 POSIX 跑，bash 语法当场报错 chmod +x deploy.sh          # 加执行位（没有它 .\u002Fdeploy.sh 报 Permission denied）\nbash deploy.sh              # 显式用 bash 跑，不需要执行位\nsource deploy.sh            # 在当前 shell 执行（cd 与 export 会留下来）；. .\u002Fenv.sh 是 POSIX 写法\n.\u002Fdeploy.sh                 # 子 shell 执行（最常见：改了 PATH 却没生效，就是用了 .\u002F）\n\nread -p \"目标环境 [dev\u002Fprod]: \" env     # 提示 + 读一行到 env\nread -r -d '' big \u003C file                 # 读整个文件（-d '' 以 NUL 结束）\nread -s -p \"密码: \" pass \u003C \u002Fdev\u002Ftty      # -s 不回显；从 tty 读而非管道\nprintf '%s\\n' a b c | while IFS= read -r line; do …; done   # 逐行读的正确姿势\nread -t 5 -n 1 -p \"按任意键继续\" _       # 带超时与单字符 read 的两个必备参数：-r（否则反斜杠被吃掉）、配 IFS=（否则行首尾空白被去）。逐行处理文件时不要用 for line in $(cat file)——它会按空白拆且丢失空行。",{"id":23010,"title":808,"titles":23011,"content":23012,"level":594},"\u002Ftutorials\u002Flinux#踩坑提醒-15",[5,18329],"set -e 与 if\u002F&& 组合时不生效是设计，不是 bug；关键步骤显式判返回值或加 || exit 1。 \"$*\" 与 \"$@\" 写反了不会报错，只会把带空格的参数粘成一坨——这是脚本处理文件名出问题的第一来源。 反引号里的 $( ) 混用导致转义地狱：全文统一 $()。 修改 PATH\u002Fcd 之后在下一个命令里「没变化」，几乎一定是因为你用了 .\u002Fscript.sh（子 shell）而不是 source。 .bashrc 里写了 set -e 会让交互 shell 在命令失败后直接退出——这是很危险的误配，选项只该写在脚本里。 用 echo 输出以 - 开头的内容会被当参数：统一用 printf '%s\\n'。",{"id":23014,"title":20268,"titles":23015,"content":20271,"level":276},"\u002Ftutorials\u002Flinux#十七性能与排障freeiostatstrace-与总速查表",[5],{"id":23017,"title":20279,"titles":23018,"content":23019,"level":594},"\u002Ftutorials\u002Flinux#_1-free-内存available-才是重点",[5,20268],"free -h total        used        free      shared  buff\u002Fcache   available\nMem:           7.7Gi       3.1Gi       214Mi       18Mi       4.3Gi       4.2Gi\nSwap:          1.9Gi       128Mi       1.8Gi 列 含义 读法 total 物理内存总量 减去内核占用才是可用上限 used 应用真正占用 used = total - free - buff\u002Fcache（不同版本算法略有差异） free 完全没被用过的内存 小是正常的，Linux 会把空闲内存拿去做页缓存 buff\u002Fcache 块设备缓冲 + 页缓存 可随时回收，看着大不是问题 available 估算「还能给新应用多少」（free + 可回收的 cache） 判断内存够不够只看这一列 shared tmpfs（含 \u002Fdev\u002Fshm）占用 变大会真吃掉内存 「内存吃满」的判据：available 很小 且 Swap 开始进出（vmstat 的 si\u002Fso 非 0）。只看 free 那一列得出「内存不足」的结论是错的。查谁在用：ps -eo pid,user,rss,cmd --sort=-rss | head（RSS 单位 KB）、smem -rk（含 PSS，共享内存分摊更准）、sudo systemd-cgtop memory（按 cgroup 看，容器环境最准）。",{"id":23021,"title":20457,"titles":23022,"content":23023,"level":594},"\u002Ftutorials\u002Flinux#_2-uptime-负载要结合核数看",[5,20268],"uptime    #  09:42:11 up 14 days,  3:29,  2 users,  load average: 1.85, 1.42, 0.98\nnproc     #  4 观察 判读 1 \u002F 5 \u002F 15 分钟三个数 短期抖动看第一个；持续升高看第三个；15 分钟值高才是真有问题 与核数之比 load \u002F nproc ≈ 1 表示刚好排满；4 核上 load 8 = 一半时间在排队；load 1 在 4 核上算很闲 高 load 但 %CPU 不高 说明在等 D 状态（磁盘\u002FNFS）——不是 CPU 不够，去查 IO 容器里的 load 显示的是宿主机的，不能用它判断容器健康；看 docker stats 或 cgroup 的 cpu.stat CPU 各时间的细分来源：us（应用代码）、sy（内核，sy 高常见于频繁上下文切换\u002F系统调用风暴）、wa（等 IO）、si\u002Fhi（软\u002F硬中断，网络包风暴会打高 si）、st（被宿主机「偷走」的时间，云主机常见）。工具：top\u002Fhtop、mpstat -P ALL 1（每核分布）、pidstat 1（每进程）、perf top（热点函数，需 kernel 权限）、cgroup 限额导致的 throttled_time。",{"id":23025,"title":20604,"titles":23026,"content":23027,"level":594},"\u002Ftutorials\u002Flinux#_3-iostatsarvmstat-磁盘-io",[5,20268],"工具 命令 关键列 iostat（sysstat 包） iostat -xz 1 %util（设备繁忙比例，接近 100 说明设备是瓶颈）、await（平均等待 ms，SSD 应 \u003C1，HDD \u003C10）、r_await\u002Fw_await 分开看、avgqu-sz（队列深度） sar sar -d 1 3（磁盘）、sar -u 1（CPU）、sar -r 1（内存）、sar -n DEV 1（网卡）、sar -B 1（换页） 能查历史：sar -u -f \u002Fvar\u002Flog\u002Fsa\u002Fsa04，排「昨天 3 点发生了什么」的唯一实用工具 vmstat vmstat 1 r（运行队列）、b（IO 阻塞数，持续 >0 说明 IO 堵）、si\u002Fso（swap 活动）、wa iotop sudo iotop -o 哪个进程在读写（-o 只显示有 IO 的） pidstat -d 1 -p PID 每进程读写 KB\u002Fs — blktrace\u002Fbiosnoop BCC\u002Fbpftrace 内核级追踪，深度排障 %util 对 SSD\u002FNVMe 阵列不再等于饱和（它能并行），要看 await 与队列深度。sar 的历史数据需要 sysstat 服务开启（cat \u002Fetc\u002Fsysstat 或 ENABLED=yes，RHEL 默认开）。",{"id":23029,"title":20794,"titles":23030,"content":23031,"level":594},"\u002Ftutorials\u002Flinux#_4-dmesg-内核消息与-oom-killer",[5,20268],"dmesg -T | tail -50                                  # 人类时间戳看最近内核消息\ndmesg -w                                             # 持续看，等价 journalctl -kf\ndmesg | grep -iE 'killed process|out of memory'      # 找 OOM：内存耗尽被杀的记录\njournalctl -k --since \"1 hour ago\" | grep -i oom     # 同上，走 journald\ndmesg | grep -iE 'error|I\u002FO|reset|fail'              # 硬件错：EXT4-fs error、nvme: controller down OOM 记录会写清是谁被杀：Out of memory: Killed process 28414 (java) total-vm:…。保护关键进程：echo -1000 > \u002Fproc\u002F$(pgrep app)\u002Foom_score_adj（范围 -1000~1000），systemd 里写 OOMScoreAdjust=。容器被杀看 cgroup：docker inspect --format '{{.State.OOMKilled}}' id、cat \u002Fsys\u002Ffs\u002Fcgroup\u002F...\u002Fmemory.events 的 oom_kill。 OOM 之后要做的：确认是谁涨的（ps 按 RSS + 应用日志）、给服务加 MemoryMax=（systemd）或容器 --memory，避免它把整机拖死；vm.swappiness=10 让内核更愿意用 swap 而不是直接杀（不是根治）。",{"id":23033,"title":20927,"titles":23034,"content":23035,"level":594},"\u002Ftutorials\u002Flinux#_5-lsof-谁在用这个文件端口",[5,20268],"sudo lsof -i :8080              # 按端口找进程；-i TCP:8080、-i 4（仅 IPv4）、-sTCP:LISTEN 只看监听\nsudo lsof \u002Fsrv\u002Fapp\u002Fapp.log      # 按文件找进程——排「umount busy」「空间不回收」都用它\nsudo lsof +L1                   # 已删除但被占用：链接数 \u003C1 的文件，磁盘空间被它占着\nsudo lsof -u deploy             # 按用户：看某人开了什么\nsudo lsof -p 28414              # 按 PID 全量：列它的 fd、mmap、库、socket\nsudo lsof -i                    # 只看网络，与 ss 互补（能看到 socket 文件描述符号）\nlsof | wc -l                    # 排查 fd 泄漏：ls \u002Fproc\u002FPID\u002Ffd | wc -l 对比 ulimit -n lsof 慢（要扫所有进程的 fd），生产上先看 ss\u002F\u002Fproc\u002FPID\u002Ffd，需要精确定位再 lsof。fuser -vm \u002Fdata 是它的轻量替代（fuser -k \u002Fdata 直接杀占用者——慎用）。",{"id":23037,"title":21045,"titles":23038,"content":23039,"level":594},"\u002Ftutorials\u002Flinux#_6-strace-ltrace-卡在哪个系统调用",[5,20268],"strace -f -tt .\u002Fapp                  # 从头跟踪：-f 含子进程、-tt 带时间戳\nstrace -p 28414 -f -s 256             # 附加到运行中的进程（-s 256 加长字符串打印）\nstrace -e trace=network,file -p PID   # 只看某类调用（-e trace=%file）\nstrace -c -p 28414                    # 只出统计：哪个调用耗时\u002F次数最多——排「慢」先看这个\nstrace -o \u002Ftmp\u002Fst.log -yy -ttt -f -p PID   # 落盘分析（-yy 给 socket 对端地址）\nstrace -e openat -f bash script.sh    # 「脚本到底在读哪个配置文件」的终极答案\nltrace -c -p PID                       # 跟踪库函数调用（看它调了哪个 printf\u002FSSL_read）\nperf trace                             # 更低开销的 strace（bpf 实现） strace 会让目标进程明显变慢（每次系统调用都要停下与 ptrace 通信），生产环境用 -c 抽样或短时间附加，别长时间挂着。看 EACCES\u002FENOENT\u002FEAGAIN\u002FETIMEDOUT 这几个错误码，通常一步到位定位问题。ltrace 对静态编译的程序无效（没有库调用可跟踪）。",{"id":23041,"title":21210,"titles":23042,"content":23043,"level":594},"\u002Ftutorials\u002Flinux#_7-tcpdump-与实时流量",[5,20268],"sudo tcpdump -i any -nn tcp port 8080               # 看某端口有没有流量\nsudo tcpdump -i eth0 -nn host 10.0.0.5 and port 5432  # 抓某主机与某服务的全部包\nsudo tcpdump -i any -w \u002Ftmp\u002Flost.pcap tcp port 443   # 抓下来给 Wireshark 分析（-W 5 -C 50 分片限大小）\nsudo tcpdump -nn 'tcp[tcpflags] & (tcp-syn) != 0 and dst port 443'   # 只看 SYN 无响应（被防火墙丢）\nsudo tcpdump -nn 'tcp[tcpflags] & tcp-rst != 0'      # 看谁在连不上时发 RST\nsudo tcpdump -nn -s0 -A port 53                      # 抓 DNS 问答 参数要点：-nn（不解析端口\u002F主机名，避免自己造 DNS 流量并卡住）、-i any（跨网卡，容器\u002F桥接场景必备）、-s 0（抓完整包，老版本默认截 96 字节）、-c 100（限量防写爆磁盘）、-A\u002F-X（看载荷，明文协议调试用）、-l（行缓冲实时输出，配管道用）。看实时流量分布用 nethogs（按进程看带宽，找「谁在下东西」最直接）或 iftop -P（按连接看，-i eth0，界面里 p 显示累计、b 切换源\u002F目的排序）。",{"id":23045,"title":21395,"titles":23046,"content":23047,"level":594},"\u002Ftutorials\u002Flinux#_8-现象-先跑哪几条命令",[5,20268],"现象 第一批命令（照抄） 看到什么算定位 CPU 高 \u002F 变慢 uptime; nproc → top（按 P）→ pidstat 1 5 → strace -c -p PID us 高 = 应用算法\u002F循环；sy 高 = 系统调用风暴；wa 高 = 其实是 IO；load 高但 CPU 空 = D 状态堆 内存吃满 \u002F 被 OOM free -h → dmesg | grep -i kill → ps -eo pid,user,rss,cmd --sort=-rss | head → cat \u002Fproc\u002Fmeminfo | head available 小 + si\u002Fso 活动 = 真缺；SReclaimable\u002Fbuff\u002Fcache 大 = 假象 磁盘满 df -h → df -i → du -xh \u002F --max-depth=2 | sort -h | tail → sudo lsof +L1 三者各对应：块满 \u002F inode 满 \u002F 已删除未释放 磁盘 IO 高 iostat -xz 1 → iotop -o → pidstat -d 1 %util≈100 且 await 大 = 设备瓶颈；util 低但 await 高 = 队列排队\u002F驱动 网络不通 ip a → ip route get 目标IP → ping -c 3 → nc -vz 目标 端口 → curl -v 路由不通 \u002F ICMP 被封 \u002F TCP 连不上 \u002F TLS 或 HTTP 层错，四层各归各位 端口没监听 ss -tulnp → ss -lntp | grep 8080 → systemctl status 服务 根本没起 vs 只绑了 127.0.0.1 vs 被别的服务占了 服务起不来 systemctl status 名 → journalctl -u 名 -n 50 --no-pager → systemctl cat 名 看 Main PID、Exit code、ExecStart 路径是否存在与权限 进程杀不掉 ps -o pid,stat,user,cmd -p PID → sudo kill 15 → sudo kill -9 → 若 D 状态：cat \u002Fproc\u002FPID\u002Fwchan D = 等内核 IO（杀不动，修底层）；Z = 僵尸（父进程回收）；systemd 拉起 = 用 systemctl stop 时间不对 timedatectl → chronyc sources\u002Fntpq -p → hwclock -r NTPSynchronized=no、时区错、双系统 UTC\u002F本地冲突 权限被拒 sudo -u appuser 原命令 → namei -l 全路径 → id appuser → getenforce\u002Fausearch -m avc 父目录缺 x、组变更后没重登、SELinux 上下文 文件找不到\u002F版本乱 command -v X → dpkg -S $(command -v X)（或 rpm -qf）→ update-alternatives --list X 多版本共存时看谁在前 登录不上 SSH ssh -v → journalctl -u ssh -n 50 → sudo ss -tlnp | grep 22 → sudo tail -50 \u002Fvar\u002Flog\u002Fauth.log 端口、配置、认证方式、faillock 各查一段 某个应用行为怪 strace -f -e trace=file,network .\u002Fapp → lsof -p PID 读错配置、连错地址、写到只读目录",{"id":23049,"title":21750,"titles":23050,"content":23051,"level":594},"\u002Ftutorials\u002Flinux#_9-总速查表我想干一件事-用什么命令",[5,20268],"我想 命令 看我在哪、有什么 pwd、ls -lh、tree -L 2 找文件（按名字） find \u002F -xdev -maxdepth 4 -name '*.log' -mtime -7 找文件（按内容） grep -rl '关键词' \u002Fetc 找命令属于哪个包 dpkg -S $(command -v X) \u002F rpm -qf $(command -v X) 看文件内容 less -N file、head -n 200、tail -F file 看不可见字符\u002F换行 cat -A file、od -c -N 64 file 批量替换配置 sed -i.bak 's\u002F^旧\u002F新\u002F' file 然后 diff file.bak file 按列统计 awk '{print $1}' log | sort | uniq -c | sort -rn | head 看谁占空间 du -h --max-depth=1 . | sort -h | tail -15 看磁盘\u002Finode df -hT、df -i 看挂载与读写状态 findmnt -T \u002F路径、lsblk -f 看进程 ps aux --sort=-%cpu | head、top、htop 按名字找进程 pgrep -af '关键词' 停服务 \u002F 停进程 systemctl stop app；kill 28414 → 10 秒后 kill -9 28414 看服务日志 journalctl -u app -f、journalctl -u app --since \"10 min ago\" -p err 改服务配置 systemctl edit app → systemctl daemon-reload && systemctl restart app 看谁在监听端口 sudo ss -tulnp 测端口通不通 nc -vz 10.0.0.5 5432 测接口健康 curl -sS -o \u002Fdev\u002Fnull -w '%{http_code} %{time_total}\\n' URL 验证 DNS getent hosts 域名、dig +short 域名 @1.1.1.1 走跳板连内网 ssh -J bastion app、ssh -L 5432:db:5432 bastion 备份\u002F同步目录 rsync -avz --delete src\u002F host:\u002Fbackup\u002F（先 -n 干跑） 打包 \u002F 解包 tar czf app.tar.gz 目录、tar xzf app.tar.gz -C 目标 不解压搜日志 zgrep -h 'ERROR' \u002Fvar\u002Flog\u002F*.gz 加一条定时任务 crontab -e（命令写绝对路径、输出重定向、% 转义） 建用户\u002F加组 sudo useradd -m -s \u002Fbin\u002Fbash 名字、sudo usermod -aG docker 名字（必须 -a，然后重新登录） 看自己身份 id、groups、sudo -l 修权限 chown -R app:app \u002Fsrv\u002Fapp、chmod -R a+rX \u002Fsrv\u002Fapp、namei -l 全路径 装\u002F卸包 apt install -y pkg \u002F dnf -y install pkg \u002F apk add --no-cache pkg 查包版本候选 apt policy pkg \u002F dnf list pkg 看资源总览 free -h、uptime、iostat -xz 1、vmstat 1、vgs\u002Flvs（LVM） 查历史性能 sar -u -f \u002Fvar\u002Flog\u002Fsa\u002Fsa04 抓包 sudo tcpdump -i any -nn -c 200 tcp port 443 定位「卡在哪个调用」 strace -c -p PID、strace -f -e trace=file .\u002Fapp 看内核报错 dmesg -T | tail -50、journalctl -k -b --since \"1 hour ago\" 跑个脚本看细节 bash -x script.sh、脚本头 set -euo pipefail 临时改环境变量 export PATH=\"$HOME\u002F.local\u002Fbin:$PATH\"、env -i bash 起干净环境 查\u002F改时间与时区 timedatectl、timedatectl set-timezone Asia\u002FShanghai、timedatectl set-ntp true 安全地删一批文件 find dir -name '*.tmp' -mtime +3 -print 确认后再把 -print 换 -delete",{"id":23053,"title":22271,"titles":23054,"content":23055,"level":594},"\u002Ftutorials\u002Flinux#一句话记住",[5,20268],"排障永远按资源维度各看一眼：CPU（uptime\u002Ftop）→ 内存（free\u002Fdmesg）→ 磁盘容量（df -h \u002F df -i \u002F du）→ 磁盘 IO（iostat）→ 网络（ip\u002Fss\u002Fcurl）→ 服务（systemctl\u002Fjournalctl）→ 权限（namei\u002Fid）。七条命令走完还定位不到，才需要 strace 和 tcpdump 这种深度工具。本手册第十七张表就是这张检查单的落地版本。 html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}",1793263560719]