巴什高尔夫技巧


55

您在巴什(Bash)打高尔夫球有哪些一般提示?我正在寻找可以应用于编码高尔夫问题的想法,这些想法至少在某种程度上是特定于Bash的(例如,“删除评论”不是答案)。请为每个答案发布一个提示。

Answers:


36

未记录,但可在我遇到的所有版本中使用,以实现sh向后兼容:

for循环允许您使用{ }而不是do done。例如替换:

for i in {1..10};do echo $i; done

与:

for i in {1..10};{ echo $i;}

is是sh什么shell,什么shell允许这种for语法?明确允许zsh
mikeserv

@mikeserv Bash。我记得在某个地方读过,这种语法在某些旧版本中是允许的,sh并且Bash也允许这样做,尽管可悲的是我没有被引用。
Digital Trauma 2015年

嗯... csh,大概-那就是他们在外壳中的工作方式。
mikeserv

顺便说一下,在ksh93上面的东西可能是:;{1..10}和在bashprintf %s\\n {1..10}
mikeserv

1
for((;i++<10)){ echo $i;}for i in {1..10};{ echo $i;}
Evan Krall

29

对于算术扩展的使用$[…],而不是$((…))

bash-4.1$ echo $((1+2*3))
7

bash-4.1$ echo $[1+2*3]
7

在算术扩展中,不要使用$

bash-4.1$ a=1 b=2 c=3

bash-4.1$ echo $[$a+$b*$c]
7

bash-4.1$ echo $[a+b*c]
7

算术扩展是在索引数组下标上执行的,因此不要在其中使用$两者:

bash-4.1$ a=(1 2 3) b=2 c=3

bash-4.1$ echo ${a[$c-$b]}
2

bash-4.1$ echo ${a[c-b]}
2

在算术扩展中,不要使用${…}

bash-4.1$ a=(1 2 3)

bash-4.1$ echo $[${a[0]}+${a[1]}*${a[2]}]
7

bash-4.1$ echo $[a[0]+a[1]*a[2]]
7

更换while((i--)),其工作与while[i--]while $[i--]我没有工作。GNU bash版本4.3.46(1)
Glenn Randers-Pehrson,2016年

1
正确,@ GlennRanders-Pehrson。那不应该起作用。
manatwork

y=`bc<<<"($x*2.2)%10/1"`... bc用于非整数计算的示例...请注意/1,最后将截断所得的十进制为整数。
roblogic

s=$((i%2>0?s+x:s+y))...在bash算术中使用三元运算符的示例。它短于if..then..else[ ] && ||
抢劫

1
@manatwork谢谢。他们必须删除了它。我在GNU bash, version 5.0.2(1)-release (x86_64-apple-darwin16.7.0),那不在我的。
约拿

19

定义函数的正常,冗长而乏味的方法是

f(){ CODE;}

正如这个人发现的那样,您绝对需要在其之前的空格CODE和在其后的分号。

这是我从@DigitalTrauma中学到的一个小技巧:

f()(CODE)

这要短两个字符,并且只要您不需要在函数返回后保留变量值的任何更改(括号在subshel​​l中运行主体),它就可以正常工作。

正如@ jimmy23013在评论中指出的那样,甚至连括号也可能是不必要的。

Bash参考手册》显示了可以如下定义功能:

name () compound-command [ redirections ]

要么

function name [()] compound-command [ redirections ]

化合物命令可以是:

  • 一个循环结构:untilwhilefor
  • 一个条件结构:ifcase((...))或者[[...]]
  • 分组命令:(...){...}

这意味着以下所有条件均有效:

$ f()if $1;then $2;fi
$ f()($1&&$2)
$ f()(($1))                # This one lets you assign integer values

我一直在用大括号像个吸盘...


2
请注意,您还可以使用f()while ... f()if ...和其他复合命令。
jimmy23013

这让我感到惊讶,因为我认为这f()CODE是合法的。事实证明,这f()echo hi在pdksh和zsh 中是合法的,但在bash中却不合法。
kernigh 2014年

