非常感谢您阅读本文。我对 bash 很陌生,所以我需要您在以下方面的建议:
我想编写一个 bash 脚本来读取包含 2 列的文件
f 2
g 1
s 4
d 5
f 2
g 5
d 9
g 10
h 1
s 5
d 29
我的脚本实际上会根据第一列(字母表)对该文件进行排序,并生成一个名为alpha_sorted.txt
and 的文件,然后我希望它对数字执行相同的操作并将其命名为numbers_sorted.txt
.
我对此很陌生,所以如果可能的话,我想请求您的帮助,为我提供文档或链接,甚至帮助编写代码。
该脚本旨在用于入门级别,因此不建议使方法复杂化。
更新
使用 john1024 的答案,我遇到以下问题:
Hasan@HasanChr /cygdrive/c/users/Hasan/Desktop/Bash
$ chmod +x script.sh
Hasan@HasanChr /cygdrive/c/users/Hasan/Desktop/Bash
$ ./script.sh
cat: alpha_sorted.txt: No such file or directory
这是一个屏幕截图script.sh
答案1
由于这是发布在UNIX.stackexchange.com,我假设您可以访问常用的 UNIX 工具。
第一列按字母顺序排序:
$ sort file.txt >alpha_sorted.txt
$ cat alpha_sorted.txt
d 29
d 5
d 9
f 2
f 2
g 1
g 10
g 5
h 1
s 4
s 5
数字排序:
$ sort -nk2,2 file.txt >numbers_sorted.txt
$ cat numbers_sorted.txt
g 1
h 1
f 2
f 2
s 4
d 5
g 5
s 5
d 9
g 10
d 29
-n
指定数字排序。 -k2,2
指定对第二列进行排序。
有关详细信息,请参阅man sort
。
使用记事本编辑 Unix 脚本时出现问题
我创建了一个带有 DOS 行结尾的脚本:
$ cat dos.sh
sort file.txt >alpha_sorted.txt
cat alpha_sorted.txt
虽然不可见,但我在命令末尾添加了一个空格cat
。使用此文件,我可以重现您看到的错误:
$ chmod +x dos.sh
$ dos.sh
cat: alpha_sorted.txt: No such file or directory
: No such file or directory
dos2unix
我们可以使用或等实用程序来纠正此问题tr
。使用tr
:
$ tr -d '\r' <dos.sh >fixed.sh
$ chmod +x fixed.sh
现在,我们可以成功运行命令:
$ fixed.sh
d 29
d 5
d 9
f 2
f 2
g 1
g 10
g 5
h 1
s 4
s 5
答案2
有更好的排序方法而不是纯粹在 bash 中完成。这不是对你的问题的一个很好的答案——它并不简单(因为它使用了 bash 的几个不常见的功能),而且它不做“Unix 方式”的事情,即使用是为做一件事并做好它而预先构建的(例如排序)。
我决定写下这个答案,以帮助阐明您帐户的默认 shell 是为了运行命令和重定向 I/O 而构建的。仅仅因为外壳有一个多种功能,就像 Bash 一样并不意味着它是特定工作的最佳工具。您经常会在这里看到建议使用 awk 或 perl (或 jq 或 sort ...)的答案,而不是尝试将其破解为仅 shell 脚本。
话虽如此,bash能sort——它只是不是内置的。我再说一遍:这仍然不是一个好主意。但你可以做到。下面是在 bash 中实现的四个函数,它们种类在两个字段中的每一个上都有两种不同的方式。
这些函数使用:
插入排序效率不高(O(n) 2),但对于小型数据集来说当然是合理的,例如 11 行示例。对于样本数据,这四个函数的运行时间为亚秒级,但对于随机生成的 1,000 行输入文件,“单独数组”排序花费了约 15 秒,而“就地”版本花费了约 60 秒,因为值的重新处理。将此与标准排序实用程序它以千分之一秒的时间对任一列上的 1,000 行文件进行排序。
这两个“就地”函数试图通过仅创建一个数组(以及一些用于循环和交换值的一次性变量)来节省一些字节;从好的方面来说,它使用一个简洁的 bash 函数将文件内容映射到数组中。 “键控”功能将谨慎抛诸脑后,创造二单独的数组,一个用于排序所需的键,另一个用于实际值。
function sort_inplace_f1 {
local array
mapfile -t array < "$1"
local i j tmp
for ((i=0; i <= ${#array[@]} - 2; i++))
do
for ((j=i + 1; j <= ${#array[@]} - 1; j++))
do
local ivalue jvalue
[[ ${array[i]} =~ ([^[:space:]]+)[[:space:]]+(.*) ]]
ivalue="${BASH_REMATCH[1]}"
[[ ${array[j]} =~ ([^[:space:]]+)[[:space:]]+(.*) ]]
jvalue=${BASH_REMATCH[1]}
if [[ $ivalue > $jvalue ]]
then
tmp=${array[i]}
array[i]=${array[j]}
array[j]=$tmp
fi
done
done
printf "%s\n" "${array[@]}"
}
function sort_inplace_f2 {
local array
mapfile -t array < "$1"
local i j tmp
for ((i=0; i <= ${#array[@]} - 2; i++))
do
for ((j=i + 1; j <= ${#array[@]} - 1; j++))
do
local ivalue jvalue
[[ ${array[i]} =~ ([^[:space:]]+)[[:space:]]+(.*) ]]
ivalue="${BASH_REMATCH[2]}"
[[ ${array[j]} =~ ([^[:space:]]+)[[:space:]]+(.*) ]]
jvalue=${BASH_REMATCH[2]}
if [[ $ivalue > $jvalue ]]
then
tmp=${array[i]}
array[i]=${array[j]}
array[j]=$tmp
fi
done
done
printf "%s\n" "${array[@]}"
}
function sort_keyed_f1 {
local c1 c2 keys values
while IFS=' ' read -r c1 c2
do
keys+=("$c1")
values+=("$c1 $c2")
done < "$1"
local i j tmpk tmpv
for ((i=0; i <= ${#keys[@]} - 2; i++))
do
for ((j=i + 1; j <= ${#keys[@]} - 1; j++))
do
if [[ ${keys[i]} > ${keys[j]} ]]
then
# swap keys
tmpk=${keys[i]}
keys[i]=${keys[j]}
keys[j]=$tmpk
# swap values
tmpv=${values[i]}
values[i]=${values[j]}
values[j]=$tmpv
fi
done
done
printf "%s\n" "${values[@]}"
}
function sort_keyed_f2 {
local c1 c2 keys values
while IFS=' ' read -r c1 c2
do
keys+=("$c2")
values+=("$c1 $c2")
done < "$1"
local i j tmpk tmpv
for ((i=0; i <= ${#keys[@]} - 2; i++))
do
for ((j=i + 1; j <= ${#keys[@]} - 1; j++))
do
if [[ ${keys[i]} -gt ${keys[j]} ]]
then
# swap keys
tmpk=${keys[i]}
keys[i]=${keys[j]}
keys[j]=$tmpk
# swap values
tmpv=${values[i]}
values[i]=${values[j]}
values[j]=$tmpv
fi
done
done
printf "%s\n" "${values[@]}"
}
即使经历了这一切之后,你仍然需要 shell 的核心“功能”之一,即将输出重定向到文件:
sort_keyed_f1 input-file > alpha_sorted.txt
sort_keyed_f2 input-file > numbers_sorted.txt