├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md └── workflows │ ├── ci.yml │ └── dockerimage.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── gh-md-toc └── tests ├── test directory ├── test_backquote.md ├── test_filepathwithspace.md ├── test_nonenglishchars.md ├── test_plussign.md └── test_setextwithformatting.md ├── test_helper.bash └── tests.bats /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[ BUG ] ..." 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Environment (please complete the following information):** 20 | - OS: [e.g. iOS] 21 | - Version [e.g. 0.7] 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Got a question? ask it here 4 | title: "[Question]" 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | schedule: 5 | # twice a week, on sundays, fridays at 8:00am UTC 6 | - cron: "0 8 * * 0,5" 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | # Allows you to run this workflow manually from the Actions tab 13 | workflow_dispatch: 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Setup bats 20 | uses: mig4/setup-bats@v1 21 | with: 22 | bats-version: 1.6.0 23 | 24 | - name: Check out code 25 | uses: actions/checkout@v2 26 | 27 | - name: Run tests 28 | run: make test 29 | env: 30 | GH_TOC_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | -------------------------------------------------------------------------------- /.github/workflows/dockerimage.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker image 2 | on: 3 | release: 4 | types: [published] 5 | 6 | jobs: 7 | push_to_registry: 8 | name: Push Docker image to Docker Hub 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Check out the repo 12 | uses: actions/checkout@v2 13 | - name: Push to Docker Hub 14 | uses: docker/build-push-action@v1 15 | with: 16 | username: ${{ secrets.DOCKER_USERNAME }} 17 | password: ${{ secrets.DOCKER_PASSWORD }} 18 | repository: evkalinin/gh-md-toc 19 | tag_with_ref: true 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | token.txt 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian 2 | 3 | RUN apt update -y && \ 4 | apt upgrade -y && \ 5 | apt install curl -y 6 | 7 | WORKDIR app 8 | 9 | COPY gh-md-toc . 10 | 11 | RUN chmod +x gh-md-toc 12 | 13 | ENTRYPOINT ["./gh-md-toc"] 14 | CMD [] 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Eugene Kalinin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | release: test 2 | @git tag `grep -o -E '[0-9]\.[0-9]{2}\.[0-9]{1,2}' gh-md-toc` 3 | @git push --tags origin master 4 | 5 | test: 6 | @bats tests 7 | 8 | lint: 9 | @shellcheck -e SC2008 gh-md-toc 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gh-md-toc 2 | ========= 3 | 4 | [![CI](https://github.com/ekalinin/github-markdown-toc/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/ekalinin/github-markdown-toc/actions/workflows/ci.yml) 5 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/ekalinin/github-markdown-toc) 6 | 7 | gh-md-toc — is for you if you **want to generate TOC** (Table Of Content) for a README.md or 8 | a GitHub wiki page **without installing additional software**. 9 | 10 | It's my try to fix a problem: 11 | 12 | * [github/issues/215](https://github.com/isaacs/github/issues/215) 13 | 14 | gh-md-toc is able to process: 15 | 16 | * stdin 17 | * local files (markdown files in local file system) 18 | * remote files (html files on github.com) 19 | 20 | gh-md-toc tested on Ubuntu, and macOS High Sierra (gh-md-toc release 0.4.9). If you want it on Windows, you 21 | better to use a golang based implementation: 22 | 23 | * [github-markdown-toc.go](https://github.com/ekalinin/github-markdown-toc.go) 24 | 25 | It's more solid, reliable and with ability of a parallel processing. And 26 | absolutely without dependencies. 27 | 28 | Table of contents 29 | ================= 30 | 31 | 32 | * [Installation](#installation) 33 | * [Usage](#usage) 34 | * [STDIN](#stdin) 35 | * [Local files](#local-files) 36 | * [Remote files](#remote-files) 37 | * [Multiple files](#multiple-files) 38 | * [Combo](#combo) 39 | * [Auto insert and update TOC](#auto-insert-and-update-toc) 40 | * [GitHub token](#github-token) 41 | * [TOC generation with Github Actions](#toc-generation-with-github-actions) 42 | * [Tests](#tests) 43 | * [Dependency](#dependency) 44 | * [Docker](#docker) 45 | * [Local](#local) 46 | * [Public](#public) 47 | 48 | 49 | 50 | Installation 51 | ============ 52 | 53 | Linux (manual installation) 54 | ```bash 55 | $ wget https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc 56 | $ chmod a+x gh-md-toc 57 | ``` 58 | 59 | MacOS (manual installation) 60 | ```bash 61 | $ curl https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc -o gh-md-toc 62 | $ chmod a+x gh-md-toc 63 | ``` 64 | 65 | Linux or MacOS (using [Basher](https://github.com/basherpm/basher)) 66 | ```bash 67 | $ basher install ekalinin/github-markdown-toc 68 | # `gh-md-toc` will automatically be available in the PATH 69 | ``` 70 | 71 | Usage 72 | ===== 73 | 74 | 75 | STDIN 76 | ----- 77 | 78 | Here's an example of TOC creating for markdown from STDIN: 79 | 80 | ```bash 81 | ➥ cat ~/projects/Dockerfile.vim/README.md | ./gh-md-toc - 82 | * [Dockerfile.vim](#dockerfilevim) 83 | * [Screenshot](#screenshot) 84 | * [Installation](#installation) 85 | * [OR using Pathogen:](#or-using-pathogen) 86 | * [OR using Vundle:](#or-using-vundle) 87 | * [License](#license) 88 | ``` 89 | 90 | Local files 91 | ----------- 92 | 93 | Here's an example of TOC creating for a local README.md: 94 | 95 | ```bash 96 | ➥ ./gh-md-toc ~/projects/Dockerfile.vim/README.md 97 | 98 | 99 | Table of Contents 100 | ================= 101 | 102 | * [Dockerfile.vim](#dockerfilevim) 103 | * [Screenshot](#screenshot) 104 | * [Installation](#installation) 105 | * [OR using Pathogen:](#or-using-pathogen) 106 | * [OR using Vundle:](#or-using-vundle) 107 | * [License](#license) 108 | ``` 109 | 110 | Remote files 111 | ------------ 112 | 113 | And here's an example, when you have a README.md like this: 114 | 115 | * [README.md without TOC](https://github.com/ekalinin/envirius/blob/f939d3b6882bfb6ecb28ef7b6e62862f934ba945/README.md) 116 | 117 | And you want to generate TOC for it. 118 | 119 | There is nothing easier: 120 | 121 | ```bash 122 | ➥ ./gh-md-toc https://github.com/ekalinin/envirius/blob/master/README.md 123 | 124 | Table of Contents 125 | ================= 126 | 127 | * [envirius](#envirius) 128 | * [Idea](#idea) 129 | * [Features](#features) 130 | * [Installation](#installation) 131 | * [Uninstallation](#uninstallation) 132 | * [Available plugins](#available-plugins) 133 | * [Usage](#usage) 134 | * [Check available plugins](#check-available-plugins) 135 | * [Check available versions for each plugin](#check-available-versions-for-each-plugin) 136 | * [Create an environment](#create-an-environment) 137 | * [Activate/deactivate environment](#activatedeactivate-environment) 138 | * [Activating in a new shell](#activating-in-a-new-shell) 139 | * [Activating in the same shell](#activating-in-the-same-shell) 140 | * [Get list of environments](#get-list-of-environments) 141 | * [Get current activated environment](#get-current-activated-environment) 142 | * [Do something in environment without enabling it](#do-something-in-environment-without-enabling-it) 143 | * [Get help](#get-help) 144 | * [Get help for a command](#get-help-for-a-command) 145 | * [How to add a plugin?](#how-to-add-a-plugin) 146 | * [Mandatory elements](#mandatory-elements) 147 | * [plug_list_versions](#plug_list_versions) 148 | * [plug_url_for_download](#plug_url_for_download) 149 | * [plug_build](#plug_build) 150 | * [Optional elements](#optional-elements) 151 | * [Variables](#variables) 152 | * [Functions](#functions) 153 | * [Examples](#examples) 154 | * [Example of the usage](#example-of-the-usage) 155 | * [Dependencies](#dependencies) 156 | * [Supported OS](#supported-os) 157 | * [Tests](#tests) 158 | * [Version History](#version-history) 159 | * [License](#license) 160 | * [README in another language](#readme-in-another-language) 161 | ``` 162 | 163 | That's all! Now all you need — is copy/paste result from console into original 164 | README.md. 165 | 166 | If you do not want to copy from console you can add `> YOURFILENAME.md` at the end of the command like `./gh-md-toc https://github.com/ekalinin/envirius/blob/master/README.md > table-of-contents.md` and this will store the table of contents to a file named table-of-contents.md in your current folder. 167 | 168 | And here is a result: 169 | 170 | * [README.md with TOC](https://github.com/ekalinin/envirius/blob/24ea3be0d3cc03f4235fa4879bb33dc122d0ae29/README.md) 171 | 172 | Moreover, it's able to work with GitHub's wiki pages: 173 | 174 | ```bash 175 | ➥ ./gh-md-toc https://github.com/ekalinin/nodeenv/wiki/Who-Uses-Nodeenv 176 | 177 | Table of Contents 178 | ================= 179 | 180 | * [Who Uses Nodeenv?](#who-uses-nodeenv) 181 | * [OpenStack](#openstack) 182 | * [pre-commit.com](#pre-commitcom) 183 | ``` 184 | 185 | Multiple files 186 | -------------- 187 | 188 | It supports multiple files as well: 189 | 190 | ```bash 191 | ➥ ./gh-md-toc \ 192 | https://github.com/aminb/rust-for-c/blob/master/hello_world/README.md \ 193 | https://github.com/aminb/rust-for-c/blob/master/control_flow/README.md \ 194 | https://github.com/aminb/rust-for-c/blob/master/primitive_types_and_operators/README.md \ 195 | https://github.com/aminb/rust-for-c/blob/master/unique_pointers/README.md 196 | 197 | * [Hello world](https://github.com/aminb/rust-for-c/blob/master/hello_world/README.md#hello-world) 198 | 199 | * [Control Flow](https://github.com/aminb/rust-for-c/blob/master/control_flow/README.md#control-flow) 200 | * [If](https://github.com/aminb/rust-for-c/blob/master/control_flow/README.md#if) 201 | * [Loops](https://github.com/aminb/rust-for-c/blob/master/control_flow/README.md#loops) 202 | * [For loops](https://github.com/aminb/rust-for-c/blob/master/control_flow/README.md#for-loops) 203 | * [Switch/Match](https://github.com/aminb/rust-for-c/blob/master/control_flow/README.md#switchmatch) 204 | * [Method call](https://github.com/aminb/rust-for-c/blob/master/control_flow/README.md#method-call) 205 | 206 | * [Primitive Types and Operators](https://github.com/aminb/rust-for-c/blob/master/primitive_types_and_operators/README.md#primitive-types-and-operators) 207 | 208 | * [Unique Pointers](https://github.com/aminb/rust-for-c/blob/master/unique_pointers/README.md#unique-pointers) 209 | ``` 210 | 211 | Combo 212 | ----- 213 | 214 | You can easily combine both ways: 215 | 216 | ```bash 217 | ➥ ./gh-md-toc \ 218 | ~/projects/Dockerfile.vim/README.md \ 219 | https://github.com/ekalinin/sitemap.s/blob/master/README.md 220 | 221 | * [Dockerfile.vim](~/projects/Dockerfile.vim/README.md#dockerfilevim) 222 | * [Screenshot](~/projects/Dockerfile.vim/README.md#screenshot) 223 | * [Installation](~/projects/Dockerfile.vim/README.md#installation) 224 | * [OR using Pathogen:](~/projects/Dockerfile.vim/README.md#or-using-pathogen) 225 | * [OR using Vundle:](~/projects/Dockerfile.vim/README.md#or-using-vundle) 226 | * [License](~/projects/Dockerfile.vim/README.md#license) 227 | 228 | * [sitemap.js](https://github.com/ekalinin/sitemap.js/blob/master/README.md#sitemapjs) 229 | * [Installation](https://github.com/ekalinin/sitemap.js/blob/master/README.md#installation) 230 | * [Usage](https://github.com/ekalinin/sitemap.js/blob/master/README.md#usage) 231 | * [License](https://github.com/ekalinin/sitemap.js/blob/master/README.md#license) 232 | 233 | 234 | ``` 235 | 236 | Auto insert and update TOC 237 | -------------------------- 238 | 239 | Just put into a file these two lines: 240 | 241 | ``` 242 | 243 | 244 | ``` 245 | 246 | And run: 247 | 248 | ```bash 249 | $ ./gh-md-toc --insert README.test.md 250 | 251 | Table of Contents 252 | ================= 253 | 254 | * [gh-md-toc](#gh-md-toc) 255 | * [Installation](#installation) 256 | * [Usage](#usage) 257 | * [STDIN](#stdin) 258 | * [Local files](#local-files) 259 | * [Remote files](#remote-files) 260 | * [Multiple files](#multiple-files) 261 | * [Combo](#combo) 262 | * [Tests](#tests) 263 | * [Dependency](#dependency) 264 | 265 | !! TOC was added into: 'README.test.md' 266 | !! Origin version of the file: 'README.test.md.orig.2018-02-04_192655' 267 | !! TOC added into a separate file: 'README.test.md.toc.2018-02-04_192655' 268 | 269 | 270 | 271 | ``` 272 | 273 | Now check the same file: 274 | 275 | ```bash 276 | ➜ grep -A15 "<\!\-\-ts" README.test.md 277 | 278 | * [gh-md-toc](#gh-md-toc) 279 | * [Table of contents](#table-of-contents) 280 | * [Installation](#installation) 281 | * [Usage](#usage) 282 | * [STDIN](#stdin) 283 | * [Local files](#local-files) 284 | * [Remote files](#remote-files) 285 | * [Multiple files](#multiple-files) 286 | * [Combo](#combo) 287 | * [Auto insert and update TOC](#auto-insert-and-update-toc) 288 | * [Tests](#tests) 289 | * [Dependency](#dependency) 290 | 291 | 292 | 293 | 294 | ``` 295 | 296 | Next time when your file will be changed just repeat the command (`./gh-md-toc 297 | --insert ...`) and TOC will be refreshed again. 298 | 299 | GitHub token 300 | ------------ 301 | 302 | All your tokens are [here](https://github.com/settings/tokens). 303 | 304 | You will need them if you get an error like this: 305 | 306 | ``` 307 | Parsing local markdown file requires access to github API 308 | Error: You exceeded the hourly limit. See: https://developer.github.com/v3/#rate-limiting 309 | or place github auth token here: ./token.txt 310 | ``` 311 | 312 | A token can be used as an env variable: 313 | 314 | ```bash 315 | ➥ GH_TOC_TOKEN=2a2dab...563 ./gh-md-toc README.md 316 | 317 | Table of Contents 318 | ================= 319 | 320 | * [github\-markdown\-toc](#github-markdown-toc) 321 | * [Table of Contents](#table-of-contents) 322 | * [Installation](#installation) 323 | * [Tests](#tests) 324 | * [Usage](#usage) 325 | * [LICENSE](#license) 326 | ``` 327 | 328 | Or from a file: 329 | 330 | ```bash 331 | ➥ echo "2a2dab...563" > ./token.txt 332 | ➥ ./gh-md-toc README.md 333 | 334 | Table of Contents 335 | ================= 336 | 337 | * [github\-markdown\-toc](#github-markdown-toc) 338 | * [Table of Contents](#table-of-contents) 339 | * [Installation](#installation) 340 | * [Tests](#tests) 341 | * [Usage](#usage) 342 | * [LICENSE](#license) 343 | ``` 344 | 345 | TOC generation with Github Actions 346 | ---------------------------------- 347 | 348 | Config: 349 | 350 | ```yaml 351 | on: 352 | push: 353 | branches: [main] 354 | paths: ['foo.md'] 355 | 356 | jobs: 357 | build: 358 | runs-on: ubuntu-latest 359 | timeout-minutes: 5 360 | steps: 361 | - uses: actions/checkout@v2 362 | - run: | 363 | curl https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc -o gh-md-toc 364 | chmod a+x gh-md-toc 365 | ./gh-md-toc --insert --no-backup --hide-footer foo.md 366 | rm gh-md-toc 367 | - uses: stefanzweifel/git-auto-commit-action@v4 368 | with: 369 | commit_message: Auto update markdown TOC 370 | ``` 371 | 372 | Tests 373 | ===== 374 | 375 | Done with [bats](https://github.com/bats-core/bats-core). 376 | Useful articles: 377 | 378 | * https://www.engineyard.com/blog/how-to-use-bats-to-test-your-command-line-tools/ 379 | * http://blog.spike.cx/post/60548255435/testing-bash-scripts-with-bats 380 | 381 | 382 | How to run tests: 383 | 384 | ```bash 385 | ➥ make test 386 | 387 | ✓ TOC for local README.md 388 | ✓ TOC for remote README.md 389 | ✓ TOC for mixed README.md (remote/local) 390 | ✓ TOC for markdown from stdin 391 | ✓ --help 392 | ✓ --version 393 | 394 | 6 tests, 0 failures 395 | ``` 396 | 397 | Dependency 398 | ========== 399 | 400 | * curl or wget 401 | * awk (mawk is not tested) 402 | * grep 403 | * sed 404 | * bats (for unit tests) 405 | 406 | Tested on Ubuntu 14.04/14.10 in bash/zsh. 407 | 408 | Docker 409 | ====== 410 | 411 | Local 412 | ----- 413 | 414 | * Build 415 | 416 | ```shell 417 | $ docker build -t markdown-toc-generator . 418 | ``` 419 | 420 | * Run on an URL 421 | 422 | ```shell 423 | $ docker run -it markdown-toc-generator https://github.com/ekalinin/envirius/blob/master/README.md 424 | ``` 425 | 426 | * Run on a local file (need to share volume with docker) 427 | 428 | ```shell 429 | $ docker run -it -v /data/ekalinin/envirius:/data markdown-toc-generator /data/README.md 430 | ``` 431 | 432 | Public 433 | ------- 434 | 435 | ```shell 436 | $ docker pull evkalinin/gh-md-toc:0.7.0 437 | 438 | $ docker images | grep toc 439 | evkalinin/gh-md-toc 0.7.0 0b8db6aed298 11 minutes ago 147MB 440 | 441 | $ docker run -it evkalinin/gh-md-toc:0.7.0 \ 442 | https://github.com/ekalinin/envirius/blob/master/README.md 443 | ``` 444 | -------------------------------------------------------------------------------- /gh-md-toc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # Steps: 5 | # 6 | # 1. Download corresponding html file for some README.md: 7 | # curl -s $1 8 | # 9 | # 2. Discard rows where no substring 'user-content-' (github's markup): 10 | # awk '/user-content-/ { ... 11 | # 12 | # 3.1 Get last number in each row like ' ... sitemap.js.*<\/h/)+2, RLENGTH-5) 21 | # 22 | # 5. Find anchor and insert it inside "(...)": 23 | # substr($0, match($0, "href=\"[^\"]+?\" ")+6, RLENGTH-8) 24 | # 25 | 26 | gh_toc_version="0.10.0" 27 | 28 | gh_user_agent="gh-md-toc v$gh_toc_version" 29 | 30 | # 31 | # Download rendered into html README.md by its url. 32 | # 33 | # 34 | gh_toc_load() { 35 | local gh_url=$1 36 | 37 | if type curl &>/dev/null; then 38 | curl --user-agent "$gh_user_agent" -s "$gh_url" 39 | elif type wget &>/dev/null; then 40 | wget --user-agent="$gh_user_agent" -qO- "$gh_url" 41 | else 42 | echo "Please, install 'curl' or 'wget' and try again." 43 | exit 1 44 | fi 45 | } 46 | 47 | # 48 | # Converts local md file into html by GitHub 49 | # 50 | # -> curl -X POST --data '{"text": "Hello world github/linguist#1 **cool**, and #1!"}' https://api.github.com/markdown 51 | #

