我试图概括一下:
$ awk -F":" '{ print $7 ":" $1 }' /etc/passwd
到一个脚本中,带有分隔符、输入文件和从命令行参数提供的列选择,如下所示:
#! /bin/bash
# parse command line arguments into variables `delimiter`, `cols` and `inputfile`
...
awk -F"$delimiter" '{ print '"$cols"' }' "$inputfile"
输入来自文件,因此 STDIN 输入也适用。我更喜欢按顺序将列指定为单独的参数。输出分隔符与输入分隔符相同,如示例命令中所示。
你会如何编写这样的脚本?
答案1
您可以使用巴什的getopts
(你必须向下滚动一点)进行一些命令行解析:
#!/bin/bash
delimiter=:
first=1
second=2
while getopts d:f:s: FLAG; do
case $FLAG in
d) delimiter=$OPTARG;;
f) first=$OPTARG;;
s) second=$OPTARG;;
*) echo error >&2; exit 2;;
esac
done
shift $((OPTIND-1))
awk -F"$delimiter" -v "OFS=$delimiter" -v first="$first" -v second="$second" '{ print $first OFS $second }' "$@"
答案2
以下 shell 脚本采用可选-d
选项来设置分隔符(制表符是默认值),以及-c
带有列规范的非可选选项。
列规范与 的类似,cut
但也允许重新排列和复制输出列,以及向后指定范围。还支持开放范围。
要解析的文件在命令行上作为最后一个操作数给出,或通过标准输入传递。
#!/bin/sh
delim='\t' # tab is default delimiter
# parse command line option
while getopts 'd:c:' opt; do
case $opt in
d)
delim=$OPTARG
;;
c)
cols=$OPTARG
;;
*)
echo 'Error in command line parsing' >&2
exit 1
esac
done
shift "$(( OPTIND - 1 ))"
if [ -z "$cols" ]; then
echo 'Missing column specification (the -c option)' >&2
exit 1
fi
# ${1:--} will expand to the filename or to "-" if $1 is empty or unset
cat "${1:--}" |
awk -F "$delim" -v cols="$cols" '
BEGIN {
# output delim will be same as input delim
OFS = FS
# get array of column specs
ncolspec = split(cols, colspec, ",")
}
{
# get fields of current line
# (need this as we are rewriting $0 below)
split($0, fields, FS)
nf = NF # save NF in case we have an open-ended range
$0 = ""; # empty $0
# go through given column specification and
# create a record from it
for (i = 1; i <= ncolspec; ++i)
if (split(colspec[i], r, "-") == 1)
# single column spec
$(NF+1) = fields[colspec[i]]
else {
# column range spec
if (r[1] == "") r[1] = 1 # open start range
if (r[2] == "") r[2] = nf # open end range
if (r[1] < r[2])
# forward range
for (j = r[1]; j <= r[2]; ++j)
$(NF + 1) = fields[j]
else
# backward range
for (j = r[1]; j >= r[2]; --j)
$(NF + 1) = fields[j]
}
print
}'
由于代码需要重新解析每个新行的列规范,因此效率稍低。如果不需要支持开放式范围,或者假设所有行都具有完全相同的列数,则只能在块BEGIN
(或单独的NR==1
块)中完成一次规范传递以创建数组应输出的字段。
缺少:列规范的健全性检查。格式错误的规范字符串很可能会导致奇怪的情况。
测试:
$ cat file
1:2:3
a:b:c
@:(:)
$ sh script.sh -d : -c 1,3 <file
1:3
a:c
@:)
$ sh script.sh -d : -c 3,1 <file
3:1
c:a
):@
$ sh script.sh -d : -c 3-1,1,1-3 <file
3:2:1:1:1:2:3
c:b:a:a:a:b:c
):(:@:@:@:(:)
$ sh script.sh -d : -c 1-,3 <file
1:2:3:3
a:b:c:c
@:(:):)
答案3
感谢您的回复。这是我的脚本。我通过反复试验创建了它,但这通常不会产生可行的解决方案,并且没有系统的方法来提出我一直瞄准的脚本。如果可以的话,请提供一些代码审查。谢谢。
该脚本在以下示例中有效(不确定一般情况下是否有效):
$ projection -d ":" /etc/passwd 4 3 6 7
$ projection -d "/" /etc/passwd 4 3 6 7
脚本projection
是:
#! /bin/bash
# default arg value
delim="," # CSV by default
# Parse flagged arguments:
while getopts "td:" flag
do
case $flag in
d) delim=$OPTARG;;
t) delim="\t";;
?) exit;;
esac
done
# Delete the flagged arguments:
shift $(($OPTIND -1))
inputfile="$1"
shift 1
fs=("$@")
# prepend "$" to each field number
fields=()
for f in "${fs[@]}"; do
fields+=(\$"$f")
done
awk -F"$delim" "{ print $(join_by.sh " \"$delim\" " "${fields[@]}") }" "$inputfile"
哪里join_by.sh
#! /bin/bash
# https://stackoverflow.com/questions/1527049/join-elements-of-an-array
# https://stackoverflow.com/a/2317171/
# get the separator:
d="$1";
shift;
# interpolate other parameters by teh separator
# by treating the first parameter specially
echo -n "$1";
shift;
printf "%s" "${@/#/$d}";