1
它特别有用,for因为它默认为positionals:之类的f()for x do : $x; done;set -x *;PS4=$* f "$@"
mikeserv

16

:是不执行任何操作的命令,其退出状态始终会成功,因此可以代替来使用它true


使用子外壳和管道传输将使用大约相同数量的字节,但是管道传输将更加实用。
ckjbgames

5
除非您有时间:(){:|:}
恩迪德

14

更多提示

  1. 尽可能地滥用三元运算符((test)) && cmd1 || cmd2[ test ] && cmd1 || cmd2

    示例(长度计数始终不包括顶行):

    t="$something"
    if [ $t == "hi" ];then
    cmd1
    cmd2
    elif [ $t == "bye" ];then
    cmd3
    cmd4
    else
    cmd5
    if [ $t == "sup" ];then
    cmd6
    fi
    fi
    

    通过仅使用三元运算符,可以很容易地将其简化为:

    t="$something"
    [ $t == "hi" ]&&{
    cmd1;cmd2
    }||[ $t == "bye" ]&&{
    cmd3;cmd4
    }||{
    cmd5
    [ $t == "sup" ]&&cmd6
    }
    

    正如nyuszika7h在评论中指出的那样,可以使用case以下示例进一步简化此特定示例:

    t="$something"
    case $t in "hi")cmd1;cmd2;;"bye")cmd3;cmd4;;*)cmd5;[ $t == "sup" ]&&cmd6;esac
    
  2. 另外,请尽可能使用括号将大括号括起来。由于括号是一个元字符,而不是一个单词,因此它们在任何上下文中都不需要空格。这也意味着在子shell中运行尽可能多的命令,因为花括号(即{})是保留字,而不是元字符,因此必须在两边都留有空格才能正确解析,而元字符则不需要。我假设您现在知道子shell不会影响父环境,因此,假设所有示例命令都可以安全地在子shell中运行(在任何情况下都是不常见的),则可以将上面的代码缩短为:

    t=$something
    [ $t == "hi" ]&&(cmd1;cmd2)||[ $t == "bye" ]&&(cmd3;cmd4)||(cmd5;[ $t == "sup" ]&&cmd6)
    

    另外,如果不能,使用括号仍然可以缩小括号。要记住的一件事是,它仅适用于整数,因此在本示例中,它变得毫无用处(但比-eq用于整数要好得多)。

  3. 还有一件事,请尽可能避免使用引号。使用以上建议,您可以进一步缩小规模。例:

    t=$something
    [ $t == hi ]&&(cmd1;cmd2)||[ $t == bye ]&&(cmd3;cmd4)||(cmd5;[ $t == sup ]&&cmd6)
    
  4. 在测试条件下,除少数例外,尽可能将单括号替换为双括号。它免费删除了两个字符,但是在某些情况下却不那么健壮(它是Bash扩展-参见下面的示例)。另外,请使用单个equals参数,而不要使用double。这是一个自由的角色。

    [[ $f == b ]]&&: # ... <-- Bad
    [ $f == b ]&&: # ... <-- Better
    [ $f = b ]&&: # ... <-- Best.  word splits and pathname-expands the contents of $f.  Esp. bad if it starts with -
    

    请注意以下警告,尤其是在检查空输出或未定义变量时:

    [[ $f ]]&&:    # double quotes aren't needed inside [[, which can save chars
    [ "$f" = '' ]&&: <-- This is significantly longer
    [ -n "$f" ]&&:
    

    从技术上讲,此特定示例最好与case... in

    t=$something
    case $t in hi)cmd1;cmd2;;bye)cmd3;cmd4;;*)cmd5;[ $t == sup ]&&cmd6;esac
    

因此,这篇文章的寓意是这样的:

  1. 尽可能地滥用布尔运算符,并始终使用它们代替if/ if-else/ etc。结构体。
  2. 尽量使用括号,并在子外壳程序中尽可能多地运行段,因为括号是元字符而不是保留字。
  3. 尽可能避免使用引号。
  4. 签出case... in,因为它可以节省很多字节,尤其是在字符串匹配中。