Hello world github/linguist#1 cool, and #1!

'" 52 | gh_toc_md2html() { 53 | local gh_file_md=$1 54 | local skip_header=$2 55 | 56 | URL=https://api.github.com/markdown/raw 57 | 58 | if [ -n "$GH_TOC_TOKEN" ]; then 59 | TOKEN=$GH_TOC_TOKEN 60 | else 61 | TOKEN_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/token.txt" 62 | if [ -f "$TOKEN_FILE" ]; then 63 | TOKEN="$(cat "$TOKEN_FILE")" 64 | fi 65 | fi 66 | if [ -n "${TOKEN}" ]; then 67 | AUTHORIZATION="Authorization: token ${TOKEN}" 68 | fi 69 | 70 | local gh_tmp_file_md=$gh_file_md 71 | if [ "$skip_header" = "yes" ]; then 72 | if grep -Fxq "" "$gh_src"; then 73 | # cut everything before the toc 74 | gh_tmp_file_md=$gh_file_md~~ 75 | sed '1,//d' "$gh_file_md" > "$gh_tmp_file_md" 76 | fi 77 | fi 78 | 79 | # echo $URL 1>&2 80 | OUTPUT=$(curl -s \ 81 | --user-agent "$gh_user_agent" \ 82 | --data-binary @"$gh_tmp_file_md" \ 83 | -H "Content-Type:text/plain" \ 84 | -H "$AUTHORIZATION" \ 85 | "$URL") 86 | 87 | rm -f "${gh_file_md}~~" 88 | 89 | if [ "$?" != "0" ]; then 90 | echo "XXNetworkErrorXX" 91 | fi 92 | if [ "$(echo "${OUTPUT}" | awk '/API rate limit exceeded/')" != "" ]; then 93 | echo "XXRateLimitXX" 94 | else 95 | echo "${OUTPUT}" 96 | fi 97 | } 98 | 99 | 100 | # 101 | # Is passed string url 102 | # 103 | gh_is_url() { 104 | case $1 in 105 | https* | http*) 106 | echo "yes";; 107 | *) 108 | echo "no";; 109 | esac 110 | } 111 | 112 | # 113 | # TOC generator 114 | # 115 | gh_toc(){ 116 | local gh_src=$1 117 | local gh_src_copy=$1 118 | local gh_ttl_docs=$2 119 | local need_replace=$3 120 | local no_backup=$4 121 | local no_footer=$5 122 | local indent=$6 123 | local skip_header=$7 124 | 125 | if [ "$gh_src" = "" ]; then 126 | echo "Please, enter URL or local path for a README.md" 127 | exit 1 128 | fi 129 | 130 | 131 | # Show "TOC" string only if working with one document 132 | if [ "$gh_ttl_docs" = "1" ]; then 133 | 134 | echo "Table of Contents" 135 | echo "=================" 136 | echo "" 137 | gh_src_copy="" 138 | 139 | fi 140 | 141 | if [ "$(gh_is_url "$gh_src")" == "yes" ]; then 142 | gh_toc_load "$gh_src" | gh_toc_grab "$gh_src_copy" "$indent" 143 | if [ "${PIPESTATUS[0]}" != "0" ]; then 144 | echo "Could not load remote document." 145 | echo "Please check your url or network connectivity" 146 | exit 1 147 | fi 148 | if [ "$need_replace" = "yes" ]; then 149 | echo 150 | echo "!! '$gh_src' is not a local file" 151 | echo "!! Can't insert the TOC into it." 152 | echo 153 | fi 154 | else 155 | local rawhtml 156 | rawhtml=$(gh_toc_md2html "$gh_src" "$skip_header") 157 | if [ "$rawhtml" == "XXNetworkErrorXX" ]; then 158 | echo "Parsing local markdown file requires access to github API" 159 | echo "Please make sure curl is installed and check your network connectivity" 160 | exit 1 161 | fi 162 | if [ "$rawhtml" == "XXRateLimitXX" ]; then 163 | echo "Parsing local markdown file requires access to github API" 164 | echo "Error: You exceeded the hourly limit. See: https://developer.github.com/v3/#rate-limiting" 165 | TOKEN_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/token.txt" 166 | echo "or place GitHub auth token here: ${TOKEN_FILE}" 167 | exit 1 168 | fi 169 | local toc 170 | toc=`echo "$rawhtml" | gh_toc_grab "$gh_src_copy" "$indent"` 171 | echo "$toc" 172 | if [ "$need_replace" = "yes" ]; then 173 | if grep -Fxq "" "$gh_src" && grep -Fxq "" "$gh_src"; then 174 | echo "Found markers" 175 | else 176 | echo "You don't have or in your file...exiting" 177 | exit 1 178 | fi 179 | local ts="<\!--ts-->" 180 | local te="<\!--te-->" 181 | local dt 182 | dt=$(date +'%F_%H%M%S') 183 | local ext=".orig.${dt}" 184 | local toc_path="${gh_src}.toc.${dt}" 185 | local toc_createdby="" 186 | local toc_footer 187 | toc_footer="" 188 | # http://fahdshariff.blogspot.ru/2012/12/sed-mutli-line-replacement-between-two.html 189 | # clear old TOC 190 | sed -i"${ext}" "/${ts}/,/${te}/{//!d;}" "$gh_src" 191 | # create toc file 192 | echo "${toc}" > "${toc_path}" 193 | if [ "${no_footer}" != "yes" ]; then 194 | echo -e "\n${toc_createdby}\n${toc_footer}\n" >> "$toc_path" 195 | fi 196 | 197 | # insert toc file 198 | if ! sed --version > /dev/null 2>&1; then 199 | sed -i "" "/${ts}/r ${toc_path}" "$gh_src" 200 | else 201 | sed -i "/${ts}/r ${toc_path}" "$gh_src" 202 | fi 203 | echo 204 | if [ "${no_backup}" = "yes" ]; then 205 | rm "$toc_path" "$gh_src$ext" 206 | fi 207 | echo "!! TOC was added into: '$gh_src'" 208 | if [ -z "${no_backup}" ]; then 209 | echo "!! Origin version of the file: '${gh_src}${ext}'" 210 | echo "!! TOC added into a separate file: '${toc_path}'" 211 | fi 212 | echo 213 | fi 214 | fi 215 | } 216 | 217 | # 218 | # Grabber of the TOC from rendered html 219 | # 220 | # $1 - a source url of document. 221 | # It's need if TOC is generated for multiple documents. 222 | # $2 - number of spaces used to indent. 223 | # 224 | gh_toc_grab() { 225 | 226 | href_regex="/href=\"[^\"]+?\"/" 227 | common_awk_script=' 228 | modified_href = "" 229 | split(href, chars, "") 230 | for (i=1;i <= length(href); i++) { 231 | c = chars[i] 232 | res = "" 233 | if (c == "+") { 234 | res = " " 235 | } else { 236 | if (c == "%") { 237 | res = "\\x" 238 | } else { 239 | res = c "" 240 | } 241 | } 242 | modified_href = modified_href res 243 | } 244 | print sprintf("%*s", (level-1)*'"$2"', "") "* [" text "](" gh_url modified_href ")" 245 | ' 246 | if [ "`uname -s`" == "OS/390" ]; then 247 | grepcmd="pcregrep -o" 248 | echoargs="" 249 | awkscript='{ 250 | level = substr($0, 3, 1) 251 | text = substr($0, match($0, /<\/span><\/a>[^<]*<\/h/)+11, RLENGTH-14) 252 | href = substr($0, match($0, '$href_regex')+6, RLENGTH-7) 253 | '"$common_awk_script"' 254 | }' 255 | else 256 | grepcmd="grep -Eo" 257 | echoargs="-e" 258 | awkscript='{ 259 | level = substr($0, 3, 1) 260 | text = substr($0, match($0, /">.*<\/h/)+2, RLENGTH-5) 261 | href = substr($0, match($0, '$href_regex')+6, RLENGTH-7) 262 | '"$common_awk_script"' 263 | }' 264 | fi 265 | 266 | # if closed is on the new line, then move it on the prev line 267 | # for example: 268 | # was: The command foo1 269 | # 270 | # became: The command foo1 271 | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n<\/h/<\/h/g' | 272 | 273 | # Sometimes a line can start with . Fix that. 274 | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n//g' | sed 's/<\/code>//g' | 281 | 282 | # remove g-emoji 283 | sed 's/]*[^<]*<\/g-emoji> //g' | 284 | 285 | # now all rows are like: 286 | #

