From 285035f72952348a977e95712017cbc102ec27b2 Mon Sep 17 00:00:00 2001 From: Chet Ramey Date: Mon, 12 Dec 2011 22:15:55 -0500 Subject: [PATCH] commit bash-20110203 snapshot --- CWRU/CWRU.chlog | 12 + CWRU/CWRU.chlog~ | 13 + braces.c | 2 +- doc/bash.pdf | Bin 296258 -> 296259 bytes doc/bash.ps | 4 +- doc/bashref.dvi | Bin 680368 -> 680368 bytes doc/bashref.log | 2 +- doc/bashref.pdf | Bin 570069 -> 570068 bytes doc/bashref.tmp | 2 +- examples/scripts/bash-hexdump.sh | 12 +- examples/scripts/bash-hexdump.sh~ | 69 + lib/glob/gmisc.c | 4 +- lib/glob/gmisc.c~ | 22 +- lib/glob/smatch.c | 6 +- lib/glob/smatch.c~ | 12 +- po/af.gmo | Bin 1231 -> 1231 bytes po/af.po | 2 +- po/bash.pot | 2 +- po/bg.gmo | Bin 34844 -> 34844 bytes po/bg.po | 2 +- po/ca.gmo | Bin 9819 -> 9819 bytes po/ca.po | 2 +- po/cs.gmo | Bin 134220 -> 134220 bytes po/cs.po | 2 +- po/de.gmo | Bin 45776 -> 45776 bytes po/de.po | 2 +- po/en@boldquot.gmo | Bin 161159 -> 161159 bytes po/en@boldquot.po | 4 +- po/en@quot.gmo | Bin 159607 -> 159607 bytes po/en@quot.po | 4 +- po/eo.gmo | Bin 116790 -> 116790 bytes po/eo.po | 2 +- po/es.gmo | Bin 133409 -> 133409 bytes po/es.po | 2 +- po/et.gmo | Bin 12257 -> 12257 bytes po/et.po | 2 +- po/fi.gmo | Bin 120517 -> 120517 bytes po/fi.po | 2 +- po/fr.gmo | Bin 138545 -> 138545 bytes po/fr.po | 2 +- po/ga.gmo | Bin 62011 -> 62011 bytes po/ga.po | 2 +- po/hu.gmo | Bin 134317 -> 134317 bytes po/hu.po | 2 +- po/id.gmo | Bin 131500 -> 131500 bytes po/id.po | 2 +- po/ja.gmo | Bin 145905 -> 145905 bytes po/ja.po | 2 +- po/lt.gmo | Bin 30079 -> 30079 bytes po/lt.po | 2 +- po/nl.gmo | Bin 131870 -> 131870 bytes po/nl.po | 2 +- po/pl.gmo | Bin 24983 -> 24983 bytes po/pl.po | 2 +- po/pt_BR.gmo | Bin 9658 -> 9658 bytes po/pt_BR.po | 2 +- po/ro.gmo | Bin 9415 -> 9415 bytes po/ro.po | 2 +- po/ru.gmo | Bin 9142 -> 9142 bytes po/ru.po | 2 +- po/sk.gmo | Bin 132621 -> 132621 bytes po/sk.po | 2 +- po/sv.gmo | Bin 128852 -> 128852 bytes po/sv.po | 2 +- po/tr.gmo | Bin 24589 -> 24589 bytes po/tr.po | 2 +- po/uk.gmo | Bin 138956 -> 138956 bytes po/uk.po | 2 +- po/vi.gmo | Bin 142862 -> 142862 bytes po/vi.po | 2 +- po/zh_CN.gmo | Bin 123267 -> 123267 bytes po/zh_CN.po | 2 +- po/zh_TW.gmo | Bin 5993 -> 5993 bytes po/zh_TW.po | 2 +- subst.c | 40 +- subst.c.save | 9392 +++++++++++++++++++++++++++++ subst.c~ | 47 +- tests/RUN-ONE-TEST | 2 +- 78 files changed, 9635 insertions(+), 70 deletions(-) create mode 100644 examples/scripts/bash-hexdump.sh~ create mode 100644 subst.c.save diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index 02ad0dec..c5d34c70 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -11009,3 +11009,15 @@ variables.c - change brand to set rseed to a known, constant value if it's 0, so the sequence is known. Fixes issue reported by Olivier Mehani + + 2/2 + --- +braces.c + - make sure to pass an `int' argument to asprintf in mkseq. Fixes + bug reported by Mike Frysinger + + 2/5 + --- +lib/glob/gmisc.c + - fix wmatchlen and umatchlen to initialize all state variables. Fix + from Andreas Schwab diff --git a/CWRU/CWRU.chlog~ b/CWRU/CWRU.chlog~ index 281ac5a5..32e95350 100644 --- a/CWRU/CWRU.chlog~ +++ b/CWRU/CWRU.chlog~ @@ -11002,3 +11002,16 @@ execute_cmd.c executing_builtin and executing_command_builtin before discarding the unwind-protect frame. Bug and fix from Werner Fink + + 1/24 + ---- +variables.c + - change brand to set rseed to a known, constant value if it's 0, + so the sequence is known. Fixes issue reported by Olivier + Mehani + + 2/2 + --- +braces.c + - make sure to pass an `int' argument to asprintf in mkseq. Fixes + bug reported by Mike Frysinger diff --git a/braces.c b/braces.c index 7c9e1289..2febed79 100644 --- a/braces.c +++ b/braces.c @@ -339,7 +339,7 @@ mkseq (start, end, incr, type, width) { int len, arg; arg = n; - len = asprintf (&t, "%0*d", width, n); + len = asprintf (&t, "%0*d", width, arg); result[i++] = t; } else diff --git a/doc/bash.pdf b/doc/bash.pdf index b23c7a94d1ef89a370807683c43463605322f2a3..5bcb0c55dad1dbf6bc874e41be45faeee51908dc 100644 GIT binary patch delta 11165 zcma)B2{f1K_b1;dLnLWa6cI)5z6gc3Ur1U+q{R}FU83bhN)grA+A)=qY1cw3)s&2; zg*GWlqLgYXQ(FJ`ZBl)o*E#?Bp5r*K=ehT}`{&;8*(9xVNm}b%dR|qh29!_8?73FU zJ#3gohRL59oov>vpFc#%($YA6{YqfeKUFoW0&B>%B?&KDs>?{zApPcXM-8s@niqbh zm3Q+FiCQyVXERx(Fu1#cbNz`>Tb&bRQ(If$-$RGVj!mw6v`p)qV{+?8J!5l?i}epK z6@6;EoEt^H(tV-UJN$;(<)Y$vAyK`LV?%1!XOE3^*{v$e9=})I_#szV)DTnHKPtMu zV5jG-`Z^V_jZMm)qZ`v7_-M4aS`-wXG*)@_>V5xc&$o^lSFRi*pT~GMkj7@FVIRUh zGY`K_KJ7NPv@yJI{JrBhT87_ZgM;=5JzBZ?&ZN78m>xB*H?gcKYfapYizq!xR>xze&gh+2#- znbs)#e8RqfHmM4z*;TVeds-Iz!6;EUqv$^_EF*I24*uN*^?c7K;X#Lng}QC|Bd&N| z>&)8b^X`=mPc{W^iZNbtXW`4TLbv1_tMeakUvW$S`dPXzlz(?2Y> znyM4&=6BS8Pk#51o=NVvFkLs#0ehQ`l$E#qGQvaK#n))Vq+qk6@ZVRJg*Z$x*9#ne z)z8E6LTp3B=jv-!3Nx3tMUg`lUYEYUi%l5Ybr})uuW4iv%_m2^n!9P*(S!zp?E_<@UpqyX~7>m$ks&*nX&`mhR)HI*07_ zoNv>5{d;KoZVi_G{>CRt)w=g_o#S&}zDyalwx+C9y^~-&I6ixM!Hy}u;R7a*D!S^Q z9N^yg`kD2DO0^1i@1D;3_BSJMZm&8sd(@(cCX3+1dAHYES{`zK=8)U$nA*6t(KuN{ ziS4_``^H**yLr={7arJkVbGIN2RG=w3-TV+<&lp4o`g=GX@&HKk$0OaeWQ)m&-R+{ zr|@oE>%#1S6#prtV7=zSrMK!^;?(h8ANI|RjVW0jdaJY|W@Z_29Fz&26vU;kyQGd+HyYoM=d6kBibse2b))?z-8^}=W=T%+ zBz*Km+ohpZDVDm6EW6RZcbjHAn4X%a-F5xo%P#2#w~OKuCe2B;(KfoCP`xPN*;~Ka zr_O|Ex;Sn86nJ1*ytm%3V}f#oZQH^=W<@w8C}oV_a{h4Pf`fYv73P}0c#fIzO}n{&pl2hzh}Wki;;_5?pn2k54Gw}DeLu5dgA3Vav@Q*?v3Ti z?VpAWdL7al5#L94NJ;5PeZN%AG3E(|Sh`Y($CAqXd)Qx;KLzWp>l`;?yiwDnZG!cK z%IK}jHVig@yb4=4jIdqqbWhz$*>y&9`84xMy@H(PHVKM7&s<*|o=v*4ed4cv{7fAlu=A3-%A9|^ zH|-6JJzO$v+dl^G$CbON%*bxs-q*=?oy;Iz!RElxm(_a7HD1$|9G-Np^{h3o^_)4( zeAcLWi`tqq2K(6TAyaDW9#;20G@_gGm7}|?ehE@oMMAf-JBl7Lfmk|(sf>RvR1`!eY)PY&I!;ybZ}C#;XNl;zsdpKuXQe( zT)HB(Vc7Fu_AVNqv_~P!H^((vvv1a<@?{z=57!mQOu8_+20L6_Dwdp%-7tac`d&}- zLe&Ij{i=YpT0Fs9*Y#{h$}?lk+j38r#Ntx5W4UXU`>Y?~8og*>b>@Z7>GvF3w`M&& z_)E&hYU?$TX?iL>1#kYSX~A$8cTGEculMnKc_upBoy+}>jVd4EaX^zeu`loMeIF*O zSSm+r+LaFM=d3g2pTyb`59~%N^Wn@QXHr|oH(e!4OCj%Hxh-6+QyM|M72 zy2ktR`@FakBcH81eNv}Qx&go3WU3C?Z|Ya|^!Iix4jN{hTbM$v5jHdiCXltGNB*x` zFV1>Gy?>KYrQTw@XCG6Wt<(8B3%6~H z{C({Z3+=Vdqh}6SwnVVY%rttLyH91+f){5F3HDF(3KVZ(b?N69&(GlXTTAWA3N~y~ zF!L!KQkbyX|H4z#TV`(xjRl9p!|I|2wXU_AIB>k>tFoKfK?;4k^;J{1x~4E7ZOz&R zn}f7;ZOp?gReQPqLC2mx5kuSee!Nmv^l8Q%^C7D@s1r}>8(W{ge%j0Bty;>Ixp~{; zH{T~x$2!c8RVhkP^&T2_Znud3fkx5le{|ITOmvFB#so!=XF>U_D$2D3D+iLhM! z;ji?giI!@cMg;A8J=;iE*U)La%Ew3B3}?D0)C9*_^z9e2+bbw<&H%mqUiS7M0)Ia> z5#P7n*L7@(>!MXt|ExAVdTK-A#6(hIiqgZ>JEgsiwT$+jv5wZgbUC(YC;x+xs z^MdbZ_88>U`|_ZW-Tz#T9&Gh2F>Xh<R^7(C51+&t)>>|e^PI3}*|D<;ch=3+*urkQ zut%1@v2&mL#A^q(?AO+8w%MA0VnF)*;<_gP?apOF$zLX>`Q97!iUWK1F2B7$Z1*LP zv*}hnZYEdgD5eLW+?!OF`X(jfe8J)K*_rptBTsEo%!vJxUln{TU`Aw&ZU4~|x@5EZ zV~=t71FsiG)63Iem*}rgo?T?)X*wlcnW-F>(1}zkE-UO4WK2$9MG@9TvpeI9f3+@q zYaQmlC7_=$$W<+@bB+;3ggv2pni^wk20gSD-npHuEKSQfh(|818x?YTYlTH>9BpS` z6Lc{2&W)!Y0XbzCUg*q;_T`R7yt=Zro88$#xBnVo?3`tksqC_xw+|e&JNn$_U1dJ~ zAD8FqIF%Rgtc=WiR5s^Ud8Xcx3tisd`V=JGzNK>Gx~sZyPQPIIf+eN7f9NG>44;_l zn2U|O-5MAfpY>s4Lhyb2R*OfET2K5vI*{nM!y59k-q|R#Z)^-#$o^$&exC`;9cN|E^39#l`p{A@#k|{? zX%Q+fPY&Ls6nitbn!J_xc3*RG+o*F76w><*e7|zy%C{AciRNh!-!%H!v?w^0lz0cO zs-0)^Wx(Ywj>j&ZDeE$o@ATvtDZTb^g40uX$f$_kU5e&%MB;%~01>#Fs~@Cq}6&!cQmoQ92f- zE*mm!IK#~$eK{O6V@MV^!*QRvW*B_Tc-D8W7wbKj^`S^({Y47`gTgIBynVA~dC7FL z8s^Fz4FAi`tf^iyJ5_?4N)Q$pYk_6uddcvg|Gw)b6aE_~!CPkg-%e_J$@Krz$zvuy zva^aN1V<1Ui}7X{ZH!^YCaS|7gS|)3iSQXR+JeAv9K*kz$kLuCbF*UgMFU~_LHerV zZ-V0}fScXB~j$tH4BM-zdmZor&Aw%OG2wiV-}; zA{qlf29wHQNJd`}^X5kaF&smou>cHt#H<)e;=CmKF}N*p6N_2~6yd&`KS=mDbAcfw zzWim3WC=6~K@&(5`Ew$TM`;YlGl&&LejI8ofr4~L1Or2^1HW@pzwd{Yu{%_%Ac#6)73zYQ` z7{sj@EtXt)`M?*)q_S`lr7=9DuKZo#M`EZKXqx;MV#1GD1R>HG!wE^%Wq1}1ct{@# zjXR9ycoI<#6d_nS8u$ZGEx7PQ5gs}w4AZZUmXlb(GG|CV(q0mAn z@$TO^*c!pwov)sjgd)g*YW|hqPq7UUM^f%Miozss=ZSXD{3sGQCn*vjghRuQg&dU& zS+Uk|Jh%bT4hjs5T7km>Hp!m@O?bpx(EV|OMl&4vq2UR2jphY;SA$mxXp^WA0xAST zV{r;c9wQb?0y&5A9Dz#|A-*^J@39SnaS}bq;xvkvM1~}i15i;p0*MciA9@#P1OgfX zYq2;WPkHS`hG@6J0clBtiy)+}56Yp`?>rhazz^_9mR7h-OXz?HDQz5tG`Lv;LmL5( zL6jh#!9|>iz6nhBErEVY0w78NB})+KV?>4=5;{0I3q?aDuNdhXE{5g0uhh#G(0T9tZB3Keb z{V6g;gB2&CA0ie6e!L``frjEgkU>xsGH?Jx@&_9I7mVU0&!E~t7X}c3Y6pXk#3dw! zb}y0NcgwU795^HdQXjxFWCys4g!_nKmZZ`65c!eFV{nS0&;kns2Vi%J2?2g1)fCLd zQmEg7p_B&mfyD15gbng&jue64SHSxNG!}p%g#tg3CaG{>D|B)a`Jt@>7id;Gt#BlbHaHMMQxd-eL_mTN zr&(zJ@}|Zah|BL9x9>(sMyvr}?f(NyK=@1i4#NfFJCPweV&QPJ1HUtp)aGED^`qGe z{|+21#iIBE7)rnn4ht0&Uw~y`)REU%q@kDtY9o)XUT{WWBoGZ}p)&t?;kQ*1De1}} zgkzBugY}2B+yW>}1i2_p+iDoztK0#v!_Dk{;ggD|*G*Z*V zjt&bBIiH9iiykJ|(GQ9M zIFXMd0@!`GV0%Z0)hds6Wv~ovC6UWOLxUBBz%l_j2mB-!#5oBvgpN*elEo)SNu~gT zWnm6Lg>Z~S2m#OpJqP?c$P)2^Xh#PKkNOE1N;HPa9|hq67GYLL9tbpaqz6UFAFbzG zM~CT1vZ4XdfOjASM=^|uQY9lW3p;T1F~AQk#DoCjlhph)3^Ec;2x*qEu;fI}L8Xu& zeG>4eq|5_cfxC|4JkeelKob&$fnY8e`%xi)l+a2@5jc?*7b|W`q_WxvxF4dRzDfoN zV1W*}P$B`MAmTZi>VVchzx~uwpeaZUNx^7^CKbg?!;XX2gPsH0kI?xBjo=Fqc| z6ksUP1ln+W)&0rf4BE@U36d-&Sn)N86`+(#iY^WHP(V=z2av1-n#BAp2ib=TfqIC# z6S@ovB;nDpe*X9FPb~$OB4`q@9DEQVz)q6&3?OIpF&s3}4rs{yCT>c2OaktKp=8a% zz*O7;L&-Ugk&as|#0;t&@Z%*dg<+wkBN_ukv{S>pib>Wn9IRfXccs0NW9aX9>F-}? zb_9Q+a7xmNh3|c`@)BfLv&I6?fNv6ENrl-E)|UV8iJ7X_ d)~ewVbHXAPh57ixcf=4HgzDhIW1PmS{vWy@HSGWZ delta 11149 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 R$}q0e9ImXZJIT&c`G5T0WPJbt diff --git a/doc/bash.ps b/doc/bash.ps index f5eaeb50..fb9aa8c5 100644 --- a/doc/bash.ps +++ b/doc/bash.ps @@ -1,6 +1,6 @@ %!PS-Adobe-3.0 %%Creator: groff version 1.19.2 -%%CreationDate: Mon Jan 10 10:31:48 2011 +%%CreationDate: Fri Jan 28 22:07:07 2011 %%DocumentNeededResources: font Times-Roman %%+ font Times-Bold %%+ font Times-Italic @@ -238,7 +238,7 @@ BP (bash \255 GNU Bourne-Ag)108 96 Q(ain SHell)-.05 E F1(SYNOPSIS)72 112.8 Q/F2 10/Times-Bold@0 SF(bash)108 124.8 Q F0([options] [\214le])2.5 E F1 (COPYRIGHT)72 141.6 Q F0(Bash is Cop)108 153.6 Q -(yright \251 1989-2010 by the Free Softw)-.1 E(are F)-.1 E +(yright \251 1989-2011 by the Free Softw)-.1 E(are F)-.1 E (oundation, Inc.)-.15 E F1(DESCRIPTION)72 170.4 Q F2(Bash)108 182.4 Q F0 .973(is an)3.474 F F2(sh)3.473 E F0 .973 (-compatible command language interpreter that e)B -.15(xe)-.15 G .973 diff --git a/doc/bashref.dvi b/doc/bashref.dvi index 6908dff9f6387981396359dc274d0560ccd25d45..0c293893b2afcdc218a5665e08d891d071d1289e 100644 GIT binary patch delta 77 zcmdmRSaZW+%?ZjpMiy2^Mh52H3=9m6(-prmt85IO!OUpb{D!&x4KpJUGXXJ(%>u-% VK+Fcj>_E%`#GKpTFmoM=1pxj28!i9< delta 77 zcmdmRSaZW+%?Zjph6YxK2F8Zn3=9m6(-prmt85IO!OUpT{D!&x4KpJUGXXJ(%>u-% VK+Fcj>_E%`#GKpTFmoM=1pxW{8y)}v diff --git a/doc/bashref.log b/doc/bashref.log index 3798cd43..069cde6b 100644 --- a/doc/bashref.log +++ b/doc/bashref.log @@ -1,4 +1,4 @@ -This is TeX, Version 3.141592 (Web2C 7.5.4) (format=tex 2008.12.11) 10 JAN 2011 10:31 +This is TeX, Version 3.141592 (Web2C 7.5.4) (format=tex 2008.12.11) 28 JAN 2011 22:07 **/Users/chet/src/bash/src/doc/bashref.texi (/Users/chet/src/bash/src/doc/bashref.texi (./texinfo.tex Loading texinfo [version 2009-01-18.17]: diff --git a/doc/bashref.pdf b/doc/bashref.pdf index 70c5b012a9048d4c83cff1cd2f53e3a36cb95dab..dbec106c975a6ae4f01ea670f4c1f1ebde7c92b8 100644 GIT binary patch delta 41251 zcmYhhb8se3v;-R4wl>zrwrz7`TN{3{?POy+`C{9)ZQFdm`|hoJZ>pwh>QvAC*FC4Y z&(aau;t^ThEjXf-g0_^l7M-ZPh!!(T5(*JT0~v%rIGmD*riixUe`S>Z9)IqMKLlz}dr3*9t+Dk2(` z@a;H4T5zxUKbChj4aCnl-Swd@QT4>_&K`J|5|-f(wl~zt>r!9|EhF>II9KaPY!b+Y zJ?pJe45(GlK%1D5()x5T5{;=*fS{X_JzU}>yfLWFdOpO&F!j=5Tsy*JE)~ENsx0*M z3+WA`*L88mwlE3F8GKr7PTdRi!A4OUA^q$N@0c8Hatuxzb!wBmkz9UZ1!X~d{Jj0D zu12@fr410^&q^UHZu7wj! zuRj0DaNA~rZXP2nALa53GWe`20gdskjFo-w(q(O#%l0FVCoJ{=#9GO## zm5}6qe)DWc-<4tYEF09`nw zPX3^THqC8Qhj#ekq7=bxPn9a-WnwRSL*b50%|%W}C@{g_kLu5Y=nJA0-w+QZN2p>y zR0L&%fAd}tBn!je$D^44JbLa*%f@kvR}1SDL_+_xlwzBw0*F?HWl$U0i=y*>Ut2Lg zYdx3#I=0$Cq&N0^13)291N%B=(~B`PT53i=#U0C**_zH6%dA*_F;cv@@XM+MDbFo$ zV>0qS6bj8Oex|IF^5G*Eb+M}Z2?ua5{@Dh!wTTpwwmM z)kP&5{G9F(ku{Z7L`DCHsj6~nYRpV6j*w7ZU_q||VXpz8Ew2G!iEU8QYGM+?YGLub z;QwuimUv#sr7=hm1vM?r7HdeT3K%F6aRoUQnHD@Gs3UNA31t}xN$vmPIQRb$zvVX? zR0|i>{|Z9Or3@4n7_HjAYX##^#R4t(X zP-mb}*lOsD@r5n70Z`q-5bEFcq5pehXaLIo|Folpdk_jA3`#>nN?RknMQ0eQnh)X# zkL)6CZs@1 z6}lfF2DvXsGKhjDsuh{;j{>$2Cdv|=FeaBSx2mVD2WKfPhEdd7*Q$9QS_V^KnIKOG zM;k@7Nb3b6Ok-J$+L&Yf^Z00}Xz@My(sk4M(zer8ve4y2|Hi_C<3<6yj%ME9EJX6C z4JHa=E`p${3K~MjCV@5$6SEGMJ`na_a`-y`4TS%K6DP$%G1s)oJkTg$% zlVSp4vh7XnK^6>=mESB^{YfpJmn<^?X>P-H0)ki^O@3=hA}WgWAXa3~t~PKV2mA(v zTK{dW#2XP0-}n98%@bHag$$JhIL5;jXG+kJkegA9BecBdi#m#^g_dIusC(e`G1KJ6 zA5hl?S7*dydQ0`H)o5YRKJ;5!QNsv3V)Cp?!mc@DOua%0AA8F1Wrq+Q6UVwsJ&7;{ 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 delta 41199 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 y1^Yj>P% 7 )) diff --git a/examples/scripts/bash-hexdump.sh~ b/examples/scripts/bash-hexdump.sh~ new file mode 100644 index 00000000..421b45ce --- /dev/null +++ b/examples/scripts/bash-hexdump.sh~ @@ -0,0 +1,69 @@ +#From: "dennis" +#To: +#Subject: New example script: bash-hexdump +#Date: Mon, 4 Jan 2010 22:48:19 -0700 +#Message-ID: <6dbec42d$64fcdbd2$4a32cf2d$@com> + +#I've written a script that functions like "hexdump -C" or "hd". If you'd +#like to include it in a future distribution of example Bash scripts, I have +#included it here: + +#!/bin/bash +# bash-hexdump# pure Bash, no externals +# by Dennis Williamson - 2010-01-04 +# in response to +http://stackoverflow.com/questions/2003803/show-hexadecimal-numbers-of-a-fil +e +# usage: bash-hexdump file +saveIFS="$IFS" +IFS="" # disables interpretation of \t, \n and space +saveLANG="$LANG" +LANG=C # allows characters > 0x7F +bytecount=0 +valcount=0 +printf "%08x " $bytecount +while read -d '' -r -n 1 char # -d '' allows newlines, -r allows \ +do + ((bytecount++)) # for information about the apostrophe in this printf +command, see # +http://www.opengroup.org/onlinepubs/009695399/utilities/printf.html + printf -v val "%02x" "'$char" + echo -n "$val " + ((valcount++)) + if [[ "$val" < 20 || "$val" > 7e ]] + then + string+="." # show unprintable characters as a dot + else + string+=$char + fi + if (( bytecount % 8 == 0 )) # add a space down the middle + then + echo -n " " + fi + if (( bytecount % 16 == 0 )) # print 16 values per line + then + echo "|$string|" + string='' + valcount=0 + printf "%08x " $bytecount + fi +done < "$1" + +if [[ "$string" != "" ]] # if the last line wasn't full, pad it +out +then + length=${#string} + if (( length > 7 )) + then + ((length--)) + fi + (( length += (16 - valcount) * 3 + 4)) + printf "%${length}s\n" "|$string|" + printf "%08x " $bytecount +fi +echo + +LANG="$saveLANG"; +IFS="$saveIFS" + +exit 0 diff --git a/lib/glob/gmisc.c b/lib/glob/gmisc.c index 6547af43..84794cd1 100644 --- a/lib/glob/gmisc.c +++ b/lib/glob/gmisc.c @@ -83,7 +83,7 @@ wmatchlen (wpat, wmax) if (*wpat == 0) return (0); - matlen = 0; + matlen = in_cclass = in_collsym = in_equiv = 0; while (wc = *wpat++) { switch (wc) @@ -219,7 +219,7 @@ umatchlen (pat, max) if (*pat == 0) return (0); - matlen = 0; + matlen = in_cclass = in_collsym = in_equiv = 0; while (c = *pat++) { switch (c) diff --git a/lib/glob/gmisc.c~ b/lib/glob/gmisc.c~ index 79de6a36..6547af43 100644 --- a/lib/glob/gmisc.c~ +++ b/lib/glob/gmisc.c~ @@ -60,13 +60,13 @@ match_pattern_wchar (wpat, wstring) case L'\\': return (*wstring == *wpat); case L'?': - return (*wpat == LPAREN ? 1 : (*wstring != L'\0')); + return (*wpat == WLPAREN ? 1 : (*wstring != L'\0')); case L'*': return (1); case L'+': case L'!': case L'@': - return (*wpat == LPAREN ? 1 : (*wstring == wc)); + return (*wpat == WLPAREN ? 1 : (*wstring == wc)); case L'[': return (*wstring != L'\0'); } @@ -101,7 +101,7 @@ wmatchlen (wpat, wmax) } break; case L'?': - if (*wpat == LPAREN) + if (*wpat == WLPAREN) return (matlen = -1); /* XXX for now */ else matlen++; @@ -111,7 +111,7 @@ wmatchlen (wpat, wmax) case L'+': case L'!': case L'@': - if (*wpat == LPAREN) + if (*wpat == WLPAREN) return (matlen = -1); /* XXX for now */ else matlen++; @@ -227,7 +227,7 @@ umatchlen (pat, max) default: matlen++; break; - case L'\\': + case '\\': if (*pat == 0) return ++matlen; else @@ -236,23 +236,23 @@ umatchlen (pat, max) pat++; } break; - case L'?': + case '?': if (*pat == LPAREN) return (matlen = -1); /* XXX for now */ else matlen++; break; - case L'*': + case '*': return (matlen = -1); - case L'+': - case L'!': - case L'@': + case '+': + case '!': + case '@': if (*pat == LPAREN) return (matlen = -1); /* XXX for now */ else matlen++; break; - case L'[': + case '[': /* scan for ending `]', skipping over embedded [:...:] */ brack = pat; c = *pat++; diff --git a/lib/glob/smatch.c b/lib/glob/smatch.c index bd2c3d59..061142be 100644 --- a/lib/glob/smatch.c +++ b/lib/glob/smatch.c @@ -1,7 +1,7 @@ /* strmatch.c -- ksh-like extended pattern matching for the shell and filename globbing. */ -/* Copyright (C) 1991-2005 Free Software Foundation, Inc. +/* Copyright (C) 1991-2011 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -241,6 +241,8 @@ is_cclass (c, name) # define STREQ(s1, s2) ((wcscmp (s1, s2) == 0)) # define STREQN(a, b, n) ((a)[0] == (b)[0] && wcsncmp(a, b, n) == 0) +extern char *mbsmbchar __P((const char *)); + static int rangecmp_wc (c1, c2) wint_t c1, c2; @@ -314,7 +316,7 @@ is_wcclass (wc, name) memset (&state, '\0', sizeof (mbstate_t)); mbs = (char *) malloc (wcslen(name) * MB_CUR_MAX + 1); - mbslength = wcsrtombs(mbs, (const wchar_t **)&name, (wcslen(name) * MB_CUR_MAX + 1), &state); + mbslength = wcsrtombs (mbs, (const wchar_t **)&name, (wcslen(name) * MB_CUR_MAX + 1), &state); if (mbslength == (size_t)-1 || mbslength == (size_t)-2) { diff --git a/lib/glob/smatch.c~ b/lib/glob/smatch.c~ index 0e480245..91492ed4 100644 --- a/lib/glob/smatch.c~ +++ b/lib/glob/smatch.c~ @@ -1,7 +1,7 @@ /* strmatch.c -- ksh-like extended pattern matching for the shell and filename globbing. */ -/* Copyright (C) 1991-2005 Free Software Foundation, Inc. +/* Copyright (C) 1991-2011 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -30,6 +30,8 @@ #include "shmbutil.h" #include "xmalloc.h" +#include "externs.h" + /* First, compile `sm_loop.c' for single-byte characters. */ #define CHAR unsigned char #define U_CHAR unsigned char @@ -241,6 +243,8 @@ is_cclass (c, name) # define STREQ(s1, s2) ((wcscmp (s1, s2) == 0)) # define STREQN(a, b, n) ((a)[0] == (b)[0] && wcsncmp(a, b, n) == 0) +extern char *mbsmbchar __P((const char *)); + static int rangecmp_wc (c1, c2) wint_t c1, c2; @@ -314,7 +318,7 @@ is_wcclass (wc, name) memset (&state, '\0', sizeof (mbstate_t)); mbs = (char *) malloc (wcslen(name) * MB_CUR_MAX + 1); - mbslength = wcsrtombs(mbs, (const wchar_t **)&name, (wcslen(name) * MB_CUR_MAX + 1), &state); + mbslength = wcsrtombs (mbs, (const wchar_t **)&name, (wcslen(name) * MB_CUR_MAX + 1), &state); if (mbslength == (size_t)-1 || mbslength == (size_t)-2) { @@ -367,9 +371,13 @@ xstrmatch (pattern, string, flags) wchar_t *wpattern, *wstring; size_t plen, slen, mplen, mslen; +#if 0 plen = strlen (pattern); mplen = mbstrlen (pattern); if (plen == mplen && strlen (string) == mbstrlen (string)) +#else + if (mbsmbchar (string) == 0 && mbsmbchar (pattern) == 0) +#endif return (internal_strmatch ((unsigned char *)pattern, (unsigned char *)string, flags)); if (MB_CUR_MAX == 1) diff --git a/po/af.gmo b/po/af.gmo index b8b6241dcec4cdf1bb1875c8bb01de80cda7e845..338cbdd723cd20f35b443f02d60a20bcf1595859 100644 GIT binary patch delta 14 VcmX@ld7g8_DJDkC&8L}Im;fve1nmF- delta 14 VcmX@ld7g8_DJDkq&8L}Im;fvS1nU3* diff --git a/po/af.po b/po/af.po index e618af9c..a15e987d 100644 --- a/po/af.po +++ b/po/af.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: bash 2.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2004-03-17 13:48+0200\n" "Last-Translator: Petri Jooste \n" "Language-Team: Afrikaans \n" diff --git a/po/bash.pot b/po/bash.pot index 70afa15d..e54dabd4 100644 --- a/po/bash.pot +++ b/po/bash.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/po/bg.gmo b/po/bg.gmo index c8cef79063ff247fc8bef0574f0f76136360d411..d6a7faf6373e6f88bd3b7aace79857d49910b786 100644 GIT binary patch delta 16 YcmbO;foaYJrVSnDjFy`_%_j!{05_QiTmS$7 delta 16 YcmbO;foaYJrVSnDjOLp=%_j!{05^>WS^xk5 diff --git a/po/bg.po b/po/bg.po index 7d45942f..757a0343 100644 --- a/po/bg.po +++ b/po/bg.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bash 3.2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2007-07-26 07:18+0300\n" "Last-Translator: Alexander Shopov \n" "Language-Team: Bulgarian \n" diff --git a/po/ca.gmo b/po/ca.gmo index a308de2b343cef0cf0ec53ac4a9dcce62b28a705..2e1552b4aed058c3ed05d6a81c442fe9a5fe118b 100644 GIT binary patch delta 14 VcmccZbK7S_oEW3!=6JC``~WcP1>gVx delta 14 VcmccZbK7S_oEW3|=6JC``~WcD1>OJv diff --git a/po/ca.po b/po/ca.po index 38ed93a8..a707980e 100644 --- a/po/ca.po +++ b/po/ca.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bash-2.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2003-12-28 19:59+0100\n" "Last-Translator: Montxo Vicente i Sempere \n" "Language-Team: Catalan \n" diff --git a/po/cs.gmo b/po/cs.gmo index 6d7520383277409985cc0ba84c0462b465918afe..b2d64a085b33a6c35fec622fcb45cb3168ea85e7 100644 GIT binary patch delta 23 fcmX>zgX7E$j)pCaH@Fxr+i!9)ZokRJRLlqfb!7;p delta 23 fcmX>zgX7E$j)pCaH@Fzh+i!9)ZokRJRLlqfbx#PR diff --git a/po/cs.po b/po/cs.po index c4e9c465..25f40437 100644 --- a/po/cs.po +++ b/po/cs.po @@ -13,7 +13,7 @@ msgid "" msgstr "" "Project-Id-Version: bash 4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2011-01-05 13:03+0100\n" "Last-Translator: Petr Pisar \n" "Language-Team: Czech \n" diff --git a/po/de.gmo b/po/de.gmo index a12476e04184420a5941a206f01ced57c258784d..61eb8bcc2598cc43da14346e5c30b0135359d851 100644 GIT binary patch delta 16 Xcmcccl\n" "Language-Team: German \n" diff --git a/po/en@boldquot.gmo b/po/en@boldquot.gmo index 8183c2f722be3fd5ea7dd0b23d7f8fb026f28779..bb64e1ffc7a8646176a13b224a9f99e5b8e5cf43 100644 GIT binary patch delta 30 mcmZpF&Ds8%vtbM4H6KRH>DPT2)fp|@|NAg*|L?=pp#cELCJZY8 delta 30 mcmZpF&Ds8%vtbM4H6KRv>DPT2)fvs(|NAg*|L?=pp#cEK>kQWb diff --git a/po/en@quot.po b/po/en@quot.po index 50547821..738d2a3e 100644 --- a/po/en@quot.po +++ b/po/en@quot.po @@ -29,8 +29,8 @@ msgid "" msgstr "" "Project-Id-Version: GNU bash 4.2-rc2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" -"PO-Revision-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" +"PO-Revision-Date: 2011-01-28 22:09-0500\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "MIME-Version: 1.0\n" diff --git a/po/eo.gmo b/po/eo.gmo index 14adef145e3a7c17872761d2c2e18911450698af..c1367bcafb2e4c4e4260365b4be8ebcebe286ae8 100644 GIT binary patch delta 16 YcmdlsfqmNq_6=9RGg@xG_C4VO07pv+0RR91 delta 16 YcmdlsfqmNq_6=9RGn#L{_C4VO07pLv{{R30 diff --git a/po/eo.po b/po/eo.po index 36d5abd6..ec26269f 100644 --- a/po/eo.po +++ b/po/eo.po @@ -20,7 +20,7 @@ msgid "" msgstr "" "Project-Id-Version: GNU bash 4.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2009-06-01 00:31+0600\n" "Last-Translator: Sergio Pokrovskij \n" "Language-Team: Esperanto \n" diff --git a/po/es.gmo b/po/es.gmo index 1ac1cc6d5eeef9bf5eba3fb636a542074b30022d..309e997582adbe0bfc954c28a4792da62eb307c4 100644 GIT binary patch delta 19 bcmZ2DiDTg;j)pCaceofWx8LPrbomDWO|S>8 delta 19 bcmZ2DiDTg;j)pCaceohMx8LPrbomDWO`->@ diff --git a/po/es.po b/po/es.po index dadbca19..34789338 100644 --- a/po/es.po +++ b/po/es.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: GNU bash 4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2010-01-04 12:56-0600\n" "Last-Translator: Cristian Othón Martínez Vera \n" "Language-Team: Spanish \n" diff --git a/po/et.gmo b/po/et.gmo index 45e6944684c2caa8adcaaba3744032f023257b3e..f020fcbeaf008a1fc6000bca4b78249b7bebf02f 100644 GIT binary patch delta 14 VcmaDD|1f^Ta#=>p%`0Rrg#b0q1@Zs@ delta 14 VcmaDD|1f^Ta#=?6%`0Rrg#b0e1@Hg> diff --git a/po/et.po b/po/et.po index 5a1543fe..7b2961ca 100644 --- a/po/et.po +++ b/po/et.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: bash 3.2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2006-11-11 16:38+0200\n" "Last-Translator: Toomas Soome \n" "Language-Team: Estonian \n" diff --git a/po/fi.gmo b/po/fi.gmo index ac95fd9cdac514f0c97bda3d45193781df3eacc5..3d35b85f3aa9e0f9e2aa72d7e0baf6d5044fe6a4 100644 GIT binary patch delta 16 YcmX@Qmi_2j_6--mGg@xG^!@Wa08I@E@&Et; delta 16 YcmX@Qmi_2j_6--mGn#L{^!@Wa08If2@Bjb+ diff --git a/po/fi.po b/po/fi.po index db9226f4..b6e67a7c 100644 --- a/po/fi.po +++ b/po/fi.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: bash-4.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2009-05-09 15:13+0300\n" "Last-Translator: Pekka Niemi \n" "Language-Team: Finnish \n" diff --git a/po/fr.gmo b/po/fr.gmo index 7b7228f1702a66ef6d05f7438035f1e294e26e16..a6025a639f0e2e577dd4b3ffbd2f9b153c654103 100644 GIT binary patch delta 23 fcmdnEi(}(1j)pCaH@Fxr+i!9)ZokRJ6d?ovb}0y{ delta 23 fcmdnEi(}(1j)pCaH@Fzh+i!9)ZokRJ6d?ovb`uDv diff --git a/po/fr.po b/po/fr.po index 7d7933a2..e209fb03 100644 --- a/po/fr.po +++ b/po/fr.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: bash-4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2010-04-10 13:44+0100\n" "Last-Translator: Christophe Combelles \n" "Language-Team: French \n" diff --git a/po/ga.gmo b/po/ga.gmo index 4bed3c9b82053cf8e41c43034462219c31fc8071..d9152837264878ce9c9bc2842847b21e16c79230 100644 GIT binary patch delta 16 Ycmdn}gn9Q9<_&&LjFy}Io8IID07WSW-T(jq delta 16 Ycmdn}gn9Q9<_&&LjOLsDo8IID07V@K+yDRo diff --git a/po/ga.po b/po/ga.po index f30f7d9b..00ff6938 100644 --- a/po/ga.po +++ b/po/ga.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bash 4.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2009-09-24 23:08+0100\n" "Last-Translator: Séamus Ó Ciardhuáin \n" "Language-Team: Irish \n" diff --git a/po/hu.gmo b/po/hu.gmo index 7a637613952d48866c7a192d6286e0c5ea12cbaa..c3f005a7451aa83454e7a8f5acf7933afba59cd0 100644 GIT binary patch delta 23 fcmZ2GlVj~nj)pCaH@Fxr+i!9)ZokRJbcGQBaT^HA delta 23 fcmZ2GlVj~nj)pCaH@Fzh+i!9)ZokRJbcGQBaRms- diff --git a/po/hu.po b/po/hu.po index 9e9f4351..c9e96371 100644 --- a/po/hu.po +++ b/po/hu.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bash-4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2010-08-06 17:44+0200\n" "Last-Translator: Mate Ory \n" "Language-Team: Hungarian \n" diff --git a/po/id.gmo b/po/id.gmo index 8c708b509d2efa3c8ee37e5c4a931747c89a61d0..d8d80c8848e3aeccc91a639def427fd439fa818e 100644 GIT binary patch delta 19 bcmZ3}%(14KqhSl<4K7B@?Kim?FMk67P8\n" "Language-Team: Indonesian \n" diff --git a/po/ja.gmo b/po/ja.gmo index bfc9353d6ddb6c5a0a95ecd5c8942d3ed866749c..f1d41c317bb240c13ad881ae0988396d66765638 100644 GIT binary patch delta 23 fcmezPnd9SUj)pCaceofW+wXEQZokXLB%%)hiVq2Z delta 23 fcmezPnd9SUj)pCaceohM+wXEQZokXLB%%)hiTMeB diff --git a/po/ja.po b/po/ja.po index 548a24b2..d6abbcdf 100644 --- a/po/ja.po +++ b/po/ja.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: GNU bash 4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2010-10-17 19:38+0900\n" "Last-Translator: Yasuaki Taniguchi \n" "Language-Team: Japanese \n" diff --git a/po/lt.gmo b/po/lt.gmo index da7e685aad1afdfd9d2b0425f6b59ec16d514a3f..ea18836182ee1d77bf8e8b9f84503359f950467c 100644 GIT binary patch delta 16 YcmezWit+y|#tjo)7%ewXa(Qb208PyZIsgCw delta 16 YcmezWit+y|#tjo)7|l0Na(Qb208PONH~;_u diff --git a/po/lt.po b/po/lt.po index f3e40af0..058c614e 100644 --- a/po/lt.po +++ b/po/lt.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bash-4.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2009-03-25 16:49+0200\n" "Last-Translator: Gintautas Miliauskas \n" "Language-Team: Lithuanian \n" diff --git a/po/nl.gmo b/po/nl.gmo index a3240109de5d6785be39f94d7a679e7b2b4a608d..2530a1250c0508d808e5b9546630541f11451a01 100644 GIT binary patch delta 19 bcmbQ vrqhSl<4K7B@?Kim?U4H-oOM3@` delta 19 bcmbQ vrqhSl<4K7CW?Kim?U4H-oOKk^$ diff --git a/po/nl.po b/po/nl.po index 1a72ea59..0f87ec65 100644 --- a/po/nl.po +++ b/po/nl.po @@ -23,7 +23,7 @@ msgid "" msgstr "" "Project-Id-Version: bash-4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2010-04-20 21:06+0200\n" "Last-Translator: Benno Schulenberg \n" "Language-Team: Dutch \n" diff --git a/po/pl.gmo b/po/pl.gmo index c0c02c57f208e361a8e34acc9e94999b0e949820..bf26b62623d0670eebc03e9d589cf215f05ae146 100644 GIT binary patch delta 16 YcmbP!m~r}H#tp~J87(&-H@~0>06)nGNB{r; delta 16 YcmbP!m~r}H#tp~J8O=8zH@~0>06)D4MgRZ+ diff --git a/po/pl.po b/po/pl.po index a1bf0d10..75c44899 100644 --- a/po/pl.po +++ b/po/pl.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bash 3.2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2007-11-30 08:49+0100\n" "Last-Translator: Andrzej M. Krzysztofowicz \n" "Language-Team: Polish \n" diff --git a/po/pt_BR.gmo b/po/pt_BR.gmo index 90fb5f851930445001943354e1937ed97a0920df..1d1f8677409d9e42affd19b45695d845f9b66992 100644 GIT binary patch delta 14 Vcmdnxy~}$;oEW3!=6JDaegG^t1p5F0 delta 14 Vcmdnxy~}$;oEW3|=6JDaegG^h1o;2} diff --git a/po/pt_BR.po b/po/pt_BR.po index 52ca7361..6c0e3f6b 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: bash 2.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2002-05-08 13:50GMT -3\n" "Last-Translator: Halley Pacheco de Oliveira \n" "Language-Team: Brazilian Portuguese \n" diff --git a/po/ro.gmo b/po/ro.gmo index 3a65c8dd9cd63360b0a83fea05dfd1ac77f39d40..d19197aecb26063997ca97fade1159047bd99c7c 100644 GIT binary patch delta 14 VcmX@^dE9fuYB5I3&1=M@`2aA%1!@2Q delta 14 VcmX@^dE9fuYB5Ih&1=M@`2aAr1!w>O diff --git a/po/ro.po b/po/ro.po index b31658d6..5b699980 100644 --- a/po/ro.po +++ b/po/ro.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: bash 2.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 1997-08-17 18:42+0300\n" "Last-Translator: Eugen Hoanca \n" "Language-Team: Romanian \n" diff --git a/po/ru.gmo b/po/ru.gmo index 43aeb6ed03484bc97c141aaa3b63c0c81582163c..c43e01cb648c9e04d3f5dc263321eea14733f46a 100644 GIT binary patch delta 14 VcmdnyzRi6@uLz^%<~|V(egG|21rh)N delta 14 VcmdnyzRi6@uLz_0<~|V(egG{>1rPuL diff --git a/po/ru.po b/po/ru.po index 597d2e03..fb4ca297 100644 --- a/po/ru.po +++ b/po/ru.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: GNU bash 3.1-release\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2006-01-05 21:28+0300\n" "Last-Translator: Evgeniy Dushistov \n" "Language-Team: Russian \n" diff --git a/po/sk.gmo b/po/sk.gmo index f1720accb1851129483645d3da7c23636f28dcee..6d4b62059603a959da3442cfb69497f7eb0729c1 100644 GIT binary patch delta 19 acmeC};ppw*XxPGdgNxB}`%NxJlivVI&j(%r delta 19 acmeC};ppw*XxPGdgNxC8`%NxJlivVI!3SIb diff --git a/po/sk.po b/po/sk.po index 3748eeff..3522036f 100644 --- a/po/sk.po +++ b/po/sk.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bash 4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2010-01-07 19:18+0100\n" "Last-Translator: Ivan Masár \n" "Language-Team: Slovak \n" diff --git a/po/sv.gmo b/po/sv.gmo index f5f2216cd713fab21c203519c5283900f0fedbd7..e890373a3aa7c9f24cfc53fbb977f7f6df62620e 100644 GIT binary patch delta 19 bcmccej{V9z_J%EtH@Fxrx8LMqtbPLkV1@|h delta 19 bcmccej{V9z_J%EtH@Fzhx8LMqtbPLkV0Z}R diff --git a/po/sv.po b/po/sv.po index 1ca2fb0d..1ad6ad18 100644 --- a/po/sv.po +++ b/po/sv.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: bash 4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2010-10-24 22:35+0200\n" "Last-Translator: Göran Uddeborg \n" "Language-Team: Swedish \n" diff --git a/po/tr.gmo b/po/tr.gmo index 6cd3880e8f44cf8ac2becba968811cfafb3ab009..d41c8171e3adfb01b2c3abbc85bf9937214132a5 100644 GIT binary patch delta 16 XcmeA@z}S0$al\n" "Language-Team: Turkish \n" diff --git a/po/uk.gmo b/po/uk.gmo index d25eb3035f9fe267bcd3a41406c199a4e2e3b00d..138d66ecdf0c8acf1b7c7031993d048822fc9f67 100644 GIT binary patch delta 20 ccmX@Jm*dP{jt#}v87-Sju5T~7&KThi0Bt`B#{d8T delta 20 ccmX@Jm*dP{jt#}v8O@tZu5T~7&KThi0BtJ?#Q*>R diff --git a/po/uk.po b/po/uk.po index f9f09a94..098ba728 100644 --- a/po/uk.po +++ b/po/uk.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: bash 4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2010-06-01 14:53+0300\n" "Last-Translator: Maxim V. Dziumanenko \n" "Language-Team: Ukrainian \n" diff --git a/po/vi.gmo b/po/vi.gmo index dcdf6beb3eab3a430f15cb3a450715079ff6809e..32b989046f4d9b7ea9637b32b71bc2c3f556b4cd 100644 GIT binary patch delta 23 fcmeCX!_jw#qhSl<4K7B@_M2Rc+i!9)nJNPSb%h9( delta 23 fcmeCX!_jw#qhSl<4K7CW_M2Rc+i!9)nJNPSb#Dlh diff --git a/po/vi.po b/po/vi.po index a07d0448..2f4484e5 100644 --- a/po/vi.po +++ b/po/vi.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bash 4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2010-02-11 19:42+0930\n" "Last-Translator: Clytie Siddall \n" "Language-Team: Vietnamese \n" diff --git a/po/zh_CN.gmo b/po/zh_CN.gmo index 52bed5e18fd423dbd746b1d695c10d1d4b9e58cb..2ccc4b1e061bccda925b05bfdb7d45b810916649 100644 GIT binary patch delta 19 bcmZoZ%-(#My\n" "Language-Team: Chinese (simplified) \n" diff --git a/po/zh_TW.gmo b/po/zh_TW.gmo index cd374b375d272d45a4d0c4e993df699368b8f59f..b965d5f729935ca96dae5c1138978eeda7a417b9 100644 GIT binary patch delta 14 VcmaE<_fl^|Egz%h<~qLD8~`wx1=aun delta 14 VcmaE<_fl^|Egz%#<~qLD8~`wl1=Iil diff --git a/po/zh_TW.po b/po/zh_TW.po index e4c819f1..38de844e 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bash-3.2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-28 22:07-0500\n" +"POT-Creation-Date: 2011-01-28 22:09-0500\n" "PO-Revision-Date: 2008-08-20 20:12+0800\n" "Last-Translator: Zi-You Dai \n" "Language-Team: Chinese (traditional) \n" diff --git a/subst.c b/subst.c index 0e83e56e..23073708 100644 --- a/subst.c +++ b/subst.c @@ -2884,17 +2884,33 @@ do_assignment_no_expand (string) WORD_LIST * list_rest_of_args () { - register WORD_LIST *list, *args; + register WORD_LIST *list, *args, *last, *l; + WORD_DESC *w; int i; /* Break out of the loop as soon as one of the dollar variables is null. */ + list = last = 0; for (i = 1, list = (WORD_LIST *)NULL; i < 10 && dollar_vars[i]; i++) - list = make_word_list (make_bare_word (dollar_vars[i]), list); + { + w = make_bare_word (dollar_vars[i]); + l = make_word_list (w, (WORD_LIST *)NULL); + if (list == 0) + list = last = l; + else + { + last->next = l; + last = l; + } + } for (args = rest_of_args; args; args = args->next) - list = make_word_list (make_bare_word (args->word->word), list); + { + w = make_bare_word (args->word->word); + last->next = make_word_list (w, (WORD_LIST *)NULL); + last = last->next; + } - return (REVERSE_LIST (list, WORD_LIST *)); + return list; } int @@ -7913,6 +7929,22 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin DECLARE_MBSTATE; + /* XXX - experimental -- short-circuit "$@" */ + if (STREQ (word->word, "$@") && + ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE)) || (word->flags & (W_DQUOTE|W_NOPROCSUB))) && + (word->flags & W_NOSPLIT) == 0 && + dollar_vars[1] && + ifs_value) + { + list = list_rest_of_args (); + quote_list (list); + if (expanded_something) + *expanded_something = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + return list; + } + istring = (char *)xmalloc (istring_size = DEFAULT_INITIAL_ARRAY_SIZE); istring[istring_index = 0] = '\0'; quoted_dollar_at = had_quoted_null = has_dollar_at = 0; diff --git a/subst.c.save b/subst.c.save new file mode 100644 index 00000000..0e83e56e --- /dev/null +++ b/subst.c.save @@ -0,0 +1,9392 @@ +/* subst.c -- The part of the shell that does parameter, command, arithmetic, + and globbing substitutions. */ + +/* ``Have a little faith, there's magic in the night. You ain't a + beauty, but, hey, you're alright.'' */ + +/* Copyright (C) 1987-2010 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#include +#include "chartypes.h" +#if defined (HAVE_PWD_H) +# include +#endif +#include +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include "posixstat.h" +#include "bashintl.h" + +#include "shell.h" +#include "parser.h" +#include "flags.h" +#include "jobs.h" +#include "execute_cmd.h" +#include "filecntl.h" +#include "trap.h" +#include "pathexp.h" +#include "mailcheck.h" + +#include "shmbutil.h" +#include "typemax.h" + +#include "builtins/getopt.h" +#include "builtins/common.h" + +#include "builtins/builtext.h" + +#include +#include + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +/* The size that strings change by. */ +#define DEFAULT_INITIAL_ARRAY_SIZE 112 +#define DEFAULT_ARRAY_SIZE 128 + +/* Variable types. */ +#define VT_VARIABLE 0 +#define VT_POSPARMS 1 +#define VT_ARRAYVAR 2 +#define VT_ARRAYMEMBER 3 +#define VT_ASSOCVAR 4 + +#define VT_STARSUB 128 /* $* or ${array[*]} -- used to split */ + +/* Flags for quoted_strchr */ +#define ST_BACKSL 0x01 +#define ST_CTLESC 0x02 +#define ST_SQUOTE 0x04 /* unused yet */ +#define ST_DQUOTE 0x08 /* unused yet */ + +/* Flags for the `pflags' argument to param_expand() */ +#define PF_NOCOMSUB 0x01 /* Do not perform command substitution */ +#define PF_IGNUNBOUND 0x02 /* ignore unbound vars even if -u set */ +#define PF_NOSPLIT2 0x04 /* same as W_NOSPLIT2 */ + +/* These defs make it easier to use the editor. */ +#define LBRACE '{' +#define RBRACE '}' +#define LPAREN '(' +#define RPAREN ')' + +#if defined (HANDLE_MULTIBYTE) +#define WLPAREN L'(' +#define WRPAREN L')' +#endif + +/* Evaluates to 1 if C is one of the shell's special parameters whose length + can be taken, but is also one of the special expansion characters. */ +#define VALID_SPECIAL_LENGTH_PARAM(c) \ + ((c) == '-' || (c) == '?' || (c) == '#') + +/* Evaluates to 1 if C is one of the shell's special parameters for which an + indirect variable reference may be made. */ +#define VALID_INDIR_PARAM(c) \ + ((posixly_correct == 0 && (c) == '#') || (posixly_correct == 0 && (c) == '?') || (c) == '@' || (c) == '*') + +/* Evaluates to 1 if C is one of the OP characters that follows the parameter + in ${parameter[:]OPword}. */ +#define VALID_PARAM_EXPAND_CHAR(c) (sh_syntaxtab[(unsigned char)c] & CSUBSTOP) + +/* Evaluates to 1 if this is one of the shell's special variables. */ +#define SPECIAL_VAR(name, wi) \ + ((DIGIT (*name) && all_digits (name)) || \ + (name[1] == '\0' && (sh_syntaxtab[(unsigned char)*name] & CSPECVAR)) || \ + (wi && name[2] == '\0' && VALID_INDIR_PARAM (name[1]))) + +/* An expansion function that takes a string and a quoted flag and returns + a WORD_LIST *. Used as the type of the third argument to + expand_string_if_necessary(). */ +typedef WORD_LIST *EXPFUNC __P((char *, int)); + +/* Process ID of the last command executed within command substitution. */ +pid_t last_command_subst_pid = NO_PID; +pid_t current_command_subst_pid = NO_PID; + +/* Variables used to keep track of the characters in IFS. */ +SHELL_VAR *ifs_var; +char *ifs_value; +unsigned char ifs_cmap[UCHAR_MAX + 1]; + +#if defined (HANDLE_MULTIBYTE) +unsigned char ifs_firstc[MB_LEN_MAX]; +size_t ifs_firstc_len; +#else +unsigned char ifs_firstc; +#endif + +/* Sentinel to tell when we are performing variable assignments preceding a + command name and putting them into the environment. Used to make sure + we use the temporary environment when looking up variable values. */ +int assigning_in_environment; + +/* Used to hold a list of variable assignments preceding a command. Global + so the SIGCHLD handler in jobs.c can unwind-protect it when it runs a + SIGCHLD trap and so it can be saved and restored by the trap handlers. */ +WORD_LIST *subst_assign_varlist = (WORD_LIST *)NULL; + +/* Extern functions and variables from different files. */ +extern int last_command_exit_value, last_command_exit_signal; +extern int subshell_environment, line_number; +extern int subshell_level, parse_and_execute_level, sourcelevel; +extern int eof_encountered; +extern int return_catch_flag, return_catch_value; +extern pid_t dollar_dollar_pid; +extern int posixly_correct; +extern char *this_command_name; +extern struct fd_bitmap *current_fds_to_close; +extern int wordexp_only; +extern int expanding_redir; +extern int tempenv_assign_error; + +#if !defined (HAVE_WCSDUP) && defined (HANDLE_MULTIBYTE) +extern wchar_t *wcsdup __P((const wchar_t *)); +#endif + +/* Non-zero means to allow unmatched globbed filenames to expand to + a null file. */ +int allow_null_glob_expansion; + +/* Non-zero means to throw an error when globbing fails to match anything. */ +int fail_glob_expansion; + +#if 0 +/* Variables to keep track of which words in an expanded word list (the + output of expand_word_list_internal) are the result of globbing + expansions. GLOB_ARGV_FLAGS is used by execute_cmd.c. + (CURRENTLY UNUSED). */ +char *glob_argv_flags; +static int glob_argv_flags_size; +#endif + +static WORD_LIST expand_word_error, expand_word_fatal; +static WORD_DESC expand_wdesc_error, expand_wdesc_fatal; +static char expand_param_error, expand_param_fatal; +static char extract_string_error, extract_string_fatal; + +/* Tell the expansion functions to not longjmp back to top_level on fatal + errors. Enabled when doing completion and prompt string expansion. */ +static int no_longjmp_on_fatal_error = 0; + +/* Set by expand_word_unsplit; used to inhibit splitting and re-joining + $* on $IFS, primarily when doing assignment statements. */ +static int expand_no_split_dollar_star = 0; + +/* A WORD_LIST of words to be expanded by expand_word_list_internal, + without any leading variable assignments. */ +static WORD_LIST *garglist = (WORD_LIST *)NULL; + +static char *quoted_substring __P((char *, int, int)); +static int quoted_strlen __P((char *)); +static char *quoted_strchr __P((char *, int, int)); + +static char *expand_string_if_necessary __P((char *, int, EXPFUNC *)); +static inline char *expand_string_to_string_internal __P((char *, int, EXPFUNC *)); +static WORD_LIST *call_expand_word_internal __P((WORD_DESC *, int, int, int *, int *)); +static WORD_LIST *expand_string_internal __P((char *, int)); +static WORD_LIST *expand_string_leave_quoted __P((char *, int)); +static WORD_LIST *expand_string_for_rhs __P((char *, int, int *, int *)); + +static WORD_LIST *list_quote_escapes __P((WORD_LIST *)); +static char *make_quoted_char __P((int)); +static WORD_LIST *quote_list __P((WORD_LIST *)); + +static int unquoted_substring __P((char *, char *)); +static int unquoted_member __P((int, char *)); + +#if defined (ARRAY_VARS) +static SHELL_VAR *do_compound_assignment __P((char *, char *, int)); +#endif +static int do_assignment_internal __P((const WORD_DESC *, int)); + +static char *string_extract_verbatim __P((char *, size_t, int *, char *, int)); +static char *string_extract __P((char *, int *, char *, int)); +static char *string_extract_double_quoted __P((char *, int *, int)); +static inline char *string_extract_single_quoted __P((char *, int *)); +static inline int skip_single_quoted __P((const char *, size_t, int)); +static int skip_double_quoted __P((char *, size_t, int)); +static char *extract_delimited_string __P((char *, int *, char *, char *, char *, int)); +static char *extract_dollar_brace_string __P((char *, int *, int, int)); +static int skip_matched_pair __P((const char *, int, int, int, int)); + +static char *pos_params __P((char *, int, int, int)); + +static unsigned char *mb_getcharlens __P((char *, int)); + +static char *remove_upattern __P((char *, char *, int)); +#if defined (HANDLE_MULTIBYTE) +static wchar_t *remove_wpattern __P((wchar_t *, size_t, wchar_t *, int)); +#endif +static char *remove_pattern __P((char *, char *, int)); + +static int match_upattern __P((char *, char *, int, char **, char **)); +#if defined (HANDLE_MULTIBYTE) +static int match_wpattern __P((wchar_t *, char **, size_t, wchar_t *, int, char **, char **)); +#endif +static int match_pattern __P((char *, char *, int, char **, char **)); +static int getpatspec __P((int, char *)); +static char *getpattern __P((char *, int, int)); +static char *variable_remove_pattern __P((char *, char *, int, int)); +static char *list_remove_pattern __P((WORD_LIST *, char *, int, int, int)); +static char *parameter_list_remove_pattern __P((int, char *, int, int)); +#ifdef ARRAY_VARS +static char *array_remove_pattern __P((SHELL_VAR *, char *, int, char *, int)); +#endif +static char *parameter_brace_remove_pattern __P((char *, char *, int, char *, int, int, int)); + +static char *process_substitute __P((char *, int)); + +static char *read_comsub __P((int, int, int *)); + +#ifdef ARRAY_VARS +static arrayind_t array_length_reference __P((char *)); +#endif + +static int valid_brace_expansion_word __P((char *, int)); +static int chk_atstar __P((char *, int, int *, int *)); +static int chk_arithsub __P((const char *, int)); + +static WORD_DESC *parameter_brace_expand_word __P((char *, int, int, int, arrayind_t *)); +static WORD_DESC *parameter_brace_expand_indir __P((char *, int, int, int *, int *)); +static WORD_DESC *parameter_brace_expand_rhs __P((char *, char *, int, int, int *, int *)); +static void parameter_brace_expand_error __P((char *, char *)); + +static int valid_length_expression __P((char *)); +static intmax_t parameter_brace_expand_length __P((char *)); + +static char *skiparith __P((char *, int)); +static int verify_substring_values __P((SHELL_VAR *, char *, char *, int, intmax_t *, intmax_t *)); +static int get_var_and_type __P((char *, char *, arrayind_t, int, int, SHELL_VAR **, char **)); +static char *mb_substring __P((char *, int, int)); +static char *parameter_brace_substring __P((char *, char *, int, char *, int, int)); + +static int shouldexp_replacement __P((char *)); + +static char *pos_params_pat_subst __P((char *, char *, char *, int)); + +static char *parameter_brace_patsub __P((char *, char *, int, char *, int, int)); + +static char *pos_params_casemod __P((char *, char *, int, int)); +static char *parameter_brace_casemod __P((char *, char *, int, int, char *, int, int)); + +static WORD_DESC *parameter_brace_expand __P((char *, int *, int, int, int *, int *)); +static WORD_DESC *param_expand __P((char *, int *, int, int *, int *, int *, int *, int)); + +static WORD_LIST *expand_word_internal __P((WORD_DESC *, int, int, int *, int *)); + +static WORD_LIST *word_list_split __P((WORD_LIST *)); + +static void exp_jump_to_top_level __P((int)); + +static WORD_LIST *separate_out_assignments __P((WORD_LIST *)); +static WORD_LIST *glob_expand_word_list __P((WORD_LIST *, int)); +#ifdef BRACE_EXPANSION +static WORD_LIST *brace_expand_word_list __P((WORD_LIST *, int)); +#endif +#if defined (ARRAY_VARS) +static int make_internal_declare __P((char *, char *)); +#endif +static WORD_LIST *shell_expand_word_list __P((WORD_LIST *, int)); +static WORD_LIST *expand_word_list_internal __P((WORD_LIST *, int)); + +/* **************************************************************** */ +/* */ +/* Utility Functions */ +/* */ +/* **************************************************************** */ + +#if defined (DEBUG) +void +dump_word_flags (flags) + int flags; +{ + int f; + + f = flags; + fprintf (stderr, "%d -> ", f); + if (f & W_ASSIGNASSOC) + { + f &= ~W_ASSIGNASSOC; + fprintf (stderr, "W_ASSIGNASSOC%s", f ? "|" : ""); + } + if (f & W_HASCTLESC) + { + f &= ~W_HASCTLESC; + fprintf (stderr, "W_HASCTLESC%s", f ? "|" : ""); + } + if (f & W_NOPROCSUB) + { + f &= ~W_NOPROCSUB; + fprintf (stderr, "W_NOPROCSUB%s", f ? "|" : ""); + } + if (f & W_DQUOTE) + { + f &= ~W_DQUOTE; + fprintf (stderr, "W_DQUOTE%s", f ? "|" : ""); + } + if (f & W_HASQUOTEDNULL) + { + f &= ~W_HASQUOTEDNULL; + fprintf (stderr, "W_HASQUOTEDNULL%s", f ? "|" : ""); + } + if (f & W_ASSIGNARG) + { + f &= ~W_ASSIGNARG; + fprintf (stderr, "W_ASSIGNARG%s", f ? "|" : ""); + } + if (f & W_ASSNBLTIN) + { + f &= ~W_ASSNBLTIN; + fprintf (stderr, "W_ASSNBLTIN%s", f ? "|" : ""); + } + if (f & W_COMPASSIGN) + { + f &= ~W_COMPASSIGN; + fprintf (stderr, "W_COMPASSIGN%s", f ? "|" : ""); + } + if (f & W_NOEXPAND) + { + f &= ~W_NOEXPAND; + fprintf (stderr, "W_NOEXPAND%s", f ? "|" : ""); + } + if (f & W_ITILDE) + { + f &= ~W_ITILDE; + fprintf (stderr, "W_ITILDE%s", f ? "|" : ""); + } + if (f & W_NOTILDE) + { + f &= ~W_NOTILDE; + fprintf (stderr, "W_NOTILDE%s", f ? "|" : ""); + } + if (f & W_ASSIGNRHS) + { + f &= ~W_ASSIGNRHS; + fprintf (stderr, "W_ASSIGNRHS%s", f ? "|" : ""); + } + if (f & W_NOCOMSUB) + { + f &= ~W_NOCOMSUB; + fprintf (stderr, "W_NOCOMSUB%s", f ? "|" : ""); + } + if (f & W_DOLLARSTAR) + { + f &= ~W_DOLLARSTAR; + fprintf (stderr, "W_DOLLARSTAR%s", f ? "|" : ""); + } + if (f & W_DOLLARAT) + { + f &= ~W_DOLLARAT; + fprintf (stderr, "W_DOLLARAT%s", f ? "|" : ""); + } + if (f & W_TILDEEXP) + { + f &= ~W_TILDEEXP; + fprintf (stderr, "W_TILDEEXP%s", f ? "|" : ""); + } + if (f & W_NOSPLIT2) + { + f &= ~W_NOSPLIT2; + fprintf (stderr, "W_NOSPLIT2%s", f ? "|" : ""); + } + if (f & W_NOGLOB) + { + f &= ~W_NOGLOB; + fprintf (stderr, "W_NOGLOB%s", f ? "|" : ""); + } + if (f & W_NOSPLIT) + { + f &= ~W_NOSPLIT; + fprintf (stderr, "W_NOSPLIT%s", f ? "|" : ""); + } + if (f & W_GLOBEXP) + { + f &= ~W_GLOBEXP; + fprintf (stderr, "W_GLOBEXP%s", f ? "|" : ""); + } + if (f & W_ASSIGNMENT) + { + f &= ~W_ASSIGNMENT; + fprintf (stderr, "W_ASSIGNMENT%s", f ? "|" : ""); + } + if (f & W_QUOTED) + { + f &= ~W_QUOTED; + fprintf (stderr, "W_QUOTED%s", f ? "|" : ""); + } + if (f & W_HASDOLLAR) + { + f &= ~W_HASDOLLAR; + fprintf (stderr, "W_HASDOLLAR%s", f ? "|" : ""); + } + fprintf (stderr, "\n"); + fflush (stderr); +} +#endif + +#ifdef INCLUDE_UNUSED +static char * +quoted_substring (string, start, end) + char *string; + int start, end; +{ + register int len, l; + register char *result, *s, *r; + + len = end - start; + + /* Move to string[start], skipping quoted characters. */ + for (s = string, l = 0; *s && l < start; ) + { + if (*s == CTLESC) + { + s++; + continue; + } + l++; + if (*s == 0) + break; + } + + r = result = (char *)xmalloc (2*len + 1); /* save room for quotes */ + + /* Copy LEN characters, including quote characters. */ + s = string + l; + for (l = 0; l < len; s++) + { + if (*s == CTLESC) + *r++ = *s++; + *r++ = *s; + l++; + if (*s == 0) + break; + } + *r = '\0'; + return result; +} +#endif + +#ifdef INCLUDE_UNUSED +/* Return the length of S, skipping over quoted characters */ +static int +quoted_strlen (s) + char *s; +{ + register char *p; + int i; + + i = 0; + for (p = s; *p; p++) + { + if (*p == CTLESC) + { + p++; + if (*p == 0) + return (i + 1); + } + i++; + } + + return i; +} +#endif + +/* Find the first occurrence of character C in string S, obeying shell + quoting rules. If (FLAGS & ST_BACKSL) is non-zero, backslash-escaped + characters are skipped. If (FLAGS & ST_CTLESC) is non-zero, characters + escaped with CTLESC are skipped. */ +static char * +quoted_strchr (s, c, flags) + char *s; + int c, flags; +{ + register char *p; + + for (p = s; *p; p++) + { + if (((flags & ST_BACKSL) && *p == '\\') + || ((flags & ST_CTLESC) && *p == CTLESC)) + { + p++; + if (*p == '\0') + return ((char *)NULL); + continue; + } + else if (*p == c) + return p; + } + return ((char *)NULL); +} + +/* Return 1 if CHARACTER appears in an unquoted portion of + STRING. Return 0 otherwise. CHARACTER must be a single-byte character. */ +static int +unquoted_member (character, string) + int character; + char *string; +{ + size_t slen; + int sindex, c; + DECLARE_MBSTATE; + + slen = strlen (string); + sindex = 0; + while (c = string[sindex]) + { + if (c == character) + return (1); + + switch (c) + { + default: + ADVANCE_CHAR (string, slen, sindex); + break; + + case '\\': + sindex++; + if (string[sindex]) + ADVANCE_CHAR (string, slen, sindex); + break; + + case '\'': + sindex = skip_single_quoted (string, slen, ++sindex); + break; + + case '"': + sindex = skip_double_quoted (string, slen, ++sindex); + break; + } + } + return (0); +} + +/* Return 1 if SUBSTR appears in an unquoted portion of STRING. */ +static int +unquoted_substring (substr, string) + char *substr, *string; +{ + size_t slen; + int sindex, c, sublen; + DECLARE_MBSTATE; + + if (substr == 0 || *substr == '\0') + return (0); + + slen = strlen (string); + sublen = strlen (substr); + for (sindex = 0; c = string[sindex]; ) + { + if (STREQN (string + sindex, substr, sublen)) + return (1); + + switch (c) + { + case '\\': + sindex++; + if (string[sindex]) + ADVANCE_CHAR (string, slen, sindex); + break; + + case '\'': + sindex = skip_single_quoted (string, slen, ++sindex); + break; + + case '"': + sindex = skip_double_quoted (string, slen, ++sindex); + break; + + default: + ADVANCE_CHAR (string, slen, sindex); + break; + } + } + return (0); +} + +/* Most of the substitutions must be done in parallel. In order + to avoid using tons of unclear goto's, I have some functions + for manipulating malloc'ed strings. They all take INDX, a + pointer to an integer which is the offset into the string + where manipulation is taking place. They also take SIZE, a + pointer to an integer which is the current length of the + character array for this string. */ + +/* Append SOURCE to TARGET at INDEX. SIZE is the current amount + of space allocated to TARGET. SOURCE can be NULL, in which + case nothing happens. Gets rid of SOURCE by freeing it. + Returns TARGET in case the location has changed. */ +INLINE char * +sub_append_string (source, target, indx, size) + char *source, *target; + int *indx, *size; +{ + if (source) + { + int srclen, n; + + srclen = STRLEN (source); + if (srclen >= (int)(*size - *indx)) + { + n = srclen + *indx; + n = (n + DEFAULT_ARRAY_SIZE) - (n % DEFAULT_ARRAY_SIZE); + target = (char *)xrealloc (target, (*size = n)); + } + + FASTCOPY (source, target + *indx, srclen); + *indx += srclen; + target[*indx] = '\0'; + + free (source); + } + return (target); +} + +#if 0 +/* UNUSED */ +/* Append the textual representation of NUMBER to TARGET. + INDX and SIZE are as in SUB_APPEND_STRING. */ +char * +sub_append_number (number, target, indx, size) + intmax_t number; + int *indx, *size; + char *target; +{ + char *temp; + + temp = itos (number); + return (sub_append_string (temp, target, indx, size)); +} +#endif + +/* Extract a substring from STRING, starting at SINDEX and ending with + one of the characters in CHARLIST. Don't make the ending character + part of the string. Leave SINDEX pointing at the ending character. + Understand about backslashes in the string. If (flags & SX_VARNAME) + is non-zero, and array variables have been compiled into the shell, + everything between a `[' and a corresponding `]' is skipped over. + If (flags & SX_NOALLOC) is non-zero, don't return the substring, just + update SINDEX. If (flags & SX_REQMATCH) is non-zero, the string must + contain a closing character from CHARLIST. */ +static char * +string_extract (string, sindex, charlist, flags) + char *string; + int *sindex; + char *charlist; + int flags; +{ + register int c, i; + int found; + size_t slen; + char *temp; + DECLARE_MBSTATE; + + slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 0; + i = *sindex; + found = 0; + while (c = string[i]) + { + if (c == '\\') + { + if (string[i + 1]) + i++; + else + break; + } +#if defined (ARRAY_VARS) + else if ((flags & SX_VARNAME) && c == '[') + { + int ni; + /* If this is an array subscript, skip over it and continue. */ + ni = skipsubscript (string, i, 0); + if (string[ni] == ']') + i = ni; + } +#endif + else if (MEMBER (c, charlist)) + { + found = 1; + break; + } + + ADVANCE_CHAR (string, slen, i); + } + + /* If we had to have a matching delimiter and didn't find one, return an + error and let the caller deal with it. */ + if ((flags & SX_REQMATCH) && found == 0) + { + *sindex = i; + return (&extract_string_error); + } + + temp = (flags & SX_NOALLOC) ? (char *)NULL : substring (string, *sindex, i); + *sindex = i; + + return (temp); +} + +/* Extract the contents of STRING as if it is enclosed in double quotes. + SINDEX, when passed in, is the offset of the character immediately + following the opening double quote; on exit, SINDEX is left pointing after + the closing double quote. If STRIPDQ is non-zero, unquoted double + quotes are stripped and the string is terminated by a null byte. + Backslashes between the embedded double quotes are processed. If STRIPDQ + is zero, an unquoted `"' terminates the string. */ +static char * +string_extract_double_quoted (string, sindex, stripdq) + char *string; + int *sindex, stripdq; +{ + size_t slen; + char *send; + int j, i, t; + unsigned char c; + char *temp, *ret; /* The new string we return. */ + int pass_next, backquote, si; /* State variables for the machine. */ + int dquote; + DECLARE_MBSTATE; + + slen = strlen (string + *sindex) + *sindex; + send = string + slen; + + pass_next = backquote = dquote = 0; + temp = (char *)xmalloc (1 + slen - *sindex); + + j = 0; + i = *sindex; + while (c = string[i]) + { + /* Process a character that was quoted by a backslash. */ + if (pass_next) + { + /* XXX - take another look at this in light of Interp 221 */ + /* Posix.2 sez: + + ``The backslash shall retain its special meaning as an escape + character only when followed by one of the characters: + $ ` " \ ''. + + If STRIPDQ is zero, we handle the double quotes here and let + expand_word_internal handle the rest. If STRIPDQ is non-zero, + we have already been through one round of backslash stripping, + and want to strip these backslashes only if DQUOTE is non-zero, + indicating that we are inside an embedded double-quoted string. */ + + /* If we are in an embedded quoted string, then don't strip + backslashes before characters for which the backslash + retains its special meaning, but remove backslashes in + front of other characters. If we are not in an + embedded quoted string, don't strip backslashes at all. + This mess is necessary because the string was already + surrounded by double quotes (and sh has some really weird + quoting rules). + The returned string will be run through expansion as if + it were double-quoted. */ + if ((stripdq == 0 && c != '"') || + (stripdq && ((dquote && (sh_syntaxtab[c] & CBSDQUOTE)) || dquote == 0))) + temp[j++] = '\\'; + pass_next = 0; + +add_one_character: + COPY_CHAR_I (temp, j, string, send, i); + continue; + } + + /* A backslash protects the next character. The code just above + handles preserving the backslash in front of any character but + a double quote. */ + if (c == '\\') + { + pass_next++; + i++; + continue; + } + + /* Inside backquotes, ``the portion of the quoted string from the + initial backquote and the characters up to the next backquote + that is not preceded by a backslash, having escape characters + removed, defines that command''. */ + if (backquote) + { + if (c == '`') + backquote = 0; + temp[j++] = c; + i++; + continue; + } + + if (c == '`') + { + temp[j++] = c; + backquote++; + i++; + continue; + } + + /* Pass everything between `$(' and the matching `)' or a quoted + ${ ... } pair through according to the Posix.2 specification. */ + if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE))) + { + int free_ret = 1; + + si = i + 2; + if (string[i + 1] == LPAREN) + ret = extract_command_subst (string, &si, 0); + else + ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, 0); + + temp[j++] = '$'; + temp[j++] = string[i + 1]; + + /* Just paranoia; ret will not be 0 unless no_longjmp_on_fatal_error + is set. */ + if (ret == 0 && no_longjmp_on_fatal_error) + { + free_ret = 0; + ret = string + i + 2; + } + + for (t = 0; ret[t]; t++, j++) + temp[j] = ret[t]; + temp[j] = string[si]; + + if (string[si]) + { + j++; + i = si + 1; + } + else + i = si; + + if (free_ret) + free (ret); + continue; + } + + /* Add any character but a double quote to the quoted string we're + accumulating. */ + if (c != '"') + goto add_one_character; + + /* c == '"' */ + if (stripdq) + { + dquote ^= 1; + i++; + continue; + } + + break; + } + temp[j] = '\0'; + + /* Point to after the closing quote. */ + if (c) + i++; + *sindex = i; + + return (temp); +} + +/* This should really be another option to string_extract_double_quoted. */ +static int +skip_double_quoted (string, slen, sind) + char *string; + size_t slen; + int sind; +{ + int c, i; + char *ret; + int pass_next, backquote, si; + DECLARE_MBSTATE; + + pass_next = backquote = 0; + i = sind; + while (c = string[i]) + { + if (pass_next) + { + pass_next = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '\\') + { + pass_next++; + i++; + continue; + } + else if (backquote) + { + if (c == '`') + backquote = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '`') + { + backquote++; + i++; + continue; + } + else if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE))) + { + si = i + 2; + if (string[i + 1] == LPAREN) + ret = extract_command_subst (string, &si, SX_NOALLOC); + else + ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, SX_NOALLOC); + + i = si + 1; + continue; + } + else if (c != '"') + { + ADVANCE_CHAR (string, slen, i); + continue; + } + else + break; + } + + if (c) + i++; + + return (i); +} + +/* Extract the contents of STRING as if it is enclosed in single quotes. + SINDEX, when passed in, is the offset of the character immediately + following the opening single quote; on exit, SINDEX is left pointing after + the closing single quote. */ +static inline char * +string_extract_single_quoted (string, sindex) + char *string; + int *sindex; +{ + register int i; + size_t slen; + char *t; + DECLARE_MBSTATE; + + /* Don't need slen for ADVANCE_CHAR unless multibyte chars possible. */ + slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 0; + i = *sindex; + while (string[i] && string[i] != '\'') + ADVANCE_CHAR (string, slen, i); + + t = substring (string, *sindex, i); + + if (string[i]) + i++; + *sindex = i; + + return (t); +} + +static inline int +skip_single_quoted (string, slen, sind) + const char *string; + size_t slen; + int sind; +{ + register int c; + DECLARE_MBSTATE; + + c = sind; + while (string[c] && string[c] != '\'') + ADVANCE_CHAR (string, slen, c); + + if (string[c]) + c++; + return c; +} + +/* Just like string_extract, but doesn't hack backslashes or any of + that other stuff. Obeys CTLESC quoting. Used to do splitting on $IFS. */ +static char * +string_extract_verbatim (string, slen, sindex, charlist, flags) + char *string; + size_t slen; + int *sindex; + char *charlist; + int flags; +{ + register int i; +#if defined (HANDLE_MULTIBYTE) + size_t clen; + wchar_t *wcharlist; +#endif + int c; + char *temp; + DECLARE_MBSTATE; + + if (charlist[0] == '\'' && charlist[1] == '\0') + { + temp = string_extract_single_quoted (string, sindex); + --*sindex; /* leave *sindex at separator character */ + return temp; + } + + i = *sindex; +#if 0 + /* See how the MBLEN and ADVANCE_CHAR macros work to understand why we need + this only if MB_CUR_MAX > 1. */ + slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 1; +#endif +#if defined (HANDLE_MULTIBYTE) + clen = strlen (charlist); + wcharlist = 0; +#endif + while (c = string[i]) + { +#if defined (HANDLE_MULTIBYTE) + size_t mblength; +#endif + if ((flags & SX_NOCTLESC) == 0 && c == CTLESC) + { + i += 2; + continue; + } + /* Even if flags contains SX_NOCTLESC, we let CTLESC quoting CTLNUL + through, to protect the CTLNULs from later calls to + remove_quoted_nulls. */ + else if ((flags & SX_NOESCCTLNUL) == 0 && c == CTLESC && string[i+1] == CTLNUL) + { + i += 2; + continue; + } + +#if defined (HANDLE_MULTIBYTE) + mblength = MBLEN (string + i, slen - i); + if (mblength > 1) + { + wchar_t wc; + mblength = mbtowc (&wc, string + i, slen - i); + if (MB_INVALIDCH (mblength)) + { + if (MEMBER (c, charlist)) + break; + } + else + { + if (wcharlist == 0) + { + size_t len; + len = mbstowcs (wcharlist, charlist, 0); + if (len == -1) + len = 0; + wcharlist = (wchar_t *)xmalloc (sizeof (wchar_t) * (len + 1)); + mbstowcs (wcharlist, charlist, len + 1); + } + + if (wcschr (wcharlist, wc)) + break; + } + } + else +#endif + if (MEMBER (c, charlist)) + break; + + ADVANCE_CHAR (string, slen, i); + } + +#if defined (HANDLE_MULTIBYTE) + FREE (wcharlist); +#endif + + temp = substring (string, *sindex, i); + *sindex = i; + + return (temp); +} + +/* Extract the $( construct in STRING, and return a new string. + Start extracting at (SINDEX) as if we had just seen "$(". + Make (SINDEX) get the position of the matching ")". ) + XFLAGS is additional flags to pass to other extraction functions. */ +char * +extract_command_subst (string, sindex, xflags) + char *string; + int *sindex; + int xflags; +{ + if (string[*sindex] == LPAREN) + return (extract_delimited_string (string, sindex, "$(", "(", ")", xflags|SX_COMMAND)); /*)*/ + else + { + xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0); + return (xparse_dolparen (string, string+*sindex, sindex, xflags)); + } +} + +/* Extract the $[ construct in STRING, and return a new string. (]) + Start extracting at (SINDEX) as if we had just seen "$[". + Make (SINDEX) get the position of the matching "]". */ +char * +extract_arithmetic_subst (string, sindex) + char *string; + int *sindex; +{ + return (extract_delimited_string (string, sindex, "$[", "[", "]", 0)); /*]*/ +} + +#if defined (PROCESS_SUBSTITUTION) +/* Extract the <( or >( construct in STRING, and return a new string. + Start extracting at (SINDEX) as if we had just seen "<(". + Make (SINDEX) get the position of the matching ")". */ /*))*/ +char * +extract_process_subst (string, starter, sindex) + char *string; + char *starter; + int *sindex; +{ + return (extract_delimited_string (string, sindex, starter, "(", ")", 0)); +} +#endif /* PROCESS_SUBSTITUTION */ + +#if defined (ARRAY_VARS) +/* This can be fooled by unquoted right parens in the passed string. If + each caller verifies that the last character in STRING is a right paren, + we don't even need to call extract_delimited_string. */ +char * +extract_array_assignment_list (string, sindex) + char *string; + int *sindex; +{ + int slen; + char *ret; + + slen = strlen (string); /* ( */ + if (string[slen - 1] == ')') + { + ret = substring (string, *sindex, slen - 1); + *sindex = slen - 1; + return ret; + } + return 0; +} +#endif + +/* Extract and create a new string from the contents of STRING, a + character string delimited with OPENER and CLOSER. SINDEX is + the address of an int describing the current offset in STRING; + it should point to just after the first OPENER found. On exit, + SINDEX gets the position of the last character of the matching CLOSER. + If OPENER is more than a single character, ALT_OPENER, if non-null, + contains a character string that can also match CLOSER and thus + needs to be skipped. */ +static char * +extract_delimited_string (string, sindex, opener, alt_opener, closer, flags) + char *string; + int *sindex; + char *opener, *alt_opener, *closer; + int flags; +{ + int i, c, si; + size_t slen; + char *t, *result; + int pass_character, nesting_level, in_comment; + int len_closer, len_opener, len_alt_opener; + DECLARE_MBSTATE; + + slen = strlen (string + *sindex) + *sindex; + len_opener = STRLEN (opener); + len_alt_opener = STRLEN (alt_opener); + len_closer = STRLEN (closer); + + pass_character = in_comment = 0; + + nesting_level = 1; + i = *sindex; + + while (nesting_level) + { + c = string[i]; + + if (c == 0) + break; + + if (in_comment) + { + if (c == '\n') + in_comment = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + + if (pass_character) /* previous char was backslash */ + { + pass_character = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + + /* Not exactly right yet; should handle shell metacharacters and + multibyte characters, too. See COMMENT_BEGIN define in parse.y */ + if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || shellblank (string[i - 1]))) + { + in_comment = 1; + ADVANCE_CHAR (string, slen, i); + continue; + } + + if (c == CTLESC || c == '\\') + { + pass_character++; + i++; + continue; + } + + /* Process a nested command substitution, but only if we're parsing an + arithmetic substitution. */ + if ((flags & SX_COMMAND) && string[i] == '$' && string[i+1] == LPAREN) + { + si = i + 2; + t = extract_command_subst (string, &si, flags|SX_NOALLOC); + i = si + 1; + continue; + } + + /* Process a nested OPENER. */ + if (STREQN (string + i, opener, len_opener)) + { + si = i + len_opener; + t = extract_delimited_string (string, &si, opener, alt_opener, closer, flags|SX_NOALLOC); + i = si + 1; + continue; + } + + /* Process a nested ALT_OPENER */ + if (len_alt_opener && STREQN (string + i, alt_opener, len_alt_opener)) + { + si = i + len_alt_opener; + t = extract_delimited_string (string, &si, alt_opener, alt_opener, closer, flags|SX_NOALLOC); + i = si + 1; + continue; + } + + /* If the current substring terminates the delimited string, decrement + the nesting level. */ + if (STREQN (string + i, closer, len_closer)) + { + i += len_closer - 1; /* move to last byte of the closer */ + nesting_level--; + if (nesting_level == 0) + break; + } + + /* Pass old-style command substitution through verbatim. */ + if (c == '`') + { + si = i + 1; + t = string_extract (string, &si, "`", flags|SX_NOALLOC); + i = si + 1; + continue; + } + + /* Pass single-quoted and double-quoted strings through verbatim. */ + if (c == '\'' || c == '"') + { + si = i + 1; + i = (c == '\'') ? skip_single_quoted (string, slen, si) + : skip_double_quoted (string, slen, si); + continue; + } + + /* move past this character, which was not special. */ + ADVANCE_CHAR (string, slen, i); + } + + if (c == 0 && nesting_level) + { + if (no_longjmp_on_fatal_error == 0) + { + report_error (_("bad substitution: no closing `%s' in %s"), closer, string); + last_command_exit_value = EXECUTION_FAILURE; + exp_jump_to_top_level (DISCARD); + } + else + { + *sindex = i; + return (char *)NULL; + } + } + + si = i - *sindex - len_closer + 1; + if (flags & SX_NOALLOC) + result = (char *)NULL; + else + { + result = (char *)xmalloc (1 + si); + strncpy (result, string + *sindex, si); + result[si] = '\0'; + } + *sindex = i; + + return (result); +} + +/* Extract a parameter expansion expression within ${ and } from STRING. + Obey the Posix.2 rules for finding the ending `}': count braces while + skipping over enclosed quoted strings and command substitutions. + SINDEX is the address of an int describing the current offset in STRING; + it should point to just after the first `{' found. On exit, SINDEX + gets the position of the matching `}'. QUOTED is non-zero if this + occurs inside double quotes. */ +/* XXX -- this is very similar to extract_delimited_string -- XXX */ +static char * +extract_dollar_brace_string (string, sindex, quoted, flags) + char *string; + int *sindex, quoted, flags; +{ + register int i, c; + size_t slen; + int pass_character, nesting_level, si, dolbrace_state; + char *result, *t; + DECLARE_MBSTATE; + + pass_character = 0; + nesting_level = 1; + slen = strlen (string + *sindex) + *sindex; + + /* The handling of dolbrace_state needs to agree with the code in parse.y: + parse_matched_pair() */ + dolbrace_state = 0; + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + dolbrace_state = (flags & SX_POSIXEXP) ? DOLBRACE_QUOTE : DOLBRACE_PARAM; + + i = *sindex; + while (c = string[i]) + { + if (pass_character) + { + pass_character = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + + /* CTLESCs and backslashes quote the next character. */ + if (c == CTLESC || c == '\\') + { + pass_character++; + i++; + continue; + } + + if (string[i] == '$' && string[i+1] == LBRACE) + { + nesting_level++; + i += 2; + continue; + } + + if (c == RBRACE) + { + nesting_level--; + if (nesting_level == 0) + break; + i++; + continue; + } + + /* Pass the contents of old-style command substitutions through + verbatim. */ + if (c == '`') + { + si = i + 1; + t = string_extract (string, &si, "`", flags|SX_NOALLOC); + i = si + 1; + continue; + } + + /* Pass the contents of new-style command substitutions and + arithmetic substitutions through verbatim. */ + if (string[i] == '$' && string[i+1] == LPAREN) + { + si = i + 2; + t = extract_command_subst (string, &si, flags|SX_NOALLOC); + i = si + 1; + continue; + } + +#if 0 + /* Pass the contents of single-quoted and double-quoted strings + through verbatim. */ + if (c == '\'' || c == '"') + { + si = i + 1; + i = (c == '\'') ? skip_single_quoted (string, slen, si) + : skip_double_quoted (string, slen, si); + /* skip_XXX_quoted leaves index one past close quote */ + continue; + } +#else /* XXX - bash-4.2 */ + /* Pass the contents of double-quoted strings through verbatim. */ + if (c == '"') + { + si = i + 1; + i = skip_double_quoted (string, slen, si); + /* skip_XXX_quoted leaves index one past close quote */ + continue; + } + + if (c == '\'') + { +/*itrace("extract_dollar_brace_string: c == single quote flags = %d quoted = %d dolbrace_state = %d", flags, quoted, dolbrace_state);*/ + if (posixly_correct && shell_compatibility_level > 41 && dolbrace_state != DOLBRACE_QUOTE && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ADVANCE_CHAR (string, slen, i); + else + { + si = i + 1; + i = skip_single_quoted (string, slen, si); + } + + continue; + } +#endif + + /* move past this character, which was not special. */ + ADVANCE_CHAR (string, slen, i); + + /* This logic must agree with parse.y:parse_matched_pair, since they + share the same defines. */ + if (dolbrace_state == DOLBRACE_PARAM && c == '%' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if (dolbrace_state == DOLBRACE_PARAM && c == '#' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if (dolbrace_state == DOLBRACE_PARAM && c == '/' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if (dolbrace_state == DOLBRACE_PARAM && c == '^' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if (dolbrace_state == DOLBRACE_PARAM && c == ',' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if (dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", c) != 0) + dolbrace_state = DOLBRACE_OP; + else if (dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", c) == 0) + dolbrace_state = DOLBRACE_WORD; + } + + if (c == 0 && nesting_level) + { + if (no_longjmp_on_fatal_error == 0) + { /* { */ + report_error (_("bad substitution: no closing `%s' in %s"), "}", string); + last_command_exit_value = EXECUTION_FAILURE; + exp_jump_to_top_level (DISCARD); + } + else + { + *sindex = i; + return ((char *)NULL); + } + } + + result = (flags & SX_NOALLOC) ? (char *)NULL : substring (string, *sindex, i); + *sindex = i; + + return (result); +} + +/* Remove backslashes which are quoting backquotes from STRING. Modifies + STRING, and returns a pointer to it. */ +char * +de_backslash (string) + char *string; +{ + register size_t slen; + register int i, j, prev_i; + DECLARE_MBSTATE; + + slen = strlen (string); + i = j = 0; + + /* Loop copying string[i] to string[j], i >= j. */ + while (i < slen) + { + if (string[i] == '\\' && (string[i + 1] == '`' || string[i + 1] == '\\' || + string[i + 1] == '$')) + i++; + prev_i = i; + ADVANCE_CHAR (string, slen, i); + if (j < prev_i) + do string[j++] = string[prev_i++]; while (prev_i < i); + else + j = i; + } + string[j] = '\0'; + + return (string); +} + +#if 0 +/*UNUSED*/ +/* Replace instances of \! in a string with !. */ +void +unquote_bang (string) + char *string; +{ + register int i, j; + register char *temp; + + temp = (char *)xmalloc (1 + strlen (string)); + + for (i = 0, j = 0; (temp[j] = string[i]); i++, j++) + { + if (string[i] == '\\' && string[i + 1] == '!') + { + temp[j] = '!'; + i++; + } + } + strcpy (string, temp); + free (temp); +} +#endif + +#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0) + +/* This function assumes s[i] == open; returns with s[ret] == close; used to + parse array subscripts. FLAGS & 1 means to not attempt to skip over + matched pairs of quotes or backquotes, or skip word expansions; it is + intended to be used after expansion has been performed and during final + assignment parsing (see arrayfunc.c:assign_compound_array_list()). */ +static int +skip_matched_pair (string, start, open, close, flags) + const char *string; + int start, open, close, flags; +{ + int i, pass_next, backq, si, c, count; + size_t slen; + char *temp, *ss; + DECLARE_MBSTATE; + + slen = strlen (string + start) + start; + no_longjmp_on_fatal_error = 1; + + i = start + 1; /* skip over leading bracket */ + count = 1; + pass_next = backq = 0; + ss = (char *)string; + while (c = string[i]) + { + if (pass_next) + { + pass_next = 0; + if (c == 0) + CQ_RETURN(i); + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '\\') + { + pass_next = 1; + i++; + continue; + } + else if (backq) + { + if (c == '`') + backq = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if ((flags & 1) == 0 && c == '`') + { + backq = 1; + i++; + continue; + } + else if ((flags & 1) == 0 && c == open) + { + count++; + i++; + continue; + } + else if (c == close) + { + count--; + if (count == 0) + break; + i++; + continue; + } + else if ((flags & 1) == 0 && (c == '\'' || c == '"')) + { + i = (c == '\'') ? skip_single_quoted (ss, slen, ++i) + : skip_double_quoted (ss, slen, ++i); + /* no increment, the skip functions increment past the closing quote. */ + } + else if ((flags&1) == 0 && c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE)) + { + si = i + 2; + if (string[si] == '\0') + CQ_RETURN(si); + + if (string[i+1] == LPAREN) + temp = extract_delimited_string (ss, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */ + else + temp = extract_dollar_brace_string (ss, &si, 0, SX_NOALLOC); + i = si; + if (string[i] == '\0') /* don't increment i past EOS in loop */ + break; + i++; + continue; + } + else + ADVANCE_CHAR (string, slen, i); + } + + CQ_RETURN(i); +} + +#if defined (ARRAY_VARS) +int +skipsubscript (string, start, flags) + const char *string; + int start, flags; +{ + return (skip_matched_pair (string, start, '[', ']', flags)); +} +#endif + +/* Skip characters in STRING until we find a character in DELIMS, and return + the index of that character. START is the index into string at which we + begin. This is similar in spirit to strpbrk, but it returns an index into + STRING and takes a starting index. This little piece of code knows quite + a lot of shell syntax. It's very similar to skip_double_quoted and other + functions of that ilk. */ +int +skip_to_delim (string, start, delims, flags) + char *string; + int start; + char *delims; + int flags; +{ + int i, pass_next, backq, si, c, invert, skipquote, skipcmd; + size_t slen; + char *temp, open[3]; + DECLARE_MBSTATE; + + slen = strlen (string + start) + start; + if (flags & SD_NOJMP) + no_longjmp_on_fatal_error = 1; + invert = (flags & SD_INVERT); + skipcmd = (flags & SD_NOSKIPCMD) == 0; + + i = start; + pass_next = backq = 0; + while (c = string[i]) + { + /* If this is non-zero, we should not let quote characters be delimiters + and the current character is a single or double quote. We should not + test whether or not it's a delimiter until after we skip single- or + double-quoted strings. */ + skipquote = ((flags & SD_NOQUOTEDELIM) && (c == '\'' || c =='"')); + if (pass_next) + { + pass_next = 0; + if (c == 0) + CQ_RETURN(i); + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '\\') + { + pass_next = 1; + i++; + continue; + } + else if (backq) + { + if (c == '`') + backq = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '`') + { + backq = 1; + i++; + continue; + } + else if (skipquote == 0 && invert == 0 && member (c, delims)) + break; + else if (c == '\'' || c == '"') + { + i = (c == '\'') ? skip_single_quoted (string, slen, ++i) + : skip_double_quoted (string, slen, ++i); + /* no increment, the skip functions increment past the closing quote. */ + } + else if (c == '$' && ((skipcmd && string[i+1] == LPAREN) || string[i+1] == LBRACE)) + { + si = i + 2; + if (string[si] == '\0') + CQ_RETURN(si); + + if (string[i+1] == LPAREN) + temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */ + else + temp = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC); + i = si; + if (string[i] == '\0') /* don't increment i past EOS in loop */ + break; + i++; + continue; + } +#if defined (PROCESS_SUBSTITUTION) + else if (skipcmd && (c == '<' || c == '>') && string[i+1] == LPAREN) + { + si = i + 2; + if (string[si] == '\0') + CQ_RETURN(si); + temp = extract_process_subst (string, (c == '<') ? "<(" : ">(", &si); + i = si; + if (string[i] == '\0') + break; + i++; + continue; + } +#endif /* PROCESS_SUBSTITUTION */ +#if defined (EXTENDED_GLOB) + else if ((flags & SD_EXTGLOB) && extended_glob && string[i+1] == LPAREN && member (c, "?*+!@")) + { + si = i + 2; + if (string[si] == '\0') + CQ_RETURN(si); + + open[0] = c; + open[1] = LPAREN; + open[2] = '\0'; + temp = extract_delimited_string (string, &si, open, "(", ")", SX_NOALLOC); /* ) */ + + i = si; + if (string[i] == '\0') /* don't increment i past EOS in loop */ + break; + i++; + continue; + } +#endif + else if ((skipquote || invert) && (member (c, delims) == 0)) + break; + else + ADVANCE_CHAR (string, slen, i); + } + + CQ_RETURN(i); +} + +#if defined (READLINE) +/* Return 1 if the portion of STRING ending at EINDEX is quoted (there is + an unclosed quoted string), or if the character at EINDEX is quoted + by a backslash. NO_LONGJMP_ON_FATAL_ERROR is used to flag that the various + single and double-quoted string parsing functions should not return an + error if there are unclosed quotes or braces. The characters that this + recognizes need to be the same as the contents of + rl_completer_quote_characters. */ + +int +char_is_quoted (string, eindex) + char *string; + int eindex; +{ + int i, pass_next, c; + size_t slen; + DECLARE_MBSTATE; + + slen = strlen (string); + no_longjmp_on_fatal_error = 1; + i = pass_next = 0; + while (i <= eindex) + { + c = string[i]; + + if (pass_next) + { + pass_next = 0; + if (i >= eindex) /* XXX was if (i >= eindex - 1) */ + CQ_RETURN(1); + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '\\') + { + pass_next = 1; + i++; + continue; + } + else if (c == '\'' || c == '"') + { + i = (c == '\'') ? skip_single_quoted (string, slen, ++i) + : skip_double_quoted (string, slen, ++i); + if (i > eindex) + CQ_RETURN(1); + /* no increment, the skip_xxx functions go one past end */ + } + else + ADVANCE_CHAR (string, slen, i); + } + + CQ_RETURN(0); +} + +int +unclosed_pair (string, eindex, openstr) + char *string; + int eindex; + char *openstr; +{ + int i, pass_next, openc, olen; + size_t slen; + DECLARE_MBSTATE; + + slen = strlen (string); + olen = strlen (openstr); + i = pass_next = openc = 0; + while (i <= eindex) + { + if (pass_next) + { + pass_next = 0; + if (i >= eindex) /* XXX was if (i >= eindex - 1) */ + return 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (string[i] == '\\') + { + pass_next = 1; + i++; + continue; + } + else if (STREQN (string + i, openstr, olen)) + { + openc = 1 - openc; + i += olen; + } + else if (string[i] == '\'' || string[i] == '"') + { + i = (string[i] == '\'') ? skip_single_quoted (string, slen, i) + : skip_double_quoted (string, slen, i); + if (i > eindex) + return 0; + } + else + ADVANCE_CHAR (string, slen, i); + } + return (openc); +} + +/* Split STRING (length SLEN) at DELIMS, and return a WORD_LIST with the + individual words. If DELIMS is NULL, the current value of $IFS is used + to split the string, and the function follows the shell field splitting + rules. SENTINEL is an index to look for. NWP, if non-NULL, + gets the number of words in the returned list. CWP, if non-NULL, gets + the index of the word containing SENTINEL. Non-whitespace chars in + DELIMS delimit separate fields. */ +WORD_LIST * +split_at_delims (string, slen, delims, sentinel, flags, nwp, cwp) + char *string; + int slen; + char *delims; + int sentinel, flags; + int *nwp, *cwp; +{ + int ts, te, i, nw, cw, ifs_split, dflags; + char *token, *d, *d2; + WORD_LIST *ret, *tl; + + if (string == 0 || *string == '\0') + { + if (nwp) + *nwp = 0; + if (cwp) + *cwp = 0; + return ((WORD_LIST *)NULL); + } + + d = (delims == 0) ? ifs_value : delims; + ifs_split = delims == 0; + + /* Make d2 the non-whitespace characters in delims */ + d2 = 0; + if (delims) + { + size_t slength; +#if defined (HANDLE_MULTIBYTE) + size_t mblength = 1; +#endif + DECLARE_MBSTATE; + + slength = strlen (delims); + d2 = (char *)xmalloc (slength + 1); + i = ts = 0; + while (delims[i]) + { +#if defined (HANDLE_MULTIBYTE) + mbstate_t state_bak; + state_bak = state; + mblength = MBRLEN (delims + i, slength, &state); + if (MB_INVALIDCH (mblength)) + state = state_bak; + else if (mblength > 1) + { + memcpy (d2 + ts, delims + i, mblength); + ts += mblength; + i += mblength; + slength -= mblength; + continue; + } +#endif + if (whitespace (delims[i]) == 0) + d2[ts++] = delims[i]; + + i++; + slength--; + } + d2[ts] = '\0'; + } + + ret = (WORD_LIST *)NULL; + + /* Remove sequences of whitespace characters at the start of the string, as + long as those characters are delimiters. */ + for (i = 0; member (string[i], d) && spctabnl (string[i]); i++) + ; + if (string[i] == '\0') + return (ret); + + ts = i; + nw = 0; + cw = -1; + dflags = flags|SD_NOJMP; + while (1) + { + te = skip_to_delim (string, ts, d, dflags); + + /* If we have a non-whitespace delimiter character, use it to make a + separate field. This is just about what $IFS splitting does and + is closer to the behavior of the shell parser. */ + if (ts == te && d2 && member (string[ts], d2)) + { + te = ts + 1; + /* If we're using IFS splitting, the non-whitespace delimiter char + and any additional IFS whitespace delimits a field. */ + if (ifs_split) + while (member (string[te], d) && spctabnl (string[te])) + te++; + else + while (member (string[te], d2)) + te++; + } + + token = substring (string, ts, te); + + ret = add_string_to_list (token, ret); + free (token); + nw++; + + if (sentinel >= ts && sentinel <= te) + cw = nw; + + /* If the cursor is at whitespace just before word start, set the + sentinel word to the current word. */ + if (cwp && cw == -1 && sentinel == ts-1) + cw = nw; + + /* If the cursor is at whitespace between two words, make a new, empty + word, add it before (well, after, since the list is in reverse order) + the word we just added, and set the current word to that one. */ + if (cwp && cw == -1 && sentinel < ts) + { + tl = make_word_list (make_word (""), ret->next); + ret->next = tl; + cw = nw; + nw++; + } + + if (string[te] == 0) + break; + + i = te; + while (member (string[i], d) && (ifs_split || spctabnl(string[i]))) + i++; + + if (string[i]) + ts = i; + else + break; + } + + /* Special case for SENTINEL at the end of STRING. If we haven't found + the word containing SENTINEL yet, and the index we're looking for is at + the end of STRING (or past the end of the previously-found token, + possible if the end of the line is composed solely of IFS whitespace) + add an additional null argument and set the current word pointer to that. */ + if (cwp && cw == -1 && (sentinel >= slen || sentinel >= te)) + { + if (whitespace (string[sentinel - 1])) + { + token = ""; + ret = add_string_to_list (token, ret); + nw++; + } + cw = nw; + } + + if (nwp) + *nwp = nw; + if (cwp) + *cwp = cw; + + return (REVERSE_LIST (ret, WORD_LIST *)); +} +#endif /* READLINE */ + +#if 0 +/* UNUSED */ +/* Extract the name of the variable to bind to from the assignment string. */ +char * +assignment_name (string) + char *string; +{ + int offset; + char *temp; + + offset = assignment (string, 0); + if (offset == 0) + return (char *)NULL; + temp = substring (string, 0, offset); + return (temp); +} +#endif + +/* **************************************************************** */ +/* */ +/* Functions to convert strings to WORD_LISTs and vice versa */ +/* */ +/* **************************************************************** */ + +/* Return a single string of all the words in LIST. SEP is the separator + to put between individual elements of LIST in the output string. */ +char * +string_list_internal (list, sep) + WORD_LIST *list; + char *sep; +{ + register WORD_LIST *t; + char *result, *r; + int word_len, sep_len, result_size; + + if (list == 0) + return ((char *)NULL); + + /* Short-circuit quickly if we don't need to separate anything. */ + if (list->next == 0) + return (savestring (list->word->word)); + + /* This is nearly always called with either sep[0] == 0 or sep[1] == 0. */ + sep_len = STRLEN (sep); + result_size = 0; + + for (t = list; t; t = t->next) + { + if (t != list) + result_size += sep_len; + result_size += strlen (t->word->word); + } + + r = result = (char *)xmalloc (result_size + 1); + + for (t = list; t; t = t->next) + { + if (t != list && sep_len) + { + if (sep_len > 1) + { + FASTCOPY (sep, r, sep_len); + r += sep_len; + } + else + *r++ = sep[0]; + } + + word_len = strlen (t->word->word); + FASTCOPY (t->word->word, r, word_len); + r += word_len; + } + + *r = '\0'; + return (result); +} + +/* Return a single string of all the words present in LIST, separating + each word with a space. */ +char * +string_list (list) + WORD_LIST *list; +{ + return (string_list_internal (list, " ")); +} + +/* An external interface that can be used by the rest of the shell to + obtain a string containing the first character in $IFS. Handles all + the multibyte complications. If LENP is non-null, it is set to the + length of the returned string. */ +char * +ifs_firstchar (lenp) + int *lenp; +{ + char *ret; + int len; + + ret = xmalloc (MB_LEN_MAX + 1); +#if defined (HANDLE_MULTIBYTE) + if (ifs_firstc_len == 1) + { + ret[0] = ifs_firstc[0]; + ret[1] = '\0'; + len = ret[0] ? 1 : 0; + } + else + { + memcpy (ret, ifs_firstc, ifs_firstc_len); + ret[len = ifs_firstc_len] = '\0'; + } +#else + ret[0] = ifs_firstc; + ret[1] = '\0'; + len = ret[0] ? 0 : 1; +#endif + + if (lenp) + *lenp = len; + + return ret; +} + +/* Return a single string of all the words present in LIST, obeying the + quoting rules for "$*", to wit: (P1003.2, draft 11, 3.5.2) "If the + expansion [of $*] appears within a double quoted string, it expands + to a single field with the value of each parameter separated by the + first character of the IFS variable, or by a if IFS is unset." */ +char * +string_list_dollar_star (list) + WORD_LIST *list; +{ + char *ret; +#if defined (HANDLE_MULTIBYTE) +# if defined (__GNUC__) + char sep[MB_CUR_MAX + 1]; +# else + char *sep = 0; +# endif +#else + char sep[2]; +#endif + +#if defined (HANDLE_MULTIBYTE) +# if !defined (__GNUC__) + sep = (char *)xmalloc (MB_CUR_MAX + 1); +# endif /* !__GNUC__ */ + if (ifs_firstc_len == 1) + { + sep[0] = ifs_firstc[0]; + sep[1] = '\0'; + } + else + { + memcpy (sep, ifs_firstc, ifs_firstc_len); + sep[ifs_firstc_len] = '\0'; + } +#else + sep[0] = ifs_firstc; + sep[1] = '\0'; +#endif + + ret = string_list_internal (list, sep); +#if defined (HANDLE_MULTIBYTE) && !defined (__GNUC__) + free (sep); +#endif + return ret; +} + +/* Turn $@ into a string. If (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + is non-zero, the $@ appears within double quotes, and we should quote + the list before converting it into a string. If IFS is unset, and the + word is not quoted, we just need to quote CTLESC and CTLNUL characters + in the words in the list, because the default value of $IFS is + , IFS characters in the words in the list should + also be split. If IFS is null, and the word is not quoted, we need + to quote the words in the list to preserve the positional parameters + exactly. */ +char * +string_list_dollar_at (list, quoted) + WORD_LIST *list; + int quoted; +{ + char *ifs, *ret; +#if defined (HANDLE_MULTIBYTE) +# if defined (__GNUC__) + char sep[MB_CUR_MAX + 1]; +# else + char *sep = 0; +# endif /* !__GNUC__ */ +#else + char sep[2]; +#endif + WORD_LIST *tlist; + + /* XXX this could just be ifs = ifs_value; */ + ifs = ifs_var ? value_cell (ifs_var) : (char *)0; + +#if defined (HANDLE_MULTIBYTE) +# if !defined (__GNUC__) + sep = (char *)xmalloc (MB_CUR_MAX + 1); +# endif /* !__GNUC__ */ + if (ifs && *ifs) + { + if (ifs_firstc_len == 1) + { + sep[0] = ifs_firstc[0]; + sep[1] = '\0'; + } + else + { + memcpy (sep, ifs_firstc, ifs_firstc_len); + sep[ifs_firstc_len] = '\0'; + } + } + else + { + sep[0] = ' '; + sep[1] = '\0'; + } +#else + sep[0] = (ifs == 0 || *ifs == 0) ? ' ' : *ifs; + sep[1] = '\0'; +#endif + + /* XXX -- why call quote_list if ifs == 0? we can get away without doing + it now that quote_escapes quotes spaces */ + tlist = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE)) + ? quote_list (list) + : list_quote_escapes (list); + + ret = string_list_internal (tlist, sep); +#if defined (HANDLE_MULTIBYTE) && !defined (__GNUC__) + free (sep); +#endif + return ret; +} + +/* Turn the positional paramters into a string, understanding quoting and + the various subtleties of using the first character of $IFS as the + separator. Calls string_list_dollar_at, string_list_dollar_star, and + string_list as appropriate. */ +char * +string_list_pos_params (pchar, list, quoted) + int pchar; + WORD_LIST *list; + int quoted; +{ + char *ret; + WORD_LIST *tlist; + + if (pchar == '*' && (quoted & Q_DOUBLE_QUOTES)) + { + tlist = quote_list (list); + word_list_remove_quoted_nulls (tlist); + ret = string_list_dollar_star (tlist); + } + else if (pchar == '*' && (quoted & Q_HERE_DOCUMENT)) + { + tlist = quote_list (list); + word_list_remove_quoted_nulls (tlist); + ret = string_list (tlist); + } + else if (pchar == '*') + { + /* Even when unquoted, string_list_dollar_star does the right thing + making sure that the first character of $IFS is used as the + separator. */ + ret = string_list_dollar_star (list); + } + else if (pchar == '@' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + /* We use string_list_dollar_at, but only if the string is quoted, since + that quotes the escapes if it's not, which we don't want. We could + use string_list (the old code did), but that doesn't do the right + thing if the first character of $IFS is not a space. We use + string_list_dollar_star if the string is unquoted so we make sure that + the elements of $@ are separated by the first character of $IFS for + later splitting. */ + ret = string_list_dollar_at (list, quoted); + else if (pchar == '@') + ret = string_list_dollar_star (list); + else + ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (list) : list); + + return ret; +} + +/* Return the list of words present in STRING. Separate the string into + words at any of the characters found in SEPARATORS. If QUOTED is + non-zero then word in the list will have its quoted flag set, otherwise + the quoted flag is left as make_word () deemed fit. + + This obeys the P1003.2 word splitting semantics. If `separators' is + exactly , then the splitting algorithm is that of + the Bourne shell, which treats any sequence of characters from `separators' + as a delimiter. If IFS is unset, which results in `separators' being set + to "", no splitting occurs. If separators has some other value, the + following rules are applied (`IFS white space' means zero or more + occurrences of , , or , as long as those characters + are in `separators'): + + 1) IFS white space is ignored at the start and the end of the + string. + 2) Each occurrence of a character in `separators' that is not + IFS white space, along with any adjacent occurrences of + IFS white space delimits a field. + 3) Any nonzero-length sequence of IFS white space delimits a field. + */ + +/* BEWARE! list_string strips null arguments. Don't call it twice and + expect to have "" preserved! */ + +/* This performs word splitting and quoted null character removal on + STRING. */ +#define issep(c) \ + (((separators)[0]) ? ((separators)[1] ? isifs(c) \ + : (c) == (separators)[0]) \ + : 0) + +WORD_LIST * +list_string (string, separators, quoted) + register char *string, *separators; + int quoted; +{ + WORD_LIST *result; + WORD_DESC *t; + char *current_word, *s; + int sindex, sh_style_split, whitesep, xflags; + size_t slen; + + if (!string || !*string) + return ((WORD_LIST *)NULL); + + sh_style_split = separators && separators[0] == ' ' && + separators[1] == '\t' && + separators[2] == '\n' && + separators[3] == '\0'; + for (xflags = 0, s = ifs_value; s && *s; s++) + { + if (*s == CTLESC) xflags |= SX_NOCTLESC; + else if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL; + } + + slen = 0; + /* Remove sequences of whitespace at the beginning of STRING, as + long as those characters appear in IFS. Do not do this if + STRING is quoted or if there are no separator characters. */ + if (!quoted || !separators || !*separators) + { + for (s = string; *s && spctabnl (*s) && issep (*s); s++); + + if (!*s) + return ((WORD_LIST *)NULL); + + string = s; + } + + /* OK, now STRING points to a word that does not begin with white space. + The splitting algorithm is: + extract a word, stopping at a separator + skip sequences of spc, tab, or nl as long as they are separators + This obeys the field splitting rules in Posix.2. */ + slen = (MB_CUR_MAX > 1) ? strlen (string) : 1; + for (result = (WORD_LIST *)NULL, sindex = 0; string[sindex]; ) + { + /* Don't need string length in ADVANCE_CHAR or string_extract_verbatim + unless multibyte chars are possible. */ + current_word = string_extract_verbatim (string, slen, &sindex, separators, xflags); + if (current_word == 0) + break; + + /* If we have a quoted empty string, add a quoted null argument. We + want to preserve the quoted null character iff this is a quoted + empty string; otherwise the quoted null characters are removed + below. */ + if (QUOTED_NULL (current_word)) + { + t = alloc_word_desc (); + t->word = make_quoted_char ('\0'); + t->flags |= W_QUOTED|W_HASQUOTEDNULL; + result = make_word_list (t, result); + } + else if (current_word[0] != '\0') + { + /* If we have something, then add it regardless. However, + perform quoted null character removal on the current word. */ + remove_quoted_nulls (current_word); + result = add_string_to_list (current_word, result); + result->word->flags &= ~W_HASQUOTEDNULL; /* just to be sure */ + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + result->word->flags |= W_QUOTED; + } + + /* If we're not doing sequences of separators in the traditional + Bourne shell style, then add a quoted null argument. */ + else if (!sh_style_split && !spctabnl (string[sindex])) + { + t = alloc_word_desc (); + t->word = make_quoted_char ('\0'); + t->flags |= W_QUOTED|W_HASQUOTEDNULL; + result = make_word_list (t, result); + } + + free (current_word); + + /* Note whether or not the separator is IFS whitespace, used later. */ + whitesep = string[sindex] && spctabnl (string[sindex]); + + /* Move past the current separator character. */ + if (string[sindex]) + { + DECLARE_MBSTATE; + ADVANCE_CHAR (string, slen, sindex); + } + + /* Now skip sequences of space, tab, or newline characters if they are + in the list of separators. */ + while (string[sindex] && spctabnl (string[sindex]) && issep (string[sindex])) + sindex++; + + /* If the first separator was IFS whitespace and the current character + is a non-whitespace IFS character, it should be part of the current + field delimiter, not a separate delimiter that would result in an + empty field. Look at POSIX.2, 3.6.5, (3)(b). */ + if (string[sindex] && whitesep && issep (string[sindex]) && !spctabnl (string[sindex])) + { + sindex++; + /* An IFS character that is not IFS white space, along with any + adjacent IFS white space, shall delimit a field. (SUSv3) */ + while (string[sindex] && spctabnl (string[sindex]) && isifs (string[sindex])) + sindex++; + } + } + return (REVERSE_LIST (result, WORD_LIST *)); +} + +/* Parse a single word from STRING, using SEPARATORS to separate fields. + ENDPTR is set to the first character after the word. This is used by + the `read' builtin. This is never called with SEPARATORS != $IFS; + it should be simplified. + + XXX - this function is very similar to list_string; they should be + combined - XXX */ +char * +get_word_from_string (stringp, separators, endptr) + char **stringp, *separators, **endptr; +{ + register char *s; + char *current_word; + int sindex, sh_style_split, whitesep, xflags; + size_t slen; + + if (!stringp || !*stringp || !**stringp) + return ((char *)NULL); + + sh_style_split = separators && separators[0] == ' ' && + separators[1] == '\t' && + separators[2] == '\n' && + separators[3] == '\0'; + for (xflags = 0, s = ifs_value; s && *s; s++) + { + if (*s == CTLESC) xflags |= SX_NOCTLESC; + if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL; + } + + s = *stringp; + slen = 0; + + /* Remove sequences of whitespace at the beginning of STRING, as + long as those characters appear in IFS. */ + if (sh_style_split || !separators || !*separators) + { + for (; *s && spctabnl (*s) && isifs (*s); s++); + + /* If the string is nothing but whitespace, update it and return. */ + if (!*s) + { + *stringp = s; + if (endptr) + *endptr = s; + return ((char *)NULL); + } + } + + /* OK, S points to a word that does not begin with white space. + Now extract a word, stopping at a separator, save a pointer to + the first character after the word, then skip sequences of spc, + tab, or nl as long as they are separators. + + This obeys the field splitting rules in Posix.2. */ + sindex = 0; + /* Don't need string length in ADVANCE_CHAR or string_extract_verbatim + unless multibyte chars are possible. */ + slen = (MB_CUR_MAX > 1) ? strlen (s) : 1; + current_word = string_extract_verbatim (s, slen, &sindex, separators, xflags); + + /* Set ENDPTR to the first character after the end of the word. */ + if (endptr) + *endptr = s + sindex; + + /* Note whether or not the separator is IFS whitespace, used later. */ + whitesep = s[sindex] && spctabnl (s[sindex]); + + /* Move past the current separator character. */ + if (s[sindex]) + { + DECLARE_MBSTATE; + ADVANCE_CHAR (s, slen, sindex); + } + + /* Now skip sequences of space, tab, or newline characters if they are + in the list of separators. */ + while (s[sindex] && spctabnl (s[sindex]) && isifs (s[sindex])) + sindex++; + + /* If the first separator was IFS whitespace and the current character is + a non-whitespace IFS character, it should be part of the current field + delimiter, not a separate delimiter that would result in an empty field. + Look at POSIX.2, 3.6.5, (3)(b). */ + if (s[sindex] && whitesep && isifs (s[sindex]) && !spctabnl (s[sindex])) + { + sindex++; + /* An IFS character that is not IFS white space, along with any adjacent + IFS white space, shall delimit a field. */ + while (s[sindex] && spctabnl (s[sindex]) && isifs (s[sindex])) + sindex++; + } + + /* Update STRING to point to the next field. */ + *stringp = s + sindex; + return (current_word); +} + +/* Remove IFS white space at the end of STRING. Start at the end + of the string and walk backwards until the beginning of the string + or we find a character that's not IFS white space and not CTLESC. + Only let CTLESC escape a white space character if SAW_ESCAPE is + non-zero. */ +char * +strip_trailing_ifs_whitespace (string, separators, saw_escape) + char *string, *separators; + int saw_escape; +{ + char *s; + + s = string + STRLEN (string) - 1; + while (s > string && ((spctabnl (*s) && isifs (*s)) || + (saw_escape && *s == CTLESC && spctabnl (s[1])))) + s--; + *++s = '\0'; + return string; +} + +#if 0 +/* UNUSED */ +/* Split STRING into words at whitespace. Obeys shell-style quoting with + backslashes, single and double quotes. */ +WORD_LIST * +list_string_with_quotes (string) + char *string; +{ + WORD_LIST *list; + char *token, *s; + size_t s_len; + int c, i, tokstart, len; + + for (s = string; s && *s && spctabnl (*s); s++) + ; + if (s == 0 || *s == 0) + return ((WORD_LIST *)NULL); + + s_len = strlen (s); + tokstart = i = 0; + list = (WORD_LIST *)NULL; + while (1) + { + c = s[i]; + if (c == '\\') + { + i++; + if (s[i]) + i++; + } + else if (c == '\'') + i = skip_single_quoted (s, s_len, ++i); + else if (c == '"') + i = skip_double_quoted (s, s_len, ++i); + else if (c == 0 || spctabnl (c)) + { + /* We have found the end of a token. Make a word out of it and + add it to the word list. */ + token = substring (s, tokstart, i); + list = add_string_to_list (token, list); + free (token); + while (spctabnl (s[i])) + i++; + if (s[i]) + tokstart = i; + else + break; + } + else + i++; /* normal character */ + } + return (REVERSE_LIST (list, WORD_LIST *)); +} +#endif + +/********************************************************/ +/* */ +/* Functions to perform assignment statements */ +/* */ +/********************************************************/ + +#if defined (ARRAY_VARS) +static SHELL_VAR * +do_compound_assignment (name, value, flags) + char *name, *value; + int flags; +{ + SHELL_VAR *v; + int mklocal, mkassoc; + WORD_LIST *list; + + mklocal = flags & ASS_MKLOCAL; + mkassoc = flags & ASS_MKASSOC; + + if (mklocal && variable_context) + { + v = find_variable (name); + list = expand_compound_array_assignment (v, value, flags); + if (mkassoc) + v = make_local_assoc_variable (name); + else if (v == 0 || (array_p (v) == 0 && assoc_p (v) == 0) || v->context != variable_context) + v = make_local_array_variable (name); + assign_compound_array_list (v, list, flags); + } + else + v = assign_array_from_string (name, value, flags); + + return (v); +} +#endif + +/* Given STRING, an assignment string, get the value of the right side + of the `=', and bind it to the left side. If EXPAND is true, then + perform parameter expansion, command substitution, and arithmetic + expansion on the right-hand side. Perform tilde expansion in any + case. Do not perform word splitting on the result of expansion. */ +static int +do_assignment_internal (word, expand) + const WORD_DESC *word; + int expand; +{ + int offset, appendop, assign_list, aflags, retval; + char *name, *value, *temp; + SHELL_VAR *entry; +#if defined (ARRAY_VARS) + char *t; + int ni; +#endif + const char *string; + + if (word == 0 || word->word == 0) + return 0; + + appendop = assign_list = aflags = 0; + string = word->word; + offset = assignment (string, 0); + name = savestring (string); + value = (char *)NULL; + + if (name[offset] == '=') + { + if (name[offset - 1] == '+') + { + appendop = 1; + name[offset - 1] = '\0'; + } + + name[offset] = 0; /* might need this set later */ + temp = name + offset + 1; + +#if defined (ARRAY_VARS) + if (expand && (word->flags & W_COMPASSIGN)) + { + assign_list = ni = 1; + value = extract_array_assignment_list (temp, &ni); + } + else +#endif + if (expand && temp[0]) + value = expand_string_if_necessary (temp, 0, expand_string_assignment); + else + value = savestring (temp); + } + + if (value == 0) + { + value = (char *)xmalloc (1); + value[0] = '\0'; + } + + if (echo_command_at_execute) + { + if (appendop) + name[offset - 1] = '+'; + xtrace_print_assignment (name, value, assign_list, 1); + if (appendop) + name[offset - 1] = '\0'; + } + +#define ASSIGN_RETURN(r) do { FREE (value); free (name); return (r); } while (0) + + if (appendop) + aflags |= ASS_APPEND; + +#if defined (ARRAY_VARS) + if (t = mbschr (name, '[')) /*]*/ + { + if (assign_list) + { + report_error (_("%s: cannot assign list to array member"), name); + ASSIGN_RETURN (0); + } + entry = assign_array_element (name, value, aflags); + if (entry == 0) + ASSIGN_RETURN (0); + } + else if (assign_list) + { + if (word->flags & W_ASSIGNARG) + aflags |= ASS_MKLOCAL; + if (word->flags & W_ASSIGNASSOC) + aflags |= ASS_MKASSOC; + entry = do_compound_assignment (name, value, aflags); + } + else +#endif /* ARRAY_VARS */ + entry = bind_variable (name, value, aflags); + + stupidly_hack_special_variables (name); + +#if 1 + /* Return 1 if the assignment seems to have been performed correctly. */ + if (entry == 0 || readonly_p (entry)) + retval = 0; /* assignment failure */ + else if (noassign_p (entry)) + { + last_command_exit_value = EXECUTION_FAILURE; + retval = 1; /* error status, but not assignment failure */ + } + else + retval = 1; + + if (entry && retval != 0 && noassign_p (entry) == 0) + VUNSETATTR (entry, att_invisible); + + ASSIGN_RETURN (retval); +#else + if (entry) + VUNSETATTR (entry, att_invisible); + + ASSIGN_RETURN (entry ? ((readonly_p (entry) == 0) && noassign_p (entry) == 0) : 0); +#endif +} + +/* Perform the assignment statement in STRING, and expand the + right side by doing tilde, command and parameter expansion. */ +int +do_assignment (string) + char *string; +{ + WORD_DESC td; + + td.flags = W_ASSIGNMENT; + td.word = string; + + return do_assignment_internal (&td, 1); +} + +int +do_word_assignment (word, flags) + WORD_DESC *word; + int flags; +{ + return do_assignment_internal (word, 1); +} + +/* Given STRING, an assignment string, get the value of the right side + of the `=', and bind it to the left side. Do not perform any word + expansions on the right hand side. */ +int +do_assignment_no_expand (string) + char *string; +{ + WORD_DESC td; + + td.flags = W_ASSIGNMENT; + td.word = string; + + return (do_assignment_internal (&td, 0)); +} + +/*************************************************** + * * + * Functions to manage the positional parameters * + * * + ***************************************************/ + +/* Return the word list that corresponds to `$*'. */ +WORD_LIST * +list_rest_of_args () +{ + register WORD_LIST *list, *args; + int i; + + /* Break out of the loop as soon as one of the dollar variables is null. */ + for (i = 1, list = (WORD_LIST *)NULL; i < 10 && dollar_vars[i]; i++) + list = make_word_list (make_bare_word (dollar_vars[i]), list); + + for (args = rest_of_args; args; args = args->next) + list = make_word_list (make_bare_word (args->word->word), list); + + return (REVERSE_LIST (list, WORD_LIST *)); +} + +int +number_of_args () +{ + register WORD_LIST *list; + int n; + + for (n = 0; n < 9 && dollar_vars[n+1]; n++) + ; + for (list = rest_of_args; list; list = list->next) + n++; + return n; +} + +/* Return the value of a positional parameter. This handles values > 10. */ +char * +get_dollar_var_value (ind) + intmax_t ind; +{ + char *temp; + WORD_LIST *p; + + if (ind < 10) + temp = dollar_vars[ind] ? savestring (dollar_vars[ind]) : (char *)NULL; + else /* We want something like ${11} */ + { + ind -= 10; + for (p = rest_of_args; p && ind--; p = p->next) + ; + temp = p ? savestring (p->word->word) : (char *)NULL; + } + return (temp); +} + +/* Make a single large string out of the dollar digit variables, + and the rest_of_args. If DOLLAR_STAR is 1, then obey the special + case of "$*" with respect to IFS. */ +char * +string_rest_of_args (dollar_star) + int dollar_star; +{ + register WORD_LIST *list; + char *string; + + list = list_rest_of_args (); + string = dollar_star ? string_list_dollar_star (list) : string_list (list); + dispose_words (list); + return (string); +} + +/* Return a string containing the positional parameters from START to + END, inclusive. If STRING[0] == '*', we obey the rules for $*, + which only makes a difference if QUOTED is non-zero. If QUOTED includes + Q_HERE_DOCUMENT or Q_DOUBLE_QUOTES, this returns a quoted list, otherwise + no quoting chars are added. */ +static char * +pos_params (string, start, end, quoted) + char *string; + int start, end, quoted; +{ + WORD_LIST *save, *params, *h, *t; + char *ret; + int i; + + /* see if we can short-circuit. if start == end, we want 0 parameters. */ + if (start == end) + return ((char *)NULL); + + save = params = list_rest_of_args (); + if (save == 0) + return ((char *)NULL); + + if (start == 0) /* handle ${@:0[:x]} specially */ + { + t = make_word_list (make_word (dollar_vars[0]), params); + save = params = t; + } + + for (i = start ? 1 : 0; params && i < start; i++) + params = params->next; + if (params == 0) + return ((char *)NULL); + for (h = t = params; params && i < end; i++) + { + t = params; + params = params->next; + } + + t->next = (WORD_LIST *)NULL; + + ret = string_list_pos_params (string[0], h, quoted); + + if (t != params) + t->next = params; + + dispose_words (save); + return (ret); +} + +/******************************************************************/ +/* */ +/* Functions to expand strings to strings or WORD_LISTs */ +/* */ +/******************************************************************/ + +#if defined (PROCESS_SUBSTITUTION) +#define EXP_CHAR(s) (s == '$' || s == '`' || s == '<' || s == '>' || s == CTLESC || s == '~') +#else +#define EXP_CHAR(s) (s == '$' || s == '`' || s == CTLESC || s == '~') +#endif + +/* If there are any characters in STRING that require full expansion, + then call FUNC to expand STRING; otherwise just perform quote + removal if necessary. This returns a new string. */ +static char * +expand_string_if_necessary (string, quoted, func) + char *string; + int quoted; + EXPFUNC *func; +{ + WORD_LIST *list; + size_t slen; + int i, saw_quote; + char *ret; + DECLARE_MBSTATE; + + /* Don't need string length for ADVANCE_CHAR unless multibyte chars possible. */ + slen = (MB_CUR_MAX > 1) ? strlen (string) : 0; + i = saw_quote = 0; + while (string[i]) + { + if (EXP_CHAR (string[i])) + break; + else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"') + saw_quote = 1; + ADVANCE_CHAR (string, slen, i); + } + + if (string[i]) + { + list = (*func) (string, quoted); + if (list) + { + ret = string_list (list); + dispose_words (list); + } + else + ret = (char *)NULL; + } + else if (saw_quote && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) + ret = string_quote_removal (string, quoted); + else + ret = savestring (string); + + return ret; +} + +static inline char * +expand_string_to_string_internal (string, quoted, func) + char *string; + int quoted; + EXPFUNC *func; +{ + WORD_LIST *list; + char *ret; + + if (string == 0 || *string == '\0') + return ((char *)NULL); + + list = (*func) (string, quoted); + if (list) + { + ret = string_list (list); + dispose_words (list); + } + else + ret = (char *)NULL; + + return (ret); +} + +char * +expand_string_to_string (string, quoted) + char *string; + int quoted; +{ + return (expand_string_to_string_internal (string, quoted, expand_string)); +} + +char * +expand_string_unsplit_to_string (string, quoted) + char *string; + int quoted; +{ + return (expand_string_to_string_internal (string, quoted, expand_string_unsplit)); +} + +char * +expand_assignment_string_to_string (string, quoted) + char *string; + int quoted; +{ + return (expand_string_to_string_internal (string, quoted, expand_string_assignment)); +} + +char * +expand_arith_string (string, quoted) + char *string; + int quoted; +{ + return (expand_string_if_necessary (string, quoted, expand_string)); +} + +#if defined (COND_COMMAND) +/* Just remove backslashes in STRING. Returns a new string. */ +char * +remove_backslashes (string) + char *string; +{ + char *r, *ret, *s; + + r = ret = (char *)xmalloc (strlen (string) + 1); + for (s = string; s && *s; ) + { + if (*s == '\\') + s++; + if (*s == 0) + break; + *r++ = *s++; + } + *r = '\0'; + return ret; +} + +/* This needs better error handling. */ +/* Expand W for use as an argument to a unary or binary operator in a + [[...]] expression. If SPECIAL is 1, this is the rhs argument + to the != or == operator, and should be treated as a pattern. In + this case, we quote the string specially for the globbing code. If + SPECIAL is 2, this is an rhs argument for the =~ operator, and should + be quoted appropriately for regcomp/regexec. The caller is responsible + for removing the backslashes if the unquoted word is needed later. */ +char * +cond_expand_word (w, special) + WORD_DESC *w; + int special; +{ + char *r, *p; + WORD_LIST *l; + int qflags; + + if (w->word == 0 || w->word[0] == '\0') + return ((char *)NULL); + + w->flags |= W_NOSPLIT2; + l = call_expand_word_internal (w, 0, 0, (int *)0, (int *)0); + if (l) + { + if (special == 0) + { + dequote_list (l); + r = string_list (l); + } + else + { + qflags = QGLOB_CVTNULL; + if (special == 2) + qflags |= QGLOB_REGEXP; + p = string_list (l); + r = quote_string_for_globbing (p, qflags); + free (p); + } + dispose_words (l); + } + else + r = (char *)NULL; + + return r; +} +#endif + +/* Call expand_word_internal to expand W and handle error returns. + A convenience function for functions that don't want to handle + any errors or free any memory before aborting. */ +static WORD_LIST * +call_expand_word_internal (w, q, i, c, e) + WORD_DESC *w; + int q, i, *c, *e; +{ + WORD_LIST *result; + + result = expand_word_internal (w, q, i, c, e); + if (result == &expand_word_error || result == &expand_word_fatal) + { + /* By convention, each time this error is returned, w->word has + already been freed (it sometimes may not be in the fatal case, + but that doesn't result in a memory leak because we're going + to exit in most cases). */ + w->word = (char *)NULL; + last_command_exit_value = EXECUTION_FAILURE; + exp_jump_to_top_level ((result == &expand_word_error) ? DISCARD : FORCE_EOF); + /* NOTREACHED */ + } + else + return (result); +} + +/* Perform parameter expansion, command substitution, and arithmetic + expansion on STRING, as if it were a word. Leave the result quoted. */ +static WORD_LIST * +expand_string_internal (string, quoted) + char *string; + int quoted; +{ + WORD_DESC td; + WORD_LIST *tresult; + + if (string == 0 || *string == 0) + return ((WORD_LIST *)NULL); + + td.flags = 0; + td.word = savestring (string); + + tresult = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); + + FREE (td.word); + return (tresult); +} + +/* Expand STRING by performing parameter expansion, command substitution, + and arithmetic expansion. Dequote the resulting WORD_LIST before + returning it, but do not perform word splitting. The call to + remove_quoted_nulls () is in here because word splitting normally + takes care of quote removal. */ +WORD_LIST * +expand_string_unsplit (string, quoted) + char *string; + int quoted; +{ + WORD_LIST *value; + + if (string == 0 || *string == '\0') + return ((WORD_LIST *)NULL); + + expand_no_split_dollar_star = 1; + value = expand_string_internal (string, quoted); + expand_no_split_dollar_star = 0; + + if (value) + { + if (value->word) + { + remove_quoted_nulls (value->word->word); + value->word->flags &= ~W_HASQUOTEDNULL; + } + dequote_list (value); + } + return (value); +} + +/* Expand the rhs of an assignment statement */ +WORD_LIST * +expand_string_assignment (string, quoted) + char *string; + int quoted; +{ + WORD_DESC td; + WORD_LIST *value; + + if (string == 0 || *string == '\0') + return ((WORD_LIST *)NULL); + + expand_no_split_dollar_star = 1; + + td.flags = W_ASSIGNRHS; + td.word = savestring (string); + value = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); + FREE (td.word); + + expand_no_split_dollar_star = 0; + + if (value) + { + if (value->word) + { + remove_quoted_nulls (value->word->word); + value->word->flags &= ~W_HASQUOTEDNULL; + } + dequote_list (value); + } + return (value); +} + + +/* Expand one of the PS? prompt strings. This is a sort of combination of + expand_string_unsplit and expand_string_internal, but returns the + passed string when an error occurs. Might want to trap other calls + to jump_to_top_level here so we don't endlessly loop. */ +WORD_LIST * +expand_prompt_string (string, quoted, wflags) + char *string; + int quoted; + int wflags; +{ + WORD_LIST *value; + WORD_DESC td; + + if (string == 0 || *string == 0) + return ((WORD_LIST *)NULL); + + td.flags = wflags; + td.word = savestring (string); + + no_longjmp_on_fatal_error = 1; + value = expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); + no_longjmp_on_fatal_error = 0; + + if (value == &expand_word_error || value == &expand_word_fatal) + { + value = make_word_list (make_bare_word (string), (WORD_LIST *)NULL); + return value; + } + FREE (td.word); + if (value) + { + if (value->word) + { + remove_quoted_nulls (value->word->word); + value->word->flags &= ~W_HASQUOTEDNULL; + } + dequote_list (value); + } + return (value); +} + +/* Expand STRING just as if you were expanding a word, but do not dequote + the resultant WORD_LIST. This is called only from within this file, + and is used to correctly preserve quoted characters when expanding + things like ${1+"$@"}. This does parameter expansion, command + substitution, arithmetic expansion, and word splitting. */ +static WORD_LIST * +expand_string_leave_quoted (string, quoted) + char *string; + int quoted; +{ + WORD_LIST *tlist; + WORD_LIST *tresult; + + if (string == 0 || *string == '\0') + return ((WORD_LIST *)NULL); + + tlist = expand_string_internal (string, quoted); + + if (tlist) + { + tresult = word_list_split (tlist); + dispose_words (tlist); + return (tresult); + } + return ((WORD_LIST *)NULL); +} + +/* This does not perform word splitting or dequote the WORD_LIST + it returns. */ +static WORD_LIST * +expand_string_for_rhs (string, quoted, dollar_at_p, has_dollar_at) + char *string; + int quoted, *dollar_at_p, *has_dollar_at; +{ + WORD_DESC td; + WORD_LIST *tresult; + + if (string == 0 || *string == '\0') + return (WORD_LIST *)NULL; + + td.flags = 0; + td.word = string; + tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, has_dollar_at); + return (tresult); +} + +/* Expand STRING just as if you were expanding a word. This also returns + a list of words. Note that filename globbing is *NOT* done for word + or string expansion, just when the shell is expanding a command. This + does parameter expansion, command substitution, arithmetic expansion, + and word splitting. Dequote the resultant WORD_LIST before returning. */ +WORD_LIST * +expand_string (string, quoted) + char *string; + int quoted; +{ + WORD_LIST *result; + + if (string == 0 || *string == '\0') + return ((WORD_LIST *)NULL); + + result = expand_string_leave_quoted (string, quoted); + return (result ? dequote_list (result) : result); +} + +/*************************************************** + * * + * Functions to handle quoting chars * + * * + ***************************************************/ + +/* Conventions: + + A string with s[0] == CTLNUL && s[1] == 0 is a quoted null string. + The parser passes CTLNUL as CTLESC CTLNUL. */ + +/* Quote escape characters in string s, but no other characters. This is + used to protect CTLESC and CTLNUL in variable values from the rest of + the word expansion process after the variable is expanded (word splitting + and filename generation). If IFS is null, we quote spaces as well, just + in case we split on spaces later (in the case of unquoted $@, we will + eventually attempt to split the entire word on spaces). Corresponding + code exists in dequote_escapes. Even if we don't end up splitting on + spaces, quoting spaces is not a problem. This should never be called on + a string that is quoted with single or double quotes or part of a here + document (effectively double-quoted). */ +char * +quote_escapes (string) + char *string; +{ + register char *s, *t; + size_t slen; + char *result, *send; + int quote_spaces, skip_ctlesc, skip_ctlnul; + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + + quote_spaces = (ifs_value && *ifs_value == 0); + + for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++) + skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL; + + t = result = (char *)xmalloc ((slen * 2) + 1); + s = string; + + while (*s) + { + if ((skip_ctlesc == 0 && *s == CTLESC) || (skip_ctlnul == 0 && *s == CTLNUL) || (quote_spaces && *s == ' ')) + *t++ = CTLESC; + COPY_CHAR_P (t, s, send); + } + *t = '\0'; + return (result); +} + +static WORD_LIST * +list_quote_escapes (list) + WORD_LIST *list; +{ + register WORD_LIST *w; + char *t; + + for (w = list; w; w = w->next) + { + t = w->word->word; + w->word->word = quote_escapes (t); + free (t); + } + return list; +} + +/* Inverse of quote_escapes; remove CTLESC protecting CTLESC or CTLNUL. + + The parser passes us CTLESC as CTLESC CTLESC and CTLNUL as CTLESC CTLNUL. + This is necessary to make unquoted CTLESC and CTLNUL characters in the + data stream pass through properly. + + We need to remove doubled CTLESC characters inside quoted strings before + quoting the entire string, so we do not double the number of CTLESC + characters. + + Also used by parts of the pattern substitution code. */ +char * +dequote_escapes (string) + char *string; +{ + register char *s, *t, *s1; + size_t slen; + char *result, *send; + int quote_spaces; + DECLARE_MBSTATE; + + if (string == 0) + return string; + + slen = strlen (string); + send = string + slen; + + t = result = (char *)xmalloc (slen + 1); + + if (strchr (string, CTLESC) == 0) + return (strcpy (result, string)); + + quote_spaces = (ifs_value && *ifs_value == 0); + + s = string; + while (*s) + { + if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL || (quote_spaces && s[1] == ' '))) + { + s++; + if (*s == '\0') + break; + } + COPY_CHAR_P (t, s, send); + } + *t = '\0'; + return result; +} + +/* Return a new string with the quoted representation of character C. + This turns "" into QUOTED_NULL, so the W_HASQUOTEDNULL flag needs to be + set in any resultant WORD_DESC where this value is the word. */ +static char * +make_quoted_char (c) + int c; +{ + char *temp; + + temp = (char *)xmalloc (3); + if (c == 0) + { + temp[0] = CTLNUL; + temp[1] = '\0'; + } + else + { + temp[0] = CTLESC; + temp[1] = c; + temp[2] = '\0'; + } + return (temp); +} + +/* Quote STRING, returning a new string. This turns "" into QUOTED_NULL, so + the W_HASQUOTEDNULL flag needs to be set in any resultant WORD_DESC where + this value is the word. */ +char * +quote_string (string) + char *string; +{ + register char *t; + size_t slen; + char *result, *send; + + if (*string == 0) + { + result = (char *)xmalloc (2); + result[0] = CTLNUL; + result[1] = '\0'; + } + else + { + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + + result = (char *)xmalloc ((slen * 2) + 1); + + for (t = result; string < send; ) + { + *t++ = CTLESC; + COPY_CHAR_P (t, string, send); + } + *t = '\0'; + } + return (result); +} + +/* De-quote quoted characters in STRING. */ +char * +dequote_string (string) + char *string; +{ + register char *s, *t; + size_t slen; + char *result, *send; + DECLARE_MBSTATE; + + slen = strlen (string); + + t = result = (char *)xmalloc (slen + 1); + + if (QUOTED_NULL (string)) + { + result[0] = '\0'; + return (result); + } + + /* If no character in the string can be quoted, don't bother examining + each character. Just return a copy of the string passed to us. */ + if (strchr (string, CTLESC) == NULL) + return (strcpy (result, string)); + + send = string + slen; + s = string; + while (*s) + { + if (*s == CTLESC) + { + s++; + if (*s == '\0') + break; + } + COPY_CHAR_P (t, s, send); + } + + *t = '\0'; + return (result); +} + +/* Quote the entire WORD_LIST list. */ +static WORD_LIST * +quote_list (list) + WORD_LIST *list; +{ + register WORD_LIST *w; + char *t; + + for (w = list; w; w = w->next) + { + t = w->word->word; + w->word->word = quote_string (t); + if (*t == 0) + w->word->flags |= W_HASQUOTEDNULL; /* XXX - turn on W_HASQUOTEDNULL here? */ + w->word->flags |= W_QUOTED; + free (t); + } + return list; +} + +/* De-quote quoted characters in each word in LIST. */ +WORD_LIST * +dequote_list (list) + WORD_LIST *list; +{ + register char *s; + register WORD_LIST *tlist; + + for (tlist = list; tlist; tlist = tlist->next) + { + s = dequote_string (tlist->word->word); + if (QUOTED_NULL (tlist->word->word)) + tlist->word->flags &= ~W_HASQUOTEDNULL; + free (tlist->word->word); + tlist->word->word = s; + } + return list; +} + +/* Remove CTLESC protecting a CTLESC or CTLNUL in place. Return the passed + string. */ +char * +remove_quoted_escapes (string) + char *string; +{ + char *t; + + if (string) + { + t = dequote_escapes (string); + strcpy (string, t); + free (t); + } + + return (string); +} + +/* Perform quoted null character removal on STRING. We don't allow any + quoted null characters in the middle or at the ends of strings because + of how expand_word_internal works. remove_quoted_nulls () turns + STRING into an empty string iff it only consists of a quoted null, + and removes all unquoted CTLNUL characters. */ +char * +remove_quoted_nulls (string) + char *string; +{ + register size_t slen; + register int i, j, prev_i; + DECLARE_MBSTATE; + + if (strchr (string, CTLNUL) == 0) /* XXX */ + return string; /* XXX */ + + slen = strlen (string); + i = j = 0; + + while (i < slen) + { + if (string[i] == CTLESC) + { + /* Old code had j++, but we cannot assume that i == j at this + point -- what if a CTLNUL has already been removed from the + string? We don't want to drop the CTLESC or recopy characters + that we've already copied down. */ + i++; string[j++] = CTLESC; + if (i == slen) + break; + } + else if (string[i] == CTLNUL) + i++; + + prev_i = i; + ADVANCE_CHAR (string, slen, i); + if (j < prev_i) + { + do string[j++] = string[prev_i++]; while (prev_i < i); + } + else + j = i; + } + string[j] = '\0'; + + return (string); +} + +/* Perform quoted null character removal on each element of LIST. + This modifies LIST. */ +void +word_list_remove_quoted_nulls (list) + WORD_LIST *list; +{ + register WORD_LIST *t; + + for (t = list; t; t = t->next) + { + remove_quoted_nulls (t->word->word); + t->word->flags &= ~W_HASQUOTEDNULL; + } +} + +/* **************************************************************** */ +/* */ +/* Functions for Matching and Removing Patterns */ +/* */ +/* **************************************************************** */ + +#if defined (HANDLE_MULTIBYTE) +#if 0 /* Currently unused */ +static unsigned char * +mb_getcharlens (string, len) + char *string; + int len; +{ + int i, offset, last; + unsigned char *ret; + char *p; + DECLARE_MBSTATE; + + i = offset = 0; + last = 0; + ret = (unsigned char *)xmalloc (len); + memset (ret, 0, len); + while (string[last]) + { + ADVANCE_CHAR (string, len, offset); + ret[last] = offset - last; + last = offset; + } + return ret; +} +#endif +#endif + +/* Remove the portion of PARAM matched by PATTERN according to OP, where OP + can have one of 4 values: + RP_LONG_LEFT remove longest matching portion at start of PARAM + RP_SHORT_LEFT remove shortest matching portion at start of PARAM + RP_LONG_RIGHT remove longest matching portion at end of PARAM + RP_SHORT_RIGHT remove shortest matching portion at end of PARAM +*/ + +#define RP_LONG_LEFT 1 +#define RP_SHORT_LEFT 2 +#define RP_LONG_RIGHT 3 +#define RP_SHORT_RIGHT 4 + +/* Returns its first argument if nothing matched; new memory otherwise */ +static char * +remove_upattern (param, pattern, op) + char *param, *pattern; + int op; +{ + register int len; + register char *end; + register char *p, *ret, c; + + len = STRLEN (param); + end = param + len; + + switch (op) + { + case RP_LONG_LEFT: /* remove longest match at start */ + for (p = end; p >= param; p--) + { + c = *p; *p = '\0'; + if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + *p = c; + return (savestring (p)); + } + *p = c; + + } + break; + + case RP_SHORT_LEFT: /* remove shortest match at start */ + for (p = param; p <= end; p++) + { + c = *p; *p = '\0'; + if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + *p = c; + return (savestring (p)); + } + *p = c; + } + break; + + case RP_LONG_RIGHT: /* remove longest match at end */ + for (p = param; p <= end; p++) + { + if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + c = *p; *p = '\0'; + ret = savestring (param); + *p = c; + return (ret); + } + } + break; + + case RP_SHORT_RIGHT: /* remove shortest match at end */ + for (p = end; p >= param; p--) + { + if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + c = *p; *p = '\0'; + ret = savestring (param); + *p = c; + return (ret); + } + } + break; + } + + return (param); /* no match, return original string */ +} + +#if defined (HANDLE_MULTIBYTE) +/* Returns its first argument if nothing matched; new memory otherwise */ +static wchar_t * +remove_wpattern (wparam, wstrlen, wpattern, op) + wchar_t *wparam; + size_t wstrlen; + wchar_t *wpattern; + int op; +{ + wchar_t wc, *ret; + int n; + + switch (op) + { + case RP_LONG_LEFT: /* remove longest match at start */ + for (n = wstrlen; n >= 0; n--) + { + wc = wparam[n]; wparam[n] = L'\0'; + if (wcsmatch (wpattern, wparam, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + wparam[n] = wc; + return (wcsdup (wparam + n)); + } + wparam[n] = wc; + } + break; + + case RP_SHORT_LEFT: /* remove shortest match at start */ + for (n = 0; n <= wstrlen; n++) + { + wc = wparam[n]; wparam[n] = L'\0'; + if (wcsmatch (wpattern, wparam, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + wparam[n] = wc; + return (wcsdup (wparam + n)); + } + wparam[n] = wc; + } + break; + + case RP_LONG_RIGHT: /* remove longest match at end */ + for (n = 0; n <= wstrlen; n++) + { + if (wcsmatch (wpattern, wparam + n, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + wc = wparam[n]; wparam[n] = L'\0'; + ret = wcsdup (wparam); + wparam[n] = wc; + return (ret); + } + } + break; + + case RP_SHORT_RIGHT: /* remove shortest match at end */ + for (n = wstrlen; n >= 0; n--) + { + if (wcsmatch (wpattern, wparam + n, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + wc = wparam[n]; wparam[n] = L'\0'; + ret = wcsdup (wparam); + wparam[n] = wc; + return (ret); + } + } + break; + } + + return (wparam); /* no match, return original string */ +} +#endif /* HANDLE_MULTIBYTE */ + +static char * +remove_pattern (param, pattern, op) + char *param, *pattern; + int op; +{ + char *xret; + + if (param == NULL) + return (param); + if (*param == '\0' || pattern == NULL || *pattern == '\0') /* minor optimization */ + return (savestring (param)); + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1) + { + wchar_t *ret, *oret; + size_t n; + wchar_t *wparam, *wpattern; + mbstate_t ps; + + n = xdupmbstowcs (&wpattern, NULL, pattern); + if (n == (size_t)-1) + { + xret = remove_upattern (param, pattern, op); + return ((xret == param) ? savestring (param) : xret); + } + n = xdupmbstowcs (&wparam, NULL, param); + if (n == (size_t)-1) + { + free (wpattern); + xret = remove_upattern (param, pattern, op); + return ((xret == param) ? savestring (param) : xret); + } + oret = ret = remove_wpattern (wparam, n, wpattern, op); + /* Don't bother to convert wparam back to multibyte string if nothing + matched; just return copy of original string */ + if (ret == wparam) + { + free (wparam); + free (wpattern); + return (savestring (param)); + } + + free (wparam); + free (wpattern); + + n = strlen (param); + xret = (char *)xmalloc (n + 1); + memset (&ps, '\0', sizeof (mbstate_t)); + n = wcsrtombs (xret, (const wchar_t **)&ret, n, &ps); + xret[n] = '\0'; /* just to make sure */ + free (oret); + return xret; + } + else +#endif + { + xret = remove_upattern (param, pattern, op); + return ((xret == param) ? savestring (param) : xret); + } +} + +/* Match PAT anywhere in STRING and return the match boundaries. + This returns 1 in case of a successful match, 0 otherwise. SP + and EP are pointers into the string where the match begins and + ends, respectively. MTYPE controls what kind of match is attempted. + MATCH_BEG and MATCH_END anchor the match at the beginning and end + of the string, respectively. The longest match is returned. */ +static int +match_upattern (string, pat, mtype, sp, ep) + char *string, *pat; + int mtype; + char **sp, **ep; +{ + int c, len, mlen; + register char *p, *p1, *npat; + char *end; + int n1; + + /* If the pattern doesn't match anywhere in the string, go ahead and + short-circuit right away. A minor optimization, saves a bunch of + unnecessary calls to strmatch (up to N calls for a string of N + characters) if the match is unsuccessful. To preserve the semantics + of the substring matches below, we make sure that the pattern has + `*' as first and last character, making a new pattern if necessary. */ + /* XXX - check this later if I ever implement `**' with special meaning, + since this will potentially result in `**' at the beginning or end */ + len = STRLEN (pat); + if (pat[0] != '*' || (pat[0] == '*' && pat[1] == LPAREN && extended_glob) || pat[len - 1] != '*') + { + p = npat = (char *)xmalloc (len + 3); + p1 = pat; + if (*p1 != '*' || (*p1 == '*' && p1[1] == LPAREN && extended_glob)) + *p++ = '*'; + while (*p1) + *p++ = *p1++; + if (p1[-1] != '*' || p[-2] == '\\') + *p++ = '*'; + *p = '\0'; + } + else + npat = pat; + c = strmatch (npat, string, FNMATCH_EXTFLAG); + if (npat != pat) + free (npat); + if (c == FNM_NOMATCH) + return (0); + + len = STRLEN (string); + end = string + len; + + mlen = umatchlen (pat, len); + + switch (mtype) + { + case MATCH_ANY: + for (p = string; p <= end; p++) + { + if (match_pattern_char (pat, p)) + { +#if 0 + for (p1 = end; p1 >= p; p1--) +#else + p1 = (mlen == -1) ? end : p + mlen; + /* p1 - p = length of portion of string to be considered + p = current position in string + mlen = number of characters consumed by match (-1 for entire string) + end = end of string + we want to break immediately if the potential match len + is greater than the number of characters remaining in the + string + */ + if (p1 > end) + break; + for ( ; p1 >= p; p1--) +#endif + { + c = *p1; *p1 = '\0'; + if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0) + { + *p1 = c; + *sp = p; + *ep = p1; + return 1; + } + *p1 = c; +#if 1 + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; +#endif + } + } + } + + return (0); + + case MATCH_BEG: + if (match_pattern_char (pat, string) == 0) + return (0); + +#if 0 + for (p = end; p >= string; p--) +#else + for (p = (mlen == -1) ? end : string + mlen; p >= string; p--) +#endif + { + c = *p; *p = '\0'; + if (strmatch (pat, string, FNMATCH_EXTFLAG) == 0) + { + *p = c; + *sp = string; + *ep = p; + return 1; + } + *p = c; +#if 1 + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; +#endif + } + + return (0); + + case MATCH_END: +#if 0 + for (p = string; p <= end; p++) +#else + for (p = end - ((mlen == -1) ? len : mlen); p <= end; p++) +#endif + { + if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0) + { + *sp = p; + *ep = end; + return 1; + } +#if 1 + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; +#endif + } + + return (0); + } + + return (0); +} + +#if defined (HANDLE_MULTIBYTE) +/* Match WPAT anywhere in WSTRING and return the match boundaries. + This returns 1 in case of a successful match, 0 otherwise. Wide + character version. */ +static int +match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep) + wchar_t *wstring; + char **indices; + size_t wstrlen; + wchar_t *wpat; + int mtype; + char **sp, **ep; +{ + wchar_t wc, *wp, *nwpat, *wp1; + size_t len; + int mlen; + int n, n1, n2, simple; + + simple = (wpat[0] != L'\\' && wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'['); +#if defined (EXTENDED_GLOB) + if (extended_glob) + simple |= (wpat[1] != L'(' || (wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'+' && wpat[0] != L'!' && wpat[0] != L'@')); /*)*/ +#endif + + /* If the pattern doesn't match anywhere in the string, go ahead and + short-circuit right away. A minor optimization, saves a bunch of + unnecessary calls to strmatch (up to N calls for a string of N + characters) if the match is unsuccessful. To preserve the semantics + of the substring matches below, we make sure that the pattern has + `*' as first and last character, making a new pattern if necessary. */ + len = wcslen (wpat); + if (wpat[0] != L'*' || (wpat[0] == L'*' && wpat[1] == WLPAREN && extended_glob) || wpat[len - 1] != L'*') + { + wp = nwpat = (wchar_t *)xmalloc ((len + 3) * sizeof (wchar_t)); + wp1 = wpat; + if (*wp1 != L'*' || (*wp1 == '*' && wp1[1] == WLPAREN && extended_glob)) + *wp++ = L'*'; + while (*wp1 != L'\0') + *wp++ = *wp1++; + if (wp1[-1] != L'*' || wp1[-2] == L'\\') + *wp++ = L'*'; + *wp = '\0'; + } + else + nwpat = wpat; + len = wcsmatch (nwpat, wstring, FNMATCH_EXTFLAG); + if (nwpat != wpat) + free (nwpat); + if (len == FNM_NOMATCH) + return (0); + + mlen = wmatchlen (wpat, wstrlen); + +/* itrace("wmatchlen (%ls) -> %d", wpat, mlen); */ + switch (mtype) + { + case MATCH_ANY: + for (n = 0; n <= wstrlen; n++) + { +#if 1 + n2 = simple ? (*wpat == wstring[n]) : match_pattern_wchar (wpat, wstring + n); +#else + n2 = match_pattern_wchar (wpat, wstring + n); +#endif + if (n2) + { +#if 0 + for (n1 = wstrlen; n1 >= n; n1--) +#else + n1 = (mlen == -1) ? wstrlen : n + mlen; + if (n1 > wstrlen) + break; + + for ( ; n1 >= n; n1--) +#endif + { + wc = wstring[n1]; wstring[n1] = L'\0'; + if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0) + { + wstring[n1] = wc; + *sp = indices[n]; + *ep = indices[n1]; + return 1; + } + wstring[n1] = wc; +#if 1 + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; +#endif + } + } + } + + return (0); + + case MATCH_BEG: + if (match_pattern_wchar (wpat, wstring) == 0) + return (0); + +#if 0 + for (n = wstrlen; n >= 0; n--) +#else + for (n = (mlen == -1) ? wstrlen : mlen; n >= 0; n--) +#endif + { + wc = wstring[n]; wstring[n] = L'\0'; + if (wcsmatch (wpat, wstring, FNMATCH_EXTFLAG) == 0) + { + wstring[n] = wc; + *sp = indices[0]; + *ep = indices[n]; + return 1; + } + wstring[n] = wc; +#if 1 + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; +#endif + } + + return (0); + + case MATCH_END: +#if 0 + for (n = 0; n <= wstrlen; n++) +#else + for (n = wstrlen - ((mlen == -1) ? wstrlen : mlen); n <= wstrlen; n++) +#endif + { + if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0) + { + *sp = indices[n]; + *ep = indices[wstrlen]; + return 1; + } +#if 1 + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; +#endif + } + + return (0); + } + + return (0); +} +#endif /* HANDLE_MULTIBYTE */ + +static int +match_pattern (string, pat, mtype, sp, ep) + char *string, *pat; + int mtype; + char **sp, **ep; +{ +#if defined (HANDLE_MULTIBYTE) + int ret; + size_t n; + wchar_t *wstring, *wpat; + char **indices; + size_t slen, plen, mslen, mplen; +#endif + + if (string == 0 || *string == 0 || pat == 0 || *pat == 0) + return (0); + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1) + { +#if 0 + slen = STRLEN (string); + mslen = MBSLEN (string); + plen = STRLEN (pat); + mplen = MBSLEN (pat); + if (slen == mslen && plen == mplen) +#else + if (mbsmbchar (string) == 0 && mbsmbchar (pat) == 0) +#endif + return (match_upattern (string, pat, mtype, sp, ep)); + + n = xdupmbstowcs (&wpat, NULL, pat); + if (n == (size_t)-1) + return (match_upattern (string, pat, mtype, sp, ep)); + n = xdupmbstowcs (&wstring, &indices, string); + if (n == (size_t)-1) + { + free (wpat); + return (match_upattern (string, pat, mtype, sp, ep)); + } + ret = match_wpattern (wstring, indices, n, wpat, mtype, sp, ep); + + free (wpat); + free (wstring); + free (indices); + + return (ret); + } + else +#endif + return (match_upattern (string, pat, mtype, sp, ep)); +} + +static int +getpatspec (c, value) + int c; + char *value; +{ + if (c == '#') + return ((*value == '#') ? RP_LONG_LEFT : RP_SHORT_LEFT); + else /* c == '%' */ + return ((*value == '%') ? RP_LONG_RIGHT : RP_SHORT_RIGHT); +} + +/* Posix.2 says that the WORD should be run through tilde expansion, + parameter expansion, command substitution and arithmetic expansion. + This leaves the result quoted, so quote_string_for_globbing () has + to be called to fix it up for strmatch (). If QUOTED is non-zero, + it means that the entire expression was enclosed in double quotes. + This means that quoting characters in the pattern do not make any + special pattern characters quoted. For example, the `*' in the + following retains its special meaning: "${foo#'*'}". */ +static char * +getpattern (value, quoted, expandpat) + char *value; + int quoted, expandpat; +{ + char *pat, *tword; + WORD_LIST *l; +#if 0 + int i; +#endif + /* There is a problem here: how to handle single or double quotes in the + pattern string when the whole expression is between double quotes? + POSIX.2 says that enclosing double quotes do not cause the pattern to + be quoted, but does that leave us a problem with @ and array[@] and their + expansions inside a pattern? */ +#if 0 + if (expandpat && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *tword) + { + i = 0; + pat = string_extract_double_quoted (tword, &i, 1); + free (tword); + tword = pat; + } +#endif + + /* expand_string_for_rhs () leaves WORD quoted and does not perform + word splitting. */ + l = *value ? expand_string_for_rhs (value, + (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? Q_PATQUOTE : quoted, + (int *)NULL, (int *)NULL) + : (WORD_LIST *)0; + pat = string_list (l); + dispose_words (l); + if (pat) + { + tword = quote_string_for_globbing (pat, QGLOB_CVTNULL); + free (pat); + pat = tword; + } + return (pat); +} + +#if 0 +/* Handle removing a pattern from a string as a result of ${name%[%]value} + or ${name#[#]value}. */ +static char * +variable_remove_pattern (value, pattern, patspec, quoted) + char *value, *pattern; + int patspec, quoted; +{ + char *tword; + + tword = remove_pattern (value, pattern, patspec); + + return (tword); +} +#endif + +static char * +list_remove_pattern (list, pattern, patspec, itype, quoted) + WORD_LIST *list; + char *pattern; + int patspec, itype, quoted; +{ + WORD_LIST *new, *l; + WORD_DESC *w; + char *tword; + + for (new = (WORD_LIST *)NULL, l = list; l; l = l->next) + { + tword = remove_pattern (l->word->word, pattern, patspec); + w = alloc_word_desc (); + w->word = tword ? tword : savestring (""); + new = make_word_list (w, new); + } + + l = REVERSE_LIST (new, WORD_LIST *); + tword = string_list_pos_params (itype, l, quoted); + dispose_words (l); + + return (tword); +} + +static char * +parameter_list_remove_pattern (itype, pattern, patspec, quoted) + int itype; + char *pattern; + int patspec, quoted; +{ + char *ret; + WORD_LIST *list; + + list = list_rest_of_args (); + if (list == 0) + return ((char *)NULL); + ret = list_remove_pattern (list, pattern, patspec, itype, quoted); + dispose_words (list); + return (ret); +} + +#if defined (ARRAY_VARS) +static char * +array_remove_pattern (var, pattern, patspec, varname, quoted) + SHELL_VAR *var; + char *pattern; + int patspec; + char *varname; /* so we can figure out how it's indexed */ + int quoted; +{ + ARRAY *a; + HASH_TABLE *h; + int itype; + char *ret; + WORD_LIST *list; + SHELL_VAR *v; + + /* compute itype from varname here */ + v = array_variable_part (varname, &ret, 0); + itype = ret[0]; + + a = (v && array_p (v)) ? array_cell (v) : 0; + h = (v && assoc_p (v)) ? assoc_cell (v) : 0; + + list = a ? array_to_word_list (a) : (h ? assoc_to_word_list (h) : 0); + if (list == 0) + return ((char *)NULL); + ret = list_remove_pattern (list, pattern, patspec, itype, quoted); + dispose_words (list); + + return ret; +} +#endif /* ARRAY_VARS */ + +static char * +parameter_brace_remove_pattern (varname, value, ind, patstr, rtype, quoted, flags) + char *varname, *value; + int ind; + char *patstr; + int rtype, quoted, flags; +{ + int vtype, patspec, starsub; + char *temp1, *val, *pattern; + SHELL_VAR *v; + + if (value == 0) + return ((char *)NULL); + + this_command_name = varname; + + vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val); + if (vtype == -1) + return ((char *)NULL); + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + patspec = getpatspec (rtype, patstr); + if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT) + patstr++; + + /* Need to pass getpattern newly-allocated memory in case of expansion -- + the expansion code will free the passed string on an error. */ + temp1 = savestring (patstr); + pattern = getpattern (temp1, quoted, 1); + free (temp1); + + temp1 = (char *)NULL; /* shut up gcc */ + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: + temp1 = remove_pattern (val, pattern, patspec); + if (vtype == VT_VARIABLE) + FREE (val); + if (temp1) + { + val = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + ? quote_string (temp1) + : quote_escapes (temp1); + free (temp1); + temp1 = val; + } + break; +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + temp1 = array_remove_pattern (v, pattern, patspec, varname, quoted); + if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) + { + val = quote_escapes (temp1); + free (temp1); + temp1 = val; + } + break; +#endif + case VT_POSPARMS: + temp1 = parameter_list_remove_pattern (varname[0], pattern, patspec, quoted); + if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) + { + val = quote_escapes (temp1); + free (temp1); + temp1 = val; + } + break; + } + + FREE (pattern); + return temp1; +} + +/******************************************* + * * + * Functions to expand WORD_DESCs * + * * + *******************************************/ + +/* Expand WORD, performing word splitting on the result. This does + parameter expansion, command substitution, arithmetic expansion, + word splitting, and quote removal. */ + +WORD_LIST * +expand_word (word, quoted) + WORD_DESC *word; + int quoted; +{ + WORD_LIST *result, *tresult; + + tresult = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL); + result = word_list_split (tresult); + dispose_words (tresult); + return (result ? dequote_list (result) : result); +} + +/* Expand WORD, but do not perform word splitting on the result. This + does parameter expansion, command substitution, arithmetic expansion, + and quote removal. */ +WORD_LIST * +expand_word_unsplit (word, quoted) + WORD_DESC *word; + int quoted; +{ + WORD_LIST *result; + + expand_no_split_dollar_star = 1; +#if defined (HANDLE_MULTIBYTE) + if (ifs_firstc[0] == 0) +#else + if (ifs_firstc == 0) +#endif + word->flags |= W_NOSPLIT; + result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL); + expand_no_split_dollar_star = 0; + + return (result ? dequote_list (result) : result); +} + +/* Perform shell expansions on WORD, but do not perform word splitting or + quote removal on the result. Virtually identical to expand_word_unsplit; + could be combined if implementations don't diverge. */ +WORD_LIST * +expand_word_leave_quoted (word, quoted) + WORD_DESC *word; + int quoted; +{ + WORD_LIST *result; + + expand_no_split_dollar_star = 1; +#if defined (HANDLE_MULTIBYTE) + if (ifs_firstc[0] == 0) +#else + if (ifs_firstc == 0) +#endif + word->flags |= W_NOSPLIT; + word->flags |= W_NOSPLIT2; + result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL); + expand_no_split_dollar_star = 0; + + return result; +} + +#if defined (PROCESS_SUBSTITUTION) + +/*****************************************************************/ +/* */ +/* Hacking Process Substitution */ +/* */ +/*****************************************************************/ + +#if !defined (HAVE_DEV_FD) +/* Named pipes must be removed explicitly with `unlink'. This keeps a list + of FIFOs the shell has open. unlink_fifo_list will walk the list and + unlink all of them. add_fifo_list adds the name of an open FIFO to the + list. NFIFO is a count of the number of FIFOs in the list. */ +#define FIFO_INCR 20 + +struct temp_fifo { + char *file; + pid_t proc; +}; + +static struct temp_fifo *fifo_list = (struct temp_fifo *)NULL; +static int nfifo; +static int fifo_list_size; + +char * +copy_fifo_list (sizep) + int *sizep; +{ + if (sizep) + *sizep = 0; + return (char *)NULL; +} + +static void +add_fifo_list (pathname) + char *pathname; +{ + if (nfifo >= fifo_list_size - 1) + { + fifo_list_size += FIFO_INCR; + fifo_list = (struct temp_fifo *)xrealloc (fifo_list, + fifo_list_size * sizeof (struct temp_fifo)); + } + + fifo_list[nfifo].file = savestring (pathname); + nfifo++; +} + +void +unlink_fifo (i) + int i; +{ + if ((fifo_list[i].proc == -1) || (kill(fifo_list[i].proc, 0) == -1)) + { + unlink (fifo_list[i].file); + free (fifo_list[i].file); + fifo_list[i].file = (char *)NULL; + fifo_list[i].proc = -1; + } +} + +void +unlink_fifo_list () +{ + int saved, i, j; + + if (nfifo == 0) + return; + + for (i = saved = 0; i < nfifo; i++) + { + if ((fifo_list[i].proc == -1) || (kill(fifo_list[i].proc, 0) == -1)) + { + unlink (fifo_list[i].file); + free (fifo_list[i].file); + fifo_list[i].file = (char *)NULL; + fifo_list[i].proc = -1; + } + else + saved++; + } + + /* If we didn't remove some of the FIFOs, compact the list. */ + if (saved) + { + for (i = j = 0; i < nfifo; i++) + if (fifo_list[i].file) + { + fifo_list[j].file = fifo_list[i].file; + fifo_list[j].proc = fifo_list[i].proc; + j++; + } + nfifo = j; + } + else + nfifo = 0; +} + +/* Take LIST, which is a bitmap denoting active FIFOs in fifo_list + from some point in the past, and close all open FIFOs in fifo_list + that are not marked as active in LIST. If LIST is NULL, close + everything in fifo_list. LSIZE is the number of elements in LIST, in + case it's larger than fifo_list_size (size of fifo_list). */ +void +close_new_fifos (list, lsize) + char *list; + int lsize; +{ + int i; + + if (list == 0) + { + unlink_fifo_list (); + return; + } + + for (i = 0; i < lsize; i++) + if (list[i] == 0 && i < fifo_list_size && fifo_list[i].proc != -1) + unlink_fifo (i); + + for (i = lsize; i < fifo_list_size; i++) + unlink_fifo (i); +} + +int +fifos_pending () +{ + return nfifo; +} + +int +num_fifos () +{ + return nfifo; +} + +static char * +make_named_pipe () +{ + char *tname; + + tname = sh_mktmpname ("sh-np", MT_USERANDOM|MT_USETMPDIR); + if (mkfifo (tname, 0600) < 0) + { + free (tname); + return ((char *)NULL); + } + + add_fifo_list (tname); + return (tname); +} + +#else /* HAVE_DEV_FD */ + +/* DEV_FD_LIST is a bitmap of file descriptors attached to pipes the shell + has open to children. NFDS is a count of the number of bits currently + set in DEV_FD_LIST. TOTFDS is a count of the highest possible number + of open files. */ +static char *dev_fd_list = (char *)NULL; +static int nfds; +static int totfds; /* The highest possible number of open files. */ + +char * +copy_fifo_list (sizep) + int *sizep; +{ + char *ret; + + if (nfds == 0 || totfds == 0) + { + if (sizep) + *sizep = 0; + return (char *)NULL; + } + + if (sizep) + *sizep = totfds; + ret = (char *)xmalloc (totfds); + return (memcpy (ret, dev_fd_list, totfds)); +} + +static void +add_fifo_list (fd) + int fd; +{ + if (dev_fd_list == 0 || fd >= totfds) + { + int ofds; + + ofds = totfds; + totfds = getdtablesize (); + if (totfds < 0 || totfds > 256) + totfds = 256; + if (fd >= totfds) + totfds = fd + 2; + + dev_fd_list = (char *)xrealloc (dev_fd_list, totfds); + memset (dev_fd_list + ofds, '\0', totfds - ofds); + } + + dev_fd_list[fd] = 1; + nfds++; +} + +int +fifos_pending () +{ + return 0; /* used for cleanup; not needed with /dev/fd */ +} + +int +num_fifos () +{ + return nfds; +} + +void +unlink_fifo (fd) + int fd; +{ + if (dev_fd_list[fd]) + { + close (fd); + dev_fd_list[fd] = 0; + nfds--; + } +} + +void +unlink_fifo_list () +{ + register int i; + + if (nfds == 0) + return; + + for (i = 0; nfds && i < totfds; i++) + unlink_fifo (i); + + nfds = 0; +} + +/* Take LIST, which is a snapshot copy of dev_fd_list from some point in + the past, and close all open fds in dev_fd_list that are not marked + as open in LIST. If LIST is NULL, close everything in dev_fd_list. + LSIZE is the number of elements in LIST, in case it's larger than + totfds (size of dev_fd_list). */ +void +close_new_fifos (list, lsize) + char *list; + int lsize; +{ + int i; + + if (list == 0) + { + unlink_fifo_list (); + return; + } + + for (i = 0; i < lsize; i++) + if (list[i] == 0 && i < totfds && dev_fd_list[i]) + unlink_fifo (i); + + for (i = lsize; i < totfds; i++) + unlink_fifo (i); +} + +#if defined (NOTDEF) +print_dev_fd_list () +{ + register int i; + + fprintf (stderr, "pid %ld: dev_fd_list:", (long)getpid ()); + fflush (stderr); + + for (i = 0; i < totfds; i++) + { + if (dev_fd_list[i]) + fprintf (stderr, " %d", i); + } + fprintf (stderr, "\n"); +} +#endif /* NOTDEF */ + +static char * +make_dev_fd_filename (fd) + int fd; +{ + char *ret, intbuf[INT_STRLEN_BOUND (int) + 1], *p; + + ret = (char *)xmalloc (sizeof (DEV_FD_PREFIX) + 8); + + strcpy (ret, DEV_FD_PREFIX); + p = inttostr (fd, intbuf, sizeof (intbuf)); + strcpy (ret + sizeof (DEV_FD_PREFIX) - 1, p); + + add_fifo_list (fd); + return (ret); +} + +#endif /* HAVE_DEV_FD */ + +/* Return a filename that will open a connection to the process defined by + executing STRING. HAVE_DEV_FD, if defined, means open a pipe and return + a filename in /dev/fd corresponding to a descriptor that is one of the + ends of the pipe. If not defined, we use named pipes on systems that have + them. Systems without /dev/fd and named pipes are out of luck. + + OPEN_FOR_READ_IN_CHILD, if 1, means open the named pipe for reading or + use the read end of the pipe and dup that file descriptor to fd 0 in + the child. If OPEN_FOR_READ_IN_CHILD is 0, we open the named pipe for + writing or use the write end of the pipe in the child, and dup that + file descriptor to fd 1 in the child. The parent does the opposite. */ + +static char * +process_substitute (string, open_for_read_in_child) + char *string; + int open_for_read_in_child; +{ + char *pathname; + int fd, result; + pid_t old_pid, pid; +#if defined (HAVE_DEV_FD) + int parent_pipe_fd, child_pipe_fd; + int fildes[2]; +#endif /* HAVE_DEV_FD */ +#if defined (JOB_CONTROL) + pid_t old_pipeline_pgrp; +#endif + + if (!string || !*string || wordexp_only) + return ((char *)NULL); + +#if !defined (HAVE_DEV_FD) + pathname = make_named_pipe (); +#else /* HAVE_DEV_FD */ + if (pipe (fildes) < 0) + { + sys_error (_("cannot make pipe for process substitution")); + return ((char *)NULL); + } + /* If OPEN_FOR_READ_IN_CHILD == 1, we want to use the write end of + the pipe in the parent, otherwise the read end. */ + parent_pipe_fd = fildes[open_for_read_in_child]; + child_pipe_fd = fildes[1 - open_for_read_in_child]; + /* Move the parent end of the pipe to some high file descriptor, to + avoid clashes with FDs used by the script. */ + parent_pipe_fd = move_to_high_fd (parent_pipe_fd, 1, 64); + + pathname = make_dev_fd_filename (parent_pipe_fd); +#endif /* HAVE_DEV_FD */ + + if (pathname == 0) + { + sys_error (_("cannot make pipe for process substitution")); + return ((char *)NULL); + } + + old_pid = last_made_pid; + +#if defined (JOB_CONTROL) + old_pipeline_pgrp = pipeline_pgrp; + pipeline_pgrp = shell_pgrp; + save_pipeline (1); +#endif /* JOB_CONTROL */ + + pid = make_child ((char *)NULL, 1); + if (pid == 0) + { + reset_terminating_signals (); /* XXX */ + free_pushed_string_input (); + /* Cancel traps, in trap.c. */ + restore_original_signals (); /* XXX - what about special builtins? bash-4.2 */ + setup_async_signals (); + subshell_environment |= SUBSHELL_COMSUB|SUBSHELL_PROCSUB; + } + +#if defined (JOB_CONTROL) + set_sigchld_handler (); + stop_making_children (); + /* XXX - should we only do this in the parent? (as in command subst) */ + pipeline_pgrp = old_pipeline_pgrp; +#endif /* JOB_CONTROL */ + + if (pid < 0) + { + sys_error (_("cannot make child for process substitution")); + free (pathname); +#if defined (HAVE_DEV_FD) + close (parent_pipe_fd); + close (child_pipe_fd); +#endif /* HAVE_DEV_FD */ + return ((char *)NULL); + } + + if (pid > 0) + { +#if defined (JOB_CONTROL) + restore_pipeline (1); +#endif + +#if !defined (HAVE_DEV_FD) + fifo_list[nfifo-1].proc = pid; +#endif + + last_made_pid = old_pid; + +#if defined (JOB_CONTROL) && defined (PGRP_PIPE) + close_pgrp_pipe (); +#endif /* JOB_CONTROL && PGRP_PIPE */ + +#if defined (HAVE_DEV_FD) + close (child_pipe_fd); +#endif /* HAVE_DEV_FD */ + + return (pathname); + } + + set_sigint_handler (); + +#if defined (JOB_CONTROL) + set_job_control (0); +#endif /* JOB_CONTROL */ + +#if !defined (HAVE_DEV_FD) + /* Open the named pipe in the child. */ + fd = open (pathname, open_for_read_in_child ? O_RDONLY|O_NONBLOCK : O_WRONLY); + if (fd < 0) + { + /* Two separate strings for ease of translation. */ + if (open_for_read_in_child) + sys_error (_("cannot open named pipe %s for reading"), pathname); + else + sys_error (_("cannot open named pipe %s for writing"), pathname); + + exit (127); + } + if (open_for_read_in_child) + { + if (sh_unset_nodelay_mode (fd) < 0) + { + sys_error (_("cannot reset nodelay mode for fd %d"), fd); + exit (127); + } + } +#else /* HAVE_DEV_FD */ + fd = child_pipe_fd; +#endif /* HAVE_DEV_FD */ + + if (dup2 (fd, open_for_read_in_child ? 0 : 1) < 0) + { + sys_error (_("cannot duplicate named pipe %s as fd %d"), pathname, + open_for_read_in_child ? 0 : 1); + exit (127); + } + + if (fd != (open_for_read_in_child ? 0 : 1)) + close (fd); + + /* Need to close any files that this process has open to pipes inherited + from its parent. */ + if (current_fds_to_close) + { + close_fd_bitmap (current_fds_to_close); + current_fds_to_close = (struct fd_bitmap *)NULL; + } + +#if defined (HAVE_DEV_FD) + /* Make sure we close the parent's end of the pipe and clear the slot + in the fd list so it is not closed later, if reallocated by, for + instance, pipe(2). */ + close (parent_pipe_fd); + dev_fd_list[parent_pipe_fd] = 0; +#endif /* HAVE_DEV_FD */ + + result = parse_and_execute (string, "process substitution", (SEVAL_NONINT|SEVAL_NOHIST)); + +#if !defined (HAVE_DEV_FD) + /* Make sure we close the named pipe in the child before we exit. */ + close (open_for_read_in_child ? 0 : 1); +#endif /* !HAVE_DEV_FD */ + + exit (result); + /*NOTREACHED*/ +} +#endif /* PROCESS_SUBSTITUTION */ + +/***********************************/ +/* */ +/* Command Substitution */ +/* */ +/***********************************/ + +static char * +read_comsub (fd, quoted, rflag) + int fd, quoted; + int *rflag; +{ + char *istring, buf[128], *bufp, *s; + int istring_index, istring_size, c, tflag, skip_ctlesc, skip_ctlnul; + ssize_t bufn; + + istring = (char *)NULL; + istring_index = istring_size = bufn = tflag = 0; + + for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++) + skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL; + + /* Read the output of the command through the pipe. This may need to be + changed to understand multibyte characters in the future. */ + while (1) + { + if (fd < 0) + break; + if (--bufn <= 0) + { + bufn = zread (fd, buf, sizeof (buf)); + if (bufn <= 0) + break; + bufp = buf; + } + c = *bufp++; + + if (c == 0) + { +#if 0 + internal_warning ("read_comsub: ignored null byte in input"); +#endif + continue; + } + + /* Add the character to ISTRING, possibly after resizing it. */ + RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, DEFAULT_ARRAY_SIZE); + + /* This is essentially quote_string inline */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) /* || c == CTLESC || c == CTLNUL */) + istring[istring_index++] = CTLESC; + /* Escape CTLESC and CTLNUL in the output to protect those characters + from the rest of the word expansions (word splitting and globbing.) + This is essentially quote_escapes inline. */ + else if (skip_ctlesc == 0 && c == CTLESC) + { + tflag |= W_HASCTLESC; + istring[istring_index++] = CTLESC; + } + else if ((skip_ctlnul == 0 && c == CTLNUL) || (c == ' ' && (ifs_value && *ifs_value == 0))) + istring[istring_index++] = CTLESC; + + istring[istring_index++] = c; + +#if 0 +#if defined (__CYGWIN__) + if (c == '\n' && istring_index > 1 && istring[istring_index - 2] == '\r') + { + istring_index--; + istring[istring_index - 1] = '\n'; + } +#endif +#endif + } + + if (istring) + istring[istring_index] = '\0'; + + /* If we read no output, just return now and save ourselves some + trouble. */ + if (istring_index == 0) + { + FREE (istring); + if (rflag) + *rflag = tflag; + return (char *)NULL; + } + + /* Strip trailing newlines from the output of the command. */ + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + { + while (istring_index > 0) + { + if (istring[istring_index - 1] == '\n') + { + --istring_index; + + /* If the newline was quoted, remove the quoting char. */ + if (istring[istring_index - 1] == CTLESC) + --istring_index; + } + else + break; + } + istring[istring_index] = '\0'; + } + else + strip_trailing (istring, istring_index - 1, 1); + + if (rflag) + *rflag = tflag; + return istring; +} + +/* Perform command substitution on STRING. This returns a WORD_DESC * with the + contained string possibly quoted. */ +WORD_DESC * +command_substitute (string, quoted) + char *string; + int quoted; +{ + pid_t pid, old_pid, old_pipeline_pgrp, old_async_pid; + char *istring; + int result, fildes[2], function_value, pflags, rc, tflag; + WORD_DESC *ret; + + istring = (char *)NULL; + + /* Don't fork () if there is no need to. In the case of no command to + run, just return NULL. */ + if (!string || !*string || (string[0] == '\n' && !string[1])) + return ((WORD_DESC *)NULL); + + if (wordexp_only && read_but_dont_execute) + { + last_command_exit_value = EX_WEXPCOMSUB; + jump_to_top_level (EXITPROG); + } + + /* We're making the assumption here that the command substitution will + eventually run a command from the file system. Since we'll run + maybe_make_export_env in this subshell before executing that command, + the parent shell and any other shells it starts will have to remake + the environment. If we make it before we fork, other shells won't + have to. Don't bother if we have any temporary variable assignments, + though, because the export environment will be remade after this + command completes anyway, but do it if all the words to be expanded + are variable assignments. */ + if (subst_assign_varlist == 0 || garglist == 0) + maybe_make_export_env (); /* XXX */ + + /* Flags to pass to parse_and_execute() */ + pflags = (interactive && sourcelevel == 0) ? SEVAL_RESETLINE : 0; + + /* Pipe the output of executing STRING into the current shell. */ + if (pipe (fildes) < 0) + { + sys_error (_("cannot make pipe for command substitution")); + goto error_exit; + } + + old_pid = last_made_pid; +#if defined (JOB_CONTROL) + old_pipeline_pgrp = pipeline_pgrp; + /* Don't reset the pipeline pgrp if we're already a subshell in a pipeline. */ + if ((subshell_environment & SUBSHELL_PIPE) == 0) + pipeline_pgrp = shell_pgrp; + cleanup_the_pipeline (); +#endif /* JOB_CONTROL */ + + old_async_pid = last_asynchronous_pid; + pid = make_child ((char *)NULL, subshell_environment&SUBSHELL_ASYNC); + last_asynchronous_pid = old_async_pid; + + if (pid == 0) + { + /* Reset the signal handlers in the child, but don't free the + trap strings. Set a flag noting that we have to free the + trap strings if we run trap to change a signal disposition. */ + reset_signal_handlers (); + subshell_environment |= SUBSHELL_RESETTRAP; + } + +#if defined (JOB_CONTROL) + /* XXX DO THIS ONLY IN PARENT ? XXX */ + set_sigchld_handler (); + stop_making_children (); + if (pid != 0) + pipeline_pgrp = old_pipeline_pgrp; +#else + stop_making_children (); +#endif /* JOB_CONTROL */ + + if (pid < 0) + { + sys_error (_("cannot make child for command substitution")); + error_exit: + + FREE (istring); + close (fildes[0]); + close (fildes[1]); + return ((WORD_DESC *)NULL); + } + + if (pid == 0) + { + set_sigint_handler (); /* XXX */ + + free_pushed_string_input (); + + if (dup2 (fildes[1], 1) < 0) + { + sys_error (_("command_substitute: cannot duplicate pipe as fd 1")); + exit (EXECUTION_FAILURE); + } + + /* If standard output is closed in the parent shell + (such as after `exec >&-'), file descriptor 1 will be + the lowest available file descriptor, and end up in + fildes[0]. This can happen for stdin and stderr as well, + but stdout is more important -- it will cause no output + to be generated from this command. */ + if ((fildes[1] != fileno (stdin)) && + (fildes[1] != fileno (stdout)) && + (fildes[1] != fileno (stderr))) + close (fildes[1]); + + if ((fildes[0] != fileno (stdin)) && + (fildes[0] != fileno (stdout)) && + (fildes[0] != fileno (stderr))) + close (fildes[0]); + +#ifdef __CYGWIN__ + /* Let stdio know the fd may have changed from text to binary mode, and + make sure to preserve stdout line buffering. */ + freopen (NULL, "w", stdout); + sh_setlinebuf (stdout); +#endif /* __CYGWIN__ */ + + /* The currently executing shell is not interactive. */ + interactive = 0; + + /* This is a subshell environment. */ + subshell_environment |= SUBSHELL_COMSUB; + + /* When not in POSIX mode, command substitution does not inherit + the -e flag. */ + if (posixly_correct == 0) + exit_immediately_on_error = 0; + + remove_quoted_escapes (string); + + startup_state = 2; /* see if we can avoid a fork */ + /* Give command substitution a place to jump back to on failure, + so we don't go back up to main (). */ + result = setjmp (top_level); + + /* If we're running a command substitution inside a shell function, + trap `return' so we don't return from the function in the subshell + and go off to never-never land. */ + if (result == 0 && return_catch_flag) + function_value = setjmp (return_catch); + else + function_value = 0; + + if (result == ERREXIT) + rc = last_command_exit_value; + else if (result == EXITPROG) + rc = last_command_exit_value; + else if (result) + rc = EXECUTION_FAILURE; + else if (function_value) + rc = return_catch_value; + else + { + subshell_level++; + rc = parse_and_execute (string, "command substitution", pflags|SEVAL_NOHIST); + subshell_level--; + } + + last_command_exit_value = rc; + rc = run_exit_trap (); +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif + exit (rc); + } + else + { +#if defined (JOB_CONTROL) && defined (PGRP_PIPE) + close_pgrp_pipe (); +#endif /* JOB_CONTROL && PGRP_PIPE */ + + close (fildes[1]); + + tflag = 0; + istring = read_comsub (fildes[0], quoted, &tflag); + + close (fildes[0]); + + current_command_subst_pid = pid; + last_command_exit_value = wait_for (pid); + last_command_subst_pid = pid; + last_made_pid = old_pid; + +#if defined (JOB_CONTROL) + /* If last_command_exit_value > 128, then the substituted command + was terminated by a signal. If that signal was SIGINT, then send + SIGINT to ourselves. This will break out of loops, for instance. */ + if (last_command_exit_value == (128 + SIGINT) && last_command_exit_signal == SIGINT) + kill (getpid (), SIGINT); + + /* wait_for gives the terminal back to shell_pgrp. If some other + process group should have it, give it away to that group here. + pipeline_pgrp is non-zero only while we are constructing a + pipline, so what we are concerned about is whether or not that + pipeline was started in the background. A pipeline started in + the background should never get the tty back here. */ + if (interactive && pipeline_pgrp != (pid_t)0 && (subshell_environment & SUBSHELL_ASYNC) == 0) + give_terminal_to (pipeline_pgrp, 0); +#endif /* JOB_CONTROL */ + + ret = alloc_word_desc (); + ret->word = istring; + ret->flags = tflag; + + return ret; + } +} + +/******************************************************** + * * + * Utility functions for parameter expansion * + * * + ********************************************************/ + +#if defined (ARRAY_VARS) + +static arrayind_t +array_length_reference (s) + char *s; +{ + int len; + arrayind_t ind; + char *akey; + char *t, c; + ARRAY *array; + HASH_TABLE *h; + SHELL_VAR *var; + + var = array_variable_part (s, &t, &len); + + /* If unbound variables should generate an error, report one and return + failure. */ + if ((var == 0 || (assoc_p (var) == 0 && array_p (var) == 0)) && unbound_vars_is_error) + { + c = *--t; + *t = '\0'; + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (s); + *t = c; + return (-1); + } + else if (var == 0) + return 0; + + /* We support a couple of expansions for variables that are not arrays. + We'll return the length of the value for v[0], and 1 for v[@] or + v[*]. Return 0 for everything else. */ + + array = array_p (var) ? array_cell (var) : (ARRAY *)NULL; + h = assoc_p (var) ? assoc_cell (var) : (HASH_TABLE *)NULL; + + if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']') + { + if (assoc_p (var)) + return (h ? assoc_num_elements (h) : 0); + else if (array_p (var)) + return (array ? array_num_elements (array) : 0); + else + return (var_isset (var) ? 1 : 0); + } + + if (assoc_p (var)) + { + t[len - 1] = '\0'; + akey = expand_assignment_string_to_string (t, 0); /* [ */ + t[len - 1] = ']'; + if (akey == 0 || *akey == 0) + { + err_badarraysub (t); + return (-1); + } + t = assoc_reference (assoc_cell (var), akey); + } + else + { + ind = array_expand_index (t, len); + if (ind < 0) + { + err_badarraysub (t); + return (-1); + } + if (array_p (var)) + t = array_reference (array, ind); + else + t = (ind == 0) ? value_cell (var) : (char *)NULL; + } + + len = MB_STRLEN (t); + return (len); +} +#endif /* ARRAY_VARS */ + +static int +valid_brace_expansion_word (name, var_is_special) + char *name; + int var_is_special; +{ + if (DIGIT (*name) && all_digits (name)) + return 1; + else if (var_is_special) + return 1; +#if defined (ARRAY_VARS) + else if (valid_array_reference (name)) + return 1; +#endif /* ARRAY_VARS */ + else if (legal_identifier (name)) + return 1; + else + return 0; +} + +static int +chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at) + char *name; + int quoted; + int *quoted_dollar_atp, *contains_dollar_at; +{ + char *temp1; + + if (name == 0) + { + if (quoted_dollar_atp) + *quoted_dollar_atp = 0; + if (contains_dollar_at) + *contains_dollar_at = 0; + return 0; + } + + /* check for $@ and $* */ + if (name[0] == '@' && name[1] == 0) + { + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + return 1; + } + else if (name[0] == '*' && name[1] == '\0' && quoted == 0) + { + if (contains_dollar_at) + *contains_dollar_at = 1; + return 1; + } + + /* Now check for ${array[@]} and ${array[*]} */ +#if defined (ARRAY_VARS) + else if (valid_array_reference (name)) + { + temp1 = mbschr (name, '['); + if (temp1 && temp1[1] == '@' && temp1[2] == ']') + { + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + return 1; + } /* [ */ + /* ${array[*]}, when unquoted, should be treated like ${array[@]}, + which should result in separate words even when IFS is unset. */ + if (temp1 && temp1[1] == '*' && temp1[2] == ']' && quoted == 0) + { + if (contains_dollar_at) + *contains_dollar_at = 1; + return 1; + } + } +#endif + return 0; +} + +/* Parameter expand NAME, and return a new string which is the expansion, + or NULL if there was no expansion. + VAR_IS_SPECIAL is non-zero if NAME is one of the special variables in + the shell, e.g., "@", "$", "*", etc. QUOTED, if non-zero, means that + NAME was found inside of a double-quoted expression. */ +static WORD_DESC * +parameter_brace_expand_word (name, var_is_special, quoted, pflags, indp) + char *name; + int var_is_special, quoted, pflags; + arrayind_t *indp; +{ + WORD_DESC *ret; + char *temp, *tt; + intmax_t arg_index; + SHELL_VAR *var; + int atype, rflags; + arrayind_t ind; + + ret = 0; + temp = 0; + rflags = 0; + + if (indp) + *indp = INTMAX_MIN; + + /* Handle multiple digit arguments, as in ${11}. */ + if (legal_number (name, &arg_index)) + { + tt = get_dollar_var_value (arg_index); + if (tt) + temp = (*tt && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + ? quote_string (tt) + : quote_escapes (tt); + else + temp = (char *)NULL; + FREE (tt); + } + else if (var_is_special) /* ${@} */ + { + int sindex; + tt = (char *)xmalloc (2 + strlen (name)); + tt[sindex = 0] = '$'; + strcpy (tt + 1, name); + + ret = param_expand (tt, &sindex, quoted, (int *)NULL, (int *)NULL, + (int *)NULL, (int *)NULL, pflags); + free (tt); + } +#if defined (ARRAY_VARS) + else if (valid_array_reference (name)) + { + temp = array_value (name, quoted, 0, &atype, &ind); + if (atype == 0 && temp) + { + temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + ? quote_string (temp) + : quote_escapes (temp); + rflags |= W_ARRAYIND; + if (indp) + *indp = ind; + } + else if (atype == 1 && temp && QUOTED_NULL (temp) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + rflags |= W_HASQUOTEDNULL; + } +#endif + else if (var = find_variable (name)) + { + if (var_isset (var) && invisible_p (var) == 0) + { +#if defined (ARRAY_VARS) + if (assoc_p (var)) + temp = assoc_reference (assoc_cell (var), "0"); + else if (array_p (var)) + temp = array_reference (array_cell (var), 0); + else + temp = value_cell (var); +#else + temp = value_cell (var); +#endif + + if (temp) + temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + ? quote_string (temp) + : quote_escapes (temp); + } + else + temp = (char *)NULL; + } + else + temp = (char *)NULL; + + if (ret == 0) + { + ret = alloc_word_desc (); + ret->word = temp; + ret->flags |= rflags; + } + return ret; +} + +/* Expand an indirect reference to a variable: ${!NAME} expands to the + value of the variable whose name is the value of NAME. */ +static WORD_DESC * +parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at) + char *name; + int var_is_special, quoted; + int *quoted_dollar_atp, *contains_dollar_at; +{ + char *temp, *t; + WORD_DESC *w; + + w = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND, 0); + t = w->word; + /* Have to dequote here if necessary */ + if (t) + { + temp = (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + ? dequote_string (t) + : dequote_escapes (t); + free (t); + t = temp; + } + dispose_word_desc (w); + + chk_atstar (t, quoted, quoted_dollar_atp, contains_dollar_at); + if (t == 0) + return (WORD_DESC *)NULL; + + w = parameter_brace_expand_word (t, SPECIAL_VAR(t, 0), quoted, 0, 0); + free (t); + + return w; +} + +/* Expand the right side of a parameter expansion of the form ${NAMEcVALUE}, + depending on the value of C, the separating character. C can be one of + "-", "+", or "=". QUOTED is true if the entire brace expression occurs + between double quotes. */ +static WORD_DESC * +parameter_brace_expand_rhs (name, value, c, quoted, qdollaratp, hasdollarat) + char *name, *value; + int c, quoted, *qdollaratp, *hasdollarat; +{ + WORD_DESC *w; + WORD_LIST *l; + char *t, *t1, *temp; + int hasdol; + + /* If the entire expression is between double quotes, we want to treat + the value as a double-quoted string, with the exception that we strip + embedded unescaped double quotes (for sh backwards compatibility). */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *value) + { + hasdol = 0; + temp = string_extract_double_quoted (value, &hasdol, 1); + } + else + temp = value; + + w = alloc_word_desc (); + hasdol = 0; + /* XXX was 0 not quoted */ + l = *temp ? expand_string_for_rhs (temp, quoted, &hasdol, (int *)NULL) + : (WORD_LIST *)0; + if (hasdollarat) + *hasdollarat = hasdol || (l && l->next); + if (temp != value) + free (temp); + if (l) + { + /* The expansion of TEMP returned something. We need to treat things + slightly differently if HASDOL is non-zero. If we have "$@", the + individual words have already been quoted. We need to turn them + into a string with the words separated by the first character of + $IFS without any additional quoting, so string_list_dollar_at won't + do the right thing. We use string_list_dollar_star instead. */ + temp = (hasdol || l->next) ? string_list_dollar_star (l) : string_list (l); + + /* If l->next is not null, we know that TEMP contained "$@", since that + is the only expansion that creates more than one word. */ + if (qdollaratp && ((hasdol && quoted) || l->next)) + *qdollaratp = 1; + dispose_words (l); + } + else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && hasdol) + { + /* The brace expansion occurred between double quotes and there was + a $@ in TEMP. It does not matter if the $@ is quoted, as long as + it does not expand to anything. In this case, we want to return + a quoted empty string. */ + temp = make_quoted_char ('\0'); + w->flags |= W_HASQUOTEDNULL; + } + else + temp = (char *)NULL; + + if (c == '-' || c == '+') + { + w->word = temp; + return w; + } + + /* c == '=' */ + t = temp ? savestring (temp) : savestring (""); + t1 = dequote_string (t); + free (t); +#if defined (ARRAY_VARS) + if (valid_array_reference (name)) + assign_array_element (name, t1, 0); + else +#endif /* ARRAY_VARS */ + bind_variable (name, t1, 0); + + /* From Posix group discussion Feb-March 2010. Issue 7 0000221 */ + free (temp); + + w->word = t1; + return w; +} + +/* Deal with the right hand side of a ${name:?value} expansion in the case + that NAME is null or not set. If VALUE is non-null it is expanded and + used as the error message to print, otherwise a standard message is + printed. */ +static void +parameter_brace_expand_error (name, value) + char *name, *value; +{ + WORD_LIST *l; + char *temp; + + if (value && *value) + { + l = expand_string (value, 0); + temp = string_list (l); + report_error ("%s: %s", name, temp ? temp : ""); /* XXX was value not "" */ + FREE (temp); + dispose_words (l); + } + else + report_error (_("%s: parameter null or not set"), name); + + /* Free the data we have allocated during this expansion, since we + are about to longjmp out. */ + free (name); + FREE (value); +} + +/* Return 1 if NAME is something for which parameter_brace_expand_length is + OK to do. */ +static int +valid_length_expression (name) + char *name; +{ + return (name[1] == '\0' || /* ${#} */ + ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') || /* special param */ + (DIGIT (name[1]) && all_digits (name + 1)) || /* ${#11} */ +#if defined (ARRAY_VARS) + valid_array_reference (name + 1) || /* ${#a[7]} */ +#endif + legal_identifier (name + 1)); /* ${#PS1} */ +} + +/* Handle the parameter brace expansion that requires us to return the + length of a parameter. */ +static intmax_t +parameter_brace_expand_length (name) + char *name; +{ + char *t, *newname; + intmax_t number, arg_index; + WORD_LIST *list; +#if defined (ARRAY_VARS) + SHELL_VAR *var; +#endif + + if (name[1] == '\0') /* ${#} */ + number = number_of_args (); + else if ((name[1] == '@' || name[1] == '*') && name[2] == '\0') /* ${#@}, ${#*} */ + number = number_of_args (); + else if ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') + { + /* Take the lengths of some of the shell's special parameters. */ + switch (name[1]) + { + case '-': + t = which_set_flags (); + break; + case '?': + t = itos (last_command_exit_value); + break; + case '$': + t = itos (dollar_dollar_pid); + break; + case '!': + if (last_asynchronous_pid == NO_PID) + t = (char *)NULL; /* XXX - error if set -u set? */ + else + t = itos (last_asynchronous_pid); + break; + case '#': + t = itos (number_of_args ()); + break; + } + number = STRLEN (t); + FREE (t); + } +#if defined (ARRAY_VARS) + else if (valid_array_reference (name + 1)) + number = array_length_reference (name + 1); +#endif /* ARRAY_VARS */ + else + { + number = 0; + + if (legal_number (name + 1, &arg_index)) /* ${#1} */ + { + t = get_dollar_var_value (arg_index); + if (t == 0 && unbound_vars_is_error) + return INTMAX_MIN; + number = MB_STRLEN (t); + FREE (t); + } +#if defined (ARRAY_VARS) + else if ((var = find_variable (name + 1)) && (invisible_p (var) == 0) && (array_p (var) || assoc_p (var))) + { + if (assoc_p (var)) + t = assoc_reference (assoc_cell (var), "0"); + else + t = array_reference (array_cell (var), 0); + if (t == 0 && unbound_vars_is_error) + return INTMAX_MIN; + number = MB_STRLEN (t); + } +#endif + else /* ${#PS1} */ + { + newname = savestring (name); + newname[0] = '$'; + list = expand_string (newname, Q_DOUBLE_QUOTES); + t = list ? string_list (list) : (char *)NULL; + free (newname); + if (list) + dispose_words (list); + + number = t ? MB_STRLEN (t) : 0; + FREE (t); + } + } + + return (number); +} + +/* Skip characters in SUBSTR until DELIM. SUBSTR is an arithmetic expression, + so we do some ad-hoc parsing of an arithmetic expression to find + the first DELIM, instead of using strchr(3). Two rules: + 1. If the substring contains a `(', read until closing `)'. + 2. If the substring contains a `?', read past one `:' for each `?'. +*/ + +static char * +skiparith (substr, delim) + char *substr; + int delim; +{ + size_t sublen; + int skipcol, pcount, i; + DECLARE_MBSTATE; + + sublen = strlen (substr); + i = skipcol = pcount = 0; + while (substr[i]) + { + /* Balance parens */ + if (substr[i] == LPAREN) + { + pcount++; + i++; + continue; + } + if (substr[i] == RPAREN && pcount) + { + pcount--; + i++; + continue; + } + if (pcount) + { + ADVANCE_CHAR (substr, sublen, i); + continue; + } + + /* Skip one `:' for each `?' */ + if (substr[i] == ':' && skipcol) + { + skipcol--; + i++; + continue; + } + if (substr[i] == delim) + break; + if (substr[i] == '?') + { + skipcol++; + i++; + continue; + } + ADVANCE_CHAR (substr, sublen, i); + } + + return (substr + i); +} + +/* Verify and limit the start and end of the desired substring. If + VTYPE == 0, a regular shell variable is being used; if it is 1, + then the positional parameters are being used; if it is 2, then + VALUE is really a pointer to an array variable that should be used. + Return value is 1 if both values were OK, 0 if there was a problem + with an invalid expression, or -1 if the values were out of range. */ +static int +verify_substring_values (v, value, substr, vtype, e1p, e2p) + SHELL_VAR *v; + char *value, *substr; + int vtype; + intmax_t *e1p, *e2p; +{ + char *t, *temp1, *temp2; + arrayind_t len; + int expok; +#if defined (ARRAY_VARS) + ARRAY *a; + HASH_TABLE *h; +#endif + + /* duplicate behavior of strchr(3) */ + t = skiparith (substr, ':'); + if (*t && *t == ':') + *t = '\0'; + else + t = (char *)0; + + temp1 = expand_arith_string (substr, Q_DOUBLE_QUOTES); + *e1p = evalexp (temp1, &expok); + free (temp1); + if (expok == 0) + return (0); + + len = -1; /* paranoia */ + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: + len = MB_STRLEN (value); + break; + case VT_POSPARMS: + len = number_of_args () + 1; + if (*e1p == 0) + len++; /* add one arg if counting from $0 */ + break; +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + /* For arrays, the first value deals with array indices. Negative + offsets count from one past the array's maximum index. Associative + arrays treat the number of elements as the maximum index. */ + if (assoc_p (v)) + { + h = assoc_cell (v); + len = assoc_num_elements (h) + (*e1p < 0); + } + else + { + a = (ARRAY *)value; + len = array_max_index (a) + (*e1p < 0); /* arrays index from 0 to n - 1 */ + } + break; +#endif + } + + if (len == -1) /* paranoia */ + return -1; + + if (*e1p < 0) /* negative offsets count from end */ + *e1p += len; + + if (*e1p > len || *e1p < 0) + return (-1); + +#if defined (ARRAY_VARS) + /* For arrays, the second offset deals with the number of elements. */ + if (vtype == VT_ARRAYVAR) + len = assoc_p (v) ? assoc_num_elements (h) : array_num_elements (a); +#endif + + if (t) + { + t++; + temp2 = savestring (t); + temp1 = expand_arith_string (temp2, Q_DOUBLE_QUOTES); + free (temp2); + t[-1] = ':'; + *e2p = evalexp (temp1, &expok); + free (temp1); + if (expok == 0) + return (0); + if ((vtype == VT_ARRAYVAR || vtype == VT_POSPARMS) && *e2p < 0) + { + internal_error (_("%s: substring expression < 0"), t); + return (0); + } +#if defined (ARRAY_VARS) + /* In order to deal with sparse arrays, push the intelligence about how + to deal with the number of elements desired down to the array- + specific functions. */ + if (vtype != VT_ARRAYVAR) +#endif + { + if (*e2p < 0) + { + *e2p += len; + if (*e2p < 0 || *e2p < *e1p) + { + internal_error (_("%s: substring expression < 0"), t); + return (0); + } + } + else + *e2p += *e1p; /* want E2 chars starting at E1 */ + if (*e2p > len) + *e2p = len; + } + } + else + *e2p = len; + + return (1); +} + +/* Return the type of variable specified by VARNAME (simple variable, + positional param, or array variable). Also return the value specified + by VARNAME (value of a variable or a reference to an array element). + QUOTED is the standard description of quoting state, using Q_* defines. + FLAGS is currently a set of flags to pass to array_value. If IND is + non-null and not INTMAX_MIN, and FLAGS includes AV_USEIND, IND is + passed to array_value so the array index is not computed again. + If this returns VT_VARIABLE, the caller assumes that CTLESC and CTLNUL + characters in the value are quoted with CTLESC and takes appropriate + steps. For convenience, *VALP is set to the dequoted VALUE. */ +static int +get_var_and_type (varname, value, ind, quoted, flags, varp, valp) + char *varname, *value; + arrayind_t ind; + int quoted, flags; + SHELL_VAR **varp; + char **valp; +{ + int vtype; + char *temp; +#if defined (ARRAY_VARS) + SHELL_VAR *v; +#endif + arrayind_t lind; + + /* This sets vtype to VT_VARIABLE or VT_POSPARMS */ + vtype = (varname[0] == '@' || varname[0] == '*') && varname[1] == '\0'; + if (vtype == VT_POSPARMS && varname[0] == '*') + vtype |= VT_STARSUB; + *varp = (SHELL_VAR *)NULL; + +#if defined (ARRAY_VARS) + if (valid_array_reference (varname)) + { + v = array_variable_part (varname, &temp, (int *)0); + /* If we want to signal array_value to use an already-computed index, + set LIND to that index */ + lind = (ind != INTMAX_MIN && (flags & AV_USEIND)) ? ind : 0; + if (v && (array_p (v) || assoc_p (v))) + { /* [ */ + if (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']') + { + /* Callers have to differentiate betwen indexed and associative */ + vtype = VT_ARRAYVAR; + if (temp[0] == '*') + vtype |= VT_STARSUB; + *valp = array_p (v) ? (char *)array_cell (v) : (char *)assoc_cell (v); + } + else + { + vtype = VT_ARRAYMEMBER; + *valp = array_value (varname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind); + } + *varp = v; + } + else if (v && (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']')) + { + vtype = VT_VARIABLE; + *varp = v; + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + *valp = dequote_string (value); + else + *valp = dequote_escapes (value); + } + else + { + vtype = VT_ARRAYMEMBER; + *varp = v; + *valp = array_value (varname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind); + } + } + else if ((v = find_variable (varname)) && (invisible_p (v) == 0) && (assoc_p (v) || array_p (v))) + { + vtype = VT_ARRAYMEMBER; + *varp = v; + *valp = assoc_p (v) ? assoc_reference (assoc_cell (v), "0") : array_reference (array_cell (v), 0); + } + else +#endif + { + if (value && vtype == VT_VARIABLE) + { + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + *valp = dequote_string (value); + else + *valp = dequote_escapes (value); + } + else + *valp = value; + } + + return vtype; +} + +/******************************************************/ +/* */ +/* Functions to extract substrings of variable values */ +/* */ +/******************************************************/ + +#if defined (HANDLE_MULTIBYTE) +/* Character-oriented rather than strictly byte-oriented substrings. S and + E, rather being strict indices into STRING, indicate character (possibly + multibyte character) positions that require calculation. + Used by the ${param:offset[:length]} expansion. */ +static char * +mb_substring (string, s, e) + char *string; + int s, e; +{ + char *tt; + int start, stop, i, slen; + DECLARE_MBSTATE; + + start = 0; + /* Don't need string length in ADVANCE_CHAR unless multibyte chars possible. */ + slen = (MB_CUR_MAX > 1) ? STRLEN (string) : 0; + + i = s; + while (string[start] && i--) + ADVANCE_CHAR (string, slen, start); + stop = start; + i = e - s; + while (string[stop] && i--) + ADVANCE_CHAR (string, slen, stop); + tt = substring (string, start, stop); + return tt; +} +#endif + +/* Process a variable substring expansion: ${name:e1[:e2]}. If VARNAME + is `@', use the positional parameters; otherwise, use the value of + VARNAME. If VARNAME is an array variable, use the array elements. */ + +static char * +parameter_brace_substring (varname, value, ind, substr, quoted, flags) + char *varname, *value; + int ind; + char *substr; + int quoted, flags; +{ + intmax_t e1, e2; + int vtype, r, starsub; + char *temp, *val, *tt, *oname; + SHELL_VAR *v; + + if (value == 0) + return ((char *)NULL); + + oname = this_command_name; + this_command_name = varname; + + vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val); + if (vtype == -1) + { + this_command_name = oname; + return ((char *)NULL); + } + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + r = verify_substring_values (v, val, substr, vtype, &e1, &e2); + this_command_name = oname; + if (r <= 0) + return ((r == 0) ? &expand_param_error : (char *)NULL); + + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1) + tt = mb_substring (val, e1, e2); + else +#endif + tt = substring (val, e1, e2); + + if (vtype == VT_VARIABLE) + FREE (val); + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + temp = quote_string (tt); + else + temp = tt ? quote_escapes (tt) : (char *)NULL; + FREE (tt); + break; + case VT_POSPARMS: + tt = pos_params (varname, e1, e2, quoted); + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0) + { + temp = tt ? quote_escapes (tt) : (char *)NULL; + FREE (tt); + } + else + temp = tt; + break; +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + if (assoc_p (v)) + /* we convert to list and take first e2 elements starting at e1th + element -- officially undefined for now */ + temp = assoc_subrange (assoc_cell (v), e1, e2, starsub, quoted); + else + /* We want E2 to be the number of elements desired (arrays can be sparse, + so verify_substring_values just returns the numbers specified and we + rely on array_subrange to understand how to deal with them). */ + temp = array_subrange (array_cell (v), e1, e2, starsub, quoted); + /* array_subrange now calls array_quote_escapes as appropriate, so the + caller no longer needs to. */ + break; +#endif + default: + temp = (char *)NULL; + } + + return temp; +} + +/****************************************************************/ +/* */ +/* Functions to perform pattern substitution on variable values */ +/* */ +/****************************************************************/ + +static int +shouldexp_replacement (s) + char *s; +{ + register char *p; + + for (p = s; p && *p; p++) + { + if (*p == '\\') + p++; + else if (*p == '&') + return 1; + } + return 0; +} + +char * +pat_subst (string, pat, rep, mflags) + char *string, *pat, *rep; + int mflags; +{ + char *ret, *s, *e, *str, *rstr, *mstr; + int rsize, rptr, l, replen, mtype, rxpand, rslen, mlen; + + if (string == 0) + return (savestring ("")); + + mtype = mflags & MATCH_TYPEMASK; + +#if 0 /* bash-4.2 ? */ + rxpand = (rep && *rep) ? shouldexp_replacement (rep) : 0; +#else + rxpand = 0; +#endif + + /* Special cases: + * 1. A null pattern with mtype == MATCH_BEG means to prefix STRING + * with REP and return the result. + * 2. A null pattern with mtype == MATCH_END means to append REP to + * STRING and return the result. + * These don't understand or process `&' in the replacement string. + */ + if ((pat == 0 || *pat == 0) && (mtype == MATCH_BEG || mtype == MATCH_END)) + { + replen = STRLEN (rep); + l = STRLEN (string); + ret = (char *)xmalloc (replen + l + 2); + if (replen == 0) + strcpy (ret, string); + else if (mtype == MATCH_BEG) + { + strcpy (ret, rep); + strcpy (ret + replen, string); + } + else + { + strcpy (ret, string); + strcpy (ret + l, rep); + } + return (ret); + } + + ret = (char *)xmalloc (rsize = 64); + ret[0] = '\0'; + + for (replen = STRLEN (rep), rptr = 0, str = string;;) + { + if (match_pattern (str, pat, mtype, &s, &e) == 0) + break; + l = s - str; + + if (rxpand) + { + int x; + mlen = e - s; + mstr = xmalloc (mlen + 1); + for (x = 0; x < mlen; x++) + mstr[x] = s[x]; + mstr[mlen] = '\0'; + rstr = strcreplace (rep, '&', mstr, 0); + rslen = strlen (rstr); + } + else + { + rstr = rep; + rslen = replen; + } + + RESIZE_MALLOCED_BUFFER (ret, rptr, (l + rslen), rsize, 64); + + /* OK, now copy the leading unmatched portion of the string (from + str to s) to ret starting at rptr (the current offset). Then copy + the replacement string at ret + rptr + (s - str). Increment + rptr (if necessary) and str and go on. */ + if (l) + { + strncpy (ret + rptr, str, l); + rptr += l; + } + if (replen) + { + strncpy (ret + rptr, rstr, rslen); + rptr += rslen; + } + str = e; /* e == end of match */ + + if (rstr != rep) + free (rstr); + + if (((mflags & MATCH_GLOBREP) == 0) || mtype != MATCH_ANY) + break; + + if (s == e) + { + /* On a zero-length match, make sure we copy one character, since + we increment one character to avoid infinite recursion. */ + RESIZE_MALLOCED_BUFFER (ret, rptr, 1, rsize, 64); + ret[rptr++] = *str++; + e++; /* avoid infinite recursion on zero-length match */ + } + } + + /* Now copy the unmatched portion of the input string */ + if (str && *str) + { + RESIZE_MALLOCED_BUFFER (ret, rptr, STRLEN(str) + 1, rsize, 64); + strcpy (ret + rptr, str); + } + else + ret[rptr] = '\0'; + + return ret; +} + +/* Do pattern match and replacement on the positional parameters. */ +static char * +pos_params_pat_subst (string, pat, rep, mflags) + char *string, *pat, *rep; + int mflags; +{ + WORD_LIST *save, *params; + WORD_DESC *w; + char *ret; + int pchar, qflags; + + save = params = list_rest_of_args (); + if (save == 0) + return ((char *)NULL); + + for ( ; params; params = params->next) + { + ret = pat_subst (params->word->word, pat, rep, mflags); + w = alloc_word_desc (); + w->word = ret ? ret : savestring (""); + dispose_word (params->word); + params->word = w; + } + + pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@'; + qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0; + +#if 0 + if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB)) + ret = string_list_dollar_star (quote_list (save)); + else if ((mflags & MATCH_STARSUB) == MATCH_STARSUB) + ret = string_list_dollar_star (save); + else if ((mflags & MATCH_QUOTED) == MATCH_QUOTED) + ret = string_list_dollar_at (save, qflags); + else + ret = string_list_dollar_star (save); +#else + ret = string_list_pos_params (pchar, save, qflags); +#endif + + dispose_words (save); + + return (ret); +} + +/* Perform pattern substitution on VALUE, which is the expansion of + VARNAME. PATSUB is an expression supplying the pattern to match + and the string to substitute. QUOTED is a flags word containing + the type of quoting currently in effect. */ +static char * +parameter_brace_patsub (varname, value, ind, patsub, quoted, flags) + char *varname, *value; + int ind; + char *patsub; + int quoted, flags; +{ + int vtype, mflags, starsub, delim; + char *val, *temp, *pat, *rep, *p, *lpatsub, *tt; + SHELL_VAR *v; + + if (value == 0) + return ((char *)NULL); + + this_command_name = varname; + + vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val); + if (vtype == -1) + return ((char *)NULL); + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + mflags = 0; + if (patsub && *patsub == '/') + { + mflags |= MATCH_GLOBREP; + patsub++; + } + + /* Malloc this because expand_string_if_necessary or one of the expansion + functions in its call chain may free it on a substitution error. */ + lpatsub = savestring (patsub); + + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + mflags |= MATCH_QUOTED; + + if (starsub) + mflags |= MATCH_STARSUB; + + /* If the pattern starts with a `/', make sure we skip over it when looking + for the replacement delimiter. */ +#if 0 + if (rep = quoted_strchr ((*patsub == '/') ? lpatsub+1 : lpatsub, '/', ST_BACKSL)) + *rep++ = '\0'; + else + rep = (char *)NULL; +#else + delim = skip_to_delim (lpatsub, ((*patsub == '/') ? 1 : 0), "/", 0); + if (lpatsub[delim] == '/') + { + lpatsub[delim] = 0; + rep = lpatsub + delim + 1; + } + else + rep = (char *)NULL; +#endif + + if (rep && *rep == '\0') + rep = (char *)NULL; + + /* Perform the same expansions on the pattern as performed by the + pattern removal expansions. */ + pat = getpattern (lpatsub, quoted, 1); + + if (rep) + { + if ((mflags & MATCH_QUOTED) == 0) + rep = expand_string_if_necessary (rep, quoted, expand_string_unsplit); + else + rep = expand_string_to_string_internal (rep, quoted, expand_string_unsplit); + } + + /* ksh93 doesn't allow the match specifier to be a part of the expanded + pattern. This is an extension. Make sure we don't anchor the pattern + at the beginning or end of the string if we're doing global replacement, + though. */ + p = pat; + if (mflags & MATCH_GLOBREP) + mflags |= MATCH_ANY; + else if (pat && pat[0] == '#') + { + mflags |= MATCH_BEG; + p++; + } + else if (pat && pat[0] == '%') + { + mflags |= MATCH_END; + p++; + } + else + mflags |= MATCH_ANY; + + /* OK, we now want to substitute REP for PAT in VAL. If + flags & MATCH_GLOBREP is non-zero, the substitution is done + everywhere, otherwise only the first occurrence of PAT is + replaced. The pattern matching code doesn't understand + CTLESC quoting CTLESC and CTLNUL so we use the dequoted variable + values passed in (VT_VARIABLE) so the pattern substitution + code works right. We need to requote special chars after + we're done for VT_VARIABLE and VT_ARRAYMEMBER, and for the + other cases if QUOTED == 0, since the posparams and arrays + indexed by * or @ do special things when QUOTED != 0. */ + + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: + temp = pat_subst (val, p, rep, mflags); + if (vtype == VT_VARIABLE) + FREE (val); + if (temp) + { + tt = (mflags & MATCH_QUOTED) ? quote_string (temp) : quote_escapes (temp); + free (temp); + temp = tt; + } + break; + case VT_POSPARMS: + temp = pos_params_pat_subst (val, p, rep, mflags); + if (temp && (mflags & MATCH_QUOTED) == 0) + { + tt = quote_escapes (temp); + free (temp); + temp = tt; + } + break; +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + temp = assoc_p (v) ? assoc_patsub (assoc_cell (v), p, rep, mflags) + : array_patsub (array_cell (v), p, rep, mflags); + /* Don't call quote_escapes anymore; array_patsub calls + array_quote_escapes as appropriate before adding the + space separators; ditto for assoc_patsub. */ + break; +#endif + } + + FREE (pat); + FREE (rep); + free (lpatsub); + + return temp; +} + +/****************************************************************/ +/* */ +/* Functions to perform case modification on variable values */ +/* */ +/****************************************************************/ + +/* Do case modification on the positional parameters. */ + +static char * +pos_params_modcase (string, pat, modop, mflags) + char *string, *pat; + int modop; + int mflags; +{ + WORD_LIST *save, *params; + WORD_DESC *w; + char *ret; + int pchar, qflags; + + save = params = list_rest_of_args (); + if (save == 0) + return ((char *)NULL); + + for ( ; params; params = params->next) + { + ret = sh_modcase (params->word->word, pat, modop); + w = alloc_word_desc (); + w->word = ret ? ret : savestring (""); + dispose_word (params->word); + params->word = w; + } + + pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@'; + qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0; + + ret = string_list_pos_params (pchar, save, qflags); + dispose_words (save); + + return (ret); +} + +/* Perform case modification on VALUE, which is the expansion of + VARNAME. MODSPEC is an expression supplying the type of modification + to perform. QUOTED is a flags word containing the type of quoting + currently in effect. */ +static char * +parameter_brace_casemod (varname, value, ind, modspec, patspec, quoted, flags) + char *varname, *value; + int ind, modspec; + char *patspec; + int quoted, flags; +{ + int vtype, starsub, modop, mflags, x; + char *val, *temp, *pat, *p, *lpat, *tt; + SHELL_VAR *v; + + if (value == 0) + return ((char *)NULL); + + this_command_name = varname; + + vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val); + if (vtype == -1) + return ((char *)NULL); + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + modop = 0; + mflags = 0; + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + mflags |= MATCH_QUOTED; + if (starsub) + mflags |= MATCH_STARSUB; + + p = patspec; + if (modspec == '^') + { + x = p && p[0] == modspec; + modop = x ? CASE_UPPER : CASE_UPFIRST; + p += x; + } + else if (modspec == ',') + { + x = p && p[0] == modspec; + modop = x ? CASE_LOWER : CASE_LOWFIRST; + p += x; + } + else if (modspec == '~') + { + x = p && p[0] == modspec; + modop = x ? CASE_TOGGLEALL : CASE_TOGGLE; + p += x; + } + + lpat = p ? savestring (p) : 0; + /* Perform the same expansions on the pattern as performed by the + pattern removal expansions. FOR LATER */ + pat = lpat ? getpattern (lpat, quoted, 1) : 0; + + /* OK, now we do the case modification. */ + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: + temp = sh_modcase (val, pat, modop); + if (vtype == VT_VARIABLE) + FREE (val); + if (temp) + { + tt = (mflags & MATCH_QUOTED) ? quote_string (temp) : quote_escapes (temp); + free (temp); + temp = tt; + } + break; + + case VT_POSPARMS: + temp = pos_params_modcase (val, pat, modop, mflags); + if (temp && (mflags & MATCH_QUOTED) == 0) + { + tt = quote_escapes (temp); + free (temp); + temp = tt; + } + break; + +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + temp = assoc_p (v) ? assoc_modcase (assoc_cell (v), pat, modop, mflags) + : array_modcase (array_cell (v), pat, modop, mflags); + /* Don't call quote_escapes; array_modcase calls array_quote_escapes + as appropriate before adding the space separators; ditto for + assoc_modcase. */ + break; +#endif + } + + FREE (pat); + free (lpat); + + return temp; +} + +/* Check for unbalanced parens in S, which is the contents of $(( ... )). If + any occur, this must be a nested command substitution, so return 0. + Otherwise, return 1. A valid arithmetic expression must always have a + ( before a matching ), so any cases where there are more right parens + means that this must not be an arithmetic expression, though the parser + will not accept it without a balanced total number of parens. */ +static int +chk_arithsub (s, len) + const char *s; + int len; +{ + int i, count; + DECLARE_MBSTATE; + + i = count = 0; + while (i < len) + { + if (s[i] == LPAREN) + count++; + else if (s[i] == RPAREN) + { + count--; + if (count < 0) + return 0; + } + + switch (s[i]) + { + default: + ADVANCE_CHAR (s, len, i); + break; + + case '\\': + i++; + if (s[i]) + ADVANCE_CHAR (s, len, i); + break; + + case '\'': + i = skip_single_quoted (s, len, ++i); + break; + + case '"': + i = skip_double_quoted ((char *)s, len, ++i); + break; + } + } + + return (count == 0); +} + +/****************************************************************/ +/* */ +/* Functions to perform parameter expansion on a string */ +/* */ +/****************************************************************/ + +/* ${[#][!]name[[:][^[^]][,[,]]#[#]%[%]-=?+[word][:e1[:e2]]]} */ +static WORD_DESC * +parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, contains_dollar_at) + char *string; + int *indexp, quoted, *quoted_dollar_atp, *contains_dollar_at, pflags; +{ + int check_nullness, var_is_set, var_is_null, var_is_special; + int want_substring, want_indir, want_patsub, want_casemod; + char *name, *value, *temp, *temp1; + WORD_DESC *tdesc, *ret; + int t_index, sindex, c, tflag, modspec; + intmax_t number; + arrayind_t ind; + + temp = temp1 = value = (char *)NULL; + var_is_set = var_is_null = var_is_special = check_nullness = 0; + want_substring = want_indir = want_patsub = want_casemod = 0; + + sindex = *indexp; + t_index = ++sindex; + /* ${#var} doesn't have any of the other parameter expansions on it. */ + if (string[t_index] == '#' && legal_variable_starter (string[t_index+1])) /* {{ */ + name = string_extract (string, &t_index, "}", SX_VARNAME); + else +#if defined (CASEMOD_EXPANSIONS) + /* To enable case-toggling expansions using the `~' operator character + change the 1 to 0. */ +# if defined (CASEMOD_CAPCASE) + name = string_extract (string, &t_index, "#%^,~:-=?+/}", SX_VARNAME); +# else + name = string_extract (string, &t_index, "#%^,:-=?+/}", SX_VARNAME); +# endif /* CASEMOD_CAPCASE */ +#else + name = string_extract (string, &t_index, "#%:-=?+/}", SX_VARNAME); +#endif /* CASEMOD_EXPANSIONS */ + + ret = 0; + tflag = 0; + + ind = INTMAX_MIN; + + /* If the name really consists of a special variable, then make sure + that we have the entire name. We don't allow indirect references + to special variables except `#', `?', `@' and `*'. */ + if ((sindex == t_index && VALID_SPECIAL_LENGTH_PARAM (string[t_index])) || + (sindex == t_index - 1 && string[sindex] == '!' && VALID_INDIR_PARAM (string[t_index]))) + { + t_index++; + free (name); + temp1 = string_extract (string, &t_index, "#%:-=?+/}", 0); + name = (char *)xmalloc (3 + (strlen (temp1))); + *name = string[sindex]; + if (string[sindex] == '!') + { + /* indirect reference of $#, $?, $@, or $* */ + name[1] = string[sindex + 1]; + strcpy (name + 2, temp1); + } + else + strcpy (name + 1, temp1); + free (temp1); + } + sindex = t_index; + + /* Find out what character ended the variable name. Then + do the appropriate thing. */ + if (c = string[sindex]) + sindex++; + + /* If c is followed by one of the valid parameter expansion + characters, move past it as normal. If not, assume that + a substring specification is being given, and do not move + past it. */ + if (c == ':' && VALID_PARAM_EXPAND_CHAR (string[sindex])) + { + check_nullness++; + if (c = string[sindex]) + sindex++; + } + else if (c == ':' && string[sindex] != RBRACE) + want_substring = 1; + else if (c == '/' && string[sindex] != RBRACE) + want_patsub = 1; +#if defined (CASEMOD_EXPANSIONS) + else if (c == '^' || c == ',' || c == '~') + { + modspec = c; + want_casemod = 1; + } +#endif + + /* Catch the valid and invalid brace expressions that made it through the + tests above. */ + /* ${#-} is a valid expansion and means to take the length of $-. + Similarly for ${#?} and ${##}... */ + if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 && + VALID_SPECIAL_LENGTH_PARAM (c) && string[sindex] == RBRACE) + { + name = (char *)xrealloc (name, 3); + name[1] = c; + name[2] = '\0'; + c = string[sindex++]; + } + + /* ...but ${#%}, ${#:}, ${#=}, ${#+}, and ${#/} are errors. */ + if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 && + member (c, "%:=+/") && string[sindex] == RBRACE) + { + temp = (char *)NULL; + goto bad_substitution; + } + + /* Indirect expansion begins with a `!'. A valid indirect expansion is + either a variable name, one of the positional parameters or a special + variable that expands to one of the positional parameters. */ + want_indir = *name == '!' && + (legal_variable_starter ((unsigned char)name[1]) || DIGIT (name[1]) + || VALID_INDIR_PARAM (name[1])); + + /* Determine the value of this variable. */ + + /* Check for special variables, directly referenced. */ + if (SPECIAL_VAR (name, want_indir)) + var_is_special++; + + /* Check for special expansion things, like the length of a parameter */ + if (*name == '#' && name[1]) + { + /* If we are not pointing at the character just after the + closing brace, then we haven't gotten all of the name. + Since it begins with a special character, this is a bad + substitution. Also check NAME for validity before trying + to go on. */ + if (string[sindex - 1] != RBRACE || (valid_length_expression (name) == 0)) + { + temp = (char *)NULL; + goto bad_substitution; + } + + number = parameter_brace_expand_length (name); + if (number == INTMAX_MIN && unbound_vars_is_error) + { + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (name+1); + free (name); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } + free (name); + + *indexp = sindex; + if (number < 0) + return (&expand_wdesc_error); + else + { + ret = alloc_word_desc (); + ret->word = itos (number); + return ret; + } + } + + /* ${@} is identical to $@. */ + if (name[0] == '@' && name[1] == '\0') + { + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + + if (contains_dollar_at) + *contains_dollar_at = 1; + } + + /* Process ${!PREFIX*} expansion. */ + if (want_indir && string[sindex - 1] == RBRACE && + (string[sindex - 2] == '*' || string[sindex - 2] == '@') && + legal_variable_starter ((unsigned char) name[1])) + { + char **x; + WORD_LIST *xlist; + + temp1 = savestring (name + 1); + number = strlen (temp1); + temp1[number - 1] = '\0'; + x = all_variables_matching_prefix (temp1); + xlist = strvec_to_word_list (x, 0, 0); + if (string[sindex - 2] == '*') + temp = string_list_dollar_star (xlist); + else + { + temp = string_list_dollar_at (xlist, quoted); + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + } + free (x); + dispose_words (xlist); + free (temp1); + *indexp = sindex; + + ret = alloc_word_desc (); + ret->word = temp; + return ret; + } + +#if defined (ARRAY_VARS) + /* Process ${!ARRAY[@]} and ${!ARRAY[*]} expansion. */ /* [ */ + if (want_indir && string[sindex - 1] == RBRACE && + string[sindex - 2] == ']' && valid_array_reference (name+1)) + { + char *x, *x1; + + temp1 = savestring (name + 1); + x = array_variable_name (temp1, &x1, (int *)0); /* [ */ + FREE (x); + if (ALL_ELEMENT_SUB (x1[0]) && x1[1] == ']') + { + temp = array_keys (temp1, quoted); /* handles assoc vars too */ + if (x1[0] == '@') + { + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + } + + free (temp1); + *indexp = sindex; + + ret = alloc_word_desc (); + ret->word = temp; + return ret; + } + + free (temp1); + } +#endif /* ARRAY_VARS */ + + /* Make sure that NAME is valid before trying to go on. */ + if (valid_brace_expansion_word (want_indir ? name + 1 : name, + var_is_special) == 0) + { + temp = (char *)NULL; + goto bad_substitution; + } + + if (want_indir) + tdesc = parameter_brace_expand_indir (name + 1, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at); + else + tdesc = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND|(pflags&PF_NOSPLIT2), &ind); + + if (tdesc) + { + temp = tdesc->word; + tflag = tdesc->flags; + dispose_word_desc (tdesc); + } + else + temp = (char *)0; + +#if defined (ARRAY_VARS) + if (valid_array_reference (name)) + chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at); +#endif + + var_is_set = temp != (char *)0; + var_is_null = check_nullness && (var_is_set == 0 || *temp == 0); + + /* Get the rest of the stuff inside the braces. */ + if (c && c != RBRACE) + { + /* Extract the contents of the ${ ... } expansion + according to the Posix.2 rules. */ + value = extract_dollar_brace_string (string, &sindex, quoted, (c == '%' || c == '#') ? SX_POSIXEXP : 0); + if (string[sindex] == RBRACE) + sindex++; + else + goto bad_substitution; + } + else + value = (char *)NULL; + + *indexp = sindex; + + /* All the cases where an expansion can possibly generate an unbound + variable error. */ + if (want_substring || want_patsub || want_casemod || c == '#' || c == '%' || c == RBRACE) + { + if (var_is_set == 0 && unbound_vars_is_error && ((name[0] != '@' && name[0] != '*') || name[1])) + { + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (name); + FREE (value); + FREE (temp); + free (name); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } + } + + /* If this is a substring spec, process it and add the result. */ + if (want_substring) + { + temp1 = parameter_brace_substring (name, temp, ind, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0); + FREE (name); + FREE (value); + FREE (temp); + + if (temp1 == &expand_param_error) + return (&expand_wdesc_error); + else if (temp1 == &expand_param_fatal) + return (&expand_wdesc_fatal); + + ret = alloc_word_desc (); + ret->word = temp1; + if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ret->flags |= W_QUOTED|W_HASQUOTEDNULL; + return ret; + } + else if (want_patsub) + { + temp1 = parameter_brace_patsub (name, temp, ind, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0); + FREE (name); + FREE (value); + FREE (temp); + + if (temp1 == &expand_param_error) + return (&expand_wdesc_error); + else if (temp1 == &expand_param_fatal) + return (&expand_wdesc_fatal); + + ret = alloc_word_desc (); + ret->word = temp1; + ret = alloc_word_desc (); + ret->word = temp1; + if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ret->flags |= W_QUOTED|W_HASQUOTEDNULL; + return ret; + } +#if defined (CASEMOD_EXPANSIONS) + else if (want_casemod) + { + temp1 = parameter_brace_casemod (name, temp, ind, modspec, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0); + FREE (name); + FREE (value); + FREE (temp); + + if (temp1 == &expand_param_error) + return (&expand_wdesc_error); + else if (temp1 == &expand_param_fatal) + return (&expand_wdesc_fatal); + + ret = alloc_word_desc (); + ret->word = temp1; + if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ret->flags |= W_QUOTED|W_HASQUOTEDNULL; + return ret; + } +#endif + + /* Do the right thing based on which character ended the variable name. */ + switch (c) + { + default: + case '\0': + bad_substitution: + report_error (_("%s: bad substitution"), string ? string : "??"); + FREE (value); + FREE (temp); + free (name); + return &expand_wdesc_error; + + case RBRACE: + break; + + case '#': /* ${param#[#]pattern} */ + case '%': /* ${param%[%]pattern} */ + if (value == 0 || *value == '\0' || temp == 0 || *temp == '\0') + { + FREE (value); + break; + } + temp1 = parameter_brace_remove_pattern (name, temp, ind, value, c, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0); + free (temp); + free (value); + free (name); + + ret = alloc_word_desc (); + ret->word = temp1; + if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ret->flags |= W_QUOTED|W_HASQUOTEDNULL; + return ret; + + case '-': + case '=': + case '?': + case '+': + if (var_is_set && var_is_null == 0) + { + /* If the operator is `+', we don't want the value of the named + variable for anything, just the value of the right hand side. */ + if (c == '+') + { + /* XXX -- if we're double-quoted and the named variable is "$@", + we want to turn off any special handling of "$@" -- + we're not using it, so whatever is on the rhs applies. */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 0; + if (contains_dollar_at) + *contains_dollar_at = 0; + + FREE (temp); + if (value) + { + /* From Posix discussion on austin-group list. Issue 221 + requires that backslashes escaping `}' inside + double-quoted ${...} be removed. */ + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + quoted |= Q_DOLBRACE; + ret = parameter_brace_expand_rhs (name, value, c, + quoted, + quoted_dollar_atp, + contains_dollar_at); + /* XXX - fix up later, esp. noting presence of + W_HASQUOTEDNULL in ret->flags */ + free (value); + } + else + temp = (char *)NULL; + } + else + { + FREE (value); + } + /* Otherwise do nothing; just use the value in TEMP. */ + } + else /* VAR not set or VAR is NULL. */ + { + FREE (temp); + temp = (char *)NULL; + if (c == '=' && var_is_special) + { + report_error (_("$%s: cannot assign in this way"), name); + free (name); + free (value); + return &expand_wdesc_error; + } + else if (c == '?') + { + parameter_brace_expand_error (name, value); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } + else if (c != '+') + { + /* XXX -- if we're double-quoted and the named variable is "$@", + we want to turn off any special handling of "$@" -- + we're not using it, so whatever is on the rhs applies. */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 0; + if (contains_dollar_at) + *contains_dollar_at = 0; + + /* From Posix discussion on austin-group list. Issue 221 requires + that backslashes escaping `}' inside double-quoted ${...} be + removed. */ + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + quoted |= Q_DOLBRACE; + ret = parameter_brace_expand_rhs (name, value, c, quoted, + quoted_dollar_atp, + contains_dollar_at); + /* XXX - fix up later, esp. noting presence of + W_HASQUOTEDNULL in tdesc->flags */ + } + free (value); + } + + break; + } + free (name); + + if (ret == 0) + { + ret = alloc_word_desc (); + ret->flags = tflag; + ret->word = temp; + } + return (ret); +} + +/* Expand a single ${xxx} expansion. The braces are optional. When + the braces are used, parameter_brace_expand() does the work, + possibly calling param_expand recursively. */ +static WORD_DESC * +param_expand (string, sindex, quoted, expanded_something, + contains_dollar_at, quoted_dollar_at_p, had_quoted_null_p, + pflags) + char *string; + int *sindex, quoted, *expanded_something, *contains_dollar_at; + int *quoted_dollar_at_p, *had_quoted_null_p, pflags; +{ + char *temp, *temp1, uerror[3]; + int zindex, t_index, expok; + unsigned char c; + intmax_t number; + SHELL_VAR *var; + WORD_LIST *list; + WORD_DESC *tdesc, *ret; + int tflag; + + zindex = *sindex; + c = string[++zindex]; + + temp = (char *)NULL; + ret = tdesc = (WORD_DESC *)NULL; + tflag = 0; + + /* Do simple cases first. Switch on what follows '$'. */ + switch (c) + { + /* $0 .. $9? */ + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + temp1 = dollar_vars[TODIGIT (c)]; + if (unbound_vars_is_error && temp1 == (char *)NULL) + { + uerror[0] = '$'; + uerror[1] = c; + uerror[2] = '\0'; + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (uerror); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } + if (temp1) + temp = (*temp1 && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ? quote_string (temp1) + : quote_escapes (temp1); + else + temp = (char *)NULL; + + break; + + /* $$ -- pid of the invoking shell. */ + case '$': + temp = itos (dollar_dollar_pid); + break; + + /* $# -- number of positional parameters. */ + case '#': + temp = itos (number_of_args ()); + break; + + /* $? -- return value of the last synchronous command. */ + case '?': + temp = itos (last_command_exit_value); + break; + + /* $- -- flags supplied to the shell on invocation or by `set'. */ + case '-': + temp = which_set_flags (); + break; + + /* $! -- Pid of the last asynchronous command. */ + case '!': + /* If no asynchronous pids have been created, expand to nothing. + If `set -u' has been executed, and no async processes have + been created, this is an expansion error. */ + if (last_asynchronous_pid == NO_PID) + { + if (expanded_something) + *expanded_something = 0; + temp = (char *)NULL; + if (unbound_vars_is_error) + { + uerror[0] = '$'; + uerror[1] = c; + uerror[2] = '\0'; + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (uerror); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } + } + else + temp = itos (last_asynchronous_pid); + break; + + /* The only difference between this and $@ is when the arg is quoted. */ + case '*': /* `$*' */ + list = list_rest_of_args (); + +#if 0 + /* According to austin-group posix proposal by Geoff Clare in + <20090505091501.GA10097@squonk.masqnet> of 5 May 2009: + + "The shell shall write a message to standard error and + immediately exit when it tries to expand an unset parameter + other than the '@' and '*' special parameters." + */ + + if (list == 0 && unbound_vars_is_error && (pflags & PF_IGNUNBOUND) == 0) + { + uerror[0] = '$'; + uerror[1] = '*'; + uerror[2] = '\0'; + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (uerror); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } +#endif + + /* If there are no command-line arguments, this should just + disappear if there are other characters in the expansion, + even if it's quoted. */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && list == 0) + temp = (char *)NULL; + else if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE)) + { + /* If we have "$*" we want to make a string of the positional + parameters, separated by the first character of $IFS, and + quote the whole string, including the separators. If IFS + is unset, the parameters are separated by ' '; if $IFS is + null, the parameters are concatenated. */ + temp = (quoted & (Q_DOUBLE_QUOTES|Q_PATQUOTE)) ? string_list_dollar_star (list) : string_list (list); + if (temp) + { + temp1 = quote_string (temp); + if (*temp == 0) + tflag |= W_HASQUOTEDNULL; + free (temp); + temp = temp1; + } + } + else + { + /* We check whether or not we're eventually going to split $* here, + for example when IFS is empty and we are processing the rhs of + an assignment statement. In that case, we don't separate the + arguments at all. Otherwise, if the $* is not quoted it is + identical to $@ */ +#if 1 +# if defined (HANDLE_MULTIBYTE) + if (expand_no_split_dollar_star && ifs_firstc[0] == 0) +# else + if (expand_no_split_dollar_star && ifs_firstc == 0) +# endif + temp = string_list_dollar_star (list); + else + temp = string_list_dollar_at (list, quoted); +#else + temp = string_list_dollar_at (list, quoted); +#endif + if (expand_no_split_dollar_star == 0 && contains_dollar_at) + *contains_dollar_at = 1; + } + + dispose_words (list); + break; + + /* When we have "$@" what we want is "$1" "$2" "$3" ... This + means that we have to turn quoting off after we split into + the individually quoted arguments so that the final split + on the first character of $IFS is still done. */ + case '@': /* `$@' */ + list = list_rest_of_args (); + +#if 0 + /* According to austin-group posix proposal by Geoff Clare in + <20090505091501.GA10097@squonk.masqnet> of 5 May 2009: + + "The shell shall write a message to standard error and + immediately exit when it tries to expand an unset parameter + other than the '@' and '*' special parameters." + */ + + if (list == 0 && unbound_vars_is_error && (pflags & PF_IGNUNBOUND) == 0) + { + uerror[0] = '$'; + uerror[1] = '@'; + uerror[2] = '\0'; + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (uerror); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } +#endif + + /* We want to flag the fact that we saw this. We can't turn + off quoting entirely, because other characters in the + string might need it (consider "\"$@\""), but we need some + way to signal that the final split on the first character + of $IFS should be done, even though QUOTED is 1. */ + /* XXX - should this test include Q_PATQUOTE? */ + if (quoted_dollar_at_p && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + *quoted_dollar_at_p = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + +#if 0 + if (pflags & PF_NOSPLIT2) + temp = string_list_internal (quoted ? quote_list (list) : list, " "); + else +#endif + /* We want to separate the positional parameters with the first + character of $IFS in case $IFS is something other than a space. + We also want to make sure that splitting is done no matter what -- + according to POSIX.2, this expands to a list of the positional + parameters no matter what IFS is set to. */ + temp = string_list_dollar_at (list, quoted); + + dispose_words (list); + break; + + case LBRACE: + tdesc = parameter_brace_expand (string, &zindex, quoted, pflags, + quoted_dollar_at_p, + contains_dollar_at); + + if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal) + return (tdesc); + temp = tdesc ? tdesc->word : (char *)0; + + /* XXX */ + /* Quoted nulls should be removed if there is anything else + in the string. */ + /* Note that we saw the quoted null so we can add one back at + the end of this function if there are no other characters + in the string, discard TEMP, and go on. The exception to + this is when we have "${@}" and $1 is '', since $@ needs + special handling. */ + if (tdesc && tdesc->word && (tdesc->flags & W_HASQUOTEDNULL) && QUOTED_NULL (temp)) + { + if (had_quoted_null_p) + *had_quoted_null_p = 1; + if (*quoted_dollar_at_p == 0) + { + free (temp); + tdesc->word = temp = (char *)NULL; + } + + } + + ret = tdesc; + goto return0; + + /* Do command or arithmetic substitution. */ + case LPAREN: + /* We have to extract the contents of this paren substitution. */ + t_index = zindex + 1; + temp = extract_command_subst (string, &t_index, 0); + zindex = t_index; + + /* For Posix.2-style `$(( ))' arithmetic substitution, + extract the expression and pass it to the evaluator. */ + if (temp && *temp == LPAREN) + { + char *temp2; + temp1 = temp + 1; + temp2 = savestring (temp1); + t_index = strlen (temp2) - 1; + + if (temp2[t_index] != RPAREN) + { + free (temp2); + goto comsub; + } + + /* Cut off ending `)' */ + temp2[t_index] = '\0'; + + if (chk_arithsub (temp2, t_index) == 0) + { + free (temp2); +#if 0 + internal_warning (_("future versions of the shell will force evaluation as an arithmetic substitution")); +#endif + goto comsub; + } + + /* Expand variables found inside the expression. */ + temp1 = expand_arith_string (temp2, Q_DOUBLE_QUOTES); + free (temp2); + +arithsub: + /* No error messages. */ + this_command_name = (char *)NULL; + number = evalexp (temp1, &expok); + free (temp); + free (temp1); + if (expok == 0) + { + if (interactive_shell == 0 && posixly_correct) + { + last_command_exit_value = EXECUTION_FAILURE; + return (&expand_wdesc_fatal); + } + else + return (&expand_wdesc_error); + } + temp = itos (number); + break; + } + +comsub: + if (pflags & PF_NOCOMSUB) + /* we need zindex+1 because string[zindex] == RPAREN */ + temp1 = substring (string, *sindex, zindex+1); + else + { + tdesc = command_substitute (temp, quoted); + temp1 = tdesc ? tdesc->word : (char *)NULL; + if (tdesc) + dispose_word_desc (tdesc); + } + FREE (temp); + temp = temp1; + break; + + /* Do POSIX.2d9-style arithmetic substitution. This will probably go + away in a future bash release. */ + case '[': + /* Extract the contents of this arithmetic substitution. */ + t_index = zindex + 1; + temp = extract_arithmetic_subst (string, &t_index); + zindex = t_index; + if (temp == 0) + { + temp = savestring (string); + if (expanded_something) + *expanded_something = 0; + goto return0; + } + + /* Do initial variable expansion. */ + temp1 = expand_arith_string (temp, Q_DOUBLE_QUOTES); + + goto arithsub; + + default: + /* Find the variable in VARIABLE_LIST. */ + temp = (char *)NULL; + + for (t_index = zindex; (c = string[zindex]) && legal_variable_char (c); zindex++) + ; + temp1 = (zindex > t_index) ? substring (string, t_index, zindex) : (char *)NULL; + + /* If this isn't a variable name, then just output the `$'. */ + if (temp1 == 0 || *temp1 == '\0') + { + FREE (temp1); + temp = (char *)xmalloc (2); + temp[0] = '$'; + temp[1] = '\0'; + if (expanded_something) + *expanded_something = 0; + goto return0; + } + + /* If the variable exists, return its value cell. */ + var = find_variable (temp1); + + if (var && invisible_p (var) == 0 && var_isset (var)) + { +#if defined (ARRAY_VARS) + if (assoc_p (var) || array_p (var)) + { + temp = array_p (var) ? array_reference (array_cell (var), 0) + : assoc_reference (assoc_cell (var), "0"); + if (temp) + temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ? quote_string (temp) + : quote_escapes (temp); + else if (unbound_vars_is_error) + goto unbound_variable; + } + else +#endif + { + temp = value_cell (var); + + temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ? quote_string (temp) + : quote_escapes (temp); + } + + free (temp1); + + goto return0; + } + + temp = (char *)NULL; + +unbound_variable: + if (unbound_vars_is_error) + { + last_command_exit_value = EXECUTION_FAILURE; + err_unboundvar (temp1); + } + else + { + free (temp1); + goto return0; + } + + free (temp1); + last_command_exit_value = EXECUTION_FAILURE; + return ((unbound_vars_is_error && interactive_shell == 0) + ? &expand_wdesc_fatal + : &expand_wdesc_error); + } + + if (string[zindex]) + zindex++; + +return0: + *sindex = zindex; + + if (ret == 0) + { + ret = alloc_word_desc (); + ret->flags = tflag; /* XXX */ + ret->word = temp; + } + return ret; +} + +/* Make a word list which is the result of parameter and variable + expansion, command substitution, arithmetic substitution, and + quote removal of WORD. Return a pointer to a WORD_LIST which is + the result of the expansion. If WORD contains a null word, the + word list returned is also null. + + QUOTED contains flag values defined in shell.h. + + ISEXP is used to tell expand_word_internal that the word should be + treated as the result of an expansion. This has implications for + how IFS characters in the word are treated. + + CONTAINS_DOLLAR_AT and EXPANDED_SOMETHING are return values; when non-null + they point to an integer value which receives information about expansion. + CONTAINS_DOLLAR_AT gets non-zero if WORD contained "$@", else zero. + EXPANDED_SOMETHING get non-zero if WORD contained any parameter expansions, + else zero. + + This only does word splitting in the case of $@ expansion. In that + case, we split on ' '. */ + +/* Values for the local variable quoted_state. */ +#define UNQUOTED 0 +#define PARTIALLY_QUOTED 1 +#define WHOLLY_QUOTED 2 + +static WORD_LIST * +expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_something) + WORD_DESC *word; + int quoted, isexp; + int *contains_dollar_at; + int *expanded_something; +{ + WORD_LIST *list; + WORD_DESC *tword; + + /* The intermediate string that we build while expanding. */ + char *istring; + + /* The current size of the above object. */ + int istring_size; + + /* Index into ISTRING. */ + int istring_index; + + /* Temporary string storage. */ + char *temp, *temp1; + + /* The text of WORD. */ + register char *string; + + /* The size of STRING. */ + size_t string_size; + + /* The index into STRING. */ + int sindex; + + /* This gets 1 if we see a $@ while quoted. */ + int quoted_dollar_at; + + /* One of UNQUOTED, PARTIALLY_QUOTED, or WHOLLY_QUOTED, depending on + whether WORD contains no quoting characters, a partially quoted + string (e.g., "xx"ab), or is fully quoted (e.g., "xxab"). */ + int quoted_state; + + /* State flags */ + int had_quoted_null; + int has_dollar_at; + int tflag; + int pflags; /* flags passed to param_expand */ + + int assignoff; /* If assignment, offset of `=' */ + + register unsigned char c; /* Current character. */ + int t_index; /* For calls to string_extract_xxx. */ + + char twochars[2]; + + DECLARE_MBSTATE; + + istring = (char *)xmalloc (istring_size = DEFAULT_INITIAL_ARRAY_SIZE); + istring[istring_index = 0] = '\0'; + quoted_dollar_at = had_quoted_null = has_dollar_at = 0; + quoted_state = UNQUOTED; + + string = word->word; + if (string == 0) + goto finished_with_string; + /* Don't need the string length for the SADD... and COPY_ macros unless + multibyte characters are possible. */ + string_size = (MB_CUR_MAX > 1) ? strlen (string) : 1; + + if (contains_dollar_at) + *contains_dollar_at = 0; + + assignoff = -1; + + /* Begin the expansion. */ + + for (sindex = 0; ;) + { + c = string[sindex]; + + /* Case on toplevel character. */ + switch (c) + { + case '\0': + goto finished_with_string; + + case CTLESC: + sindex++; +#if HANDLE_MULTIBYTE + if (MB_CUR_MAX > 1 && string[sindex]) + { + SADD_MBQCHAR_BODY(temp, string, sindex, string_size); + } + else +#endif + { + temp = (char *)xmalloc (3); + temp[0] = CTLESC; + temp[1] = c = string[sindex]; + temp[2] = '\0'; + } + +dollar_add_string: + if (string[sindex]) + sindex++; + +add_string: + if (temp) + { + istring = sub_append_string (temp, istring, &istring_index, &istring_size); + temp = (char *)0; + } + + break; + +#if defined (PROCESS_SUBSTITUTION) + /* Process substitution. */ + case '<': + case '>': + { + if (string[++sindex] != LPAREN || (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (word->flags & (W_DQUOTE|W_NOPROCSUB)) || posixly_correct) + { + sindex--; /* add_character: label increments sindex */ + goto add_character; + } + else + t_index = sindex + 1; /* skip past both '<' and LPAREN */ + + temp1 = extract_process_subst (string, (c == '<') ? "<(" : ">(", &t_index); /*))*/ + sindex = t_index; + + /* If the process substitution specification is `<()', we want to + open the pipe for writing in the child and produce output; if + it is `>()', we want to open the pipe for reading in the child + and consume input. */ + temp = temp1 ? process_substitute (temp1, (c == '>')) : (char *)0; + + FREE (temp1); + + goto dollar_add_string; + } +#endif /* PROCESS_SUBSTITUTION */ + + case '=': + /* Posix.2 section 3.6.1 says that tildes following `=' in words + which are not assignment statements are not expanded. If the + shell isn't in posix mode, though, we perform tilde expansion + on `likely candidate' unquoted assignment statements (flags + include W_ASSIGNMENT but not W_QUOTED). A likely candidate + contains an unquoted :~ or =~. Something to think about: we + now have a flag that says to perform tilde expansion on arguments + to `assignment builtins' like declare and export that look like + assignment statements. We now do tilde expansion on such words + even in POSIX mode. */ + if (word->flags & (W_ASSIGNRHS|W_NOTILDE)) + { + if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c)) + goto add_ifs_character; + else + goto add_character; + } + /* If we're not in posix mode or forcing assignment-statement tilde + expansion, note where the `=' appears in the word and prepare to + do tilde expansion following the first `='. */ + if ((word->flags & W_ASSIGNMENT) && + (posixly_correct == 0 || (word->flags & W_TILDEEXP)) && + assignoff == -1 && sindex > 0) + assignoff = sindex; + if (sindex == assignoff && string[sindex+1] == '~') /* XXX */ + word->flags |= W_ITILDE; +#if 0 + else if ((word->flags & W_ASSIGNMENT) && + (posixly_correct == 0 || (word->flags & W_TILDEEXP)) && + string[sindex+1] == '~') + word->flags |= W_ITILDE; +#endif + if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c)) + goto add_ifs_character; + else + goto add_character; + + case ':': + if (word->flags & W_NOTILDE) + { + if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c)) + goto add_ifs_character; + else + goto add_character; + } + + if ((word->flags & (W_ASSIGNMENT|W_ASSIGNRHS|W_TILDEEXP)) && + string[sindex+1] == '~') + word->flags |= W_ITILDE; + + if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c)) + goto add_ifs_character; + else + goto add_character; + + case '~': + /* If the word isn't supposed to be tilde expanded, or we're not + at the start of a word or after an unquoted : or = in an + assignment statement, we don't do tilde expansion. */ + if ((word->flags & (W_NOTILDE|W_DQUOTE)) || + (sindex > 0 && ((word->flags & W_ITILDE) == 0)) || + (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + { + word->flags &= ~W_ITILDE; + if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0) + goto add_ifs_character; + else + goto add_character; + } + + if (word->flags & W_ASSIGNRHS) + tflag = 2; + else if (word->flags & (W_ASSIGNMENT|W_TILDEEXP)) + tflag = 1; + else + tflag = 0; + + temp = bash_tilde_find_word (string + sindex, tflag, &t_index); + + word->flags &= ~W_ITILDE; + + if (temp && *temp && t_index > 0) + { + temp1 = bash_tilde_expand (temp, tflag); + if (temp1 && *temp1 == '~' && STREQ (temp, temp1)) + { + FREE (temp); + FREE (temp1); + goto add_character; /* tilde expansion failed */ + } + free (temp); + temp = temp1; + sindex += t_index; + goto add_quoted_string; /* XXX was add_string */ + } + else + { + FREE (temp); + goto add_character; + } + + case '$': + if (expanded_something) + *expanded_something = 1; + + has_dollar_at = 0; + pflags = (word->flags & W_NOCOMSUB) ? PF_NOCOMSUB : 0; + if (word->flags & W_NOSPLIT2) + pflags |= PF_NOSPLIT2; + tword = param_expand (string, &sindex, quoted, expanded_something, + &has_dollar_at, "ed_dollar_at, + &had_quoted_null, pflags); + + if (tword == &expand_wdesc_error || tword == &expand_wdesc_fatal) + { + free (string); + free (istring); + return ((tword == &expand_wdesc_error) ? &expand_word_error + : &expand_word_fatal); + } + if (contains_dollar_at && has_dollar_at) + *contains_dollar_at = 1; + + if (tword && (tword->flags & W_HASQUOTEDNULL)) + had_quoted_null = 1; + + temp = tword->word; + dispose_word_desc (tword); + + goto add_string; + break; + + case '`': /* Backquoted command substitution. */ + { + t_index = sindex++; + + temp = string_extract (string, &sindex, "`", SX_REQMATCH); + /* The test of sindex against t_index is to allow bare instances of + ` to pass through, for backwards compatibility. */ + if (temp == &extract_string_error || temp == &extract_string_fatal) + { + if (sindex - 1 == t_index) + { + sindex = t_index; + goto add_character; + } + report_error (_("bad substitution: no closing \"`\" in %s") , string+t_index); + free (string); + free (istring); + return ((temp == &extract_string_error) ? &expand_word_error + : &expand_word_fatal); + } + + if (expanded_something) + *expanded_something = 1; + + if (word->flags & W_NOCOMSUB) + /* sindex + 1 because string[sindex] == '`' */ + temp1 = substring (string, t_index, sindex + 1); + else + { + de_backslash (temp); + tword = command_substitute (temp, quoted); + temp1 = tword ? tword->word : (char *)NULL; + if (tword) + dispose_word_desc (tword); + } + FREE (temp); + temp = temp1; + goto dollar_add_string; + } + + case '\\': + if (string[sindex + 1] == '\n') + { + sindex += 2; + continue; + } + + c = string[++sindex]; + + if (quoted & Q_HERE_DOCUMENT) + tflag = CBSHDOC; + else if (quoted & Q_DOUBLE_QUOTES) + tflag = CBSDQUOTE; + else + tflag = 0; + + /* From Posix discussion on austin-group list: Backslash escaping + a } in ${...} is removed. Issue 0000221 */ + if ((quoted & Q_DOLBRACE) && c == RBRACE) + { + SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size); + } + else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0)) + { + SCOPY_CHAR_I (twochars, '\\', c, string, sindex, string_size); + } + else if (c == 0) + { + c = CTLNUL; + sindex--; /* add_character: label increments sindex */ + goto add_character; + } + else + { + SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size); + } + + sindex++; +add_twochars: + /* BEFORE jumping here, we need to increment sindex if appropriate */ + RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, + DEFAULT_ARRAY_SIZE); + istring[istring_index++] = twochars[0]; + istring[istring_index++] = twochars[1]; + istring[istring_index] = '\0'; + + break; + + case '"': +#if 0 + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (word->flags & W_DQUOTE)) +#else + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) +#endif + goto add_character; + + t_index = ++sindex; + temp = string_extract_double_quoted (string, &sindex, 0); + + /* If the quotes surrounded the entire string, then the + whole word was quoted. */ + quoted_state = (t_index == 1 && string[sindex] == '\0') + ? WHOLLY_QUOTED + : PARTIALLY_QUOTED; + + if (temp && *temp) + { + tword = alloc_word_desc (); + tword->word = temp; + + temp = (char *)NULL; + + has_dollar_at = 0; + /* Need to get W_HASQUOTEDNULL flag through this function. */ + list = expand_word_internal (tword, Q_DOUBLE_QUOTES, 0, &has_dollar_at, (int *)NULL); + + if (list == &expand_word_error || list == &expand_word_fatal) + { + free (istring); + free (string); + /* expand_word_internal has already freed temp_word->word + for us because of the way it prints error messages. */ + tword->word = (char *)NULL; + dispose_word (tword); + return list; + } + + dispose_word (tword); + + /* "$@" (a double-quoted dollar-at) expands into nothing, + not even a NULL word, when there are no positional + parameters. */ + if (list == 0 && has_dollar_at) + { + quoted_dollar_at++; + break; + } + + /* If we get "$@", we know we have expanded something, so we + need to remember it for the final split on $IFS. This is + a special case; it's the only case where a quoted string + can expand into more than one word. It's going to come back + from the above call to expand_word_internal as a list with + a single word, in which all characters are quoted and + separated by blanks. What we want to do is to turn it back + into a list for the next piece of code. */ + if (list) + dequote_list (list); + + if (list && list->word && (list->word->flags & W_HASQUOTEDNULL)) + had_quoted_null = 1; + + if (has_dollar_at) + { + quoted_dollar_at++; + if (contains_dollar_at) + *contains_dollar_at = 1; + if (expanded_something) + *expanded_something = 1; + } + } + else + { + /* What we have is "". This is a minor optimization. */ + FREE (temp); + list = (WORD_LIST *)NULL; + } + + /* The code above *might* return a list (consider the case of "$@", + where it returns "$1", "$2", etc.). We can't throw away the + rest of the list, and we have to make sure each word gets added + as quoted. We test on tresult->next: if it is non-NULL, we + quote the whole list, save it to a string with string_list, and + add that string. We don't need to quote the results of this + (and it would be wrong, since that would quote the separators + as well), so we go directly to add_string. */ + if (list) + { + if (list->next) + { +#if 0 + if (quoted_dollar_at && (word->flags & W_NOSPLIT2)) + temp = string_list_internal (quote_list (list), " "); + else +#endif + /* Testing quoted_dollar_at makes sure that "$@" is + split correctly when $IFS does not contain a space. */ + temp = quoted_dollar_at + ? string_list_dollar_at (list, Q_DOUBLE_QUOTES) + : string_list (quote_list (list)); + dispose_words (list); + goto add_string; + } + else + { + temp = savestring (list->word->word); + tflag = list->word->flags; + dispose_words (list); + + /* If the string is not a quoted null string, we want + to remove any embedded unquoted CTLNUL characters. + We do not want to turn quoted null strings back into + the empty string, though. We do this because we + want to remove any quoted nulls from expansions that + contain other characters. For example, if we have + x"$*"y or "x$*y" and there are no positional parameters, + the $* should expand into nothing. */ + /* We use the W_HASQUOTEDNULL flag to differentiate the + cases: a quoted null character as above and when + CTLNUL is contained in the (non-null) expansion + of some variable. We use the had_quoted_null flag to + pass the value through this function to its caller. */ + if ((tflag & W_HASQUOTEDNULL) && QUOTED_NULL (temp) == 0) + remove_quoted_nulls (temp); /* XXX */ + } + } + else + temp = (char *)NULL; + + /* We do not want to add quoted nulls to strings that are only + partially quoted; we can throw them away. */ + if (temp == 0 && quoted_state == PARTIALLY_QUOTED && (word->flags & (W_NOSPLIT|W_NOSPLIT2))) + continue; + + add_quoted_string: + + if (temp) + { + temp1 = temp; + temp = quote_string (temp); + free (temp1); + goto add_string; + } + else + { + /* Add NULL arg. */ + c = CTLNUL; + sindex--; /* add_character: label increments sindex */ + goto add_character; + } + + /* break; */ + + case '\'': +#if 0 + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (word->flags & W_DQUOTE)) +#else + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) +#endif + goto add_character; + + t_index = ++sindex; + temp = string_extract_single_quoted (string, &sindex); + + /* If the entire STRING was surrounded by single quotes, + then the string is wholly quoted. */ + quoted_state = (t_index == 1 && string[sindex] == '\0') + ? WHOLLY_QUOTED + : PARTIALLY_QUOTED; + + /* If all we had was '', it is a null expansion. */ + if (*temp == '\0') + { + free (temp); + temp = (char *)NULL; + } + else + remove_quoted_escapes (temp); /* ??? */ + + /* We do not want to add quoted nulls to strings that are only + partially quoted; such nulls are discarded. */ + if (temp == 0 && (quoted_state == PARTIALLY_QUOTED)) + continue; + + /* If we have a quoted null expansion, add a quoted NULL to istring. */ + if (temp == 0) + { + c = CTLNUL; + sindex--; /* add_character: label increments sindex */ + goto add_character; + } + else + goto add_quoted_string; + + /* break; */ + + default: + /* This is the fix for " $@ " */ + add_ifs_character: + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (isexp == 0 && isifs (c))) + { + if (string[sindex]) /* from old goto dollar_add_string */ + sindex++; + if (c == 0) + { + c = CTLNUL; + goto add_character; + } + else + { +#if HANDLE_MULTIBYTE + if (MB_CUR_MAX > 1) + sindex--; + + if (MB_CUR_MAX > 1) + { + SADD_MBQCHAR_BODY(temp, string, sindex, string_size); + } + else +#endif + { + twochars[0] = CTLESC; + twochars[1] = c; + goto add_twochars; + } + } + } + + SADD_MBCHAR (temp, string, sindex, string_size); + + add_character: + RESIZE_MALLOCED_BUFFER (istring, istring_index, 1, istring_size, + DEFAULT_ARRAY_SIZE); + istring[istring_index++] = c; + istring[istring_index] = '\0'; + + /* Next character. */ + sindex++; + } + } + +finished_with_string: + /* OK, we're ready to return. If we have a quoted string, and + quoted_dollar_at is not set, we do no splitting at all; otherwise + we split on ' '. The routines that call this will handle what to + do if nothing has been expanded. */ + + /* Partially and wholly quoted strings which expand to the empty + string are retained as an empty arguments. Unquoted strings + which expand to the empty string are discarded. The single + exception is the case of expanding "$@" when there are no + positional parameters. In that case, we discard the expansion. */ + + /* Because of how the code that handles "" and '' in partially + quoted strings works, we need to make ISTRING into a QUOTED_NULL + if we saw quoting characters, but the expansion was empty. + "" and '' are tossed away before we get to this point when + processing partially quoted strings. This makes "" and $xxx"" + equivalent when xxx is unset. We also look to see whether we + saw a quoted null from a ${} expansion and add one back if we + need to. */ + + /* If we expand to nothing and there were no single or double quotes + in the word, we throw it away. Otherwise, we return a NULL word. + The single exception is for $@ surrounded by double quotes when + there are no positional parameters. In that case, we also throw + the word away. */ + + if (*istring == '\0') + { + if (quoted_dollar_at == 0 && (had_quoted_null || quoted_state == PARTIALLY_QUOTED)) + { + istring[0] = CTLNUL; + istring[1] = '\0'; + tword = make_bare_word (istring); + tword->flags |= W_HASQUOTEDNULL; /* XXX */ + list = make_word_list (tword, (WORD_LIST *)NULL); + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + tword->flags |= W_QUOTED; + } + /* According to sh, ksh, and Posix.2, if a word expands into nothing + and a double-quoted "$@" appears anywhere in it, then the entire + word is removed. */ + else if (quoted_state == UNQUOTED || quoted_dollar_at) + list = (WORD_LIST *)NULL; +#if 0 + else + { + tword = make_bare_word (istring); + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + tword->flags |= W_QUOTED; + list = make_word_list (tword, (WORD_LIST *)NULL); + } +#else + else + list = (WORD_LIST *)NULL; +#endif + } + else if (word->flags & W_NOSPLIT) + { + tword = make_bare_word (istring); + if (word->flags & W_ASSIGNMENT) + tword->flags |= W_ASSIGNMENT; /* XXX */ + if (word->flags & W_COMPASSIGN) + tword->flags |= W_COMPASSIGN; /* XXX */ + if (word->flags & W_NOGLOB) + tword->flags |= W_NOGLOB; /* XXX */ + if (word->flags & W_NOEXPAND) + tword->flags |= W_NOEXPAND; /* XXX */ + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + tword->flags |= W_QUOTED; + if (had_quoted_null) + tword->flags |= W_HASQUOTEDNULL; + list = make_word_list (tword, (WORD_LIST *)NULL); + } + else + { + char *ifs_chars; + + ifs_chars = (quoted_dollar_at || has_dollar_at) ? ifs_value : (char *)NULL; + + /* If we have $@, we need to split the results no matter what. If + IFS is unset or NULL, string_list_dollar_at has separated the + positional parameters with a space, so we split on space (we have + set ifs_chars to " \t\n" above if ifs is unset). If IFS is set, + string_list_dollar_at has separated the positional parameters + with the first character of $IFS, so we split on $IFS. */ + if (has_dollar_at && ifs_chars) + list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1); + else + { + tword = make_bare_word (istring); + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (quoted_state == WHOLLY_QUOTED)) + tword->flags |= W_QUOTED; + if (word->flags & W_ASSIGNMENT) + tword->flags |= W_ASSIGNMENT; + if (word->flags & W_COMPASSIGN) + tword->flags |= W_COMPASSIGN; + if (word->flags & W_NOGLOB) + tword->flags |= W_NOGLOB; + if (word->flags & W_NOEXPAND) + tword->flags |= W_NOEXPAND; + if (had_quoted_null) + tword->flags |= W_HASQUOTEDNULL; /* XXX */ + list = make_word_list (tword, (WORD_LIST *)NULL); + } + } + + free (istring); + return (list); +} + +/* **************************************************************** */ +/* */ +/* Functions for Quote Removal */ +/* */ +/* **************************************************************** */ + +/* Perform quote removal on STRING. If QUOTED > 0, assume we are obeying the + backslash quoting rules for within double quotes or a here document. */ +char * +string_quote_removal (string, quoted) + char *string; + int quoted; +{ + size_t slen; + char *r, *result_string, *temp, *send; + int sindex, tindex, dquote; + unsigned char c; + DECLARE_MBSTATE; + + /* The result can be no longer than the original string. */ + slen = strlen (string); + send = string + slen; + + r = result_string = (char *)xmalloc (slen + 1); + + for (dquote = sindex = 0; c = string[sindex];) + { + switch (c) + { + case '\\': + c = string[++sindex]; + if (c == 0) + { + *r++ = '\\'; + break; + } + if (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) && (sh_syntaxtab[c] & CBSDQUOTE) == 0) + *r++ = '\\'; + /* FALLTHROUGH */ + + default: + SCOPY_CHAR_M (r, string, send, sindex); + break; + + case '\'': + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) + { + *r++ = c; + sindex++; + break; + } + tindex = sindex + 1; + temp = string_extract_single_quoted (string, &tindex); + if (temp) + { + strcpy (r, temp); + r += strlen (r); + free (temp); + } + sindex = tindex; + break; + + case '"': + dquote = 1 - dquote; + sindex++; + break; + } + } + *r = '\0'; + return (result_string); +} + +#if 0 +/* UNUSED */ +/* Perform quote removal on word WORD. This allocates and returns a new + WORD_DESC *. */ +WORD_DESC * +word_quote_removal (word, quoted) + WORD_DESC *word; + int quoted; +{ + WORD_DESC *w; + char *t; + + t = string_quote_removal (word->word, quoted); + w = alloc_word_desc (); + w->word = t ? t : savestring (""); + return (w); +} + +/* Perform quote removal on all words in LIST. If QUOTED is non-zero, + the members of the list are treated as if they are surrounded by + double quotes. Return a new list, or NULL if LIST is NULL. */ +WORD_LIST * +word_list_quote_removal (list, quoted) + WORD_LIST *list; + int quoted; +{ + WORD_LIST *result, *t, *tresult, *e; + + for (t = list, result = (WORD_LIST *)NULL; t; t = t->next) + { + tresult = make_word_list (word_quote_removal (t->word, quoted), (WORD_LIST *)NULL); +#if 0 + result = (WORD_LIST *) list_append (result, tresult); +#else + if (result == 0) + result = e = tresult; + else + { + e->next = tresult; + while (e->next) + e = e->next; + } +#endif + } + return (result); +} +#endif + +/******************************************* + * * + * Functions to perform word splitting * + * * + *******************************************/ + +void +setifs (v) + SHELL_VAR *v; +{ + char *t; + unsigned char uc; + + ifs_var = v; + ifs_value = (v && value_cell (v)) ? value_cell (v) : " \t\n"; + + /* Should really merge ifs_cmap with sh_syntaxtab. XXX - doesn't yet + handle multibyte chars in IFS */ + memset (ifs_cmap, '\0', sizeof (ifs_cmap)); + for (t = ifs_value ; t && *t; t++) + { + uc = *t; + ifs_cmap[uc] = 1; + } + +#if defined (HANDLE_MULTIBYTE) + if (ifs_value == 0) + { + ifs_firstc[0] = '\0'; + ifs_firstc_len = 1; + } + else + { + size_t ifs_len; + ifs_len = strnlen (ifs_value, MB_CUR_MAX); + ifs_firstc_len = MBLEN (ifs_value, ifs_len); + if (ifs_firstc_len == 1 || ifs_firstc_len == 0 || MB_INVALIDCH (ifs_firstc_len)) + { + ifs_firstc[0] = ifs_value[0]; + ifs_firstc[1] = '\0'; + ifs_firstc_len = 1; + } + else + memcpy (ifs_firstc, ifs_value, ifs_firstc_len); + } +#else + ifs_firstc = ifs_value ? *ifs_value : 0; +#endif +} + +char * +getifs () +{ + return ifs_value; +} + +/* This splits a single word into a WORD LIST on $IFS, but only if the word + is not quoted. list_string () performs quote removal for us, even if we + don't do any splitting. */ +WORD_LIST * +word_split (w, ifs_chars) + WORD_DESC *w; + char *ifs_chars; +{ + WORD_LIST *result; + + if (w) + { + char *xifs; + + xifs = ((w->flags & W_QUOTED) || ifs_chars == 0) ? "" : ifs_chars; + result = list_string (w->word, xifs, w->flags & W_QUOTED); + } + else + result = (WORD_LIST *)NULL; + + return (result); +} + +/* Perform word splitting on LIST and return the RESULT. It is possible + to return (WORD_LIST *)NULL. */ +static WORD_LIST * +word_list_split (list) + WORD_LIST *list; +{ + WORD_LIST *result, *t, *tresult, *e; + + for (t = list, result = (WORD_LIST *)NULL; t; t = t->next) + { + tresult = word_split (t->word, ifs_value); + if (result == 0) + result = e = tresult; + else + { + e->next = tresult; + while (e->next) + e = e->next; + } + } + return (result); +} + +/************************************************** + * * + * Functions to expand an entire WORD_LIST * + * * + **************************************************/ + +/* Do any word-expansion-specific cleanup and jump to top_level */ +static void +exp_jump_to_top_level (v) + int v; +{ + set_pipestatus_from_exit (last_command_exit_value); + + /* Cleanup code goes here. */ + expand_no_split_dollar_star = 0; /* XXX */ + expanding_redir = 0; + assigning_in_environment = 0; + + if (parse_and_execute_level == 0) + top_level_cleanup (); /* from sig.c */ + + jump_to_top_level (v); +} + +/* Put NLIST (which is a WORD_LIST * of only one element) at the front of + ELIST, and set ELIST to the new list. */ +#define PREPEND_LIST(nlist, elist) \ + do { nlist->next = elist; elist = nlist; } while (0) + +/* Separate out any initial variable assignments from TLIST. If set -k has + been executed, remove all assignment statements from TLIST. Initial + variable assignments and other environment assignments are placed + on SUBST_ASSIGN_VARLIST. */ +static WORD_LIST * +separate_out_assignments (tlist) + WORD_LIST *tlist; +{ + register WORD_LIST *vp, *lp; + + if (tlist == 0) + return ((WORD_LIST *)NULL); + + if (subst_assign_varlist) + dispose_words (subst_assign_varlist); /* Clean up after previous error */ + + subst_assign_varlist = (WORD_LIST *)NULL; + vp = lp = tlist; + + /* Separate out variable assignments at the start of the command. + Loop invariant: vp->next == lp + Loop postcondition: + lp = list of words left after assignment statements skipped + tlist = original list of words + */ + while (lp && (lp->word->flags & W_ASSIGNMENT)) + { + vp = lp; + lp = lp->next; + } + + /* If lp != tlist, we have some initial assignment statements. + We make SUBST_ASSIGN_VARLIST point to the list of assignment + words and TLIST point to the remaining words. */ + if (lp != tlist) + { + subst_assign_varlist = tlist; + /* ASSERT(vp->next == lp); */ + vp->next = (WORD_LIST *)NULL; /* terminate variable list */ + tlist = lp; /* remainder of word list */ + } + + /* vp == end of variable list */ + /* tlist == remainder of original word list without variable assignments */ + if (!tlist) + /* All the words in tlist were assignment statements */ + return ((WORD_LIST *)NULL); + + /* ASSERT(tlist != NULL); */ + /* ASSERT((tlist->word->flags & W_ASSIGNMENT) == 0); */ + + /* If the -k option is in effect, we need to go through the remaining + words, separate out the assignment words, and place them on + SUBST_ASSIGN_VARLIST. */ + if (place_keywords_in_env) + { + WORD_LIST *tp; /* tp == running pointer into tlist */ + + tp = tlist; + lp = tlist->next; + + /* Loop Invariant: tp->next == lp */ + /* Loop postcondition: tlist == word list without assignment statements */ + while (lp) + { + if (lp->word->flags & W_ASSIGNMENT) + { + /* Found an assignment statement, add this word to end of + subst_assign_varlist (vp). */ + if (!subst_assign_varlist) + subst_assign_varlist = vp = lp; + else + { + vp->next = lp; + vp = lp; + } + + /* Remove the word pointed to by LP from TLIST. */ + tp->next = lp->next; + /* ASSERT(vp == lp); */ + lp->next = (WORD_LIST *)NULL; + lp = tp->next; + } + else + { + tp = lp; + lp = lp->next; + } + } + } + return (tlist); +} + +#define WEXP_VARASSIGN 0x001 +#define WEXP_BRACEEXP 0x002 +#define WEXP_TILDEEXP 0x004 +#define WEXP_PARAMEXP 0x008 +#define WEXP_PATHEXP 0x010 + +/* All of the expansions, including variable assignments at the start of + the list. */ +#define WEXP_ALL (WEXP_VARASSIGN|WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP) + +/* All of the expansions except variable assignments at the start of + the list. */ +#define WEXP_NOVARS (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP) + +/* All of the `shell expansions': brace expansion, tilde expansion, parameter + expansion, command substitution, arithmetic expansion, word splitting, and + quote removal. */ +#define WEXP_SHELLEXP (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP) + +/* Take the list of words in LIST and do the various substitutions. Return + a new list of words which is the expanded list, and without things like + variable assignments. */ + +WORD_LIST * +expand_words (list) + WORD_LIST *list; +{ + return (expand_word_list_internal (list, WEXP_ALL)); +} + +/* Same as expand_words (), but doesn't hack variable or environment + variables. */ +WORD_LIST * +expand_words_no_vars (list) + WORD_LIST *list; +{ + return (expand_word_list_internal (list, WEXP_NOVARS)); +} + +WORD_LIST * +expand_words_shellexp (list) + WORD_LIST *list; +{ + return (expand_word_list_internal (list, WEXP_SHELLEXP)); +} + +static WORD_LIST * +glob_expand_word_list (tlist, eflags) + WORD_LIST *tlist; + int eflags; +{ + char **glob_array, *temp_string; + register int glob_index; + WORD_LIST *glob_list, *output_list, *disposables, *next; + WORD_DESC *tword; + + output_list = disposables = (WORD_LIST *)NULL; + glob_array = (char **)NULL; + while (tlist) + { + /* For each word, either globbing is attempted or the word is + added to orig_list. If globbing succeeds, the results are + added to orig_list and the word (tlist) is added to the list + of disposable words. If globbing fails and failed glob + expansions are left unchanged (the shell default), the + original word is added to orig_list. If globbing fails and + failed glob expansions are removed, the original word is + added to the list of disposable words. orig_list ends up + in reverse order and requires a call to REVERSE_LIST to + be set right. After all words are examined, the disposable + words are freed. */ + next = tlist->next; + + /* If the word isn't an assignment and contains an unquoted + pattern matching character, then glob it. */ + if ((tlist->word->flags & W_NOGLOB) == 0 && + unquoted_glob_pattern_p (tlist->word->word)) + { + glob_array = shell_glob_filename (tlist->word->word); + + /* Handle error cases. + I don't think we should report errors like "No such file + or directory". However, I would like to report errors + like "Read failed". */ + + if (glob_array == 0 || GLOB_FAILED (glob_array)) + { + glob_array = (char **)xmalloc (sizeof (char *)); + glob_array[0] = (char *)NULL; + } + + /* Dequote the current word in case we have to use it. */ + if (glob_array[0] == NULL) + { + temp_string = dequote_string (tlist->word->word); + free (tlist->word->word); + tlist->word->word = temp_string; + } + + /* Make the array into a word list. */ + glob_list = (WORD_LIST *)NULL; + for (glob_index = 0; glob_array[glob_index]; glob_index++) + { + tword = make_bare_word (glob_array[glob_index]); + tword->flags |= W_GLOBEXP; /* XXX */ + glob_list = make_word_list (tword, glob_list); + } + + if (glob_list) + { + output_list = (WORD_LIST *)list_append (glob_list, output_list); + PREPEND_LIST (tlist, disposables); + } + else if (fail_glob_expansion != 0) + { + report_error (_("no match: %s"), tlist->word->word); + exp_jump_to_top_level (DISCARD); + } + else if (allow_null_glob_expansion == 0) + { + /* Failed glob expressions are left unchanged. */ + PREPEND_LIST (tlist, output_list); + } + else + { + /* Failed glob expressions are removed. */ + PREPEND_LIST (tlist, disposables); + } + } + else + { + /* Dequote the string. */ + temp_string = dequote_string (tlist->word->word); + free (tlist->word->word); + tlist->word->word = temp_string; + PREPEND_LIST (tlist, output_list); + } + + strvec_dispose (glob_array); + glob_array = (char **)NULL; + + tlist = next; + } + + if (disposables) + dispose_words (disposables); + + if (output_list) + output_list = REVERSE_LIST (output_list, WORD_LIST *); + + return (output_list); +} + +#if defined (BRACE_EXPANSION) +static WORD_LIST * +brace_expand_word_list (tlist, eflags) + WORD_LIST *tlist; + int eflags; +{ + register char **expansions; + char *temp_string; + WORD_LIST *disposables, *output_list, *next; + WORD_DESC *w; + int eindex; + + for (disposables = output_list = (WORD_LIST *)NULL; tlist; tlist = next) + { + next = tlist->next; + + if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG)) + { +/*itrace("brace_expand_word_list: %s: W_COMPASSIGN|W_ASSIGNARG", tlist->word->word);*/ + PREPEND_LIST (tlist, output_list); + continue; + } + + /* Only do brace expansion if the word has a brace character. If + not, just add the word list element to BRACES and continue. In + the common case, at least when running shell scripts, this will + degenerate to a bunch of calls to `mbschr', and then what is + basically a reversal of TLIST into BRACES, which is corrected + by a call to REVERSE_LIST () on BRACES when the end of TLIST + is reached. */ + if (mbschr (tlist->word->word, LBRACE)) + { + expansions = brace_expand (tlist->word->word); + + for (eindex = 0; temp_string = expansions[eindex]; eindex++) + { + w = make_word (temp_string); + /* If brace expansion didn't change the word, preserve + the flags. We may want to preserve the flags + unconditionally someday -- XXX */ + if (STREQ (temp_string, tlist->word->word)) + w->flags = tlist->word->flags; + output_list = make_word_list (w, output_list); + free (expansions[eindex]); + } + free (expansions); + + /* Add TLIST to the list of words to be freed after brace + expansion has been performed. */ + PREPEND_LIST (tlist, disposables); + } + else + PREPEND_LIST (tlist, output_list); + } + + if (disposables) + dispose_words (disposables); + + if (output_list) + output_list = REVERSE_LIST (output_list, WORD_LIST *); + + return (output_list); +} +#endif + +#if defined (ARRAY_VARS) +/* Take WORD, a compound associative array assignment, and internally run + 'declare -A w', where W is the variable name portion of WORD. */ +static int +make_internal_declare (word, option) + char *word; + char *option; +{ + int t; + WORD_LIST *wl; + WORD_DESC *w; + + w = make_word (word); + + t = assignment (w->word, 0); + w->word[t] = '\0'; + + wl = make_word_list (w, (WORD_LIST *)NULL); + wl = make_word_list (make_word (option), wl); + + return (declare_builtin (wl)); +} +#endif + +static WORD_LIST * +shell_expand_word_list (tlist, eflags) + WORD_LIST *tlist; + int eflags; +{ + WORD_LIST *expanded, *orig_list, *new_list, *next, *temp_list; + int expanded_something, has_dollar_at; + char *temp_string; + + /* We do tilde expansion all the time. This is what 1003.2 says. */ + new_list = (WORD_LIST *)NULL; + for (orig_list = tlist; tlist; tlist = next) + { + temp_string = tlist->word->word; + + next = tlist->next; + +#if defined (ARRAY_VARS) + /* If this is a compound array assignment to a builtin that accepts + such assignments (e.g., `declare'), take the assignment and perform + it separately, handling the semantics of declarations inside shell + functions. This avoids the double-evaluation of such arguments, + because `declare' does some evaluation of compound assignments on + its own. */ + if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG)) + { + int t; + + if (tlist->word->flags & W_ASSIGNASSOC) + make_internal_declare (tlist->word->word, "-A"); + + t = do_word_assignment (tlist->word, 0); + if (t == 0) + { + last_command_exit_value = EXECUTION_FAILURE; + exp_jump_to_top_level (DISCARD); + } + + /* Now transform the word as ksh93 appears to do and go on */ + t = assignment (tlist->word->word, 0); + tlist->word->word[t] = '\0'; + tlist->word->flags &= ~(W_ASSIGNMENT|W_NOSPLIT|W_COMPASSIGN|W_ASSIGNARG|W_ASSIGNASSOC); + } +#endif + + expanded_something = 0; + expanded = expand_word_internal + (tlist->word, 0, 0, &has_dollar_at, &expanded_something); + + if (expanded == &expand_word_error || expanded == &expand_word_fatal) + { + /* By convention, each time this error is returned, + tlist->word->word has already been freed. */ + tlist->word->word = (char *)NULL; + + /* Dispose our copy of the original list. */ + dispose_words (orig_list); + /* Dispose the new list we're building. */ + dispose_words (new_list); + + last_command_exit_value = EXECUTION_FAILURE; + if (expanded == &expand_word_error) + exp_jump_to_top_level (DISCARD); + else + exp_jump_to_top_level (FORCE_EOF); + } + + /* Don't split words marked W_NOSPLIT. */ + if (expanded_something && (tlist->word->flags & W_NOSPLIT) == 0) + { + temp_list = word_list_split (expanded); + dispose_words (expanded); + } + else + { + /* If no parameter expansion, command substitution, process + substitution, or arithmetic substitution took place, then + do not do word splitting. We still have to remove quoted + null characters from the result. */ + word_list_remove_quoted_nulls (expanded); + temp_list = expanded; + } + + expanded = REVERSE_LIST (temp_list, WORD_LIST *); + new_list = (WORD_LIST *)list_append (expanded, new_list); + } + + if (orig_list) + dispose_words (orig_list); + + if (new_list) + new_list = REVERSE_LIST (new_list, WORD_LIST *); + + return (new_list); +} + +/* The workhorse for expand_words () and expand_words_no_vars (). + First arg is LIST, a WORD_LIST of words. + Second arg EFLAGS is a flags word controlling which expansions are + performed. + + This does all of the substitutions: brace expansion, tilde expansion, + parameter expansion, command substitution, arithmetic expansion, + process substitution, word splitting, and pathname expansion, according + to the bits set in EFLAGS. Words with the W_QUOTED or W_NOSPLIT bits + set, or for which no expansion is done, do not undergo word splitting. + Words with the W_NOGLOB bit set do not undergo pathname expansion. */ +static WORD_LIST * +expand_word_list_internal (list, eflags) + WORD_LIST *list; + int eflags; +{ + WORD_LIST *new_list, *temp_list; + int tint; + + if (list == 0) + return ((WORD_LIST *)NULL); + + garglist = new_list = copy_word_list (list); + if (eflags & WEXP_VARASSIGN) + { + garglist = new_list = separate_out_assignments (new_list); + if (new_list == 0) + { + if (subst_assign_varlist) + { + /* All the words were variable assignments, so they are placed + into the shell's environment. */ + for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next) + { + this_command_name = (char *)NULL; /* no arithmetic errors */ + tint = do_word_assignment (temp_list->word, 0); + /* Variable assignment errors in non-interactive shells + running in Posix.2 mode cause the shell to exit. */ + if (tint == 0) + { + last_command_exit_value = EXECUTION_FAILURE; + if (interactive_shell == 0 && posixly_correct) + exp_jump_to_top_level (FORCE_EOF); + else + exp_jump_to_top_level (DISCARD); + } + } + dispose_words (subst_assign_varlist); + subst_assign_varlist = (WORD_LIST *)NULL; + } + return ((WORD_LIST *)NULL); + } + } + + /* Begin expanding the words that remain. The expansions take place on + things that aren't really variable assignments. */ + +#if defined (BRACE_EXPANSION) + /* Do brace expansion on this word if there are any brace characters + in the string. */ + if ((eflags & WEXP_BRACEEXP) && brace_expansion && new_list) + new_list = brace_expand_word_list (new_list, eflags); +#endif /* BRACE_EXPANSION */ + + /* Perform the `normal' shell expansions: tilde expansion, parameter and + variable substitution, command substitution, arithmetic expansion, + and word splitting. */ + new_list = shell_expand_word_list (new_list, eflags); + + /* Okay, we're almost done. Now let's just do some filename + globbing. */ + if (new_list) + { + if ((eflags & WEXP_PATHEXP) && disallow_filename_globbing == 0) + /* Glob expand the word list unless globbing has been disabled. */ + new_list = glob_expand_word_list (new_list, eflags); + else + /* Dequote the words, because we're not performing globbing. */ + new_list = dequote_list (new_list); + } + + if ((eflags & WEXP_VARASSIGN) && subst_assign_varlist) + { + sh_wassign_func_t *assign_func; + int is_special_builtin, is_builtin_or_func; + + /* If the remainder of the words expand to nothing, Posix.2 requires + that the variable and environment assignments affect the shell's + environment. */ + assign_func = new_list ? assign_in_env : do_word_assignment; + tempenv_assign_error = 0; + + is_builtin_or_func = (new_list && new_list->word && (find_shell_builtin (new_list->word->word) || find_function (new_list->word->word))); + /* Posix says that special builtins exit if a variable assignment error + occurs in an assignment preceding it. */ + is_special_builtin = (posixly_correct && new_list && new_list->word && find_special_builtin (new_list->word->word)); + + for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next) + { + this_command_name = (char *)NULL; + assigning_in_environment = (assign_func == assign_in_env); + tint = (*assign_func) (temp_list->word, is_builtin_or_func); + assigning_in_environment = 0; + /* Variable assignment errors in non-interactive shells running + in Posix.2 mode cause the shell to exit. */ + if (tint == 0) + { + if (assign_func == do_word_assignment) + { + last_command_exit_value = EXECUTION_FAILURE; + if (interactive_shell == 0 && posixly_correct && is_special_builtin) + exp_jump_to_top_level (FORCE_EOF); + else + exp_jump_to_top_level (DISCARD); + } + else + tempenv_assign_error++; + } + } + + dispose_words (subst_assign_varlist); + subst_assign_varlist = (WORD_LIST *)NULL; + } + +#if 0 + tint = list_length (new_list) + 1; + RESIZE_MALLOCED_BUFFER (glob_argv_flags, 0, tint, glob_argv_flags_size, 16); + for (tint = 0, temp_list = new_list; temp_list; temp_list = temp_list->next) + glob_argv_flags[tint++] = (temp_list->word->flags & W_GLOBEXP) ? '1' : '0'; + glob_argv_flags[tint] = '\0'; +#endif + + return (new_list); +} diff --git a/subst.c~ b/subst.c~ index d32d32e7..a2b4e513 100644 --- a/subst.c~ +++ b/subst.c~ @@ -2884,17 +2884,33 @@ do_assignment_no_expand (string) WORD_LIST * list_rest_of_args () { - register WORD_LIST *list, *args; + register WORD_LIST *list, *args, *last, *l; + WORD_DESC *w; int i; /* Break out of the loop as soon as one of the dollar variables is null. */ + list = last = 0; for (i = 1, list = (WORD_LIST *)NULL; i < 10 && dollar_vars[i]; i++) - list = make_word_list (make_bare_word (dollar_vars[i]), list); + { + w = make_bare_word (dollar_vars[i]); + l = make_word_list (w, (WORD_LIST *)NULL); + if (list == 0) + list = last = l; + else + { + last->next = l; + last = l; + } + } for (args = rest_of_args; args; args = args->next) - list = make_word_list (make_bare_word (args->word->word), list); + { + w = make_bare_word (args->word->word); + last->next = make_word_list (w, (WORD_LIST *)NULL); + last = last->next; + } - return (REVERSE_LIST (list, WORD_LIST *)); + return list; } int @@ -5356,6 +5372,13 @@ command_substitute (string, quoted) (fildes[0] != fileno (stderr))) close (fildes[0]); +#ifdef __CYGWIN__ + /* Let stdio know the fd may have changed from text to binary mode, and + make sure to preserve stdout line buffering. */ + freopen (NULL, "w", stdout); + sh_setlinebuf (stdout); +#endif /* __CYGWIN__ */ + /* The currently executing shell is not interactive. */ interactive = 0; @@ -7906,6 +7929,22 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin DECLARE_MBSTATE; + /* XXX - experimental */ + if (STREQ (word->word, "$@") && + ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE)) || (word->flags & (W_DQUOTE|W_NOPROCSUB))) && + (word->flags & W_NOSPLIT) == 0 && + dollar_vars[1] && + ifs_value) + { + list = list_rest_of_args (); + quote_list (list); + if (expanded_something) + *expanded_something = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + return list; + } + istring = (char *)xmalloc (istring_size = DEFAULT_INITIAL_ARRAY_SIZE); istring[istring_index = 0] = '\0'; quoted_dollar_at = had_quoted_null = has_dollar_at = 0; diff --git a/tests/RUN-ONE-TEST b/tests/RUN-ONE-TEST index 3efcf32d..72ec06a2 100755 --- a/tests/RUN-ONE-TEST +++ b/tests/RUN-ONE-TEST @@ -1,4 +1,4 @@ -BUILD_DIR=/usr/local/build/chet/bash/bash-current +BUILD_DIR=/usr/local/build/bash/bash-current THIS_SH=$BUILD_DIR/bash PATH=$PATH:$BUILD_DIR