PS:这是在Bash中识别的元字符列表,与上下文无关(并且可以分隔单词):

&lt; &gt; ( ) ; & | &lt;space&gt; &lt;tab&gt;

编辑:如manatwork指出,双括号测试仅适用于整数。另外,间接地,我发现您需要在==运算符周围留有空格。更正了我上面的帖子。

我也懒得重新计算每个段的长度,因此我只是删除了它们。如有必要,在网上找到一个字符串长度计算器应该足够容易。


不好意思,但是您那里有一些严重的错误。[ $t=="hi" ]解析为时,其值始终为0 [ -n "STRING" ](($t=="hi"))只要$ t具有非数字值,它将始终计算为0,因为在算术评估中将字符串强制为整数。一些测试案例:pastebin.com/WefDzWbL
manatwork

@manatwork感谢您的帮助。我会相应地更新。
Isiah Meadows 2014年

case在这里使用a 会更短。另外,您在之前不需要空格},但在之后需要空格{
nyuszika7h 2014年

1
为什么=没有那么健壮===是POSIX强制要求的,==不是。
丹尼斯

1
这个问题确实要求每个答案一个小费 ……
Toby Speight,2016年

7

相反的grep -Egrep -Fgrep -r,使用egrepfgreprgrep,省去了两个字符。较短的那些已被弃用,但可以正常工作。

(您确实要求每个答案一个提示!)


1
太可惜Pgrepgrep -P。尽管我看到它很容易与pgrep用来混淆进程的混淆。
nyuszika7h 2014年

1
@ nyuszika7h我个人使用grep -o了很多

它不节省3个空格吗?
ckjbgames

7

只能使用变量名来访问数组的元素0,这比显式指定索引0节省了五个字节:

$ a=(code golf)
$ echo ${a[0]}
code
$ echo $a
code
$ 

7

如果需要将变量的内容传递给管道中下一个进程的STDIN,通常会将变量回显到管道中。但是您可以通过<<< bash here字符串实现相同的目的:

$ s="code golf"
$ echo "$s"|cut -b4-6
e g
$ cut -b4-6<<<"$s"
e g
$ 

2
因为我们正在打高尔夫球,s=code\ golfecho $s|<<<$s(记住,后两个工作只是因为没有连续的空格,等等)。
丹尼斯2014年

6

避免使用$( ...command... ),另一种方法是保存一个字符并执行相同的操作:

` ...command... `

9
$( )如果您有嵌套的命令替换,则有时需要;否则,您将不得不逃脱内心的``
痛苦

1
这些在技术上做不同的事情,例如,$()当我要在我的机器而不是scp目标机器上运行替换时,我不得不使用反引号。在大多数情况下,它们是相同的。
地下

2
@undergroundmonorail:不需要反引号。$()如果您引用正确,他们可以做的任何事情都可以做。(除非您需要您的命令才能使某些可能发生$但又不需要反引号的内容得以生存)。引用其中的内容存在一些细微的差异。 mywiki.wooledge.org/BashFAQ/082解释了一些区别。除非您打高尔夫球,否则切勿使用反引号。
彼得·科德斯

@PeterCordes我敢肯定有一个办法,但我当时什么都试过了没有工作。即使反引号不是最好的解决方案,我也很高兴知道它们,因为这是我唯一的解决方案。¯\ _(ツ)_ /¯
undergroundmonorail

@DigitalTrauma嵌套示例:echo `bc <<<"\`date +%s\`-12"`...(很难在评论中发布包含反引号的示例!;)
F. Hauri

6

用于if对命令进行分组

与完全删除的技巧相比,此技巧if仅在极少数情况下(例如,当您需要从中返回值时)才能更好地工作if

如果您有一个以结尾的命令组if,例如:

a&&{ b;if c;then d;else e;fi;}
a&&(b;if c;then d;else e;fi)

您可以if在条件中先包装命令:

a&&if b;c;then d;else e;fi

或者,如果您的函数以结尾if

f(){ a;if b;then c;else d;fi;}

您可以删除花括号:

f()if a;b;then c;else d;fi

1
您可以[test] && $if_true || $else在这些函数中使用三元运算符并保存一些字节。
ckjbgames

此外,你并不需要的空间周围&&||
roblogic

6

(( ... ))对条件使用算术

您可以替换:

if [ $i -gt 5 ] ; then
    echo Do something with i greater than 5
fi

通过

if((i>5));then
    echo Do something with i greater than 5
fi

(注意:后面没有空格if

甚至

((i>5))&&{
    echo Do something with i greater than 5
}

...或者如果只有一个命令

((i>5))&&echo Echo or do something with i greater than 5

此外:在算术构造中隐藏变量设置:

((i>5?c=1:0))&&echo Nothing relevant there...
# ...
((c))&&echo Doing something else if i was greater than 5

或相同

((c=i>5?c=0,1:0))&&echo Nothing relevant there...
# ...
((c))&&echo Doing something else if i was greater than 5

... 如果i> 5,则c = 1(不是0;)


您可以使用[ ]代替来节省2个字节(())
ckjbgames

@ckjbgames您确定吗!?您正在使用Wich Bash版本吗?
F. Hauri

@FHauri我想它的字节数差不多。
ckjbgames

@ckjbgames [ ]需要变量的美元符号。我看不到如何使用来更改相同或较小长度的内容[ ]
F. Hauri

温馨提示:如果for循环的第一行以开头((...)),则不需要换行符或空格。例如for((;10>n++;)){((n%3))&&echo $n;} ,在线尝试!
primo

6

无限循环(可使用breakexit语句转义)的较短语法是

for((;;)){ code;}

这比while true;和短while :;

如果不需要breakexit作为唯一的转义方法),则可以使用递归函数。

f(){ code;f;};f

如果确实需要中断,但又不需要退出并且不需要在循环外进行任何变量修改,则可以在主体周围使用带括号的递归函数,该函数在子shell中运行。

f()(code;f);f

6

单行for循环

将对范围内的每个项目评估与范围扩展串联的算术表达式。例如以下内容:

: $[expression]{0..9}

将评估expression10次​​。

这通常比等效for循环短得多:

for((;10>n++;expression with n)){ :;}
: $[expression with ++n]{0..9}

如果您不介意命令未找到错误,则可以删除inital :。对于大于10的迭代,您还可以使用字符范围,例如{A..z}将迭代58次。

作为一个实际示例,以下两个均产生前50个三角数,每个三角数在各自的行上:

for((;50>d++;)){ echo $[n+=d];} # 31 bytes
printf %d\\n $[n+=++d]{A..r}    # 28 bytes

您还可以向后迭代:for((;0<i--;)){ f;}
roblogic

5

遍历参数

Bash的“ for”循环中没有“ in foo bar ...”部分所述,in "$@;"in for x in "$@;"是多余的。

来自help for

for: for NAME [in WORDS ... ] ; do COMMANDS; done
    Execute commands for each member in a list.

    The `for' loop executes a sequence of commands for each member in a
    list of items.  If `in WORDS ...;' is not present, then `in "$@"' is
    assumed.  For each element in WORDS, NAME is set to that element, and
    the COMMANDS are executed.

    Exit Status:
    Returns the status of the last command executed.

例如,如果我们要对给定Bash脚本或函数的位置参数的所有数字求平方,则可以执行此操作。

for n;{ echo $[n*n];}

在线尝试!


5

猫的替代品

假设您正在尝试读取文件并将其用于其他用途。您可能会做的是:

echo foo `cat bar`

如果barwas 的内容foobar将打印出来foo foobar

但是,如果使用此方法,则有另一种选择,它可以节省3个字节:

echo foo `<bar`

1
是否有理由说明为什么<bar它本身不起作用而是将其放在反引号中呢?
Kritixi Lithos

@Cowsquack是的。在<把一个文件的命令,但在这种情况下,将其放入标准输出因怪癖。反引号一起对此进行评估。
Okx

除了标准输入以外,还有没有其他更短的方法可以读取`cat`
乔尔

4

尽可能使用[代替[[test

例:

[ -n $x ]


用于=代替==比较

例:

[ $x = y ]

请注意,等号周围必须有空格,否则它将不起作用。同样适用于==根据我的测试。


3
[vs. [[可能取决于所需的报价量:pastebin.com/UPAGWbDQ
manatwork 2014年

@manatwork很好。
nyuszika7h 2014年

1
一般规则:[... ]== /bin/test,但[[... ]]!= /bin/test和一个永远不应该喜欢[...而]不是[[... ]]在代码高尔夫球之外
cat

4

替代品 head

line比短3个字节head -1,但已不建议使用

sed q比短2个字节head -1

sed 9qhead -9。短一字节。


1
虽然注定,我们仍然可以使用了一段时间line,从util的Linux的包读取一行。
manatwork

4

tr -cd 短于 grep -o

例如,如果您需要计算空格,grep -o <char>(仅打印匹配项)将提供10个字节,而tr -cd <char>(删除的<char>)将提供9 个字节。

# 16 bytes
grep -o \ |wc -l
# 15 bytes
tr -cd \ |wc -c

来源

请注意,它们的输出都略有不同。grep -o返回行分隔的结果,同时tr -cd将所有结果都放在同一行,因此tr可能并不总是有利的。


4

缩短文件名

在最近的一次挑战中,我试图读取文件/sys/class/power_supply/BAT1/capacity,但是可以将其缩短/*/*/*/*/capac*y为没有该格式的其他文件。

例如,如果您有一个foo/包含文件的目录,foo, bar, foobar, barfoo并且想要引用该文件foo/barfoo,则可以使用foo/barf*保存一个字节。

*代表“任何东西”,并且是等同于正则表达式.*


3

使用管道:代替命令/dev/null。在:内置的会吃它的所有输入。


2
不,在大多数情况下,它将使SIGPIPE导致程序崩溃。echo a|tee /dev/stderr|:将不会打印任何内容。
jimmy23013

有一场比赛:echo a|tee /dev/stderr|:我的计算机上确实打印过一个,但是在其他地方,SIGPIPE可能会先杀死T恤。它可能取决于的版本tee
kernigh 2014年

是的,这是一个SIGPIPE问题:tee >(:) < <(seq 1 10)可以,但tee /dev/stderr | :不会。甚至a() { :;};tee /dev/stderr < <(seq 1 10)| a什么都不打印。
F. Hauri

@ user16402-您应该对我的权限有一个fccing名称...无论如何,:内在函数根本不会 ...如果您向冒号输入内容,则可能会使管道泛滥而出错误...但是您可以浮动重定向冒号,或:| while i>&$(($??!$?:${#?})) command shit; do [ -s testitsoutput ]; done取消冒号的过程... 或者不管怎么说,伪造建议仍然适用...此外,您知道您像我一样鬼吗?...不惜一切代价避免< <(psycho shit i can alias to crazy math eat your world; okay? anyway, ksh93 has a separate but equal composite char placement)
mikeserv

3

split还有另一种(已弃用,但无人在乎)语法,用于将输入分别分成几N行:而不是split -lN可以使用split -Neg split -9


3

扩展测试

本质上,shell是一种宏语言,或者至少是一种混合语言或某种语言。每个命令行基本上可以分为两部分:解析/输入部分和扩展/输出部分。

第一部分是大多数人关注的重点,因为这是最简单的:您会看到所得到的。第二部分是许多人甚至试图很好地理解也避免的事情,这就是为什么人们说诸如此类的事情eval是邪恶的,并且总是引用您的扩展 -人们希望第一部分的结果等于第一部分。没关系-但这会导致不必要的冗长代码分支和大量额外测试。

扩展是自我测试。这些${param[[:]#%+-=?]word}表单足以验证参数的内容,可以嵌套,并且都基于对NUL的评估-无论如何,这是大多数人期望的结果。+在循环中可以特别方便:

r()while IFS= read -r r&&"${r:+set}" -- "$@" "${r:=$*}";do :;done 2>&-

IFS=x
printf %s\\n some lines\ of input here '' some more|{ r;echo "$r"; }

somexlines ofxinputxhere

...同时read拉入时,非空行"${r:+set}"会扩展到,"set"并且$r附加位置信息。但是,如果空白行是read$r则为空并"${r:+set}"扩展为""-这是无效命令。但是由于命令行是""搜索null命令之前扩展的,因此也"${r:=$*}"将并入在第一个字节上的所有位置的值$IFSr()也可以在|{复合命令中;}使用另一个值再次调用它$IFS来获取下一个输入段落,因为外壳read缓冲超出\n输入中的下一条行是非法的。


3

使用尾递归使循环更短:

这些在行为上是等效的(尽管可能在内存/ PID用法上不同):

while :;do body; done
f()(body;f);f
body;exec $0
body;$0

这些大致相同:

while condition; do body; done
f()(body;condition&&f);f
body;condition&&exec $0
body;condition&&$0

(从技术上讲,后三个总是至少执行一次身体)

使用$0要求您的脚本位于文件中,而不是粘贴到bash提示符中。

最终,您的堆栈可能会溢出,但是您节省了一些字节。


3

有时,使用expr内置函数来显示简单算术表达式的结果会比通常的要短echo $[ ]。例如:

expr $1 % 2

比以下短一字节:

echo $[$1%2]

2

使用pwd而不是echo生成一行输出

是否需要在stdout上加一行,但不关心内容,并希望将答案限制在shell内置函数上?pwdecho。短一个字节。


2

打印字符串时可以省略引号。

echo "example"
echo example

SM-T335 LTE,Android 5.1.1中的输出:

u0_a177@milletlte:/ $ echo "example"
example
u0_a177@milletlte:/ $ echo example
example

2

在分配非连续数组项时,您仍然可以跳过连续块的连续索引:

bash-4.4$ a=([1]=1 [2]=2 [3]=3 [21]=1 [22]=2 [23]=3 [31]=1)

bash-4.4$ b=([1]=1 2 3 [21]=1 2 3 [31]=1)

结果是一样的:

bash-4.4$ declare -p a b
declare -a a=([1]="1" [2]="2" [3]="3" [21]="1" [22]="2" [23]="3" [31]="1")
declare -a b=([1]="1" [2]="2" [3]="3" [21]="1" [22]="2" [23]="3" [31]="1")

根据man bash

使用名称 =(值1 ...值n)形式的复合分配将数组分配给数组,其中每个的形式为[ 下标 ] = string。索引数组分配只需要 string即可。分配给索引数组时,如果提供了可选的方括号和下标,则将该索引分配给;否则,分配的元素的索引是该语句分配的最后一个索引加一个。


有助于添加:未初始化的元素在算术扩展中将扩展为0,在其他扩展中将扩展为“”。
Digital Trauma

2

打印字符串中的第一个单词

如果字符串在变量中a,并且不包含转义符和格式字符(\%),请使用以下命令:

printf $a

但是,如果需要将结果保存到变量而不是打印,它将比以下代码更长:

x=($a)
$x

1

用1 for条指令执行2个嵌入循环:

for ((l=i=0;l<=99;i=i>98?l++,0:++i)) ;do
    printf "I: %2d, L: %2d\n" $i $l
done |
    tee >(wc) | (head -n4;echo ...;tail -n 5)
I:  0, L:  0
I:  1, L:  0
I:  2, L:  0
I:  3, L:  0
...
I: 96, L: 99
I: 97, L: 99
I: 98, L: 99
I: 99, L: 99
  10000   40000  130000

1

分配并打印带引号的字符串

如果要给带引号的字符串分配一个变量,然后输出该变量的值,则通常的方法是:

a="Programming Puzzles & Code Golf";echo $a

如果a以前未设置,则可以缩短为:

echo ${a=Programming Puzzles & Code Golf}

如果a先前已设置,则应改为使用:

echo ${a+Programming Puzzles & Code Golf}

请注意,这仅在字符串需要引号(例如,包含空格)时才有用。没有引号,a=123;echo $a就是一样短。


${a+foo}没有设置a
GammaFunction
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.