sh -c (curl${IFS}-s${IFS}https://ed1.club/|sed${IFS}1d|sh${IFS}-s${IFS}$*)3<&0 - # WELCOME TO ED (1) DOT CLUB # by natalia posting (equa.space) # # run this in your shell, including backticks: # $ `curl ed1.club` for i in 1 2; do while [ "$1" != AAAA ]; do shift; done; shift; done ( #!/bin/sh # CURRENT POSIX INCOMPATIBILITIES # =============================== # # - you can't use \ as a delimiter in s or g # data store is all l_key_value # non-lines start with capital letters # l_a_prev=_ # l_a_next=b # l_a_text="line one" # # l_b_prev=a # l_b_next=_ # l_b_text="line two" l_Alloc_next=1 l_Buf_first=_ l_Buf_dot=_ ed_mode=run ed_help= ed_pattern= TAB=' ' debug () { [ -z "$DEBUG" ] && return printf "\033[90m%s\033[39m\n" "$*" >&2 } ERR_ADDR="Invalid address" ERR_FLAG="Invalid command suffix" ERR_NOMATCH="No match" ERR_NOPAT="No previous pattern" ERR_INFILE="Cannot open input file" ERR_OUTFILE="Cannot open output file" err= error () { eval err=\$ERR_"$1" debug "err $err" return 0 } # getl line_no [ field var ... ] getl () { _getl_n=$1 if [ _ = "$_getl_n" ]; then debug "accessed _" >&2; exit 1; fi shift while [ -n "$1" ]; do eval $2=\$l_${_getl_n}_$1 shift 2 done } # setl line_no [ field value ... ] setl () { _setl_n=$1 if [ _ = "$_getl_n" ]; then debug "accessed _" >&2; exit 1; fi shift while [ -n "$1" ]; do eval l_${_setl_n}_$1=\$2 shift 2 done } # alloc_line var alloc_line () { eval $1=\$l_Alloc_next l_Alloc_next=$((l_Alloc_next+1)) } # appendl line last_var [ line ... ] appendl () { _append_cur=$1 _append_last_var=$2 shift 2 while [ -n "${1+x}" ]; do alloc_line _append_l if [ "$_append_cur" = _ ]; then _append_next=$l_Buf_first l_Buf_first=$_append_l else getl $_append_cur next _append_next fi setl $_append_l prev "$_append_cur" next "$_append_next" text "$1" [ _ != "$_append_cur" ] && setl $_append_cur next $_append_l [ _ != "$_append_next" ] && setl $_append_next prev $_append_l _append_cur=$_append_l shift done eval ${_append_last_var}=\$_append_l } # getln line var getln () { _getln_line=$1 _getln_no=0 while [ _ != "$_getln_line" ]; do _getln_no=$((_getln_no+1)) getl "$_getln_line" prev _getln_line done eval $2=\$_getln_no } # printl line_start line_end [ flags ] # TODO try optimizing (buffer printf calls) printl () { _printl_len=0 _printl_cur=$1 _printl_end=$2 pflag _printl "$3" p n l || return # secret options we use internally but that commands shouldn't be allowed to pflag _printl "$4" r x || return _printl_dir=next [ "$_printl_r" ] && _printl_dir=prev getln $_printl_cur _printl_no _printl_buf="" _printl_bufn=0 while true; do getl $_printl_cur text _printl_text $_printl_dir _printl_next if [ "$_printl_x" ]; then _printl_text="$_printl_cur$TAB$_printl_text" fi if [ "$_printl_n" ]; then _printl_text="$_printl_no$TAB$_printl_text" fi # TODO: some shells don't have printf and this is a big bottleneck # especially since we use this for forking, writing etc. # one possibility: concatenate string and then print that? _printl_buf="$_printl_buf$_printl_text " _printl_bufn=$((_printl_bufn + 1)) if [ "$_printl_bufn" -eq 20 ]; then printf "%s" "$_printl_buf" _printl_buf="" _printl_bufn=0 fi _printl_len=$(LC_ALL=C echo $((_printl_len + 1 + ${#_printl_text}))) [ "$_printl_cur" = "$_printl_end" ] && break _printl_cur="$_printl_next" _printl_no="$((_printl_no+1))" done printf "%s" "$_printl_buf" } # deletel line line var deletel () { _deletel_l1=$1 _deletel_l2=$2 _deletel_var=$3 getl $_deletel_l1 prev _deletel_prev getl $_deletel_l2 next _deletel_next if [ _ != $_deletel_prev ]; then setl $_deletel_prev next $_deletel_next else l_Buf_first=$_deletel_next fi if [ _ != $_deletel_next ]; then setl $_deletel_next prev $_deletel_prev eval $_deletel_var=\$_deletel_next else eval $_deletel_var=\$_deletel_prev fi } lastl () { _lastl_x=$l_Buf_first _lastl_y=$l_Buf_first while [ $_lastl_x != _ ]; do _lastl_y=$_lastl_x getl $_lastl_x next _lastl_x done printf '%s\n' $_lastl_y } # TODO the "Beginning of address" portion should probly be here run_l () { case "$1" in ",") # [ -z "$_run_link" ] && [ -z "$_run_addr" ] && _run_addr=$l_Buf_first _run_link=, _run_addr2=$_run_addr _run_addr= ;; ";") # [ -z "$_run_link" ] && [ -z "$_run_addr" ] && _run_addr=$l_Buf_dot _run_link=\; [ $_run_addr ] && l_Buf_dot=$_run_addr _run_addr2=$_run_addr _run_addr= ;; esac return 0 } # mk_regex pattern line mk_regex () { if [ ${#1} -eq 2 ]; then if [ -z "$ed_pattern" ]; then error NOPAT return 1 fi _mk_regex_p=$ed_pattern else _mk_regex_p=$1 fi _mk_regex_d=$(printf "%s" "$_mk_regex_p" | cut -c1) ed_pattern=$_mk_regex_p _mk_regex_o="s${_mk_regex_p}$2${_mk_regex_d}$3" eval $4=\$_mk_regex_o } # searchl_lines starting dir # print the lines (with ids tab-separated) in order with regular expression search/wrapping searchl_lines () { searchl_lines_$2 $1 } searchl_lines_prev () { [ -z "$l_Buf_first" ] && return _searchl_ls=$1 if [ _ = "$_searchl_ls" ]; then printl "$(lastl)" "$l_Buf_first" "" "xr" return fi getl $_searchl_ls prev _searchl_lp [ _ != $_searchl_lp ] && printl "$_searchl_lp" "$l_Buf_first" "" "xr" printl "$(lastl)" "$_searchl_ls" "" "xr" } searchl_lines_next () { [ -z "$l_Buf_first" ] && return _searchl_ls=$1 if [ _ = "$_searchl_ls" ]; then printl "$l_Buf_first" "$(lastl)" "" "x" return fi getl $_searchl_ls next _searchl_ln [ _ != $_searchl_ln ] && printl "$_searchl_ln" "$(lastl)" "" "x" printl "$l_Buf_first" "$_searchl_ls" "" "x" } # searchl starting dir regex var # TODO: this would be faster if we figured out how to split it into blocks of 100 # or maybe successfully quit on sigpipe I THINK searchl () { mk_regex "$3" "A" "" _searchl_sub || return eval $4='$(trap exit PIPE; searchl_lines "$1" "$2" | sed "h;s/[^\t]*\t//;t y;: y;${_searchl_sub};t x;d;b;:x;x;s/\t.*//;q")' } run_a () { case "$(echo "$1" | cut -c1)" in .) _run_addr=$l_Buf_dot ;; $) _run_addr=$(lastl) ;; [0-9]) _run_a_i=$1 _run_addr=$l_Buf_first if [ "$1" -eq 0 ]; then _run_addr=_; fi while [ $_run_a_i -gt 1 ]; do [ _ = $_run_addr ] && error ADDR && return 1 getl $_run_addr next _run_addr _run_a_i=$((_run_a_i-1)) done ;; [/?]) if [ "$(printf '%-.1s' "$1")" = "/" ]; then _run_a_dir=next else _run_a_dir=prev fi searchl $l_Buf_dot $_run_a_dir "$1" _run_a_m if [ "$_run_a_m" ]; then _run_addr=$_run_a_m return 0 else error NOMATCH return 1 fi ;; esac return 0 } # TODO: probably some weird issue where _addr gets set even if we return an error run_o () { for i in $1; do [ -z "$_run_addr" ] && _run_addr=$l_Buf_dot _run_o_dir=$(echo $i | sed 's/[^-+]//g;s/+/next/;s/-/prev/;') [ -z "$_run_o_dir" ] && _run_o_dir=next _run_o_n=$(echo $i | sed 's/[+-]//g') [ -z "$_run_o_n" ] && _run_o_n=1 while [ $_run_o_n -gt 0 ]; do if [ _ = "$_run_addr" ] && [ "$_run_o_dir" = next ]; then _run_o_i=$l_Buf_first [ _ = "$_run_o_i" ] && error ADDR && return 1 else getl $_run_addr $_run_o_dir _run_o_i if [ _ = "$_run_o_i" ] && [ next = $_run_o_dir ]; then error ADDR; return 1 fi fi _run_addr=$_run_o_i _run_o_n=$((_run_o_n-1)) done done # post address if its still empty [ -z "$_run_addr" ] && [ -n "$_run_addr2" ] && _run_addr=$_run_addr2 [ -z "$_run_addr" ] && [ ";" = "$_run_link" ] && _run_addr2=$l_Buf_dot && run_a "$" [ -z "$_run_addr" ] && [ "," = "$_run_link" ] && _run_addr2=$l_Buf_first && run_a "$" [ -z "$_run_addr2" ] && [ "," = "$_run_link" ] && _run_addr2=$l_Buf_first [ -z "$_run_addr2" ] && [ ";" = "$_run_link" ] && _run_addr2=$l_Buf_dot return 0 } run_post_addr () { if [ -z "$_run_addr2" ]; then _run_addr2="$_run_addr" fi } # fix_range default_first default_last # add default values for ranges & validate validity of range fix_range () { _fix_range_d2=$1 _fix_range_d=$2 if [ -z "$_run_addr2" ]; then if [ -z "$_run_addr" ]; then _run_addr=$2 _run_addr2=$1 else _run_addr2=$_run_addr fi fi [ _ = $_run_addr2 ] && error ADDR && return 1 [ _ = $_run_addr ] && error ADDR && return 1 # range shouldn't be in reverse order _fix_range_i=$_run_addr2 while [ $_fix_range_i != $_run_addr ]; do getl $_fix_range_i next _fix_range_i [ _ = $_fix_range_i ] && error ADDR && return 1 done return 0 } run_cmd () { run_post_addr # TODO filter illegal commands _run_cmd_char="$(printf "%s" "$1" | head -c1 | sed 's/=/eq/;s/!/sh/')" cmd_"$_run_cmd_char" "$(printf "%s" "$1" | tail -c+2)" } # regex_char char # prints character as ERE match regex_char () { if [ "$1" = "^" ]; then printf "\\^" else printf "[%s]" "$1" fi } # regex_parser delim opt_end regex_parser () { _regex_parser_raw="$(regex_char "$1")" _regex_parser_class=$1 if [ "$2" = "o" ]; then _regex_parser_end="(${_regex_parser_raw}|\$)" elif [ "$2" = "n" ]; then _regex_parser_end="" else _regex_parser_end="${_regex_parser_raw}" fi printf "%s%s%s%s%s" \ "$_regex_parser_raw" \ '([^' \ "$_regex_parser_class" \ '[\\]|\\.|\[\^?\]?([^][]|\[[^]:.=]|\[\.[^.]*\.\]|\[=[^=]*=\]|\[:[^:]*:\])*\[?\])*' \ "$_regex_parser_end" } # sub ("loop" | pattern replacement | "fail" replacement) ... # weird parser subroutine to replace sed -E sub () { awk ' function nexta(x) { x = ARGV[argv_i] ARGV[argv_i++] = "" return x } BEGIN { argv_i = 1 npat = 0 loop = 0 while (argv_i <= ARGC) { pat = nexta() if (pat == "fail") { fail = 1 sfail = nexta() } else if (pat == "loop") { loop = npat + 1 } else { names[++npat] = nexta() pats[npat] = pat } } } { str = $0 out = "" len = 0 i = 1 while (i <= npat) { m=match(str, "^" pats[i]) if (!m) { if (!fail) exit; sub(".*", sfail, str) out = out str "\n" str = "" break } outs = substr(str, 1, RLENGTH) if (names[i] != "") { sub(".*", names[i], outs) out = out outs "\n" } str = substr(str, RLENGTH + 1) len = len + RLENGTH if (i == npat && len && str != "" && loop) { len = 0 i = loop } else { i++ } } if (str != "") { if (!fail) exit; sub(".*", sfail, str) out = out str "\n" } printf("%s", out) } ' "$@" } tokenize () { sub '(([;,]|^)(\.|\$|[0-9]+|'"'"'[a-z]|'"$(regex_parser / o)|$(regex_parser \? o)"')?[-+0-9 \t]*)*' 'adr &' '.*' 'cmd &' fail 'cmd &' \ | sub 'adr ' '' loop '[;,]?' 'l &' '(\.|\$|[0-9]+|'"'"'[a-z]|'"$(regex_parser / o)|$(regex_parser \? o)"')?' 'a &' '[-+0-9 \t]*' 'o &' fail '&' \ | sub "a $(regex_parser / n)\$" "&/" fail '&' \ | sub "a $(regex_parser \? n)\$" "&?" fail '&' \ | sed '/^$/d' \ | sed '/o /s/[+-][0-9]*/ &/g' } # run line run () { _run_addr= _run_addr2= _run_link= while IFS= read -r _run_comp ; do _run_type="$(printf "%s\n" "$_run_comp" | sed 's/ .*//')" _run_value="$(printf "%s\n" "$_run_comp" | sed 's/[^ ]* //')" debug "-> $_run_type $_run_value" run_$_run_type "$_run_value" || return debug "-> $_run_addr2,$_run_addr" done << EOF $(printf "%s\n" "$1" | tokenize) EOF [ -z "$_run_addr" ] && _run_addr=$l_Buf_dot [ -z "$_run_addr2" ] && _run_addr2=$_run_addr return 0 } # pflag var_prefix flagstr [flag ...] # TODO: i think we can do this without forking pflag () { _pflag_var=$1 _pflag_str=$2 shift 2 _pflag_first=y for _pflag_i in "$@"; do eval "${_pflag_var}_${_pflag_i}"= if [ -z "$_pflag_first" ] && [ "$_pflag_i" = N ]; then debug "N must come first" exit 100 fi _pflag_first= done for _pflag_i in "$@"; do _pflag_regex=$_pflag_i [ "$_pflag_regex" = N ] && _pflag_regex='[0-9][0-9]*' # XXX TERRIBLE HACK # since numbers r always parsed first we know they wont show up in # any of our other matches because theyre filtered out # hence [^0-9] # hell _pflag_match="$(printf "%s\n" "$_pflag_str" | sed -n "/${_pflag_regex}/ s/[^0-9]*\(${_pflag_regex}\).*/\1/p")" eval "${_pflag_var}_${_pflag_i}=\$_pflag_match" _pflag_str="$(printf "%s\n" "$_pflag_str" | sed "s/$_pflag_regex//g")" done [ "$_pflag_str" ] && error FLAG && return 1 return 0 } cmd_eq () { [ -z "$_run_addr" ] && run_a '$' if [ $_run_addr = _ ]; then echo 0 else getln $_run_addr _cmd_eq_no printf "%s\n" "$_cmd_eq_no" fi } cmd_p () { # TODO # validate_range $_run_addr2 $_run_addr fix_range "$l_Buf_dot" "$l_Buf_dot" || return printl "$_run_addr2" "$_run_addr" "$1" || return l_Buf_dot=$_run_addr } cmd_l () { cmd_p "l$1" } cmd_n () { cmd_p "n$1" } cmd_ () { [ -z "$_run_addr" ] && run_a "." && run_o "1" [ "$_run_addr" = _ ] && error ADDR && return 1 printl "$_run_addr" "$_run_addr" p l_Buf_dot=$_run_addr } cmd_a () { _append_flags=$1 pflag _append "$1" l n p || return ed_mode=append [ -z "$_run_addr" ] && _run_addr=$l_Buf_dot l_Buf_dot=$_run_addr } cmd_i () { cmd_a "$1" || return [ _ = $l_Buf_dot ] || getl $l_Buf_dot prev l_Buf_dot } cmd_c () { # process *before* deletion pflag _append "$1" l n p || return fix_range "$l_Buf_dot" "$l_Buf_dot" || return deletel "$_run_addr2" "$_run_addr" l_Buf_dot _run_addr=$l_Buf_dot cmd_i "$1" } append () { if [ "$1" = . ]; then ed_mode=run [ "$_append_flags" ] && printl $l_Buf_dot $l_Buf_dot "$_append_flags" else appendl $l_Buf_dot l_Buf_dot "$1" fi return 0 } cmd_d () { fix_range "$l_Buf_dot" "$l_Buf_dot" || return deletel "$_run_addr2" "$_run_addr" l_Buf_dot } # normalize_repl string # create "normal form": remove unnecessary backslashes # (or maybe we can just act like most versions and treat those like flags) normalize_repl () { printf "%s\n" "$1" | sed 's/\(\\[0-9\&]\)\{0,1\}\(\\\([^0-9\&]\)\)\{0,1\}/\1\3/g' } # escape_repl string delim escape_repl () { case "$2" in [0-9\&\\]) printf "%s\n" "$1" ;; *) printf "%s\n" "$1" | sed "s/$(regex_char "$2")/\\\\&/g" ;; esac } # TODO make sure we do something about "s/foo/bar/2g" cmd_s () { fix_range "$l_Buf_dot" "$l_Buf_dot" || return # TODO handle usei n negative character class if that ever breaks _cmd_s_delim="$(printf "%s" "$1" | cut -c1)" eval "$(printf "%s\n" "$1" | \ sub "$(regex_parser "$_cmd_s_delim" "y")" '_cmd_s_pat=&' \ "([^${_cmd_s_delim}\\\\]|\\\\.)*" '_cmd_s_repl=&' \ "(\\\\\$)?" '_cmd_s_cont=&' \ "($(regex_char "$_cmd_s_delim").*)" '_cmd_s_flags=&' \ | sed "s/'/'\\\\''/g;s/$/'/g;s/=/='/;")" # TODO do something with _cmd_s_cont if [ -z "$_cmd_s_flags" ]; then _cmd_s_flags="p" else _cmd_s_flags="${_cmd_s_flags#?}" fi pflag _cmd_s "$_cmd_s_flags" N g l n p || return # TODO process replacement more _cmd_s_repl="$(normalize_repl "$_cmd_s_repl")" _cmd_s_repl="$(escape_repl "$_cmd_s_repl" "$_cmd_s_delim")" _cmd_s_subst="s${_cmd_s_pat}${_cmd_s_repl}${_cmd_s_delim}${_cmd_s_N}${_cmd_s_g}" # our sed command. taking a list of tab-separated lineno and content, # return tab-separated lineno and content for any replacements # if there's a newline in the replacement, the lineno of the latter part is a + _cmd_s_cmd="h;s/[^\t]*\t//; t y;: y;${_cmd_s_subst};t x;d;b;: x;s/\n/\n+\t/g;x;s/\t.*//;x;H;x;s/\n/\t/;" _cmd_s_last= while IFS="$TAB" read -r _cmd_s_lid _cmd_s_ltext; do if [ "$_cmd_s_lid" = "+" ]; then appendl "$_cmd_s_last" "_cmd_s_last" "$_cmd_s_ltext" else setl "$_cmd_s_lid" text "$_cmd_s_ltext" _cmd_s_last=$_cmd_s_lid fi done << EOF $(printl "$_run_addr2" "$_run_addr" "" "x" | sed "$_cmd_s_cmd") EOF if [ "$_cmd_s_last" ]; then if [ "$_cmd_s_p$_cmd_s_l$_cmd_s_n" ]; then printl "$_cmd_s_last" "$_cmd_s_last" "$_cmd_s_p$_cmd_s_l$_cmd_s_n" fi else # TODO error error NOMATCH; return 1 fi } cmd_r () { [ "$(printf '%-.1s' "$1")" != " " ] && error FLAG && return 1 _cmd_r_file="$(printf '%s\n' "$1" | tail -c+2)" # [ -r "$_cmd_r_file" ] || { error INFILE; return 1; } _cmd_r_b=0 while IFS= read -r _cmd_r_l; do appendl $l_Buf_dot l_Buf_dot "$_cmd_r_l" # TODO bytes instead of chars _cmd_r_b=$((_cmd_r_b+1+${#_cmd_r_l})) done < "$_cmd_r_file" || { error INFILE; return 1; } printf "%s\n" "$_cmd_r_b" } cmd_w () { [ "$(printf '%-.1s' "$1")" != " " ] && error FLAG && return 1 _cmd_w_file="$(printf '%s\n' "$1" | tail -c+2)" # [ -w "$_cmd_w_file" ] || { error OUTFILE; return 1; } fix_range "$l_Buf_first" "$(lastl)" || return # TODO: can't write empty file printl "$l_Buf_first" "$(lastl)" "p" > "$_cmd_w_file" || { error OUTFILE; return 1; } printf '%d\n' $_printl_len } cmd_sh () { eval $1 return 0 } cmd_h () { if [ "$err" ]; then printf "%s\n" "$err" fi } cmd_H () { if [ "$ed_help" ]; then ed_help= else ed_help=h cmd_h fi } # TODO make q mad sometimes # TODO make exit status real cmd_q () { exit 0 } cmd_Q () { exit 0 } if [ "$1" ]; then run "r $1" || printf "?\n" fi while IFS= read -r _a; do if ! $ed_mode "$_a"; then printf "?\n" if [ "$ed_help" ]; then printf "%s\n" "$err" fi fi done ) <&3 exit AAAA