title

.. 287 | # format result line 288 | # * $0 - whole string 289 | # * last element of each row: "/dev/null; then 313 | $tool --version | head -n 1 314 | else 315 | echo "not installed" 316 | fi 317 | done 318 | } 319 | 320 | show_help() { 321 | local app_name 322 | app_name=$(basename "$0") 323 | echo "GitHub TOC generator ($app_name): $gh_toc_version" 324 | echo "" 325 | echo "Usage:" 326 | echo " $app_name [options] src [src] Create TOC for a README file (url or local path)" 327 | echo " $app_name - Create TOC for markdown from STDIN" 328 | echo " $app_name --help Show help" 329 | echo " $app_name --version Show version" 330 | echo "" 331 | echo "Options:" 332 | echo " --indent Set indent size. Default: 3." 333 | echo " --insert Insert new TOC into original file. For local files only. Default: false." 334 | echo " See https://github.com/ekalinin/github-markdown-toc/issues/41 for details." 335 | echo " --no-backup Remove backup file. Set --insert as well. Default: false." 336 | echo " --hide-footer Do not write date & author of the last TOC update. Set --insert as well. Default: false." 337 | echo " --skip-header Hide entry of the topmost headlines. Default: false." 338 | echo " See https://github.com/ekalinin/github-markdown-toc/issues/125 for details." 339 | echo "" 340 | } 341 | 342 | # 343 | # Options handlers 344 | # 345 | gh_toc_app() { 346 | local need_replace="no" 347 | local indent=3 348 | 349 | if [ "$1" = '--help' ] || [ $# -eq 0 ] ; then 350 | show_help 351 | return 352 | fi 353 | 354 | if [ "$1" = '--version' ]; then 355 | show_version 356 | return 357 | fi 358 | 359 | if [ "$1" = '--indent' ]; then 360 | indent="$2" 361 | shift 2 362 | fi 363 | 364 | if [ "$1" = "-" ]; then 365 | if [ -z "$TMPDIR" ]; then 366 | TMPDIR="/tmp" 367 | elif [ -n "$TMPDIR" ] && [ ! -d "$TMPDIR" ]; then 368 | mkdir -p "$TMPDIR" 369 | fi 370 | local gh_tmp_md 371 | if [ "`uname -s`" == "OS/390" ]; then 372 | local timestamp 373 | timestamp=$(date +%m%d%Y%H%M%S) 374 | gh_tmp_md="$TMPDIR/tmp.$timestamp" 375 | else 376 | gh_tmp_md=$(mktemp "$TMPDIR/tmp.XXXXXX") 377 | fi 378 | while read -r input; do 379 | echo "$input" >> "$gh_tmp_md" 380 | done 381 | gh_toc_md2html "$gh_tmp_md" | gh_toc_grab "" "$indent" 382 | return 383 | fi 384 | 385 | if [ "$1" = '--insert' ]; then 386 | need_replace="yes" 387 | shift 388 | fi 389 | 390 | if [ "$1" = '--no-backup' ]; then 391 | need_replace="yes" 392 | no_backup="yes" 393 | shift 394 | fi 395 | 396 | if [ "$1" = '--hide-footer' ]; then 397 | need_replace="yes" 398 | no_footer="yes" 399 | shift 400 | fi 401 | 402 | if [ "$1" = '--skip-header' ]; then 403 | skip_header="yes" 404 | shift 405 | fi 406 | 407 | 408 | for md in "$@" 409 | do 410 | echo "" 411 | gh_toc "$md" "$#" "$need_replace" "$no_backup" "$no_footer" "$indent" "$skip_header" 412 | done 413 | 414 | echo "" 415 | echo "" 416 | } 417 | 418 | # 419 | # Entry point 420 | # 421 | gh_toc_app "$@" 422 | -------------------------------------------------------------------------------- /tests/test directory/test_backquote.md: -------------------------------------------------------------------------------- 1 | # The command `foo1` 2 | 3 | Blabla... 4 | 5 | ## The command `foo2` is better 6 | 7 | Blabla... 8 | 9 | # The command `bar1` 10 | 11 | Blabla... 12 | 13 | ## The command `bar2` is better 14 | 15 | Blabla... 16 | 17 | ### The command `bar3` is the best 18 | 19 | Blabla... 20 | -------------------------------------------------------------------------------- /tests/test directory/test_filepathwithspace.md: -------------------------------------------------------------------------------- 1 | # Title 2 | 3 | 4 | * [Title](#title) 5 | * [This is test for file path with space](#this-is-test-for-file-path-with-space) 6 | 7 | 8 | Blabla... 9 | 10 | ## This is test for file path with space 11 | 12 | Blabla... 13 | -------------------------------------------------------------------------------- /tests/test directory/test_nonenglishchars.md: -------------------------------------------------------------------------------- 1 | [ Languages: [English](README.md), [Español](README-es.md), [Português](README-pt.md), [中文](README-zh.md) ] 2 | 3 | 4 | # 命令行的艺术 5 | 6 | [![Join the chat at https://gitter.im/jlevy/the-art-of-command-line](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jlevy/the-art-of-command-line?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 7 | - [必读](#必读) 8 | - [基础](#基础) 9 | - [日常使用](#日常使用) 10 | - [文件及数据处理](#文件及数据处理) 11 | - [系统调试](#系统调试) 12 | - [一行代码](#一行代码) 13 | - [冷门但有用](#冷门但有用) 14 | - [更多资源](#更多资源) 15 | - [免责声明](#免责声明) 16 | - [授权条款](#授权条款) 17 | 18 | 19 | ![curl -s 'https://raw.githubusercontent.com/jlevy/the-art-of-command-line/master/README.md' | egrep -o '`\w+`' | tr -d '`' | cowsay -W50](cowsay.png) 20 | 21 | 熟练使用命令行是一种常常被忽视或被认为难以掌握的技能,但实际上,它可以提高你作为工程师的灵活性以及生产力。本文是一份我在 Linux 上工作时发现的一些关于命令行的使用的小技巧的摘要。这些小技巧有基础的、相当复杂的甚至晦涩难懂的。这篇文章并不长,但当你能够熟练掌握这里列出的所有技巧时,你就学会了很多关于命令行的东西了。 22 | 23 | 这里的大部分内容 24 | [首次](http://www.quora.com/What-are-some-lesser-known-but-useful-Unix-commands) 25 | [出现](http://www.quora.com/What-are-the-most-useful-Swiss-army-knife-one-liners-on-Unix) 26 | 于 [Quora](http://www.quora.com/What-are-some-time-saving-tips-that-every-Linux-user-should-know),但考虑到这里的人们都具有学习的天赋且乐于接受别人的建议,使用 Github 来做这件事是更佳的选择。如果你在本文中发现了错误或者存在可以改善的地方,请果断提交 Issue 或 Pull Request!(当然在提交前请看一下必读节和已有的 PR/issue)。 27 | 28 | 29 | ## 必读 30 | 31 | 涵盖范围: 32 | 33 | - 这篇文章对刚接触命令行的新手以及具有命令行使用经验的人都有用处。本文致力于做到覆盖面广(尽量包括一切重要的内容),具体(给出最常见的具体的例子)以及简洁(避免一些不必要的东西以及一些偏题的可以在其他地方翻阅到文献的东西)。 每个小技巧在某个特定情境下都是基本的或能够显著地节约时间。 34 | - 本文为 Linux 所写,但很多内容(并非所有的)同样适用于 MacOS 甚至 Cygwin。 35 | - 本文关注于交互式 Bash,尽管很多技巧适用于其他 shell 或 Bash 脚本。 36 | - 本文包括了“标准的”Unix 命令和需要安装特定包的命令,只要它们足够重要。 37 | 38 | 注意事项: 39 | 40 | - 为了能在一页内展示尽量多的东西,一些具体的信息会被间接的包含在引用页里。聪明机智的你如果掌握了使用 Google 搜索引擎的基本思路与命令,那么你将可以查阅到更多的详细信息。使用 `apt-get`/`yum`/`dnf`/`pacman`/`pip`/`brew`(以及其它合适的包管理器)来安装新程序。 41 | - 使用 [Explainshell](http://explainshell.com/) 去获取相关命令、参数、管道等内容的解释。 42 | 43 | 44 | ## 基础 45 | 46 | - 学习 Bash 的基础知识。具体来说,输入 `man bash` 并至少全文浏览一遍; 它很简单并且不长。其他的 shell 可能很好用,但 Bash 功能强大且几乎所有情况下都是可用的 ( *只*学习 zsh,fish 或其他的 shell 的话,在你自己的电脑上会显得很方便,但在很多情况下会限制你,比如当你需要在服务器上工作时)。 47 | 48 | - 学习并掌握至少一个基于文本的编辑器。通常 Vim (`vi`) 会是你最好的选择。 49 | 50 | - 学会如何使用 `man` 命令去阅读文档。学会使用 `apropos` 去查找文档。了解有些命令并不对应可执行文件,而是Bash内置的,可以使用 `help` 和 `help -d` 命令获取帮助信息。 51 | 52 | - 学会使用 `>` 和 `<` 来重定向输出和输入,学会使用 `|` 来重定向管道。了解标准输出 stdout 和标准错误 stderr。 53 | 54 | - 学会使用通配符 `*` (或许再算上 `?` 和 `{`...`}`) 和引用以及引用中 `'` 和 `"` 的区别。 55 | 56 | - 熟悉 Bash 任务管理工具:`&`,**ctrl-z**,**ctrl-c**,`jobs`,`fg`,`bg`,`kill` 等。 57 | 58 | - 了解 `ssh`,以及基本的无密码认证,`ssh-agent`,`ssh-add` 等。 59 | 60 | - 学会基本的文件管理:`ls` 和 `ls -l` (了解 `ls -l` 中每一列代表的意义),`less`,`head`,`tail` 和 `tail -f` (甚至 `less +F`),`ln` 和 `ln -s` (了解硬链接与软链接的区别),`chown`,`chmod`,`du` (硬盘使用情况概述:`du -hk *`)。 关于文件系统的管理,学习 `df`,`mount`,`fdisk`,`mkfs`,`lsblk`。 61 | 62 | - 学习基本的网络管理:`ip` 或 `ifconfig`,`dig`。 63 | 64 | - 熟悉正则表达式,以及 `grep`/`egrep` 里不同参数的作用,例如 `-i`,`-o`,`-A`,和 `-B`。 65 | 66 | - 学会使用 `apt-get`,`yum`,`dnf` 或 `pacman` (取决于你使用的 Linux 发行版)来查找或安装包。确保你的环境中有 `pip` 来安装基于 Python 的命令行工具 (部分程序使用 `pip` 来安装会很简单)。 67 | 68 | 69 | ## 日常使用 70 | 71 | - 在 Bash 中,可以使用 **Tab** 自动补全参数,使用 **ctrl-r** 搜索命令行历史。 72 | 73 | - 在 Bash 中,使用 **ctrl-w** 删除你键入的最后一个单词,使用 **ctrl-u** 删除整行,使用 **alt-b** 和 **alt-f** 按单词移动,使用 **ctrl-k** 从光标处删除到行尾,使用 **ctrl-l** 清屏。键入 `man readline` 查看 Bash 中的默认快捷键,内容很多。例如 **alt-.** 循环地移向前一个参数,以及 **alt-*** 展开通配符。 74 | 75 | - 你喜欢的话,可以键入 `set -o vi` 来使用 vi 风格的快捷键。 76 | 77 | - 键入 `history` 查看命令行历史记录。其中有许多缩写,例如 `!$`(最后键入的参数)和 `!!`(最后键入的命令),尽管通常被 **ctrl-r** 和 **alt-.** 取代。 78 | 79 | - 回到上一个工作路径:`cd -` 80 | 81 | - 如果你输入命令的时候改变了主意,按下 **alt-#** 在行首添加 `#`(将你输入的命令视为注释),并回车。这样做的话,之后你可以很方便的利用命令行历史回到你刚才输入到一半的命令。 82 | 83 | - 使用 `xargs` ( 或 `parallel`)。他们非常给力。注意到你可以控制每行参数个数(`-L`)和最大并行数(`-P`)。如果你不确定它们是否会按你想的那样工作,先使用 `xargs echo` 查看一下。此外,使用 `-I{}` 会很方便。例如: 84 | ```bash 85 | find . -name '*.py' | xargs grep some_function 86 | cat hosts | xargs -I{} ssh root@{} hostname 87 | ``` 88 | 89 | - `pstree -p` 有助于展示进程树。 90 | 91 | - 使用 `pgrep` 和 `pkill` 根据名字查找进程或发送信号。 92 | 93 | - 了解你可以发往进程的信号的种类。比如,使用 `kill -STOP [pid]` 停止一个进程。使用 `man 7 signal` 查看详细列表。 94 | 95 | - 使用 `nohup` 或 `disown` 使一个后台进程持续运行。 96 | 97 | - 使用 `netstat -lntp` 或 `ss -plat` 检查哪些进程在监听端口(默认是检查 TCP 端口; 使用参数 `-u` 检查 UDP 端口)。 98 | 99 | - 有关打开套接字和文件,请参阅 `lsof`。 100 | 101 | - 在 Bash 脚本中,使用 `set -x` 去调试输出,尽可能的使用严格模式,使用 `set -e` 令脚本在发生错误时退出而不是继续运行,使用 `set -o pipefail` 严谨地对待错误(尽管问题可能很微妙)。当牵扯到很多脚本时,使用 `trap`。 102 | 103 | - 在 Bash 脚本中,子 shell(使用括号`(...)`)是一种便捷的方式去组织参数。一个常见的例子是临时地移动工作路径,代码如下: 104 | ```bash 105 | # do something in current dir 106 | (cd /some/other/dir && other-command) 107 | # continue in original dir 108 | ``` 109 | 110 | - 在 Bash 中,注意到其中有许多形式的扩展。检查变量是否存在:`${name:?error message}`。例如,当 Bash 脚本需要一个参数时,可以使用这样的代码 `input_file=${1:?usage: $0 input_file}`。数学表达式:`i=$(( (i + 1) % 5 ))`。序列:`{1..10}`。 截断字符串:`${var%suffix}` 和 `${var#prefix}`。例如,假设 `var=foo.pdf`,那么 `echo ${var%.pdf}.txt` 将输出 `foo.txt`。 111 | 112 | - 通过使用 `<(some command)` 可以将输出视为文件。例如,对比本地文件 `/etc/hosts` 和一个远程文件: 113 | ```sh 114 | diff /etc/hosts <(ssh somehost cat /etc/hosts) 115 | ``` 116 | 117 | - 了解 Bash 中的“here documents”,例如 `cat <logfile 2>&1`。通常,为了保证命令不会在标准输入里残留一个打开了的文件句柄导致你当前所在的终端无法操作,添加 ` foo: 191 | rename 's/\.bak$//' *.bak 192 | # Full rename of filenames,directories,and contents foo -> bar: 193 | repren --full --preserve-case --from foo --to bar . 194 | ``` 195 | 196 | - 使用 `shuf` 从一个文件中随机选取行。 197 | 198 | - 了解 `sort` 的参数。明白键的工作原理(`-t` 和 `-k`)。例如,注意到你需要 `-k1,1` 来仅按第一个域来排序,而 `-k1` 意味着按整行排序。稳定排序(`sort -s`)在某些情况下很有用。例如,以第二个域为主关键字,第一个域为次关键字进行排序,你可以使用 `sort -k1,1 | sort -s -k2,2`。处理可读性数字(例如 `du -h` 的输出)的时候使用 `sort -h` 。 199 | 200 | - 如果你想在 Bash 命令行中写 tab 制表符,按下 **ctrl-v** **[Tab]** 或键入 `$'\t'` (后者可能更好,因为你可以复制粘贴它)。 201 | 202 | - 标准的源代码对比及合并工具是 `diff` 和 `patch`。使用 `diffstat` 查看变更总览数据。注意到 `diff -r` 对整个文件夹有效。使用 `diff -r tree1 tree2 | diffstat` 查看变更总览数据。 203 | 204 | - 对于二进制文件,使用 `hd` 使其以十六进制显示以及使用 `bvi` 来编辑二进制。 205 | 206 | - 同样对于二进制文件,使用 `strings`(包括 `grep` 等等)允许你查找一些文本。 207 | 208 | - 二进制文件对比(Delta 压缩),使用 `xdelta3`。 209 | 210 | - 使用 `iconv` 更改文本编码。而更高级的用法,可以使用 `uconv`,它支持一些高级的 Unicode 功能。例如,这条命令将所有元音字母转为小写并移除了: 211 | ```sh 212 | uconv -f utf-8 -t utf-8 -x '::Any-Lower; ::Any-NFD; [:Nonspacing Mark:] >; ::Any-NFC; ' < input.txt > output.txt 213 | ``` 214 | 215 | - 拆分文件,查看 `split`(按大小拆分)和 `csplit`(按模式拆分)。 216 | 217 | - 使用 `zless`,`zmore`,`zcat` 和 `zgrep`对压缩过的文件进行操作。 218 | 219 | 220 | ## 系统调试 221 | 222 | - `curl` 和 `curl -I` 可以便捷地被应用于 web 调试中,它们的好兄弟 `wget` 也可以,或者是更潮的 [`httpie`](https://github.com/jakubroztocil/httpie)。 223 | 224 | - 使用 `iostat`、`netstat`、`top` (`htop` 更佳)和 `dstat` 去获取硬盘、cpu 和网络的状态。熟练掌握这些工具可以使你快速的对系统的当前状态有一个大概的认识。 225 | 226 | - 若要对系统有一个深度的总体认识,使用 [`glances`](https://github.com/nicolargo/glances)。它在一个终端窗口中向你提供一些系统级的数据。这对于快速的检查各个子系统非常有帮助。 227 | 228 | - 若要了解内存状态,运行并理解 `free` 和 `vmstat` 的输出。尤其注意“cached”的值,它指的是 Linux 内核用来作为文件缓存的内存大小,因此它与空闲内存无关。 229 | 230 | - Java 系统调试则是一件截然不同的事,一个可以用于 Oracle 的 JVM 或其他 JVM 上的调试的小技巧是你可以运行 `kill -3 ` 同时一个完整的栈轨迹和堆概述(包括 GC 的细节)会被保存到标准输出/日志文件。 231 | 232 | - 使用 `mtr` 去跟踪路由,用于确定网络问题。 233 | 234 | - 用 `ncdu` 来查看磁盘使用情况,它比常用的命令,如 `du -sh *`,更节省时间。 235 | 236 | - 查找正在使用带宽的套接字连接或进程,使用 `iftop` 或 `nethogs`。 237 | 238 | - `ab` 工具(捆绑于 Apache)可以简单粗暴地检查 web 服务器的性能。对于更复杂的负载测试,使用 `siege`。 239 | 240 | - `wireshark`,`tshark` 和 `ngrep` 可用于复杂的网络调试。 241 | 242 | - 了解 `strace` 和 `ltrace`。这俩工具在你的程序运行失败、挂起甚至崩溃,而你却不知道为什么或你想对性能有个总体的认识的时候是非常有用的。注意 profile 参数(`-c`)和附加到一个运行的进程参数 (`-p`)。 243 | 244 | - 了解使用 `ldd` 来检查共享库。 245 | 246 | - 了解如何运用 `gdb` 连接到一个运行着的进程并获取它的堆栈轨迹。 247 | 248 | - 学会使用 `/proc`。它在调试正在出现的问题的时候有时会效果惊人。比如:`/proc/cpuinfo`,`/proc/xxx/cwd`,`/proc/xxx/exe`,`/proc/xxx/fd/`,`/proc/xxx/smaps`。 249 | 250 | - 当调试一些之前出现的问题的时候,`sar` 非常有用。它展示了 cpu、内存以及网络等的历史数据。 251 | 252 | - 关于更深层次的系统分析以及性能分析,看看 `stap`([SystemTap](https://sourceware.org/systemtap/wiki)),[`perf`](http://en.wikipedia.org/wiki/Perf_(Linux)),以及[`sysdig`](https://github.com/draios/sysdig)。 253 | 254 | - 查看你当前使用的 Linux 发行版(大部分发行版有效):`lsb_release -a` 255 | 256 | - 无论什么东西工作得很欢乐时试试 `dmesg` (可能是硬件或驱动问题)。 257 | 258 | 259 | ## 一行代码 260 | 261 | 一些命令组合的例子: 262 | 263 | - 当你需要对文本文件做集合交、并、差运算时,结合使用 `sort`/`uniq` 很有帮助。假设 `a` 与 `b` 是两内容不同的文件。这种方式效率很高,并且在小文件和上G的文件上都能运用 (`sort` 不被内存大小约束,尽管在 `/tmp` 在一个小的根分区上时你可能需要 `-T` 参数),参阅前文中关于 `LC_ALL` 和 `sort` 的 `-u` 参数的部分。 264 | ```sh 265 | cat a b | sort | uniq > c # c is a union b 266 | cat a b | sort | uniq -d > c # c is a intersect b 267 | cat a b b | sort | uniq -u > c # c is set difference a - b 268 | ``` 269 | 270 | - 使用 `grep . *` 来阅读检查目录下所有文件的内容,例如检查一个充满配置文件的目录比如 `/sys`、`/proc`、`/etc`。 271 | 272 | - 计算文本文件第三列中所有数的和(可能比同等作用的 Python 代码快三倍且代码量少三倍): 273 | ```sh 274 | awk '{ x += $3 } END { print x }' myfile 275 | ``` 276 | 277 | - 如果你想在文件树上查看大小\日期,这可能看起来像递归版的 `ls -l` 但比 `ls -lR` 更易于理解: 278 | ```sh 279 | find . -type f -ls 280 | ``` 281 | 282 | - 尽可能的使用 `xargs` 或 `parallel`。注意到你可以控制每行参数个数(`-L`)和最大并行数(`-P`)。如果你不确定它们是否会按你想的那样工作,先使用 `xargs echo` 查看一下。此外,使用 `-I{}` 会很方便。例如: 283 | ```sh 284 | find . -name '*.py' | xargs grep some_function 285 | cat hosts | xargs -I{} ssh root@{} hostname 286 | ``` 287 | 288 | - 假设你有一个类似于 web 服务器日志文件的文本文件,并且一个确定的值只会出现在某些行上,假设一个 `acct_id` 参数在URI中。如果你想计算出每个 `acct_id` 值有多少次请求,使用如下代码: 289 | ```sh 290 | cat access.log | egrep -o 'acct_id=[0-9]+' | cut -d= -f2 | sort | uniq -c | sort -rn 291 | ``` 292 | 293 | - 运行这个函数从这篇文档中随机获取一条小技巧(解析 Markdown 文件并抽取项目): 294 | ```sh 295 | function taocl() { 296 | curl -s https://raw.githubusercontent.com/jlevy/the-art-of-command-line/master/README.md | 297 | pandoc -f markdown -t html | 298 | xmlstarlet fo --html --dropdtd | 299 | xmlstarlet sel -t -v "(html/body/ul/li[count(p)>0])[$RANDOM mod last()+1]" | 300 | xmlstarlet unesc | fmt -80 301 | } 302 | ``` 303 | 304 | 305 | ## 冷门但有用 306 | 307 | - `expr`:计算表达式或正则匹配 308 | 309 | - `m4`:简单地宏处理器 310 | 311 | - `yes`:多次打印字符串 312 | 313 | - `cal`:漂亮的日历 314 | 315 | - `env`:执行一个命令(脚本文件中很有用) 316 | 317 | - `printenv`:打印环境变量(调试时或在使用脚本文件时很有用) 318 | 319 | - `look`:查找以特定字符串开头的单词 320 | 321 | - `cut`、`paste` 和 `join`:数据修改 322 | 323 | - `fmt`:格式化文本段落 324 | 325 | - `pr`:将文本格式化成页/列形式 326 | 327 | - `fold`:包裹文本中的几行 328 | 329 | - `column`:将文本格式化成多列或表格 330 | 331 | - `expand` 和 `unexpand`:制表符与空格之间转换 332 | 333 | - `nl`:添加行号 334 | 335 | - `seq`:打印数字 336 | 337 | - `bc`:计算器 338 | 339 | - `factor`:分解因数 340 | 341 | - `gpg`:加密并签名文件 342 | 343 | - `toe`:terminfo entries 列表 344 | 345 | - `nc`:网络调试及数据传输 346 | 347 | - `socat`:套接字代理,与 `netcat` 类似 348 | 349 | - `slurm`:网络可视化 350 | 351 | - `dd`:文件或设备间传输数据 352 | 353 | - `file`:确定文件类型 354 | 355 | - `tree`:以树的形式显示路径和文件,类似于递归的 `ls` 356 | 357 | - `stat`:文件信息 358 | 359 | - `tac`:反向输出文件 360 | 361 | - `shuf`:文件中随机选取几行 362 | 363 | - `comm`:一行一行的比较排序过的文件 364 | 365 | - `pv`:监视通过管道的数据 366 | 367 | - `hd` 和 `bvi`:保存或编辑二进制文件 368 | 369 | - `strings`:从二进制文件中抽取文本 370 | 371 | - `tr`:转换字母 372 | 373 | - `iconv` 或 `uconv`:简易的文件编码 374 | 375 | - `split` 和 `csplit`:分割文件 376 | 377 | - `units`:将一种计量单位转换为另一种等效的计量单位(参阅 `/usr/share/units/definitions.units`) 378 | 379 | - `7z`:高比例的文件压缩 380 | 381 | - `ldd`:动态库信息 382 | 383 | - `nm`:提取 obj 文件中的符号 384 | 385 | - `ab`:性能分析 web 服务器 386 | 387 | - `strace`:系统调用调试 388 | 389 | - `mtr`:更好的网络调试跟踪工具 390 | 391 | - `cssh`:可视化的并发 shell 392 | 393 | - `rsync`:通过 ssh 同步文件和文件夹 394 | 395 | - `wireshark` 和 `tshark`:抓包和网络调试工具 396 | 397 | - `ngrep`:网络层的 grep 398 | 399 | - `host` 和 `dig`:DNS 查找 400 | 401 | - `lsof`:列出当前系统打开文件的工具以及查看端口信息 402 | 403 | - `dstat`:系统状态查看 404 | 405 | - [`glances`](https://github.com/nicolargo/glances):高层次的多子系统总览 406 | 407 | - `iostat`:CPU 和硬盘状态 408 | 409 | - `htop`:top 的加强版 410 | 411 | - `last`:登入记录 412 | 413 | - `w`:查看处于登录状态的用户 414 | 415 | - `id`:用户/组 ID 信息 416 | 417 | - `sar`:系统历史数据 418 | 419 | - `iftop` 或 `nethogs`:套接字及进程的网络利用 420 | 421 | - `ss`:套接字数据 422 | 423 | - `dmesg`:引导及系统错误信息 424 | 425 | - `hdparm`:SATA/ATA 磁盘更改及性能分析 426 | 427 | - `lsb_release`:Linux 发行版信息 428 | 429 | - `lsblk`:列出块设备信息:以树形展示你的磁盘以及磁盘分区信息 430 | 431 | - `lshw`,`lscpu`,`lspci`,`lsusb` 和 `dmidecode`:查看硬件信息,包括 CPU、BIOS、RAID、显卡、USB设备等 432 | 433 | - `fortune`,`ddate` 和 `sl`: 额,这主要取决于你是否认为蒸汽火车和莫名其妙的名人名言是否“有用” 434 | 435 | 436 | ## 更多资源 437 | 438 | - [awesome-shell](https://github.com/alebcay/awesome-shell): 一份精心组织的命令行工具及资源的列表。 439 | - [Strict mode](http://redsymbol.net/articles/unofficial-bash-strict-mode/): 为了编写更好的脚本文件。 440 | 441 | 442 | ## 免责声明 443 | 444 | 除去特别微小的任务,记录下这些代码以便他人查看。责任往往伴随着能力,*可以*做并不意味着应该做。 445 | 446 | 447 | ## 授权条款 448 | 449 | [![Creative Commons License](https://i.creativecommons.org/l/by-sa/4.0/88x31.png)](http://creativecommons.org/licenses/by-sa/4.0/) 450 | 451 | 本文使用授权协议 [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)。 -------------------------------------------------------------------------------- /tests/test directory/test_plussign.md: -------------------------------------------------------------------------------- 1 | # C vs C++ 2 | 3 | Blabla... 4 | 5 | -------------------------------------------------------------------------------- /tests/test directory/test_setextwithformatting.md: -------------------------------------------------------------------------------- 1 | Title one 2 | ========= 3 | 4 | 5 | * [Title one](#title-one) 6 | * [This is test for setext-style without formatting](#this-is-test-for-setext-style-without-formatting) 7 | * [Title two](#title-two) 8 | * [This is test for setext-style with formatting](#this-is-test-for-setext-style-with-formatting) 9 | * [Title three](#title-three) 10 | * [This is a regression test for atx-style](#this-is-a-regression-test-for-atx-style) 11 | * [Title four is a particularly long title because of wrapping](#title-four-is-a-particularly-long-title-because-of-wrapping) 12 | * [This is a test for long titles](#this-is-a-test-for-long-titles) 13 | 14 | 15 | Blabla... 16 | 17 | ## This is test for setext-style without formatting 18 | 19 | Blabla... 20 | 21 | *Title `two`* 22 | ============= 23 | 24 | Blabla... 25 | 26 | ## This is test for setext-style with formatting 27 | 28 | Blabla... 29 | 30 | # Title `three` 31 | 32 | Blabla... 33 | 34 | ## This is a regression test for atx-style 35 | 36 | Blabla... 37 | 38 | # Title four is a particularly long title because of wrapping 39 | 40 | Blabla... 41 | 42 | ## This is a test for long titles 43 | 44 | Blabla... -------------------------------------------------------------------------------- /tests/test_helper.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | assert_equal() { 4 | if [ "$1" != "$2" ]; then 5 | echo "actual: $1" 6 | echo "expected: $2" 7 | return 1 8 | fi 9 | } 10 | 11 | assert_range() { 12 | if [ $1 -lt $2 ]; then 13 | echo "expected: $1" 14 | echo "greater than: $2" 15 | return 1 16 | fi 17 | if [ $1 -gt $3 ]; then 18 | echo "expected: $1" 19 | echo "less than: $3" 20 | return 1 21 | fi 22 | } 23 | 24 | assert_output() { 25 | assert_equal "$1" "$output" 26 | } 27 | 28 | assert_success() { 29 | if [ "$status" -ne 0 ]; then 30 | echo "command failed with exit status $status" 31 | return 1 32 | elif [ "$#" -gt 0 ]; then 33 | assert_output "$1" 34 | fi 35 | } 36 | 37 | assert_fail() { 38 | if [ "$status" -eq 0 ]; then 39 | echo "command successed, but should fail" 40 | return 1 41 | elif [ "$#" -gt 0 ]; then 42 | assert_output "$1" 43 | fi 44 | } 45 | -------------------------------------------------------------------------------- /tests/tests.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | 6 | @test "TOC for local README.md" { 7 | run $BATS_TEST_DIRNAME/../gh-md-toc README.md 8 | assert_success 9 | 10 | assert_equal "${lines[0]}" "Table of Contents" 11 | assert_equal "${lines[1]}" "=================" 12 | assert_equal "${lines[2]}" "* [gh-md-toc](#gh-md-toc)" 13 | assert_equal "${lines[3]}" "* [Table of contents](#table-of-contents)" 14 | assert_equal "${lines[4]}" "* [Installation](#installation)" 15 | assert_equal "${lines[5]}" "* [Usage](#usage)" 16 | assert_equal "${lines[6]}" " * [STDIN](#stdin)" 17 | assert_equal "${lines[7]}" " * [Local files](#local-files)" 18 | assert_equal "${lines[8]}" " * [Remote files](#remote-files)" 19 | assert_equal "${lines[9]}" " * [Multiple files](#multiple-files)" 20 | assert_equal "${lines[10]}" " * [Combo](#combo)" 21 | assert_equal "${lines[11]}" " * [Auto insert and update TOC](#auto-insert-and-update-toc)" 22 | assert_equal "${lines[12]}" " * [GitHub token](#github-token)" 23 | assert_equal "${lines[13]}" " * [TOC generation with Github Actions](#toc-generation-with-github-actions)" 24 | assert_equal "${lines[14]}" "* [Tests](#tests)" 25 | assert_equal "${lines[15]}" "* [Dependency](#dependency)" 26 | assert_equal "${lines[16]}" "* [Docker](#docker)" 27 | assert_equal "${lines[17]}" " * [Local](#local)" 28 | assert_equal "${lines[18]}" " * [Public](#public)" 29 | assert_equal "${lines[19]}" "" 30 | 31 | } 32 | 33 | @test "TOC for local README.md with skip headers" { 34 | run $BATS_TEST_DIRNAME/../gh-md-toc --skip-header README.md 35 | assert_success 36 | 37 | assert_equal "${lines[0]}" "Table of Contents" 38 | assert_equal "${lines[1]}" "=================" 39 | assert_equal "${lines[2]}" "* [Installation](#installation)" 40 | assert_equal "${lines[3]}" "* [Usage](#usage)" 41 | assert_equal "${lines[4]}" " * [STDIN](#stdin)" 42 | assert_equal "${lines[5]}" " * [Local files](#local-files)" 43 | assert_equal "${lines[6]}" " * [Remote files](#remote-files)" 44 | assert_equal "${lines[7]}" " * [Multiple files](#multiple-files)" 45 | assert_equal "${lines[8]}" " * [Combo](#combo)" 46 | assert_equal "${lines[9]}" " * [Auto insert and update TOC](#auto-insert-and-update-toc)" 47 | assert_equal "${lines[10]}" " * [GitHub token](#github-token)" 48 | assert_equal "${lines[11]}" " * [TOC generation with Github Actions](#toc-generation-with-github-actions)" 49 | assert_equal "${lines[12]}" "* [Tests](#tests)" 50 | assert_equal "${lines[13]}" "* [Dependency](#dependency)" 51 | assert_equal "${lines[14]}" "* [Docker](#docker)" 52 | assert_equal "${lines[15]}" " * [Local](#local)" 53 | assert_equal "${lines[16]}" " * [Public](#public)" 54 | assert_equal "${lines[17]}" "" 55 | 56 | } 57 | # @test "TOC for remote README.md" { 58 | # run $BATS_TEST_DIRNAME/../gh-md-toc https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md 59 | # assert_success 60 | # 61 | # assert_equal "${lines[0]}" "Table of Contents" 62 | # assert_equal "${lines[1]}" "=================" 63 | # assert_equal "${lines[2]}" "* [sitemap.js](#sitemapjs)" 64 | # assert_equal "${lines[3]}" " * [Installation](#installation)" 65 | # assert_equal "${lines[4]}" " * [Usage](#usage)" 66 | # assert_equal "${lines[5]}" " * [License](#license)" 67 | # assert_equal "${lines[6]}" "" 68 | # } 69 | 70 | # @test "TOC for mixed README.md (remote/local)" { 71 | # run $BATS_TEST_DIRNAME/../gh-md-toc \ 72 | # README.md \ 73 | # https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md 74 | # assert_success 75 | # 76 | # assert_equal "${lines[0]}" "* [gh-md-toc](README.md#gh-md-toc)" 77 | # assert_equal "${lines[1]}" "* [Table of contents](README.md#table-of-contents)" 78 | # assert_equal "${lines[2]}" "* [Installation](README.md#installation)" 79 | # assert_equal "${lines[3]}" "* [Usage](README.md#usage)" 80 | # assert_equal "${lines[4]}" " * [STDIN](README.md#stdin)" 81 | # assert_equal "${lines[5]}" " * [Local files](README.md#local-files)" 82 | # assert_equal "${lines[6]}" " * [Remote files](README.md#remote-files)" 83 | # assert_equal "${lines[7]}" " * [Multiple files](README.md#multiple-files)" 84 | # assert_equal "${lines[8]}" " * [Combo](README.md#combo)" 85 | # assert_equal "${lines[9]}" " * [Auto insert and update TOC](README.md#auto-insert-and-update-toc)" 86 | # assert_equal "${lines[10]}" " * [GitHub token](README.md#github-token)" 87 | # assert_equal "${lines[11]}" " * [TOC generation with Github Actions](README.md#toc-generation-with-github-actions)" 88 | # assert_equal "${lines[12]}" "* [Tests](README.md#tests)" 89 | # assert_equal "${lines[13]}" "* [Dependency](README.md#dependency)" 90 | # assert_equal "${lines[14]}" "* [Docker](README.md#docker)" 91 | # assert_equal "${lines[15]}" " * [Local](README.md#local)" 92 | # assert_equal "${lines[16]}" " * [Public](README.md#public)" 93 | # assert_equal "${lines[17]}" "* [sitemap.js](https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md#sitemapjs)" 94 | # assert_equal "${lines[18]}" " * [Installation](https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md#installation)" 95 | # assert_equal "${lines[19]}" " * [Usage](https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md#usage)" 96 | # assert_equal "${lines[20]}" " * [License](https://github.com/ekalinin/sitemap.js/blob/6bc3eb12c898c1037a35a11b2eb24ababdeb3580/README.md#license)" 97 | # assert_equal "${lines[21]}" "" 98 | # } 99 | 100 | @test "TOC for markdown from stdin" { 101 | cat README.md | { 102 | run $BATS_TEST_DIRNAME/../gh-md-toc - 103 | assert_success 104 | assert_equal "${lines[0]}" "* [gh-md-toc](#gh-md-toc)" 105 | assert_equal "${lines[1]}" "* [Table of contents](#table-of-contents)" 106 | assert_equal "${lines[2]}" "* [Installation](#installation)" 107 | assert_equal "${lines[3]}" "* [Usage](#usage)" 108 | assert_equal "${lines[4]}" " * [STDIN](#stdin)" 109 | assert_equal "${lines[5]}" " * [Local files](#local-files)" 110 | assert_equal "${lines[6]}" " * [Remote files](#remote-files)" 111 | assert_equal "${lines[7]}" " * [Multiple files](#multiple-files)" 112 | assert_equal "${lines[8]}" " * [Combo](#combo)" 113 | assert_equal "${lines[9]}" " * [Auto insert and update TOC](#auto-insert-and-update-toc)" 114 | assert_equal "${lines[10]}" " * [GitHub token](#github-token)" 115 | assert_equal "${lines[11]}" " * [TOC generation with Github Actions](#toc-generation-with-github-actions)" 116 | assert_equal "${lines[12]}" "* [Tests](#tests)" 117 | assert_equal "${lines[13]}" "* [Dependency](#dependency)" 118 | } 119 | } 120 | 121 | test_help() { 122 | assert_equal "${lines[1]}" "Usage:" 123 | assert_equal "${lines[2]}" " gh-md-toc [options] src [src] Create TOC for a README file (url or local path)" 124 | assert_equal "${lines[3]}" " gh-md-toc - Create TOC for markdown from STDIN" 125 | assert_equal "${lines[4]}" " gh-md-toc --help Show help" 126 | assert_equal "${lines[5]}" " gh-md-toc --version Show version" 127 | assert_equal "${lines[6]}" "Options:" 128 | assert_equal "${lines[7]}" " --indent Set indent size. Default: 3." 129 | assert_equal "${lines[8]}" " --insert Insert new TOC into original file. For local files only. Default: false." 130 | assert_equal "${lines[10]}" " --no-backup Remove backup file. Set --insert as well. Default: false." 131 | assert_equal "${lines[11]}" " --hide-footer Do not write date & author of the last TOC update. Set --insert as well. Default: false." 132 | assert_equal "${lines[12]}" " --skip-header Hide entry of the topmost headlines. Default: false." 133 | assert_equal "${#lines[@]}" "14" 134 | } 135 | 136 | @test "--help" { 137 | run $BATS_TEST_DIRNAME/../gh-md-toc --help 138 | assert_success 139 | test_help 140 | } 141 | 142 | @test "no arguments" { 143 | run $BATS_TEST_DIRNAME/../gh-md-toc 144 | assert_success 145 | test_help 146 | } 147 | 148 | @test "--version" { 149 | run $BATS_TEST_DIRNAME/../gh-md-toc --version 150 | assert_success 151 | assert_equal "${lines[0]}" "0.10.0" 152 | assert_equal "${lines[1]}" "os: `uname -s`" 153 | assert_equal "${lines[2]}" "arch: `uname -m`" 154 | } 155 | 156 | @test "TOC for non-english chars, #6, #10" { 157 | run $BATS_TEST_DIRNAME/../gh-md-toc tests/test\ directory/test_nonenglishchars.md 158 | assert_success 159 | 160 | assert_equal "${lines[2]}" "* [命令行的艺术](#命令行的艺术)" 161 | assert_equal "${lines[3]}" " * [必读](#必读)" 162 | assert_equal "${lines[4]}" " * [基础](#基础)" 163 | assert_equal "${lines[5]}" " * [日常使用](#日常使用)" 164 | } 165 | 166 | # @test "TOC for remote non-english chars (remote load), #6, #10" { 167 | # run $BATS_TEST_DIRNAME/../gh-md-toc \ 168 | # https://github.com/ekalinin/envirius/blob/f939d3b6882bfb6ecb28ef7b6e62862f934ba945/README.ru.md 169 | # assert_success 170 | # 171 | # assert_equal "${lines[2]}" "* [envirius](#envirius)" 172 | # assert_equal "${lines[3]}" " * [Идея](#идея)" 173 | # assert_equal "${lines[4]}" " * [Особенности](#особенности)" 174 | # assert_equal "${lines[5]}" "* [Установка](#установка)" 175 | # 176 | # 177 | # run $BATS_TEST_DIRNAME/../gh-md-toc \ 178 | # https://github.com/jlevy/the-art-of-command-line/blob/217da3b4fa751014ecc122fd9fede2328a7eeb3e/README-zh.md 179 | # assert_success 180 | # 181 | # assert_equal "${lines[2]}" "* [命令行的艺术](#命令行的艺术)" 182 | # assert_equal "${lines[3]}" " * [必读](#必读)" 183 | # assert_equal "${lines[4]}" " * [基础](#基础)" 184 | # assert_equal "${lines[5]}" " * [日常使用](#日常使用)" 185 | # 186 | # 187 | # run $BATS_TEST_DIRNAME/../gh-md-toc \ 188 | # https://github.com/jlevy/the-art-of-command-line/blob/217da3b4fa751014ecc122fd9fede2328a7eeb3e/README-pt.md 189 | # assert_success 190 | # 191 | # assert_equal "${lines[2]}" "* [A arte da linha de comando](#a-arte-da-linha-de-comando)" 192 | # assert_equal "${lines[3]}" " * [Meta](#meta)" 193 | # assert_equal "${lines[4]}" " * [Básico](#básico)" 194 | # assert_equal "${lines[5]}" " * [Uso diário](#uso-diário)" 195 | # } 196 | 197 | @test "TOC for text with backquote, #13" { 198 | run $BATS_TEST_DIRNAME/../gh-md-toc tests/test\ directory/test_backquote.md 199 | assert_success 200 | 201 | assert_equal "${lines[2]}" "* [The command foo1](#the-command-foo1)" 202 | assert_equal "${lines[3]}" " * [The command foo2 is better](#the-command-foo2-is-better)" 203 | assert_equal "${lines[4]}" "* [The command bar1](#the-command-bar1)" 204 | assert_equal "${lines[5]}" " * [The command bar2 is better](#the-command-bar2-is-better)" 205 | assert_equal "${lines[6]}" " * [The command bar3 is the best](#the-command-bar3-is-the-best)" 206 | } 207 | 208 | @test "TOC for text with plus signs, #100" { 209 | run $BATS_TEST_DIRNAME/../gh-md-toc tests/test\ directory/test_plussign.md 210 | assert_success 211 | 212 | assert_equal "${lines[2]}" "* [C vs C++](#c-vs-c)" 213 | } 214 | 215 | @test "Toc for file path with space, #136" { 216 | run $BATS_TEST_DIRNAME/../gh-md-toc --no-backup --hide-footer tests/test\ directory/test_filepathwithspace.md 217 | assert_success 218 | 219 | assert_equal "${lines[2]}" "* [Title](#title)" 220 | assert_equal "${lines[3]}" " * [This is test for file path with space](#this-is-test-for-file-path-with-space)" 221 | } 222 | 223 | @test "Toc for setext heading with formatting, #145" { 224 | run $BATS_TEST_DIRNAME/../gh-md-toc --no-backup --hide-footer tests/test\ directory/test_setextwithformatting.md 225 | assert_success 226 | 227 | assert_equal "${lines[2]}" "* [Title one](#title-one)" 228 | assert_equal "${lines[3]}" " * [This is test for setext-style without formatting](#this-is-test-for-setext-style-without-formatting)" 229 | assert_equal "${lines[4]}" "* [Title two](#title-two)" 230 | assert_equal "${lines[5]}" " * [This is test for setext-style with formatting](#this-is-test-for-setext-style-with-formatting)" 231 | assert_equal "${lines[6]}" "* [Title three](#title-three)" 232 | assert_equal "${lines[7]}" " * [This is a regression test for atx-style](#this-is-a-regression-test-for-atx-style)" 233 | assert_equal "${lines[8]}" "* [Title four is a particularly long title because of wrapping](#title-four-is-a-particularly-long-title-because-of-wrapping)" 234 | assert_equal "${lines[9]}" " * [This is a test for long titles](#this-is-a-test-for-long-titles)" 235 | } 236 | --------------------------------------------------------------------------------