# 修改文本

# tr

tr <源字符集> <目标字符集>   # 替换文本中的指定字符
     -c                    # 反选源字符集(即选中其它字符)
     -d                    # 删除源字符集
  • tr 命令的输入不能是文件,只能是 stdin ,且输出到 stdout 。
  • 例:
    cat f1 | tr a-z A-Z      # 将小写字母换成大写字母
    

# sed

sed [expression] [file]...  # 读取文件内容,根据表达式进行修改,然后输出
    -e                      # 读取 stdin
    -i                      # 将修改结果输出到源文件(否则默认输出到 stdout )
    -n                      # --quiet ,取消默认输出,此时使用 p 等命令才会有输出
  • 表达式示例:

    # 处理指定行
    -n '1p'                   # 只显示第 1 行
    -n '1,5p'                 # 只显示第 1~5 行
    '1,$d'                    # 删除第 1 行至最后一行(使用单引号作为定界符,避免将 $ 当做对变量取值),然后输出剩下的内容
    '1,4d;6d;$d'              # 删除第 1~4 行、第 6 行、最后一行(用 ; 分隔多个目标)
    '1i hello'                # 在第 1 行之前插入一行字符串
    '1a hello'                # 在第 1 行之后插入一行字符串
    'a  hello'                # 在每行之后插入一行字符串
    
    # 使用正则匹配
    -n '/hello/p'             # 找到正则匹配的每行并显示,相当于 grep 命令。如果不加 -n ,则会将原始文本也输出
    '/hello/d'                # 找到正则匹配的每行并删除
    
    # 使用正则替换
    's/源字符串/目标字符串/g'   # 替换字符串,源字符串采用基本正则语法
    's#^hello#hi#g'           # s 之后的第一个字符会被视作分隔符
    's#hello##g'              # 目标字符串为空,则会删除源字符串
    's#hello##2'              # 只替换第二次
    's#hello \(\w*\)#\1#g'    # 可以按 \1 的格式提取正则匹配的元素组
    
    '3 i Hello'               # 插入一行内容,作为第 3 行
    '0,/^hello/s##Hello#'     # 只执行一次正则替换
    
    • sed 正则表达式的特点:
      • 大部分元字符需要加上 \ 转义,除了 ^$.*[]
      • 支持 \w、\s 及相反字符集,不支持 \d 字符集。
  • 例:

    cat f1 | sed 's#hello##g' > f2  # 将修改结果保存到另一个文件
    sed -i 's#hello##g' f1          # 将修改结果保存到源文件
    
    [root@CentOS ~]# echo 'Hello World' | sed 's#Hello#Hi#g'
    Hi World
    [root@CentOS ~]# echo 'Hello World' | sed 's#Hello \(\w*\)#\1#g'
    World
    
    sed -i 's#first_line#Hello\
    World\
    first_line#g' f1      # 插入多行
    
  • 用 sed -i 命令修改文件时,会在源文件的同一目录下,创建一个名为 sed${random_id} 的临时文件,用于暂存修改结果。

    • 等修改完全部内容,会自动删除源文件,然后将临时文件重命名为源文件,导致源文件的 inode 变化。
    • 如果不想改变 inode ,则可以将修改结果保存到另一个文件,然后拷贝文件内容到源文件。例如:
      sed 's#hello##g' f1 > f2
      cat f2 > f1
      
      不过这样需要临时占用两倍磁盘空间。

# vim

  • vi 是类 Unix 系统的内置文本编辑器,而 vim 是一个流行的类 vi 编辑器,功能更多。

  • 同类软件:

    • nano :一个比 vi 功能更简单的文本编辑器。
    • less :一个只读的文本阅读器。
      • 用 vi/vim 打开一个文件时,会将整个文件的内容加载到内存中,并扫描每行内容,进行语法突出显示。因此打开文件时有一定耗时,占用内存比整个文件的体积还大一些。
      • 使用 less 时,只会将文件的部分内容加载到内存中,比如只加载第 100~200 行的内容,显示给用户看,因此占用的内存很少。
      • 因此 vi/vim 只适合打开低于 100MB 的文件。阅读大型文件时,建议用 less 命令。修改大型文件时,建议用 sed 命令。
  • 执行以下命令即可启动 vim 编辑器:

    vim [path]
    
    • 如果不指定 path ,则会打开一个空的编辑器界面。
    • 如果指定的 path 是一个文件,则对它进行编辑。如果该文件不存在,则可以在保存时创建它。
    • 如果指定的 path 是一个目录,则可以管理该目录下的文件。

# 命令模式

启动 vi/vim 时,默认进入命令模式(Command mode):

  • 此时不能编辑文本,只能输入键盘上的某些字符作为命令(区分大小写)。
  • 文本末尾显示的 ~ 表示空行,并不是实际存在的字符。
  • vi/vim 的命令支持组合,可以连续输入多条命令,组合它们的功能。
    • 比如输入一个数字 n 之后再输入 dd ,会连续剪贴 n 行。
  • 关于切换模式的命令:
    • i/a :进入插入模式。
    • o :在光标下方插入一行并进入插入模式。
    • r :替换光标所在的那个字符。
    • R :进入替换模式。
    • 输入以 :/? 开头的字符串会进入底线命令模式。
    • 进入其它模式之后,按 EscCtrl + c 即可退出到命令模式。
  • 关于移动光标的命令:
    • PageUpPageDown :向上或下翻页。
    • kjhl :将光标向上、下、左、右移动一格。
    • Space :将光标后移一格。
    • Enter :将光标下移一行。
    • gg :将光标移动到第一行。
    • G :将光标移动到最后一行。
    • 输入 /word(或 ?word)会向下(或向上)查找 word 字符串,然后输入 n(或 N)会切换到下一个(或上一个)匹配结果。
  • 关于撤销的命令:
    • u :撤销上一次对文件内容的修改操作。
    • Ctrl + r :重做被撤销的操作。
    • . :重做上一次操作(对 u 和 Ctrl + r 无效)。
  • 关于复制粘贴的命令:
    • yy :复制光标所在的当前行。
    • xX :删除光标之后或之前的一个字符。
    • dd :剪贴当前行。
    • pP :粘贴到下一行或上一行。
    • v :开始选中,此时光标移动过的区域都会被反白选中,然后输入 d 或 y 即可删除或复制。
    • Ctrl + v :开始矩形选中。

