如何使用shell语言格式化文件中的行?

时间:2014-11-30 05:14:27

标签: linux bash shell awk sed

该程序的目的是使文件中的注释在同一列中开始。 如果一行开头;然后它不会改变 如果一行以代码开头那么;程序应该先插入空格;所以它将与最远的同一列开始;

例如:

在:

; Also change "-f elf " for "-f elf64" in build command. 
; 
section .data                    ; section for initialized data 
str: db 'Hello world!', 0Ah                   ; message string with new-line char 
                               ; at the end (10 decimal)

后:

; Also change "-f elf " for "-f elf64" in build command.                # These two line don't change 
;                                                                       # because they start with ;
section .data                                 ; section for initialized data     
str: db 'Hello world!', 0Ah                   ; message string with new-line char
                                              ; at the end (10 decimal)

我是Linux和shell的初学者,到目前为止我已经

echo "Enter the filename"
read name

cat $name | while read line;
do ....

我们的老师告诉我们,我们应该使用两个while循环; 记录之前的最长长度;在第一个循环中,在第二个while循环中进行更改。 现在我不知道如何使用awk或sed来找到最长的长度;

有什么想法吗?

4 个答案:

答案 0 :(得分:2)

以下是解决方案,假设您的文件中的注释以不在字符串中的第一个分号(;开头

$ cat tst.awk
BEGIN{ ARGV[ARGC] = ARGV[ARGC-1]; ARGC++ }
{
    nostrings = ""
    tail = $0
    while ( match(tail,/'[^']*'/) ) {
        nostrings = nostrings substr(tail,1,RSTART-1) sprintf("%*s",RLENGTH,"")
        tail = substr(tail,RSTART+RLENGTH)
    }
    nostrings = nostrings tail
    cur = index(nostrings,";")
}
NR==FNR { max = (cur > max ? cur : max); next }
cur > 1 { $0 = sprintf("%-*s%s", max-1, substr($0,1,cur-1), substr($0,cur)) }
{ print }

$ awk -f tst.awk file
; Also change "-f elf " for "-f elf64" in build command.
;
section .data                                  ; section for initialized data
str: db 'Hello; world!', 0Ah                   ; message string with new-line char
                                               ; at the end (10 decimal)

以下是您从一个天真的起点开始的方法(我在Hello World!字符串中添加了一个分号进行测试 - 确保使用该字符串验证所有建议的解决方案。)

请注意,上面的DOES在教师建议的输入中包含2个循环,但您不需要手动编写它们,因为awk每次读取文件时都会为您提供循环。如果您的输入文件包含标签或类似标签,则需要提前删除它们,例如使用pr -e -t

以下是您如何实现上述目标:

如果你不能在其他环境中使用分号而不是评论的开头那么你只需要:

$ cat tst.awk
{ cur = index($0,";") }
NR==FNR { max = (cur > max ? cur : max); next }
cur > 1 { $0 = sprintf("%-*s%s", max-1, substr($0,1,cur-1), substr($0,cur)) }
{ print }

你执行awk -f tst.awk file file(是的,指定输入文件两次)。

如果您的代码可以在不是评论开头的上下文中包含分号,例如在字符串的中间,那么你需要告诉我们如何在注释开始与其他上下文中识别分号,但是如果它只能出现在字符串中的单引号之间,例如;里面的'Hello; World!'

$ cat file
; Also change "-f elf " for "-f elf64" in build command.
;
section .data                    ; section for initialized data
str: db 'Hello; world!', 0Ah                   ; message string with new-line char
                               ; at the end (10 decimal)

然后这就是你需要用一系列空白字符替换每个字符串然后找到第一个分号(这可能是评论的开头):

$ cat tst.awk
{
    nostrings = ""
    tail = $0
    while ( match(tail,/'[^']*'/) ) {
        nostrings = nostrings substr(tail,1,RSTART-1) sprintf("%*s",RLENGTH,"")
        tail = substr(tail,RSTART+RLENGTH)
    }
    nostrings = nostrings tail
    cur = index(nostrings,";")
}
...the rest as before...

最后如果您不想在命令行上指定文件名两次,只需在ARGV []数组中复制它的名称,方法是在顶部添加以下行:

BEGIN{ ARGV[ARGC] = ARGV[ARGC-1]; ARGC++ }

答案 1 :(得分:1)

所以是的,根据本地文件input中的输入,使用while循环查找最长的长度:

length=0
length2=0
while IFS= read -r -- i; do
(( ${#i} > length2 )) && length2=${#i}
i=${i/\;*/}
(( ${#i} > length )) && length=${#i}
done < ./input
(( length++ )); (( length2++ ))

在下一个while循环中,使用;检测该行是以[[ ${i:0:1} = ';' ]]开头并输出,或使用您确定的长度使用awk格式化输出:awk -F\; -v len=$length '{ printf "%-"len"s %-40s\n", $1, $2}'。点击此处(http://www.unix.com/shell-programming-scripting/117543-formatting-output-columns.html)了解有关列格式的详情。

编辑:如果你没弄清楚,第二个循环看起来像:

while IFS= read -r -- i; do
# echo the original if the line starts with ';'
[[ ${i:0:1} = ';' ]] && echo "$i" && continue
# column formatting with awk
(echo "$i" | grep -q ';') && echo "$i" | awk -v len=$length -v len2=$length2 -F\; '{printf "%-"len"s %-"len2"s\n",$1,";"$2}' || echo "$i"
done < ./input

这将为您提供输出所需的内容。

答案 2 :(得分:1)

有一些printf技巧使这个项目变得易于管理。看一下以下内容。该脚本使用从0列开始到code_width - 1的汇编代码格式化汇编文件,并在代码后面排列code_width列后面的注释。该脚本评论相当好,所以你应该能够跟进。

用法是:

bash nameofscript.sh input_file [code_width (default 46char)]

或者如果您nameofscript.sh executable,则只需:

./nameofscript.sh input_file [code_width (default 46char)]

注意:此脚本需要 Bash ,如果未在bash上运行,则可能会遇到不一致的结果。如果每行中有多个嵌入式;,则第一行将被视为评论的开头。如果您有疑问,请告诉我。

#!/bin/bash

## basic function to trim (or stip) the leading & trailing whitespace from a variable
#  passed to the fuction. Usage: VAR=$(trimws $VAR)
function trimws {
    [ -z "$1" ] && return 1
    local strln="${#1}"
    [ "$strln" -lt 2 ] && return 1
    local trimstr=$1
    trimstr="${trimstr#"${trimstr%%[![:space:]]*}"}"  # remove leading whitespace characters
    trimstr="${trimstr%"${trimstr##*[![:space:]]}"}"  # remove trailing whitespace characters
    printf "%s" "$trimstr"
    return 0
}

afn="$1"                        # input assembly filename
cwidth=${2:--46}                # code field width (- is left justified)

[ "${cwidth:0:1}" = '-' ] || cwidth=-${cwidth}  # make sure first char is '-'

[ -r "$afn" ] || {              # validate input file is readable
    printf "error: file not found: '%s'. Usage: %s <filename> [code_width (46 ch)]\n" "$afn" "${0//\//}"
    exit 1
}

## loop through file splitting on ';'
while IFS=$';\n' read -r code comment || [ -n "$comment" ]; do

    [ -n "$code" ] || {                 # if no '$code' comment only line
        if [ -n "$comment" ]; then
            printf ";%s\n" "$comment"   # output the line unchanged
        else
            printf "\n"                 # it was a blank line to begin with
        fi
        continue                        # read next line
    }
    code=$(trimws "$code")              # trim leading and trailing whitespace
    comment=$(trimws "$comment")        # same
    printf "%*s ; %s\n" "$cwidth" "$code" "$comment"    # output new format

done <"$afn"

exit 0

<强>输入

$ cat dat/asmfile.txt
; Also change "-f elf " for "-f elf64" in build command.
;
section .data                    ; section for initialized data
str: db 'Hello world!', 0Ah                   ; message string with new-line char
                               ; at the end (10 decimal)

<强>输出:

$ bash fmtasmcmt.sh
; Also change "-f elf " for "-f elf64" in build command.
;
section .data                                  ; section for initialized data
str: db 'Hello world!', 0Ah                    ; message string with new-line char
                                               ; at the end (10 decimal)

答案 3 :(得分:0)

我想我会将此示例用于个人格式化!

#!/usr/bin/perl -s -0
use strict;
our ($com);                          # command line option
$com = ";"  unless defined $com  ;

my $max=0;        
$_= <>;                                     # slurp file

while( /\n(.+?)$com/g ){ 
        $max=length($1) if length($1) > $max }

s/\n(.+?)$com/sprintf("\n%-$max"."s$com",$1)/ge;
print $_;                              # print file
  • 用法:align_coms input(在chmod + install之后)
  • 选项:-com=...重新定义评论(默认=;)

您可以尝试align_coms -com=# align_coms对齐此脚本perl评论:)

编辑1: 请参阅@EdMorton关于输入包含注释启动器的字符串(或类似)时的问题的(明智的)注释。

编辑2:以下版本可以处理&#39; alo;字&#39; &#34; ALO;字&#34 ;.它还是 不安全 - 真正的语言总是有一些额外的细节(例如......&#39; ...&#39;,多行注释)但它更多一点鲁棒...

#!/usr/bin/perl -s -0
use strict;
our ($com);                          # command line option
$com = ";"  unless defined $com  ;

my $nc=qr{                           # no comment regex
           (   '[^'\n]*'             # '....'
             | "[^"\n]*"             # "...."
             | .                     # common chars
           )+?
         }x;                        

my $max=0;
$_= <>;                              # slurp file

while( /\n($nc)$com/g ){
        $max=length($1) if length($1) > $max }

s/\n($nc)$com/sprintf("\n%-$max"."s$com",$1)/ge;
print $_;                            # print file