# 底线命令模式

底线命令模式(Last line mode):

  • 此时输入的字符会显示在下方的命令行中。

  • 常用命令:

    • :wq :保存并退出(输入 w 表示保存,输入 q 表示退出)
    • :wq! :强制保存再退出(输入!表示强制执行操作)
    • :wq [文件名] :保存为指定文件。
    • :s/源字符串/目标字符串/g :在当前行替换字符串。(目标字符串为空的话就是删除)
    • :%s/源字符串/目标字符串/g :在每一行替换字符串。
    • :set nu :显示行号。
    • /str :向下查找字符串。然后输入 n(或 N)会切换到下一个(或上一个)匹配结果。
    • ?str :向上查找字符串。
  • 执行 :set xxx 可临时修改 vim 的配置参数,也可以将配置参数永久保存到 /etc/vimrc~/.vimrc 文件。

  • 常见的配置参数:

    set encoding=utf-8  " 设置编码格式
    set number          " 显示行号
    set nonumber        " 不显示行号
    set ignorecase      " 搜索时不区分大小写
    set paste           " 进入粘贴模式,使粘贴的文本内容会原样地输入 vim
    set nopaste         " 退出粘贴模式
    set tabstop=4       " 每个制表符 \t 显示的宽度
    set expandtab       " 按下 Tab 键时,输入空格而不是制表符
    
    • 用双引号 " 声明单行注释。

# 交换文件

  • 用 vi/vim 命令打开文件时(即使未修改),会在源文件的同一目录下(而不是执行命令的目录),创建一个名为 .filename.swp 的交换文件。

    • 交换文件记录了源文件的原始内容、用户每次修改的增量内容,从而允许用户撤销操作、重做操作。
    • 交换文件会比源文件的体积更大一些。
    • 退出 vi/vim 时,会自动删除交换文件。
  • 执行 :w 保存文件时(即使未修改),vi/vim 会将当前内容保存到源文件。根据配置参数 backupcopy 取值的不同,有几种保存方式:

    • backupcopy=yes
      • 原理:根据交换文件,得到文件的修改结果,保存为一个名为 filename~ 的备份文件。然后拷贝备份文件的内容,覆盖式写入源文件。最后删掉备份文件。
      • 优点:源文件的 inode 不变,文件属性不变。
      • 缺点:如果文件体积大,则拷贝需要一定耗时。如果同时有其它程序在修改源文件,则可能出错。
    • backupcopy=no
      • 原理:先创建一个备份文件,并将它的文件属性改成跟源文件一致。然后删除源文件,将备份文件重命名为源文件。
      • 优点:几乎没有耗时。
      • 缺点:
        • 源文件被删了,创建新文件,inode 变化。
        • 新文件的文件属性可能不一致,比如当前用户无权修改某个属性。
        • 如果源文件是一个软链接,则新文件会是一个普通文件。
    • backupcopy=auto
      • 原理:自动判断。优先采用 no 方式,如果遇到以下情况,则采用 yes 方式:
        • 源文件是软链接。
        • 源文件的文件属性不能全部复制到新文件。
        • 默认配置了 backupskip=/tmp/* ,表示修改该路径下的文件时,不会创建备份文件。
      • Unix 系统的 vi/vim 默认采用 yes 方式,而大部分 Linux 发行版的 vi/vim 采用 auto 方式。
  • 用 vi/vim 打开一个文件时,可能遇到这种报错:

    Swap file ".f1.swp" already exists!
    [O]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort:
    

    该报错表示该文件已存在交换文件,可能是因为:

    • 此时有其他用户正在用 vi/vim 打开该文件,而一个文件同时只允许被一个 vi/vim 进程修改。
    • 之前某个用户用 vi/vim 打开了该文件,但 vi/vim 进程异常退出,没有自动删除交换文件。此时可执行以下命令:
      vim -r .filename.swp  # 恢复到文件的最后修改状态
      :wq                   # 保存文件
      rm -f .filename.swp   # 删除交换文件
      

# iconv

iconv [file]        # 转换文本文件的编码格式(默认输出到 stdout)
      -f utf-8      # 源文件的编码格式
      -t gbk        # 要转换成的编码格式
      -c            # 忽略转换失败的字符
  • 例:批量转换文件的编码格式
    file_list=`find . -name "*.txt"`
    from_encoding='utf-8'
    to_encoding='gbk'
    for f in $file_list
    do
        iconv $f -f utf-8 &> /dev/null
        if [ $? ]; then
            echo "Convert the encoding of file $f to $to_encoding"
            iconv $f -f $from_encoding -t $to_encoding > .iconv.tmp
            mv .iconv.tmp $f
        else
            echo "The encoding of file $f is not $from_encoding , skip."
            continue
        fi
    done