diff options
Diffstat (limited to '')
-rwxr-xr-x | src/env_parallel | 145 | ||||
-rwxr-xr-x | src/env_parallel.ash | 636 | ||||
-rwxr-xr-x | src/env_parallel.bash | 640 | ||||
-rwxr-xr-x | src/env_parallel.csh | 142 | ||||
-rwxr-xr-x | src/env_parallel.dash | 636 | ||||
-rwxr-xr-x | src/env_parallel.fish | 194 | ||||
-rwxr-xr-x | src/env_parallel.ksh | 636 | ||||
-rw-r--r-- | src/env_parallel.mksh | 605 | ||||
-rwxr-xr-x | src/env_parallel.pdksh | 183 | ||||
-rw-r--r-- | src/env_parallel.pod | 942 | ||||
-rwxr-xr-x | src/env_parallel.sh | 636 | ||||
-rwxr-xr-x | src/env_parallel.tcsh | 142 | ||||
-rwxr-xr-x | src/env_parallel.zsh | 636 |
13 files changed, 6173 insertions, 0 deletions
diff --git a/src/env_parallel b/src/env_parallel new file mode 100755 index 0000000..0f05192 --- /dev/null +++ b/src/env_parallel @@ -0,0 +1,145 @@ +#!/usr/bin/env bash + +# Copyright (C) 2016-2024 Ole Tange, http://ole.tange.dk and Free +# Software Foundation, Inc. +# +# This program 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. +# +# This program 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 this program; if not, see <http://www.gnu.org/licenses/> +# or write to the Free Software Foundation, Inc., 51 Franklin St, +# Fifth Floor, Boston, MA 02110-1301 USA +# +# SPDX-FileCopyrightText: 2021-2024 Ole Tange, http://ole.tange.dk and Free Software and Foundation, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later + +grepq() { + # grep -q for systems without -q + grep >/dev/null 2>/dev/null "$@" +} + +installer() { + source="$1" + script="$2" + into="$3" + if grepq $script "$into"; then + true already installed + else + echo $source \`which $script\` >> "$into" + fi +} + +while test $# -gt 0; do + key="$1" + + case $key in + -i|--install) + installer . env_parallel.bash "$HOME"/.bashrc + installer . env_parallel.sh "$HOME"/.shrc + installer . env_parallel.zsh "$HOME"/.zshenv + installer source env_parallel.ksh "$HOME"/.kshrc + installer source env_parallel.mksh "$HOME"/.mkshrc + echo $SHELL | grepq /pdksh && + installer . env_parallel.pdksh "$HOME"/.profile + echo $SHELL | grepq /ash && + installer . env_parallel.ash "$HOME"/.profile + echo $SHELL | grepq /dash && + installer . env_parallel.dash "$HOME"/.profile + installer source env_parallel.csh "$HOME"/.cshrc + installer source env_parallel.tcsh "$HOME"/.tcshrc + mkdir -p "$HOME"/.config/fish + grepq env_parallel.fish "$HOME"/.config/fish/config.fish || + echo '. (which env_parallel.fish)' >> "$HOME"/.config/fish/config.fish + echo 'Installed env_parallel in:' + echo " " "$HOME"/.bashrc + echo " " "$HOME"/.shrc + echo " " "$HOME"/.zshenv + echo " " "$HOME"/.config/fish/config.fish + echo " " "$HOME"/.kshrc + echo " " "$HOME"/.mkshrc + echo " " "$HOME"/.profile + echo " " "$HOME"/.cshrc + echo " " "$HOME"/.tcshrc + exit + ;; + *) + echo "Unknown option: $key" + ;; + esac + shift # past argument or value +done + + +cat <<'_EOS' +You have called the dummy script "env_parallel". + +env_parallel only works if it is a function. + +You need to do this and restart your shell: + +bash: Put this in $HOME/.bashrc: . env_parallel.bash + E.g. by doing: echo '. env_parallel.bash' >> $HOME/.bashrc + Supports: variables, aliases, functions, arrays + +fish: Put this in $HOME/.config/fish/config.fish: . (which env_parallel.fish) + E.g. by doing: + echo '. (which env_parallel.fish)' >> $HOME/.config/fish/config.fish + Supports: variables, aliases, functions, arrays + +ksh: Put this in $HOME/.kshrc: source env_parallel.ksh + E.g. by doing: echo 'source env_parallel.ksh' >> $HOME/.kshrc + Supports: variables, aliases, functions, arrays + +mksh: Put this in $HOME/.mkshrc: source env_parallel.mksh + E.g. by doing: echo 'source env_parallel.mksh' >> $HOME/.mkshrc + Supports: variables, aliases, functions, arrays + +pdksh: Put this in $HOME/.profile: source env_parallel.pdksh + E.g. by doing: echo '. env_parallel.pdksh' >> $HOME/.profile + Supports: variables, aliases, functions, arrays + +zsh: Put this in $HOME/.zshrc: . env_parallel.zsh + E.g. by doing: echo '. env_parallel.zsh' >> $HOME/.zshenv + Supports: variables, functions, arrays + +ash: Put this in $HOME/.profile: . env_parallel.ash + E.g. by doing: echo '. env_parallel.ash' >> $HOME/.profile + Supports: variables, aliases + +dash: Put this in $HOME/.profile: . env_parallel.dash + E.g. by doing: echo '. env_parallel.dash' >> $HOME/.profile + Supports: variables, aliases + +csh: Put this in $HOME/.cshrc: source `which env_parallel.csh` + E.g. by doing: echo 'source `which env_parallel.csh`' >> $HOME/.cshrc + Supports: variables, aliases, arrays with no special chars + +tcsh: Put this in $HOME/.tcshrc: source `which env_parallel.tcsh` + E.g. by doing: echo 'source `which env_parallel.tcsh`' >> $HOME/.tcshrc + Supports: variables, aliases, arrays with no special chars + +To install in all shells run: + + env_parallel --install + +In a script you need to run this before using env_parallel: + +bash: . env_parallel.bash +ksh: source env_parallel.ksh +mksh: source env_parallel.mksh +pdksh: source env_parallel.pdksh +zsh: . env_parallel.zsh +ash: . env_parallel.ash +dash: . env_parallel.dash + +For details: see man env_parallel + +_EOS diff --git a/src/env_parallel.ash b/src/env_parallel.ash new file mode 100755 index 0000000..b41f76e --- /dev/null +++ b/src/env_parallel.ash @@ -0,0 +1,636 @@ +#!/usr/bin/env ash + +# This file must be sourced in sh/ash/dash/bash/ksh/mksh/zsh: +# +# . env_parallel.sh +# source env_parallel.ash +# source env_parallel.dash +# source env_parallel.bash +# source env_parallel.ksh +# source env_parallel.mksh +# source env_parallel.zsh +# +# after which 'env_parallel' works +# +# +# Copyright (C) 2016-2024 Ole Tange, http://ole.tange.dk and Free +# Software Foundation, Inc. +# +# This program 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. +# +# This program 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 this program; if not, see <http://www.gnu.org/licenses/> +# or write to the Free Software Foundation, Inc., 51 Franklin St, +# Fifth Floor, Boston, MA 02110-1301 USA +# +# SPDX-FileCopyrightText: 2021-2024 Ole Tange, http://ole.tange.dk and Free Software and Foundation, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later +# shellcheck disable=SC2006 + +env_parallel() { + # env_parallel.{sh,ash,dash,bash,ksh,mksh,zsh} + + # Check shell dialect + if [ -n "$BASH_VERSION" ]; then + _shell_DIALECT=bash + _eval_needed=false + _prefix_PARALLEL_ENV=_prefix_PARALLEL_ENV_bash + elif [ -n "$ZSH_VERSION" ]; then + _shell_DIALECT=zsh + _eval_needed=true + _prefix_PARALLEL_ENV=false + elif [ -n "$KSH_VERSION" ]; then + _shell_DIALECT=ksh + _eval_needed=false + _prefix_PARALLEL_ENV=false + else + # Dash/ash - can these be detected better? + _shell_DIALECT="sh" + _eval_needed=false + _prefix_PARALLEL_ENV=false + fi + _names_of_ALIASES() { + _names_of_ALIASES_$_shell_DIALECT + } + _names_of_ALIASES_sh() { + # alias fails on Unixware 5 + for _i in `alias 2>/dev/null | perl -ne 's/^alias //;s/^(\S+)=.*/$1/ && print' 2>/dev/null`; do + # Check if this name really is an alias + # or just part of a multiline alias definition + if alias "$_i" >/dev/null 2>/dev/null; then + echo "$_i" + fi + done + } + _names_of_ALIASES_bash() { + # No aliases will return false. This error should be ignored. + # shellcheck disable=SC3044 + compgen -a || true + } + _names_of_ALIASES_ksh() { + alias | perl -pe 's/=.*//' + } + _names_of_ALIASES_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)aliases} + } + _bodies_of_ALIASES() { + _bodies_of_ALIASES_$_shell_DIALECT "$@" + } + _bodies_of_ALIASES_sh() { + # alias may return: + # myalias='definition' (GNU/Linux ash) + # alias myalias='definition' (FreeBSD ash) + # so remove 'alias ' from first line + for _i in "$@"; do + echo 'alias '"`alias "$_i" | perl -pe '1..1 and s/^alias //'`" + done + } + _bodies_of_ALIASES_bash() { + # shellcheck disable=SC3043 + local _i + for _i in "$@"; do + # shellcheck disable=SC2046 + if [ $(alias "$_i" | wc -l) = 1 ] ; then + true Alias is a single line. Good. + else + _warning_PAR "Alias '$_i' contains newline." + _warning_PAR "Make sure the command has at least one newline after '$_i'." + _warning_PAR "See BUGS in 'man env_parallel'." + fi + done + alias "$@" + } + _bodies_of_ALIASES_ksh() { + alias "$@" | perl -pe 's/^/alias /; + sub warning { print STDERR "env_parallel: Warning: @_\n"; } + if(/^alias (\S+)=\$.*\\n/) { + warning("Alias \"$1\" contains newline."); + warning("Make sure the command has at least one newline after \"$1\"."); + warning("See BUGS in \"man env_parallel\"."); + }' + + } + _bodies_of_ALIASES_zsh() { + # shellcheck disable=SC3043 + local _i + for _i in "$@"; do + echo 'alias '"$(alias "$_i")" + done + } + _names_of_FUNCTIONS() { + _names_of_FUNCTIONS_$_shell_DIALECT + } + _names_of_FUNCTIONS_bash() { + # shellcheck disable=SC3044 + compgen -A function + } + _names_of_maybe_FUNCTIONS() { + set | perl -ne '/^([A-Z_0-9]+)\s*\(\)\s*\{?$/i and print "$1\n"' + } + _names_of_FUNCTIONS_sh() { + # myfunc is a function + # shellcheck disable=SC2046 + LANG=C type `_names_of_maybe_FUNCTIONS` | + perl -ne '/^(\S+) is a function$/ and not $seen{$1}++ and print "$1\n"' + } + _names_of_FUNCTIONS_ksh() { + # shellcheck disable=SC3044 + typeset +f | perl -pe 's/\(\).*//; s/ .*//;' + } + _names_of_FUNCTIONS_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)functions} + } + _bodies_of_FUNCTIONS() { + _bodies_of_FUNCTIONS_$_shell_DIALECT "$@" + } + _bodies_of_FUNCTIONS_sh() { + LANG=C type "$@" | perl -ne '/^(\S+) is a function$/ or print' + } + _bodies_of_FUNCTIONS_bash() { + # shellcheck disable=SC3044 + typeset -f "$@" + } + _bodies_of_FUNCTIONS_ksh() { + functions "$@" + } + _bodies_of_FUNCTIONS_zsh() { + # shellcheck disable=SC3044 + typeset -f "$@" + } + _names_of_VARIABLES() { + _names_of_VARIABLES_$_shell_DIALECT + } + _names_of_VARIABLES_sh() { + # This may screw up if variables contain \n and = + set | perl -ne 's/^(\S+?)=.*/$1/ and print;' + } + _names_of_VARIABLES_bash() { + # shellcheck disable=SC3044 + compgen -A variable + } + _names_of_VARIABLES_ksh() { + # shellcheck disable=SC3044 + typeset +p | + perl -pe 's/^(type)?set( [-+][a-zA-Z0-9]*)* //; s/(\[\d+\])?=.*//' | + uniq + } + _names_of_VARIABLES_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)parameters} + } + _bodies_of_VARIABLES() { + _bodies_of_VARIABLES_$_shell_DIALECT "$@" + } + _bodies_of_VARIABLES_sh() { + # Crappy typeset -p + for _i in "$@" + do + perl -e 'print @ARGV' "$_i=" + eval echo "\"\$$_i\"" | perl -e '$/=undef; $a=<>; chop($a); print $a' | + perl -pe 's/[\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\^\*\<\=\>\~\|\; \"\!\$\&\202-\377]/\\$&/go;'"s/'/\\\'/g; s/[\n]/'\\n'/go;"; + echo + done + } + _bodies_of_VARIABLES_bash() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _bodies_of_VARIABLES_ksh() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _bodies_of_VARIABLES_zsh() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _ignore_HARDCODED() { + _ignore_HARDCODED_$_shell_DIALECT + } + _ignore_HARDCODED_sh() { + # These names cannot be detected + echo '(_|TIMEOUT|IFS)' + } + _ignore_HARDCODED_bash() { + # Copying $RANDOM will cause it not to be random + # The rest cannot be detected as read-only + echo '(RANDOM|_|TIMEOUT|GROUPS|FUNCNAME|DIRSTACK|PIPESTATUS|USERNAME|BASHPID|BASH_[A-Z_]+)' + } + _ignore_HARDCODED_ksh() { + # These names cannot be detected + echo '(_|TIMEOUT|IFS)' + } + _ignore_HARDCODED_zsh() { + # These names cannot be detected + echo '([-\?\#\!\$\*\@\_0]|zsh_eval_context|ZSH_EVAL_CONTEXT|LINENO|IFS|commands|functions|options|aliases|EUID|EGID|UID|GID|dis_patchars|patchars|terminfo|galiases|keymaps|parameters|jobdirs|dirstack|functrace|funcsourcetrace|zsh_scheduled_events|dis_aliases|dis_reswords|dis_saliases|modules|reswords|saliases|widgets|userdirs|historywords|nameddirs|termcap|dis_builtins|dis_functions|jobtexts|funcfiletrace|dis_galiases|builtins|history|jobstates|funcstack|run-help)' + } + _ignore_READONLY() { + _ignore_READONLY_$_shell_DIALECT + } + _parse_READONLY() { + # shellcheck disable=SC1078,SC1079,SC2026 + perl -e '@r = map { + chomp; + # sh on UnixWare: readonly TIMEOUT + # ash: readonly var='val' + # ksh: var='val' + # mksh: PIPESTATUS[0] + s/^(readonly )?([^=\[ ]*?)(\[\d+\])?(=.*|)$/$2/ or + # bash: declare -ar BASH_VERSINFO=([0]="4" [1]="4") + # zsh: typeset -r var='val' + s/^\S+\s+\S+\s+(\S[^=]*)(=.*|$)/$1/; + $_ } <>; + $vars = join "|",map { quotemeta $_ } @r; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + ' + } + _ignore_READONLY_sh() { + readonly | _parse_READONLY + } + _ignore_READONLY_bash() { + readonly | _parse_READONLY + } + _ignore_READONLY_ksh() { + readonly | _parse_READONLY + } + _ignore_READONLY_zsh() { + # shellcheck disable=SC3044 + typeset -pr | _parse_READONLY + } + _remove_bad_NAMES() { + # Do not transfer vars and funcs from env_parallel + # shellcheck disable=SC2006 + _ignore_RO="`_ignore_READONLY`" + # shellcheck disable=SC2006 + _ignore_HARD="`_ignore_HARDCODED`" + # To avoid depending on grep dialect, use Perl version of: + # grep -Ev '^(...)$' | + perl -ne '/^( + PARALLEL_ENV| + PARALLEL_TMP| + _alias_NAMES| + _bodies_of_ALIASES| + _bodies_of_FUNCTIONS| + _bodies_of_VARIABLES| + _error_PAR| + _function_NAMES| + _get_ignored_VARS| + _grep_REGEXP| + _ignore_HARD| + _ignore_HARDCODED| + _ignore_READONLY| + _ignore_RO| + _ignore_UNDERSCORE| + _list_alias_BODIES| + _list_function_BODIES| + _list_variable_VALUES| + _make_grep_REGEXP| + _names_of_ALIASES| + _names_of_FUNCTIONS| + _names_of_VARIABLES| + _names_of_maybe_FUNCTIONS| + _parallel_exit_CODE| + _prefix_PARALLEL_ENV| + _prefix_PARALLEL_ENV_bash| + _remove_bad_NAMES| + _remove_readonly| + _variable_NAMES| + _warning_PAR| + _which_PAR)$/x and next; + # Filter names matching --env + /^'"$_grep_REGEXP"'$/ or next; + /^'"$_ignore_UNDERSCORE"'$/ and next; + # Remove readonly variables + /^'"$_ignore_RO"'$/ and next; + /^'"$_ignore_HARD"'$/ and next; + print;' + } + _prefix_PARALLEL_ENV_bash() { + # shellcheck disable=SC3044 + shopt 2>/dev/null | + perl -pe 's:\s+off:;: and s/^/shopt -u /; + s:\s+on:;: and s/^/shopt -s /; + s:;$:&>/dev/null;:'; + echo 'shopt -s expand_aliases &>/dev/null'; + } + + _get_ignored_VARS() { + perl -e ' + for(@ARGV){ + $next_is_env and push @envvar, split/,/, $_; + $next_is_env=/^--env$/; + } + if(grep { /^_$/ } @envvar) { + if(not open(IN, "<", "$ENV{HOME}/.parallel/ignored_vars")) { + print STDERR "parallel: Error: ", + "Run \"parallel --record-env\" in a clean environment first.\n"; + } else { + chomp(@ignored_vars = <IN>); + } + } + if($ENV{PARALLEL_IGNORED_NAMES}) { + push @ignored_vars, split/\s+/, $ENV{PARALLEL_IGNORED_NAMES}; + chomp @ignored_vars; + } + $vars = join "|",map { quotemeta $_ } @ignored_vars; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + ' -- "$@" + } + + # Get the --env variables if set + # --env _ should be ignored + # and convert a b c to (a|b|c) + # If --env not set: Match everything (.*) + _make_grep_REGEXP() { + perl -e ' + for(@ARGV){ + /^_$/ and $next_is_env = 0; + $next_is_env and push @envvar, split/,/, $_; + $next_is_env = /^--env$/; + } + $vars = join "|",map { quotemeta $_ } @envvar; + print $vars ? "($vars)" : "(.*)"; + ' -- "$@" + } + _which_PAR() { + # type returns: + # ll is an alias for ls -l (in ash) + # bash is a tracked alias for /bin/bash + # true is a shell builtin (in bash) + # myfunc is a function (in bash) + # myfunc is a shell function (in zsh) + # which is /usr/bin/which (in sh, bash) + # which is hashed (/usr/bin/which) + # gi is aliased to `grep -i' (in bash) + # aliased to `alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde' + # Return 0 if found, 1 otherwise + LANG=C type "$@" | + perl -pe '$exit += (s/ is an alias for .*// || + s/ is aliased to .*// || + s/ is a function// || + s/ is a shell function// || + s/ is a shell builtin// || + s/.* is hashed .(\S+).$/$1/ || + s/.* is (a tracked alias for )?//); + END { exit not $exit }' + } + _warning_PAR() { + echo "env_parallel: Warning: $*" >&2 + } + _error_PAR() { + echo "env_parallel: Error: $*" >&2 + } + + if _which_PAR parallel >/dev/null; then + true parallel found in path + else + # shellcheck disable=SC2016 + _error_PAR 'parallel must be in $PATH.' + return 255 + fi + + # Grep regexp for vars given by --env + # shellcheck disable=SC2006 + _grep_REGEXP="`_make_grep_REGEXP \"$@\"`" + unset _make_grep_REGEXP + + # Deal with --env _ + # shellcheck disable=SC2006 + _ignore_UNDERSCORE="`_get_ignored_VARS \"$@\"`" + unset _get_ignored_VARS + + # --record-env + if perl -e 'exit grep { /^--record-env$/ } @ARGV' -- "$@"; then + true skip + else + (_names_of_ALIASES; + _names_of_FUNCTIONS; + _names_of_VARIABLES) | + cat > "$HOME"/.parallel/ignored_vars + return 0 + fi + + # --session + if perl -e 'exit grep { /^--session$/ } @ARGV' -- "$@"; then + true skip + else + # Insert ::: between each level of session + # so you can pop off the last ::: at --end-session + # shellcheck disable=SC2006 + PARALLEL_IGNORED_NAMES="`echo \"$PARALLEL_IGNORED_NAMES\"; + echo :::; + (_names_of_ALIASES; + _names_of_FUNCTIONS; + _names_of_VARIABLES) | perl -ne ' + BEGIN{ + map { $ignored_vars{$_}++ } + split/\s+/, $ENV{PARALLEL_IGNORED_NAMES}; + } + chomp; + for(split/\s+/) { + if(not $ignored_vars{$_}) { + print $_,\"\\n\"; + } + } + '`" + export PARALLEL_IGNORED_NAMES + return 0 + fi + if perl -e 'exit grep { /^--end.?session$/ } @ARGV' -- "$@"; then + true skip + else + # Pop off last ::: from PARALLEL_IGNORED_NAMES + # shellcheck disable=SC2006 + PARALLEL_IGNORED_NAMES="`perl -e ' + $ENV{PARALLEL_IGNORED_NAMES} =~ s/(.*):::.*?$/$1/s; + print $ENV{PARALLEL_IGNORED_NAMES} + '`" + return 0 + fi + # Grep alias names + # shellcheck disable=SC2006 + _alias_NAMES="`_names_of_ALIASES | _remove_bad_NAMES | xargs echo`" + _list_alias_BODIES="_bodies_of_ALIASES $_alias_NAMES" + if [ "$_alias_NAMES" = "" ] ; then + # no aliases selected + _list_alias_BODIES="true" + fi + unset _alias_NAMES + + # Grep function names + # shellcheck disable=SC2006 + _function_NAMES="`_names_of_FUNCTIONS | _remove_bad_NAMES | xargs echo`" + _list_function_BODIES="_bodies_of_FUNCTIONS $_function_NAMES" + if [ "$_function_NAMES" = "" ] ; then + # no functions selected + _list_function_BODIES="true" + fi + unset _function_NAMES + + # Grep variable names + # shellcheck disable=SC2006 + _variable_NAMES="`_names_of_VARIABLES | _remove_bad_NAMES | xargs echo`" + _list_variable_VALUES="_bodies_of_VARIABLES $_variable_NAMES" + if [ "$_variable_NAMES" = "" ] ; then + # no variables selected + _list_variable_VALUES="true" + fi + unset _variable_NAMES + + if $_eval_needed ; then + # shellcheck disable=SC2006 + PARALLEL_ENV="` + eval $_prefix_PARALLEL_ENV; + eval $_list_alias_BODIES; + eval $_list_function_BODIES; + eval $_list_variable_VALUES; + `" + else + # shellcheck disable=SC2006 + PARALLEL_ENV="` + $_prefix_PARALLEL_ENV; + $_list_alias_BODIES; + $_list_function_BODIES; + $_list_variable_VALUES; + `" + fi + export PARALLEL_ENV + # Free up some env space + unset _list_alias_BODIES _list_variable_VALUES _list_function_BODIES + unset _bodies_of_ALIASES _bodies_of_VARIABLES _bodies_of_FUNCTIONS + unset _names_of_ALIASES _names_of_VARIABLES _names_of_FUNCTIONS + unset _ignore_HARDCODED _ignore_READONLY _ignore_UNDERSCORE + unset _remove_bad_NAMES _grep_REGEXP _parse_READONLY + unset _prefix_PARALLEL_ENV + unset _ignore_READONLY_sh _ignore_READONLY_bash + unset _ignore_READONLY_ksh _ignore_READONLY_zsh + unset _ignore_HARDCODED_sh _ignore_HARDCODED_bash + unset _ignore_HARDCODED_ksh _ignore_HARDCODED_zsh + unset _bodies_of_ALIASES_ksh _bodies_of_ALIASES_sh + unset _bodies_of_ALIASES_zsh _bodies_of_FUNCTIONS_bash + unset _bodies_of_FUNCTIONS_ksh _bodies_of_FUNCTIONS_sh + unset _bodies_of_FUNCTIONS_zsh _bodies_of_VARIABLES_bash + unset _bodies_of_VARIABLES_ksh _bodies_of_VARIABLES_sh + unset _bodies_of_VARIABLES_zsh + unset _names_of_ALIASES _names_of_ALIASES_bash + unset _names_of_ALIASES_ksh _names_of_ALIASES_sh + unset _names_of_ALIASES_zsh _names_of_FUNCTIONS + unset _names_of_FUNCTIONS_bash _names_of_FUNCTIONS_ksh + unset _names_of_FUNCTIONS_sh _names_of_FUNCTIONS_zsh + unset _names_of_VARIABLES _names_of_VARIABLES_bash + unset _names_of_VARIABLES_ksh _names_of_VARIABLES_sh + unset _names_of_VARIABLES_zsh _names_of_maybe_FUNCTIONS + + # Test if environment is too big by running 'true' + # shellcheck disable=SC2006,SC2092 + if `_which_PAR true` >/dev/null 2>/dev/null ; then + parallel "$@" + _parallel_exit_CODE=$? + # Clean up variables/functions + unset PARALLEL_ENV + unset _which_PAR _which_TRUE + unset _warning_PAR _error_PAR + # Unset _parallel_exit_CODE before return + eval "unset _parallel_exit_CODE; return $_parallel_exit_CODE" + else + unset PARALLEL_ENV; + _error_PAR "Your environment is too big." + _error_PAR "You can try 3 different approaches:" + _error_PAR "1. Run 'env_parallel --session' before you set" + _error_PAR " variables or define functions." + _error_PAR "2. Use --env and only mention the names to copy." + _error_PAR "3. Try running this in a clean environment once:" + _error_PAR " env_parallel --record-env" + _error_PAR " And then use '--env _'" + _error_PAR "For details see: man env_parallel" + return 255 + fi +} + +parset() { + _parset_PARALLEL_PRG=parallel + _parset_main "$@" +} + +env_parset() { + _parset_PARALLEL_PRG=env_parallel + _parset_main "$@" +} + +_parset_main() { + # If $1 contains ',' or space: + # Split on , to get the destination variable names + # If $1 is a single destination variable name: + # Treat it as the name of an array + # + # # Create array named myvar + # parset myvar echo ::: {1..10} + # echo ${myvar[5]} + # + # # Put output into $var_a $var_b $var_c + # varnames=(var_a var_b var_c) + # parset "${varnames[*]}" echo ::: {1..3} + # echo $var_c + # + # # Put output into $var_a4 $var_b4 $var_c4 + # parset "var_a4 var_b4 var_c4" echo ::: {1..3} + # echo $var_c4 + + _parset_NAME="$1" + if [ "$_parset_NAME" = "" ] ; then + echo parset: Error: No destination variable given. >&2 + echo parset: Error: Try: >&2 + echo parset: Error: ' ' parset myarray echo ::: foo bar >&2 + return 255 + fi + if [ "$_parset_NAME" = "--help" ] ; then + echo parset: Error: Usage: >&2 + echo parset: Error: ' ' parset varname GNU Parallel options and command >&2 + echo + parallel --help + return 255 + fi + if [ "$_parset_NAME" = "--version" ] ; then + # shellcheck disable=SC2006 + echo "parset 20240222 (GNU parallel `parallel --minversion 1`)" + echo "Copyright (C) 2007-2024 Ole Tange, http://ole.tange.dk and Free Software" + echo "Foundation, Inc." + echo "License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>" + echo "This is free software: you are free to change and redistribute it." + echo "GNU parallel comes with no warranty." + echo + echo "Web site: https://www.gnu.org/software/parallel" + echo + echo "When using programs that use GNU Parallel to process data for publication" + echo "please cite as described in 'parallel --citation'." + echo + return 255 + fi + shift + + # Bash: declare -A myassoc=( ) + # Zsh: typeset -A myassoc=( ) + # Ksh: typeset -A myassoc=( ) + # shellcheck disable=SC2039,SC2169,SC3044 + if (typeset -p "$_parset_NAME" 2>/dev/null; echo) | + perl -ne 'exit not (/^declare[^=]+-A|^typeset[^=]+-A/)' ; then + # This is an associative array + # shellcheck disable=SC2006 + eval "`$_parset_PARALLEL_PRG -k --_parset assoc,"$_parset_NAME" "$@"`" + # The eval returns the function! + else + # This is a normal array or a list of variable names + # shellcheck disable=SC2006 + eval "`$_parset_PARALLEL_PRG -k --_parset var,"$_parset_NAME" "$@"`" + # The eval returns the function! + fi +} diff --git a/src/env_parallel.bash b/src/env_parallel.bash new file mode 100755 index 0000000..6869d79 --- /dev/null +++ b/src/env_parallel.bash @@ -0,0 +1,640 @@ +#!/usr/bin/env bash + +# This file must be sourced in sh/ash/dash/bash/ksh/mksh/zsh: +# +# . env_parallel.sh +# source env_parallel.ash +# source env_parallel.dash +# source env_parallel.bash +# source env_parallel.ksh +# source env_parallel.mksh +# source env_parallel.zsh +# +# after which 'env_parallel' works +# +# +# Copyright (C) 2016-2024 Ole Tange, http://ole.tange.dk and Free +# Software Foundation, Inc. +# +# This program 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. +# +# This program 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 this program; if not, see <http://www.gnu.org/licenses/> +# or write to the Free Software Foundation, Inc., 51 Franklin St, +# Fifth Floor, Boston, MA 02110-1301 USA +# +# SPDX-FileCopyrightText: 2021-2024 Ole Tange, http://ole.tange.dk and Free Software and Foundation, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later +# shellcheck disable=SC2006 + +env_parallel() { + # env_parallel.{sh,ash,dash,bash,ksh,mksh,zsh} + + # Check shell dialect + if [ -n "$BASH_VERSION" ]; then + _shell_DIALECT=bash + _eval_needed=false + _prefix_PARALLEL_ENV=_prefix_PARALLEL_ENV_bash + elif [ -n "$ZSH_VERSION" ]; then + _shell_DIALECT=zsh + _eval_needed=true + _prefix_PARALLEL_ENV=false + elif [ -n "$KSH_VERSION" ]; then + _shell_DIALECT=ksh + _eval_needed=false + _prefix_PARALLEL_ENV=false + else + # Dash/ash - can these be detected better? + _shell_DIALECT="sh" + _eval_needed=false + _prefix_PARALLEL_ENV=false + fi + _names_of_ALIASES() { + _names_of_ALIASES_$_shell_DIALECT + } + _names_of_ALIASES_sh() { + # alias fails on Unixware 5 + for _i in `alias 2>/dev/null | perl -ne 's/^alias //;s/^(\S+)=.*/$1/ && print' 2>/dev/null`; do + # Check if this name really is an alias + # or just part of a multiline alias definition + if alias "$_i" >/dev/null 2>/dev/null; then + echo "$_i" + fi + done + } + _names_of_ALIASES_bash() { + # No aliases will return false. This error should be ignored. + # shellcheck disable=SC3044 + compgen -a || true + } + _names_of_ALIASES_ksh() { + alias | perl -pe 's/=.*//' + } + _names_of_ALIASES_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)aliases} + } + _bodies_of_ALIASES() { + _bodies_of_ALIASES_$_shell_DIALECT "$@" + } + _bodies_of_ALIASES_sh() { + # alias may return: + # myalias='definition' (GNU/Linux ash) + # alias myalias='definition' (FreeBSD ash) + # so remove 'alias ' from first line + for _i in "$@"; do + echo 'alias '"`alias "$_i" | perl -pe '1..1 and s/^alias //'`" + done + } + _bodies_of_ALIASES_bash() { + # shellcheck disable=SC3043 + local _i + for _i in "$@"; do + # shellcheck disable=SC2046 + if [ $(alias "$_i" | wc -l) = 1 ] ; then + true Alias is a single line. Good. + else + _warning_PAR "Alias '$_i' contains newline." + _warning_PAR "Make sure the command has at least one newline after '$_i'." + _warning_PAR "See BUGS in 'man env_parallel'." + fi + done + alias "$@" + } + _bodies_of_ALIASES_ksh() { + alias "$@" | perl -pe 's/^/alias /; + sub warning { print STDERR "env_parallel: Warning: @_\n"; } + if(/^alias (\S+)=\$.*\\n/) { + warning("Alias \"$1\" contains newline."); + warning("Make sure the command has at least one newline after \"$1\"."); + warning("See BUGS in \"man env_parallel\"."); + }' + + } + _bodies_of_ALIASES_zsh() { + # shellcheck disable=SC3043 + local _i + for _i in "$@"; do + echo 'alias '"$(alias "$_i")" + done + } + _names_of_FUNCTIONS() { + _names_of_FUNCTIONS_$_shell_DIALECT + } + _names_of_FUNCTIONS_bash() { + # shellcheck disable=SC3044 + compgen -A function + } + _names_of_maybe_FUNCTIONS() { + set | perl -ne '/^([A-Z_0-9]+)\s*\(\)\s*\{?$/i and print "$1\n"' + } + _names_of_FUNCTIONS_sh() { + # myfunc is a function + # shellcheck disable=SC2046 + LANG=C type `_names_of_maybe_FUNCTIONS` | + perl -ne '/^(\S+) is a function$/ and not $seen{$1}++ and print "$1\n"' + } + _names_of_FUNCTIONS_ksh() { + # shellcheck disable=SC3044 + typeset +f | perl -pe 's/\(\).*//; s/ .*//;' + } + _names_of_FUNCTIONS_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)functions} + } + _bodies_of_FUNCTIONS() { + _bodies_of_FUNCTIONS_$_shell_DIALECT "$@" + } + _bodies_of_FUNCTIONS_sh() { + LANG=C type "$@" | perl -ne '/^(\S+) is a function$/ or print' + } + _bodies_of_FUNCTIONS_bash() { + # shellcheck disable=SC3044 + typeset -f "$@" + } + _bodies_of_FUNCTIONS_ksh() { + functions "$@" + } + _bodies_of_FUNCTIONS_zsh() { + # shellcheck disable=SC3044 + typeset -f "$@" + } + _names_of_VARIABLES() { + _names_of_VARIABLES_$_shell_DIALECT + } + _names_of_VARIABLES_sh() { + # This may screw up if variables contain \n and = + set | perl -ne 's/^(\S+?)=.*/$1/ and print;' + } + _names_of_VARIABLES_bash() { + # shellcheck disable=SC3044 + compgen -A variable + } + _names_of_VARIABLES_ksh() { + # mksh: typeset +p | + # perl -pe 's/^(type)?set( [-+][a-zA-Z0-9]*)* //; s/(\[\d+\])?=.*//' | + # uniq + # ksh: typeset +p | perl -pe 's/^typeset .. //' + # shellcheck disable=SC3044 + typeset +p | + perl -pe 's/^(type)?set( [-+][a-zA-Z0-9]*)* //; s/(\[\d+\])?=.*//' | + uniq + } + _names_of_VARIABLES_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)parameters} + } + _bodies_of_VARIABLES() { + _bodies_of_VARIABLES_$_shell_DIALECT "$@" + } + _bodies_of_VARIABLES_sh() { + # Crappy typeset -p + for _i in "$@" + do + perl -e 'print @ARGV' "$_i=" + eval echo "\"\$$_i\"" | perl -e '$/=undef; $a=<>; chop($a); print $a' | + perl -pe 's/[\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\^\*\<\=\>\~\|\; \"\!\$\&\202-\377]/\\$&/go;'"s/'/\\\'/g; s/[\n]/'\\n'/go;"; + echo + done + } + _bodies_of_VARIABLES_bash() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _bodies_of_VARIABLES_ksh() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _bodies_of_VARIABLES_zsh() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _ignore_HARDCODED() { + _ignore_HARDCODED_$_shell_DIALECT + } + _ignore_HARDCODED_sh() { + # These names cannot be detected + echo '(_|TIMEOUT|IFS)' + } + _ignore_HARDCODED_bash() { + # Copying $RANDOM will cause it not to be random + # The rest cannot be detected as read-only + echo '(RANDOM|_|TIMEOUT|GROUPS|FUNCNAME|DIRSTACK|PIPESTATUS|USERNAME|BASHPID|BASH_[A-Z_]+)' + } + _ignore_HARDCODED_ksh() { + # These names cannot be detected + echo '(_|TIMEOUT|IFS)' + } + _ignore_HARDCODED_zsh() { + # These names cannot be detected + echo '([-\?\#\!\$\*\@\_0]|zsh_eval_context|ZSH_EVAL_CONTEXT|LINENO|IFS|commands|functions|options|aliases|EUID|EGID|UID|GID|dis_patchars|patchars|terminfo|galiases|keymaps|parameters|jobdirs|dirstack|functrace|funcsourcetrace|zsh_scheduled_events|dis_aliases|dis_reswords|dis_saliases|modules|reswords|saliases|widgets|userdirs|historywords|nameddirs|termcap|dis_builtins|dis_functions|jobtexts|funcfiletrace|dis_galiases|builtins|history|jobstates|funcstack|run-help)' + } + _ignore_READONLY() { + _ignore_READONLY_$_shell_DIALECT + } + _parse_READONLY() { + # shellcheck disable=SC1078,SC1079,SC2026 + perl -e '@r = map { + chomp; + # sh on UnixWare: readonly TIMEOUT + # ash: readonly var='val' + # ksh: var='val' + # mksh: PIPESTATUS[0] + s/^(readonly )?([^=\[ ]*?)(\[\d+\])?(=.*|)$/$2/ or + # bash: declare -ar BASH_VERSINFO=([0]="4" [1]="4") + # zsh: typeset -r var='val' + s/^\S+\s+\S+\s+(\S[^=]*)(=.*|$)/$1/; + $_ } <>; + $vars = join "|",map { quotemeta $_ } @r; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + ' + } + _ignore_READONLY_sh() { + readonly | _parse_READONLY + } + _ignore_READONLY_bash() { + readonly | _parse_READONLY + } + _ignore_READONLY_ksh() { + readonly | _parse_READONLY + } + _ignore_READONLY_zsh() { + # shellcheck disable=SC3044 + typeset -pr | _parse_READONLY + } + _remove_bad_NAMES() { + # Do not transfer vars and funcs from env_parallel + # shellcheck disable=SC2006 + _ignore_RO="`_ignore_READONLY`" + # shellcheck disable=SC2006 + _ignore_HARD="`_ignore_HARDCODED`" + # To avoid depending on grep dialect, use Perl version of: + # grep -Ev '^(...)$' | + perl -ne '/^( + PARALLEL_ENV| + PARALLEL_TMP| + _alias_NAMES| + _bodies_of_ALIASES| + _bodies_of_FUNCTIONS| + _bodies_of_VARIABLES| + _error_PAR| + _function_NAMES| + _get_ignored_VARS| + _grep_REGEXP| + _ignore_HARD| + _ignore_HARDCODED| + _ignore_READONLY| + _ignore_RO| + _ignore_UNDERSCORE| + _list_alias_BODIES| + _list_function_BODIES| + _list_variable_VALUES| + _make_grep_REGEXP| + _names_of_ALIASES| + _names_of_FUNCTIONS| + _names_of_VARIABLES| + _names_of_maybe_FUNCTIONS| + _parallel_exit_CODE| + _prefix_PARALLEL_ENV| + _prefix_PARALLEL_ENV_bash| + _remove_bad_NAMES| + _remove_readonly| + _variable_NAMES| + _warning_PAR| + _which_PAR)$/x and next; + # Filter names matching --env + /^'"$_grep_REGEXP"'$/ or next; + /^'"$_ignore_UNDERSCORE"'$/ and next; + # Remove readonly variables + /^'"$_ignore_RO"'$/ and next; + /^'"$_ignore_HARD"'$/ and next; + print;' + } + _prefix_PARALLEL_ENV_bash() { + # shellcheck disable=SC3044 + shopt 2>/dev/null | + perl -pe 's:\s+off:;: and s/^/shopt -u /; + s:\s+on:;: and s/^/shopt -s /; + s:;$:&>/dev/null;:'; + echo 'shopt -s expand_aliases &>/dev/null'; + } + + _get_ignored_VARS() { + perl -e ' + for(@ARGV){ + $next_is_env and push @envvar, split/,/, $_; + $next_is_env=/^--env$/; + } + if(grep { /^_$/ } @envvar) { + if(not open(IN, "<", "$ENV{HOME}/.parallel/ignored_vars")) { + print STDERR "parallel: Error: ", + "Run \"parallel --record-env\" in a clean environment first.\n"; + } else { + chomp(@ignored_vars = <IN>); + } + } + if($ENV{PARALLEL_IGNORED_NAMES}) { + push @ignored_vars, split/\s+/, $ENV{PARALLEL_IGNORED_NAMES}; + chomp @ignored_vars; + } + $vars = join "|",map { quotemeta $_ } @ignored_vars; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + ' -- "$@" + } + + # Get the --env variables if set + # --env _ should be ignored + # and convert a b c to (a|b|c) + # If --env not set: Match everything (.*) + _make_grep_REGEXP() { + perl -e ' + for(@ARGV){ + /^_$/ and $next_is_env = 0; + $next_is_env and push @envvar, split/,/, $_; + $next_is_env = /^--env$/; + } + $vars = join "|",map { quotemeta $_ } @envvar; + print $vars ? "($vars)" : "(.*)"; + ' -- "$@" + } + _which_PAR() { + # type returns: + # ll is an alias for ls -l (in ash) + # bash is a tracked alias for /bin/bash + # true is a shell builtin (in bash) + # myfunc is a function (in bash) + # myfunc is a shell function (in zsh) + # which is /usr/bin/which (in sh, bash) + # which is hashed (/usr/bin/which) + # gi is aliased to `grep -i' (in bash) + # aliased to `alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde' + # Return 0 if found, 1 otherwise + LANG=C type "$@" | + perl -pe '$exit += (s/ is an alias for .*// || + s/ is aliased to .*// || + s/ is a function// || + s/ is a shell function// || + s/ is a shell builtin// || + s/.* is hashed .(\S+).$/$1/ || + s/.* is (a tracked alias for )?//); + END { exit not $exit }' + } + _warning_PAR() { + echo "env_parallel: Warning: $*" >&2 + } + _error_PAR() { + echo "env_parallel: Error: $*" >&2 + } + + if _which_PAR parallel >/dev/null; then + true parallel found in path + else + # shellcheck disable=SC2016 + _error_PAR 'parallel must be in $PATH.' + return 255 + fi + + # Grep regexp for vars given by --env + # shellcheck disable=SC2006 + _grep_REGEXP="`_make_grep_REGEXP \"$@\"`" + unset _make_grep_REGEXP + + # Deal with --env _ + # shellcheck disable=SC2006 + _ignore_UNDERSCORE="`_get_ignored_VARS \"$@\"`" + unset _get_ignored_VARS + + # --record-env + if perl -e 'exit grep { /^--record-env$/ } @ARGV' -- "$@"; then + true skip + else + (_names_of_ALIASES; + _names_of_FUNCTIONS; + _names_of_VARIABLES) | + cat > "$HOME"/.parallel/ignored_vars + return 0 + fi + + # --session + if perl -e 'exit grep { /^--session$/ } @ARGV' -- "$@"; then + true skip + else + # Insert ::: between each level of session + # so you can pop off the last ::: at --end-session + # shellcheck disable=SC2006 + PARALLEL_IGNORED_NAMES="`echo \"$PARALLEL_IGNORED_NAMES\"; + echo :::; + (_names_of_ALIASES; + _names_of_FUNCTIONS; + _names_of_VARIABLES) | perl -ne ' + BEGIN{ + map { $ignored_vars{$_}++ } + split/\s+/, $ENV{PARALLEL_IGNORED_NAMES}; + } + chomp; + for(split/\s+/) { + if(not $ignored_vars{$_}) { + print $_,\"\\n\"; + } + } + '`" + export PARALLEL_IGNORED_NAMES + return 0 + fi + if perl -e 'exit grep { /^--end.?session$/ } @ARGV' -- "$@"; then + true skip + else + # Pop off last ::: from PARALLEL_IGNORED_NAMES + # shellcheck disable=SC2006 + PARALLEL_IGNORED_NAMES="`perl -e ' + $ENV{PARALLEL_IGNORED_NAMES} =~ s/(.*):::.*?$/$1/s; + print $ENV{PARALLEL_IGNORED_NAMES} + '`" + return 0 + fi + # Grep alias names + # shellcheck disable=SC2006 + _alias_NAMES="`_names_of_ALIASES | _remove_bad_NAMES | xargs echo`" + _list_alias_BODIES="_bodies_of_ALIASES $_alias_NAMES" + if [ "$_alias_NAMES" = "" ] ; then + # no aliases selected + _list_alias_BODIES="true" + fi + unset _alias_NAMES + + # Grep function names + # shellcheck disable=SC2006 + _function_NAMES="`_names_of_FUNCTIONS | _remove_bad_NAMES | xargs echo`" + _list_function_BODIES="_bodies_of_FUNCTIONS $_function_NAMES" + if [ "$_function_NAMES" = "" ] ; then + # no functions selected + _list_function_BODIES="true" + fi + unset _function_NAMES + + # Grep variable names + # shellcheck disable=SC2006 + _variable_NAMES="`_names_of_VARIABLES | _remove_bad_NAMES | xargs echo`" + _list_variable_VALUES="_bodies_of_VARIABLES $_variable_NAMES" + if [ "$_variable_NAMES" = "" ] ; then + # no variables selected + _list_variable_VALUES="true" + fi + unset _variable_NAMES + + if $_eval_needed ; then + # shellcheck disable=SC2006 + PARALLEL_ENV="` + eval $_prefix_PARALLEL_ENV; + eval $_list_alias_BODIES; + eval $_list_function_BODIES; + eval $_list_variable_VALUES; + `" + else + # shellcheck disable=SC2006 + PARALLEL_ENV="` + $_prefix_PARALLEL_ENV; + $_list_alias_BODIES; + $_list_function_BODIES; + $_list_variable_VALUES; + `" + fi + export PARALLEL_ENV + # Free up some env space + unset _list_alias_BODIES _list_variable_VALUES _list_function_BODIES + unset _bodies_of_ALIASES _bodies_of_VARIABLES _bodies_of_FUNCTIONS + unset _names_of_ALIASES _names_of_VARIABLES _names_of_FUNCTIONS + unset _ignore_HARDCODED _ignore_READONLY _ignore_UNDERSCORE + unset _remove_bad_NAMES _grep_REGEXP _parse_READONLY + unset _prefix_PARALLEL_ENV + unset _ignore_READONLY_sh _ignore_READONLY_bash + unset _ignore_READONLY_ksh _ignore_READONLY_zsh + unset _ignore_HARDCODED_sh _ignore_HARDCODED_bash + unset _ignore_HARDCODED_ksh _ignore_HARDCODED_zsh + unset _bodies_of_ALIASES_ksh _bodies_of_ALIASES_sh + unset _bodies_of_ALIASES_zsh _bodies_of_FUNCTIONS_bash + unset _bodies_of_FUNCTIONS_ksh _bodies_of_FUNCTIONS_sh + unset _bodies_of_FUNCTIONS_zsh _bodies_of_VARIABLES_bash + unset _bodies_of_VARIABLES_ksh _bodies_of_VARIABLES_sh + unset _bodies_of_VARIABLES_zsh + unset _names_of_ALIASES _names_of_ALIASES_bash + unset _names_of_ALIASES_ksh _names_of_ALIASES_sh + unset _names_of_ALIASES_zsh _names_of_FUNCTIONS + unset _names_of_FUNCTIONS_bash _names_of_FUNCTIONS_ksh + unset _names_of_FUNCTIONS_sh _names_of_FUNCTIONS_zsh + unset _names_of_VARIABLES _names_of_VARIABLES_bash + unset _names_of_VARIABLES_ksh _names_of_VARIABLES_sh + unset _names_of_VARIABLES_zsh _names_of_maybe_FUNCTIONS + + # Test if environment is too big by running 'true' + # shellcheck disable=SC2006,SC2092 + if `_which_PAR true` >/dev/null 2>/dev/null ; then + parallel "$@" + _parallel_exit_CODE=$? + # Clean up variables/functions + unset PARALLEL_ENV + unset _which_PAR _which_TRUE + unset _warning_PAR _error_PAR + # Unset _parallel_exit_CODE before return + eval "unset _parallel_exit_CODE; return $_parallel_exit_CODE" + else + unset PARALLEL_ENV; + _error_PAR "Your environment is too big." + _error_PAR "You can try 3 different approaches:" + _error_PAR "1. Run 'env_parallel --session' before you set" + _error_PAR " variables or define functions." + _error_PAR "2. Use --env and only mention the names to copy." + _error_PAR "3. Try running this in a clean environment once:" + _error_PAR " env_parallel --record-env" + _error_PAR " And then use '--env _'" + _error_PAR "For details see: man env_parallel" + return 255 + fi +} + +parset() { + _parset_PARALLEL_PRG=parallel + _parset_main "$@" +} + +env_parset() { + _parset_PARALLEL_PRG=env_parallel + _parset_main "$@" +} + +_parset_main() { + # If $1 contains ',' or space: + # Split on , to get the destination variable names + # If $1 is a single destination variable name: + # Treat it as the name of an array + # + # # Create array named myvar + # parset myvar echo ::: {1..10} + # echo ${myvar[5]} + # + # # Put output into $var_a $var_b $var_c + # varnames=(var_a var_b var_c) + # parset "${varnames[*]}" echo ::: {1..3} + # echo $var_c + # + # # Put output into $var_a4 $var_b4 $var_c4 + # parset "var_a4 var_b4 var_c4" echo ::: {1..3} + # echo $var_c4 + + _parset_NAME="$1" + if [ "$_parset_NAME" = "" ] ; then + echo parset: Error: No destination variable given. >&2 + echo parset: Error: Try: >&2 + echo parset: Error: ' ' parset myarray echo ::: foo bar >&2 + return 255 + fi + if [ "$_parset_NAME" = "--help" ] ; then + echo parset: Error: Usage: >&2 + echo parset: Error: ' ' parset varname GNU Parallel options and command >&2 + echo + parallel --help + return 255 + fi + if [ "$_parset_NAME" = "--version" ] ; then + # shellcheck disable=SC2006 + echo "parset 20240222 (GNU parallel `parallel --minversion 1`)" + echo "Copyright (C) 2007-2024 Ole Tange, http://ole.tange.dk and Free Software" + echo "Foundation, Inc." + echo "License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>" + echo "This is free software: you are free to change and redistribute it." + echo "GNU parallel comes with no warranty." + echo + echo "Web site: https://www.gnu.org/software/parallel" + echo + echo "When using programs that use GNU Parallel to process data for publication" + echo "please cite as described in 'parallel --citation'." + echo + return 255 + fi + shift + + # Bash: declare -A myassoc=( ) + # Zsh: typeset -A myassoc=( ) + # Ksh: typeset -A myassoc=( ) + # shellcheck disable=SC2039,SC2169,SC3044 + if (typeset -p "$_parset_NAME" 2>/dev/null; echo) | + perl -ne 'exit not (/^declare[^=]+-A|^typeset[^=]+-A/)' ; then + # This is an associative array + # shellcheck disable=SC2006 + eval "`$_parset_PARALLEL_PRG -k --_parset assoc,"$_parset_NAME" "$@"`" + # The eval returns the function! + else + # This is a normal array or a list of variable names + # shellcheck disable=SC2006 + eval "`$_parset_PARALLEL_PRG -k --_parset var,"$_parset_NAME" "$@"`" + # The eval returns the function! + fi +} diff --git a/src/env_parallel.csh b/src/env_parallel.csh new file mode 100755 index 0000000..3f00e3a --- /dev/null +++ b/src/env_parallel.csh @@ -0,0 +1,142 @@ +#!/usr/bin/env csh + +# This file must be sourced in csh: +# +# source `which env_parallel.csh` +# +# after which 'env_parallel' works +# +# +# Copyright (C) 2016-2024 Ole Tange, http://ole.tange.dk and Free +# Software Foundation, Inc. +# +# This program 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. +# +# This program 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 this program; if not, see <http://www.gnu.org/licenses/> +# or write to the Free Software Foundation, Inc., 51 Franklin St, +# Fifth Floor, Boston, MA 02110-1301 USA +# +# SPDX-FileCopyrightText: 2021-2024 Ole Tange, http://ole.tange.dk and Free Software and Foundation, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later + +set _parallel_exit_CODE=0 +if ("`alias env_parallel`" == '' || ! $?PARALLEL_CSH) then + # Activate alias + alias env_parallel '(setenv PARALLEL_CSH "\!*"; source `which env_parallel.csh`)' +else + # Get the --env variables if set + # --env _ should be ignored + # and convert a b c to (a|b|c) + # If --env not set: Match everything (.*) + + # simple 'tempfile': Return nonexisting filename: /tmp/parXXXXX + alias _tempfile 'perl -e do\{\$t\=\"/tmp/par\".join\"\",map\{\(0..9,\"a\"..\"z\",\"A\"..\"Z\"\)\[rand\(62\)\]\}\(1..5\)\;\}while\(-e\$t\)\;print\"\$t\\n\"' + set _tMpscRIpt=`_tempfile` + + cat <<'EOF' > $_tMpscRIpt + #!/usr/bin/perl + + for(@ARGV){ + /^_$/ and $next_is_env = 0; + $next_is_env and push @envvar, split/,/, $_; + $next_is_env = /^--env$/; + } + $vars = join "|",map { quotemeta $_ } @envvar; + print $vars ? "($vars)" : "(.*)"; +'EOF' + set _grep_REGEXP="`perl $_tMpscRIpt -- $PARALLEL_CSH`" + + # Deal with --env _ + cat <<'EOF' > $_tMpscRIpt + #!/usr/bin/perl + + for(@ARGV){ + $next_is_env and push @envvar, split/,/, $_; + $next_is_env=/^--env$/; + } + if(grep { /^_$/ } @envvar) { + if(not open(IN, "<", "$ENV{HOME}/.parallel/ignored_vars")) { + print STDERR "parallel: Error: ", + "Run \"parallel --record-env\" in a clean environment first.\n"; + } else { + chomp(@ignored_vars = <IN>); + $vars = join "|",map { quotemeta $_ } @ignored_vars; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + } + } +'EOF' + set _ignore_UNDERSCORE="`perl $_tMpscRIpt -- $PARALLEL_CSH`" + rm $_tMpscRIpt + + # Get the scalar and array variable names + set _vARnAmES=(`set | perl -ne 's/\s.*//; /^(#|_|killring|prompt2|command|PARALLEL_ENV|PARALLEL_TMP)$/ and next; /^'"$_grep_REGEXP"'$/ or next; /^'"$_ignore_UNDERSCORE"'$/ and next; print'`) + + # Make a tmpfile for the variable definitions + set _tMpvARfILe=`_tempfile` + touch $_tMpvARfILe + # Make a tmpfile for the variable definitions + alias + set _tMpaLLfILe=`_tempfile` + foreach _vARnAmE ($_vARnAmES); + # These 3 lines break in csh ver. 20110502-3 + # if not defined: next + eval if'(! $?'$_vARnAmE') continue' + # if $#myvar <= 1 echo scalar_myvar=$var + eval if'(${#'$_vARnAmE'} <= 1) echo scalar_'$_vARnAmE'='\"\$$_vARnAmE\" >> $_tMpvARfILe; + # if $#myvar > 1 echo array_myvar=$var + eval if'(${#'$_vARnAmE'} > 1) echo array_'$_vARnAmE'="$'$_vARnAmE'"' >> $_tMpvARfILe; + end + unset _vARnAmE _vARnAmES + # shell quote variables (--plain needed due to ignore if $PARALLEL is set) + # Convert 'scalar_myvar=...' to 'set myvar=...' + # Convert 'array_myvar=...' to 'set array=(...)' + cat $_tMpvARfILe | parallel --plain --shellquote | perl -pe 's/^scalar_(\S+).=/set $1=/ or s/^array_(\S+).=(.*)/set $1=($2)/ && s/\\ / /g;' > $_tMpaLLfILe + # Cleanup + rm $_tMpvARfILe; unset _tMpvARfILe + +# ALIAS TO EXPORT ALIASES: + +# Quote ' by putting it inside " +# s/'/'"'"'/g; +# ' => \047 " => \042 +# s/\047/\047\042\047\042\047/g; +# Quoted: s/\\047/\\047\\042\\047\\042\\047/g\; + +# Remove () from second column +# s/^(\S+)(\s+)\((.*)\)/\1\2\3/; +# Quoted: s/\^\(\\S+\)\(\\s+\)\\\(\(.\*\)\\\)/\\1\\2\\3/\; + +# Add ' around second column +# s/^(\S+)(\s+)(.*)/\1\2'\3'/ +# \047 => ' +# s/^(\S+)(\s+)(.*)/\1\2\047\3\047/; +# Quoted: s/\^\(\\S+\)\(\\s+\)\(.\*\)/\\1\\2\\047\\3\\047/\; + +# Quote ! as \! +# s/\!/\\\!/g; +# Quoted: s/\\\!/\\\\\\\!/g; + +# Prepend with "\nalias " +# s/^/\001alias /; +# Quoted: s/\^/\\001alias\ /\; + alias | \ + perl -ne '/^'"$_grep_REGEXP"'/ or next; /^'"$_ignore_UNDERSCORE"'[^_a-zA-Z]/ and next; print' | \ + perl -pe s/\\047/\\047\\042\\047\\042\\047/g\;s/\^\(\\S+\)\(\\s+\)\\\(\(.\*\)\\\)/\\1\\2\\3/\;s/\^\(\\S+\)\(\\s+\)\(.\*\)/\\1\\2\\047\\3\\047/\;s/\^/\\001alias\ /\;s/\\\!/\\\\\\\!/g >> $_tMpaLLfILe + + setenv PARALLEL_ENV "`cat $_tMpaLLfILe; rm $_tMpaLLfILe`"; + unset _tMpaLLfILe; + # Use $PARALLEL_CSH set in calling alias + parallel + set _parallel_exit_CODE=$status + setenv PARALLEL_ENV + setenv PARALLEL_CSH +endif +(exit $_parallel_exit_CODE) diff --git a/src/env_parallel.dash b/src/env_parallel.dash new file mode 100755 index 0000000..0d09760 --- /dev/null +++ b/src/env_parallel.dash @@ -0,0 +1,636 @@ +#!/usr/bin/env dash + +# This file must be sourced in sh/ash/dash/bash/ksh/mksh/zsh: +# +# . env_parallel.sh +# source env_parallel.ash +# source env_parallel.dash +# source env_parallel.bash +# source env_parallel.ksh +# source env_parallel.mksh +# source env_parallel.zsh +# +# after which 'env_parallel' works +# +# +# Copyright (C) 2016-2024 Ole Tange, http://ole.tange.dk and Free +# Software Foundation, Inc. +# +# This program 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. +# +# This program 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 this program; if not, see <http://www.gnu.org/licenses/> +# or write to the Free Software Foundation, Inc., 51 Franklin St, +# Fifth Floor, Boston, MA 02110-1301 USA +# +# SPDX-FileCopyrightText: 2021-2024 Ole Tange, http://ole.tange.dk and Free Software and Foundation, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later +# shellcheck disable=SC2006 + +env_parallel() { + # env_parallel.{sh,ash,dash,bash,ksh,mksh,zsh} + + # Check shell dialect + if [ -n "$BASH_VERSION" ]; then + _shell_DIALECT=bash + _eval_needed=false + _prefix_PARALLEL_ENV=_prefix_PARALLEL_ENV_bash + elif [ -n "$ZSH_VERSION" ]; then + _shell_DIALECT=zsh + _eval_needed=true + _prefix_PARALLEL_ENV=false + elif [ -n "$KSH_VERSION" ]; then + _shell_DIALECT=ksh + _eval_needed=false + _prefix_PARALLEL_ENV=false + else + # Dash/ash - can these be detected better? + _shell_DIALECT="sh" + _eval_needed=false + _prefix_PARALLEL_ENV=false + fi + _names_of_ALIASES() { + _names_of_ALIASES_$_shell_DIALECT + } + _names_of_ALIASES_sh() { + # alias fails on Unixware 5 + for _i in `alias 2>/dev/null | perl -ne 's/^alias //;s/^(\S+)=.*/$1/ && print' 2>/dev/null`; do + # Check if this name really is an alias + # or just part of a multiline alias definition + if alias "$_i" >/dev/null 2>/dev/null; then + echo "$_i" + fi + done + } + _names_of_ALIASES_bash() { + # No aliases will return false. This error should be ignored. + # shellcheck disable=SC3044 + compgen -a || true + } + _names_of_ALIASES_ksh() { + alias | perl -pe 's/=.*//' + } + _names_of_ALIASES_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)aliases} + } + _bodies_of_ALIASES() { + _bodies_of_ALIASES_$_shell_DIALECT "$@" + } + _bodies_of_ALIASES_sh() { + # alias may return: + # myalias='definition' (GNU/Linux ash) + # alias myalias='definition' (FreeBSD ash) + # so remove 'alias ' from first line + for _i in "$@"; do + echo 'alias '"`alias "$_i" | perl -pe '1..1 and s/^alias //'`" + done + } + _bodies_of_ALIASES_bash() { + # shellcheck disable=SC3043 + local _i + for _i in "$@"; do + # shellcheck disable=SC2046 + if [ $(alias "$_i" | wc -l) = 1 ] ; then + true Alias is a single line. Good. + else + _warning_PAR "Alias '$_i' contains newline." + _warning_PAR "Make sure the command has at least one newline after '$_i'." + _warning_PAR "See BUGS in 'man env_parallel'." + fi + done + alias "$@" + } + _bodies_of_ALIASES_ksh() { + alias "$@" | perl -pe 's/^/alias /; + sub warning { print STDERR "env_parallel: Warning: @_\n"; } + if(/^alias (\S+)=\$.*\\n/) { + warning("Alias \"$1\" contains newline."); + warning("Make sure the command has at least one newline after \"$1\"."); + warning("See BUGS in \"man env_parallel\"."); + }' + + } + _bodies_of_ALIASES_zsh() { + # shellcheck disable=SC3043 + local _i + for _i in "$@"; do + echo 'alias '"$(alias "$_i")" + done + } + _names_of_FUNCTIONS() { + _names_of_FUNCTIONS_$_shell_DIALECT + } + _names_of_FUNCTIONS_bash() { + # shellcheck disable=SC3044 + compgen -A function + } + _names_of_maybe_FUNCTIONS() { + set | perl -ne '/^([A-Z_0-9]+)\s*\(\)\s*\{?$/i and print "$1\n"' + } + _names_of_FUNCTIONS_sh() { + # myfunc is a function + # shellcheck disable=SC2046 + LANG=C type `_names_of_maybe_FUNCTIONS` | + perl -ne '/^(\S+) is a function$/ and not $seen{$1}++ and print "$1\n"' + } + _names_of_FUNCTIONS_ksh() { + # shellcheck disable=SC3044 + typeset +f | perl -pe 's/\(\).*//; s/ .*//;' + } + _names_of_FUNCTIONS_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)functions} + } + _bodies_of_FUNCTIONS() { + _bodies_of_FUNCTIONS_$_shell_DIALECT "$@" + } + _bodies_of_FUNCTIONS_sh() { + LANG=C type "$@" | perl -ne '/^(\S+) is a function$/ or print' + } + _bodies_of_FUNCTIONS_bash() { + # shellcheck disable=SC3044 + typeset -f "$@" + } + _bodies_of_FUNCTIONS_ksh() { + functions "$@" + } + _bodies_of_FUNCTIONS_zsh() { + # shellcheck disable=SC3044 + typeset -f "$@" + } + _names_of_VARIABLES() { + _names_of_VARIABLES_$_shell_DIALECT + } + _names_of_VARIABLES_sh() { + # This may screw up if variables contain \n and = + set | perl -ne 's/^(\S+?)=.*/$1/ and print;' + } + _names_of_VARIABLES_bash() { + # shellcheck disable=SC3044 + compgen -A variable + } + _names_of_VARIABLES_ksh() { + # shellcheck disable=SC3044 + typeset +p | + perl -pe 's/^(type)?set( [-+][a-zA-Z0-9]*)* //; s/(\[\d+\])?=.*//' | + uniq + } + _names_of_VARIABLES_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)parameters} + } + _bodies_of_VARIABLES() { + _bodies_of_VARIABLES_$_shell_DIALECT "$@" + } + _bodies_of_VARIABLES_sh() { + # Crappy typeset -p + for _i in "$@" + do + perl -e 'print @ARGV' "$_i=" + eval echo "\"\$$_i\"" | perl -e '$/=undef; $a=<>; chop($a); print $a' | + perl -pe 's/[\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\^\*\<\=\>\~\|\; \"\!\$\&\202-\377]/\\$&/go;'"s/'/\\\'/g; s/[\n]/'\\n'/go;"; + echo + done + } + _bodies_of_VARIABLES_bash() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _bodies_of_VARIABLES_ksh() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _bodies_of_VARIABLES_zsh() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _ignore_HARDCODED() { + _ignore_HARDCODED_$_shell_DIALECT + } + _ignore_HARDCODED_sh() { + # These names cannot be detected + echo '(_|TIMEOUT|IFS)' + } + _ignore_HARDCODED_bash() { + # Copying $RANDOM will cause it not to be random + # The rest cannot be detected as read-only + echo '(RANDOM|_|TIMEOUT|GROUPS|FUNCNAME|DIRSTACK|PIPESTATUS|USERNAME|BASHPID|BASH_[A-Z_]+)' + } + _ignore_HARDCODED_ksh() { + # These names cannot be detected + echo '(_|TIMEOUT|IFS)' + } + _ignore_HARDCODED_zsh() { + # These names cannot be detected + echo '([-\?\#\!\$\*\@\_0]|zsh_eval_context|ZSH_EVAL_CONTEXT|LINENO|IFS|commands|functions|options|aliases|EUID|EGID|UID|GID|dis_patchars|patchars|terminfo|galiases|keymaps|parameters|jobdirs|dirstack|functrace|funcsourcetrace|zsh_scheduled_events|dis_aliases|dis_reswords|dis_saliases|modules|reswords|saliases|widgets|userdirs|historywords|nameddirs|termcap|dis_builtins|dis_functions|jobtexts|funcfiletrace|dis_galiases|builtins|history|jobstates|funcstack|run-help)' + } + _ignore_READONLY() { + _ignore_READONLY_$_shell_DIALECT + } + _parse_READONLY() { + # shellcheck disable=SC1078,SC1079,SC2026 + perl -e '@r = map { + chomp; + # sh on UnixWare: readonly TIMEOUT + # ash: readonly var='val' + # ksh: var='val' + # mksh: PIPESTATUS[0] + s/^(readonly )?([^=\[ ]*?)(\[\d+\])?(=.*|)$/$2/ or + # bash: declare -ar BASH_VERSINFO=([0]="4" [1]="4") + # zsh: typeset -r var='val' + s/^\S+\s+\S+\s+(\S[^=]*)(=.*|$)/$1/; + $_ } <>; + $vars = join "|",map { quotemeta $_ } @r; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + ' + } + _ignore_READONLY_sh() { + readonly | _parse_READONLY + } + _ignore_READONLY_bash() { + readonly | _parse_READONLY + } + _ignore_READONLY_ksh() { + readonly | _parse_READONLY + } + _ignore_READONLY_zsh() { + # shellcheck disable=SC3044 + typeset -pr | _parse_READONLY + } + _remove_bad_NAMES() { + # Do not transfer vars and funcs from env_parallel + # shellcheck disable=SC2006 + _ignore_RO="`_ignore_READONLY`" + # shellcheck disable=SC2006 + _ignore_HARD="`_ignore_HARDCODED`" + # To avoid depending on grep dialect, use Perl version of: + # grep -Ev '^(...)$' | + perl -ne '/^( + PARALLEL_ENV| + PARALLEL_TMP| + _alias_NAMES| + _bodies_of_ALIASES| + _bodies_of_FUNCTIONS| + _bodies_of_VARIABLES| + _error_PAR| + _function_NAMES| + _get_ignored_VARS| + _grep_REGEXP| + _ignore_HARD| + _ignore_HARDCODED| + _ignore_READONLY| + _ignore_RO| + _ignore_UNDERSCORE| + _list_alias_BODIES| + _list_function_BODIES| + _list_variable_VALUES| + _make_grep_REGEXP| + _names_of_ALIASES| + _names_of_FUNCTIONS| + _names_of_VARIABLES| + _names_of_maybe_FUNCTIONS| + _parallel_exit_CODE| + _prefix_PARALLEL_ENV| + _prefix_PARALLEL_ENV_bash| + _remove_bad_NAMES| + _remove_readonly| + _variable_NAMES| + _warning_PAR| + _which_PAR)$/x and next; + # Filter names matching --env + /^'"$_grep_REGEXP"'$/ or next; + /^'"$_ignore_UNDERSCORE"'$/ and next; + # Remove readonly variables + /^'"$_ignore_RO"'$/ and next; + /^'"$_ignore_HARD"'$/ and next; + print;' + } + _prefix_PARALLEL_ENV_bash() { + # shellcheck disable=SC3044 + shopt 2>/dev/null | + perl -pe 's:\s+off:;: and s/^/shopt -u /; + s:\s+on:;: and s/^/shopt -s /; + s:;$:&>/dev/null;:'; + echo 'shopt -s expand_aliases &>/dev/null'; + } + + _get_ignored_VARS() { + perl -e ' + for(@ARGV){ + $next_is_env and push @envvar, split/,/, $_; + $next_is_env=/^--env$/; + } + if(grep { /^_$/ } @envvar) { + if(not open(IN, "<", "$ENV{HOME}/.parallel/ignored_vars")) { + print STDERR "parallel: Error: ", + "Run \"parallel --record-env\" in a clean environment first.\n"; + } else { + chomp(@ignored_vars = <IN>); + } + } + if($ENV{PARALLEL_IGNORED_NAMES}) { + push @ignored_vars, split/\s+/, $ENV{PARALLEL_IGNORED_NAMES}; + chomp @ignored_vars; + } + $vars = join "|",map { quotemeta $_ } @ignored_vars; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + ' -- "$@" + } + + # Get the --env variables if set + # --env _ should be ignored + # and convert a b c to (a|b|c) + # If --env not set: Match everything (.*) + _make_grep_REGEXP() { + perl -e ' + for(@ARGV){ + /^_$/ and $next_is_env = 0; + $next_is_env and push @envvar, split/,/, $_; + $next_is_env = /^--env$/; + } + $vars = join "|",map { quotemeta $_ } @envvar; + print $vars ? "($vars)" : "(.*)"; + ' -- "$@" + } + _which_PAR() { + # type returns: + # ll is an alias for ls -l (in ash) + # bash is a tracked alias for /bin/bash + # true is a shell builtin (in bash) + # myfunc is a function (in bash) + # myfunc is a shell function (in zsh) + # which is /usr/bin/which (in sh, bash) + # which is hashed (/usr/bin/which) + # gi is aliased to `grep -i' (in bash) + # aliased to `alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde' + # Return 0 if found, 1 otherwise + LANG=C type "$@" | + perl -pe '$exit += (s/ is an alias for .*// || + s/ is aliased to .*// || + s/ is a function// || + s/ is a shell function// || + s/ is a shell builtin// || + s/.* is hashed .(\S+).$/$1/ || + s/.* is (a tracked alias for )?//); + END { exit not $exit }' + } + _warning_PAR() { + echo "env_parallel: Warning: $*" >&2 + } + _error_PAR() { + echo "env_parallel: Error: $*" >&2 + } + + if _which_PAR parallel >/dev/null; then + true parallel found in path + else + # shellcheck disable=SC2016 + _error_PAR 'parallel must be in $PATH.' + return 255 + fi + + # Grep regexp for vars given by --env + # shellcheck disable=SC2006 + _grep_REGEXP="`_make_grep_REGEXP \"$@\"`" + unset _make_grep_REGEXP + + # Deal with --env _ + # shellcheck disable=SC2006 + _ignore_UNDERSCORE="`_get_ignored_VARS \"$@\"`" + unset _get_ignored_VARS + + # --record-env + if perl -e 'exit grep { /^--record-env$/ } @ARGV' -- "$@"; then + true skip + else + (_names_of_ALIASES; + _names_of_FUNCTIONS; + _names_of_VARIABLES) | + cat > "$HOME"/.parallel/ignored_vars + return 0 + fi + + # --session + if perl -e 'exit grep { /^--session$/ } @ARGV' -- "$@"; then + true skip + else + # Insert ::: between each level of session + # so you can pop off the last ::: at --end-session + # shellcheck disable=SC2006 + PARALLEL_IGNORED_NAMES="`echo \"$PARALLEL_IGNORED_NAMES\"; + echo :::; + (_names_of_ALIASES; + _names_of_FUNCTIONS; + _names_of_VARIABLES) | perl -ne ' + BEGIN{ + map { $ignored_vars{$_}++ } + split/\s+/, $ENV{PARALLEL_IGNORED_NAMES}; + } + chomp; + for(split/\s+/) { + if(not $ignored_vars{$_}) { + print $_,\"\\n\"; + } + } + '`" + export PARALLEL_IGNORED_NAMES + return 0 + fi + if perl -e 'exit grep { /^--end.?session$/ } @ARGV' -- "$@"; then + true skip + else + # Pop off last ::: from PARALLEL_IGNORED_NAMES + # shellcheck disable=SC2006 + PARALLEL_IGNORED_NAMES="`perl -e ' + $ENV{PARALLEL_IGNORED_NAMES} =~ s/(.*):::.*?$/$1/s; + print $ENV{PARALLEL_IGNORED_NAMES} + '`" + return 0 + fi + # Grep alias names + # shellcheck disable=SC2006 + _alias_NAMES="`_names_of_ALIASES | _remove_bad_NAMES | xargs echo`" + _list_alias_BODIES="_bodies_of_ALIASES $_alias_NAMES" + if [ "$_alias_NAMES" = "" ] ; then + # no aliases selected + _list_alias_BODIES="true" + fi + unset _alias_NAMES + + # Grep function names + # shellcheck disable=SC2006 + _function_NAMES="`_names_of_FUNCTIONS | _remove_bad_NAMES | xargs echo`" + _list_function_BODIES="_bodies_of_FUNCTIONS $_function_NAMES" + if [ "$_function_NAMES" = "" ] ; then + # no functions selected + _list_function_BODIES="true" + fi + unset _function_NAMES + + # Grep variable names + # shellcheck disable=SC2006 + _variable_NAMES="`_names_of_VARIABLES | _remove_bad_NAMES | xargs echo`" + _list_variable_VALUES="_bodies_of_VARIABLES $_variable_NAMES" + if [ "$_variable_NAMES" = "" ] ; then + # no variables selected + _list_variable_VALUES="true" + fi + unset _variable_NAMES + + if $_eval_needed ; then + # shellcheck disable=SC2006 + PARALLEL_ENV="` + eval $_prefix_PARALLEL_ENV; + eval $_list_alias_BODIES; + eval $_list_function_BODIES; + eval $_list_variable_VALUES; + `" + else + # shellcheck disable=SC2006 + PARALLEL_ENV="` + $_prefix_PARALLEL_ENV; + $_list_alias_BODIES; + $_list_function_BODIES; + $_list_variable_VALUES; + `" + fi + export PARALLEL_ENV + # Free up some env space + unset _list_alias_BODIES _list_variable_VALUES _list_function_BODIES + unset _bodies_of_ALIASES _bodies_of_VARIABLES _bodies_of_FUNCTIONS + unset _names_of_ALIASES _names_of_VARIABLES _names_of_FUNCTIONS + unset _ignore_HARDCODED _ignore_READONLY _ignore_UNDERSCORE + unset _remove_bad_NAMES _grep_REGEXP _parse_READONLY + unset _prefix_PARALLEL_ENV + unset _ignore_READONLY_sh _ignore_READONLY_bash + unset _ignore_READONLY_ksh _ignore_READONLY_zsh + unset _ignore_HARDCODED_sh _ignore_HARDCODED_bash + unset _ignore_HARDCODED_ksh _ignore_HARDCODED_zsh + unset _bodies_of_ALIASES_ksh _bodies_of_ALIASES_sh + unset _bodies_of_ALIASES_zsh _bodies_of_FUNCTIONS_bash + unset _bodies_of_FUNCTIONS_ksh _bodies_of_FUNCTIONS_sh + unset _bodies_of_FUNCTIONS_zsh _bodies_of_VARIABLES_bash + unset _bodies_of_VARIABLES_ksh _bodies_of_VARIABLES_sh + unset _bodies_of_VARIABLES_zsh + unset _names_of_ALIASES _names_of_ALIASES_bash + unset _names_of_ALIASES_ksh _names_of_ALIASES_sh + unset _names_of_ALIASES_zsh _names_of_FUNCTIONS + unset _names_of_FUNCTIONS_bash _names_of_FUNCTIONS_ksh + unset _names_of_FUNCTIONS_sh _names_of_FUNCTIONS_zsh + unset _names_of_VARIABLES _names_of_VARIABLES_bash + unset _names_of_VARIABLES_ksh _names_of_VARIABLES_sh + unset _names_of_VARIABLES_zsh _names_of_maybe_FUNCTIONS + + # Test if environment is too big by running 'true' + # shellcheck disable=SC2006,SC2092 + if `_which_PAR true` >/dev/null 2>/dev/null ; then + parallel "$@" + _parallel_exit_CODE=$? + # Clean up variables/functions + unset PARALLEL_ENV + unset _which_PAR _which_TRUE + unset _warning_PAR _error_PAR + # Unset _parallel_exit_CODE before return + eval "unset _parallel_exit_CODE; return $_parallel_exit_CODE" + else + unset PARALLEL_ENV; + _error_PAR "Your environment is too big." + _error_PAR "You can try 3 different approaches:" + _error_PAR "1. Run 'env_parallel --session' before you set" + _error_PAR " variables or define functions." + _error_PAR "2. Use --env and only mention the names to copy." + _error_PAR "3. Try running this in a clean environment once:" + _error_PAR " env_parallel --record-env" + _error_PAR " And then use '--env _'" + _error_PAR "For details see: man env_parallel" + return 255 + fi +} + +parset() { + _parset_PARALLEL_PRG=parallel + _parset_main "$@" +} + +env_parset() { + _parset_PARALLEL_PRG=env_parallel + _parset_main "$@" +} + +_parset_main() { + # If $1 contains ',' or space: + # Split on , to get the destination variable names + # If $1 is a single destination variable name: + # Treat it as the name of an array + # + # # Create array named myvar + # parset myvar echo ::: {1..10} + # echo ${myvar[5]} + # + # # Put output into $var_a $var_b $var_c + # varnames=(var_a var_b var_c) + # parset "${varnames[*]}" echo ::: {1..3} + # echo $var_c + # + # # Put output into $var_a4 $var_b4 $var_c4 + # parset "var_a4 var_b4 var_c4" echo ::: {1..3} + # echo $var_c4 + + _parset_NAME="$1" + if [ "$_parset_NAME" = "" ] ; then + echo parset: Error: No destination variable given. >&2 + echo parset: Error: Try: >&2 + echo parset: Error: ' ' parset myarray echo ::: foo bar >&2 + return 255 + fi + if [ "$_parset_NAME" = "--help" ] ; then + echo parset: Error: Usage: >&2 + echo parset: Error: ' ' parset varname GNU Parallel options and command >&2 + echo + parallel --help + return 255 + fi + if [ "$_parset_NAME" = "--version" ] ; then + # shellcheck disable=SC2006 + echo "parset 20240222 (GNU parallel `parallel --minversion 1`)" + echo "Copyright (C) 2007-2024 Ole Tange, http://ole.tange.dk and Free Software" + echo "Foundation, Inc." + echo "License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>" + echo "This is free software: you are free to change and redistribute it." + echo "GNU parallel comes with no warranty." + echo + echo "Web site: https://www.gnu.org/software/parallel" + echo + echo "When using programs that use GNU Parallel to process data for publication" + echo "please cite as described in 'parallel --citation'." + echo + return 255 + fi + shift + + # Bash: declare -A myassoc=( ) + # Zsh: typeset -A myassoc=( ) + # Ksh: typeset -A myassoc=( ) + # shellcheck disable=SC2039,SC2169,SC3044 + if (typeset -p "$_parset_NAME" 2>/dev/null; echo) | + perl -ne 'exit not (/^declare[^=]+-A|^typeset[^=]+-A/)' ; then + # This is an associative array + # shellcheck disable=SC2006 + eval "`$_parset_PARALLEL_PRG -k --_parset assoc,"$_parset_NAME" "$@"`" + # The eval returns the function! + else + # This is a normal array or a list of variable names + # shellcheck disable=SC2006 + eval "`$_parset_PARALLEL_PRG -k --_parset var,"$_parset_NAME" "$@"`" + # The eval returns the function! + fi +} diff --git a/src/env_parallel.fish b/src/env_parallel.fish new file mode 100755 index 0000000..b0abc50 --- /dev/null +++ b/src/env_parallel.fish @@ -0,0 +1,194 @@ +#!/usr/bin/env fish + +# This file must be sourced in fish: +# +# . (which env_parallel.fish) +# +# after which 'env_parallel' works +# +# +# Copyright (C) 2016-2024 Ole Tange, http://ole.tange.dk and Free +# Software Foundation, Inc. +# +# This program 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. +# +# This program 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 this program; if not, see <http://www.gnu.org/licenses/> +# or write to the Free Software Foundation, Inc., 51 Franklin St, +# Fifth Floor, Boston, MA 02110-1301 USA +# +# SPDX-FileCopyrightText: 2021-2024 Ole Tange, http://ole.tange.dk and Free Software and Foundation, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later + +# If you are a fisherman feel free to improve the code +# +# The code needs to deal with variables like: +# set funky (perl -e 'print pack "c*", 2..254') +# +# Problem: +# Tell the difference between: +# set tmp "a' 'b' 'c" +# set tmparr1 "a' 'b" 'c' +# set tmparr2 'a' "b' 'c" +# The output from `set` is exactly the same. +# Solution: +# for-loop for each variable. Each value is separated with a +# separator. + +function env_parallel + # env_parallel.fish + + # --session + perl -e 'exit grep { /^--session/ } @ARGV' -- $argv; or begin; + setenv PARALLEL_IGNORED_NAMES ( + begin; + functions -n + set -n; + end | perl -pe 's/\n/,/g'; + ) + return 0 + end; + setenv PARALLEL_ENV ( + begin; + set _grep_REGEXP ( + begin; + perl -e ' + for(@ARGV){ + /^_$/ and $next_is_env = 0; + $next_is_env and push @envvar, split/,/, $_; + $next_is_env = /^--env$/; + } + $vars = join "|",map { quotemeta $_ } @envvar; + print $vars ? "($vars)" : "(.*)"; + ' -- $argv; + end; + ) + # Deal with --env _ + set _ignore_UNDERSCORE ( + begin; + perl -e ' + for(@ARGV){ + $next_is_env and push @envvar, split/,/, $_; + $next_is_env=/^--env$/; + } + if(grep { /^_$/ } @envvar) { + if(not open(IN, "<", "$ENV{HOME}/.parallel/ignored_vars")) { + print STDERR "parallel: Error: ", + "Run \"parallel --record-env\" in a clean environment first.\n"; + } else { + chomp(@ignored_vars = <IN>); + } + } + if($ENV{PARALLEL_IGNORED_NAMES}) { + push @ignored_vars, split/,/, $ENV{PARALLEL_IGNORED_NAMES}; + chomp @ignored_vars; + } + $vars = join "|",map { quotemeta $_ } @ignored_vars; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + ' -- $argv; + end; + ) + + # --record-env + perl -e 'exit grep { /^--record-env$/ } @ARGV' -- $argv; or begin; + begin; + functions -n | perl -pe 's/,/\n/g'; + set -n; + end | cat > $HOME/.parallel/ignored_vars; + end; + + # Export function definitions + # Keep the ones from --env + # Ignore the ones from ~/.parallel/ignored_vars + # Dump each function defition + # Replace \001 with \002 because \001 is used by env_parallel + # Convert \n to \001 + functions -n | perl -pe 's/,/\n/g' | \ + grep -Ev '^(PARALLEL_ENV|PARALLEL_TMP)$' | \ + grep -E "^$_grep_REGEXP"\$ | grep -vE "^$_ignore_UNDERSCORE"\$ | \ + while read d; functions $d; end | \ + perl -pe 's/\001/\002/g and not $printed++ and print STDERR + "env_parallel: Warning: ASCII value 1 in functions is not supported\n"; + s/\n/\001/g'; + # Convert scalar vars to fish \XX quoting + # Keep the ones from --env + # Ignore the ones from ~/.parallel/ignored_vars + # Ignore read only vars + # Execute 'set' of the content + eval (set -L | \ + grep -Ev '^(PARALLEL_TMP)$' | \ + grep -E "^$_grep_REGEXP " | grep -vE "^$_ignore_UNDERSCORE " | \ + perl -ne 'chomp; + ($name,$val)=split(/ /,$_,2); + $name=~/^(HOME|USER|COLUMNS|FISH_VERSION|LINES|PWD|SHLVL|_| + history|status|version)$|\./x and next; + if($val=~/^'"'"'/) { next; } + print "set $name \"\$$name\";\n"; + ') + # Generate commands to set scalar variables + # Keep the ones from --env + # Ignore the ones from ~/.parallel/ignored_vars + # + begin; + for v in (set -n | \ + grep -Ev '^(PARALLEL_TMP)$' | \ + grep -E "^$_grep_REGEXP\$" | grep -vE "^$_ignore_UNDERSCORE\$"); + # Separate variables with the string: \000 + # array_name1 val1\0 + # array_name1 val2\0 + # array_name2 val3\0 + # array_name2 val4\0 + eval "for i in \$$v; + echo -n $v \$i; + perl -e print\\\"\\\\0\\\"; + end;" + end; + # A final line to flush the last variable in Perl + perl -e print\"\\0\"; + end | perl -0 -ne ' + # Remove separator string \0 + chop; + # Split line into name and value + ($name,$val)=split(/ /,$_,2); + # Ignore read-only vars + $name=~/^(HOME|USER|COLUMNS|FISH_VERSION|LINES|PWD|SHLVL|_| + fish_pid|history|hostname|status|version)$/x and next; + # Single quote $val + if($val =~ /[^-_.+a-z0-9\/]/i) { + $val =~ s/\047/\047"\047"\047/g; # "-quote single quotes + $val = "\047$val\047"; # single-quote entire string + $val =~ s/^\047\047|\047\047$//g; # Remove unneeded '' at ends + } elsif ($val eq "") { + $val = "\047\047"; + } + + if($name ne $last and $last) { + # The $name is different, so this is a new variable. + # Print the last one. + # Separate list elements by 2 spaces + $"=" "; + print "set $last @qval;\n"; + @qval=(); + } + push @qval,$val; + $last=$name; + '| \ + perl -pe 's/\001/\002/g and not $printed++ and print STDERR + "env_parallel: Warning: ASCII value 1 in variables is not supported\n"; + s/\n/\001/g' + end; + ) + # If --record-env: exit + perl -e 'exit grep { /^--record-env$/ } @ARGV' -- $argv; and parallel $argv; + set _parallel_exit_CODE $status + set -e PARALLEL_ENV + return $_parallel_exit_CODE +end diff --git a/src/env_parallel.ksh b/src/env_parallel.ksh new file mode 100755 index 0000000..bc20d41 --- /dev/null +++ b/src/env_parallel.ksh @@ -0,0 +1,636 @@ +#!/usr/bin/env ksh + +# This file must be sourced in sh/ash/dash/bash/ksh/mksh/zsh: +# +# . env_parallel.sh +# source env_parallel.ash +# source env_parallel.dash +# source env_parallel.bash +# source env_parallel.ksh +# source env_parallel.mksh +# source env_parallel.zsh +# +# after which 'env_parallel' works +# +# +# Copyright (C) 2016-2024 Ole Tange, http://ole.tange.dk and Free +# Software Foundation, Inc. +# +# This program 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. +# +# This program 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 this program; if not, see <http://www.gnu.org/licenses/> +# or write to the Free Software Foundation, Inc., 51 Franklin St, +# Fifth Floor, Boston, MA 02110-1301 USA +# +# SPDX-FileCopyrightText: 2021-2024 Ole Tange, http://ole.tange.dk and Free Software and Foundation, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later +# shellcheck disable=SC2006 + +env_parallel() { + # env_parallel.{sh,ash,dash,bash,ksh,mksh,zsh} + + # Check shell dialect + if [ -n "$BASH_VERSION" ]; then + _shell_DIALECT=bash + _eval_needed=false + _prefix_PARALLEL_ENV=_prefix_PARALLEL_ENV_bash + elif [ -n "$ZSH_VERSION" ]; then + _shell_DIALECT=zsh + _eval_needed=true + _prefix_PARALLEL_ENV=false + elif [ -n "$KSH_VERSION" ]; then + _shell_DIALECT=ksh + _eval_needed=false + _prefix_PARALLEL_ENV=false + else + # Dash/ash - can these be detected better? + _shell_DIALECT="sh" + _eval_needed=false + _prefix_PARALLEL_ENV=false + fi + _names_of_ALIASES() { + _names_of_ALIASES_$_shell_DIALECT + } + _names_of_ALIASES_sh() { + # alias fails on Unixware 5 + for _i in `alias 2>/dev/null | perl -ne 's/^alias //;s/^(\S+)=.*/$1/ && print' 2>/dev/null`; do + # Check if this name really is an alias + # or just part of a multiline alias definition + if alias "$_i" >/dev/null 2>/dev/null; then + echo "$_i" + fi + done + } + _names_of_ALIASES_bash() { + # No aliases will return false. This error should be ignored. + # shellcheck disable=SC3044 + compgen -a || true + } + _names_of_ALIASES_ksh() { + alias | perl -pe 's/=.*//' + } + _names_of_ALIASES_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)aliases} + } + _bodies_of_ALIASES() { + _bodies_of_ALIASES_$_shell_DIALECT "$@" + } + _bodies_of_ALIASES_sh() { + # alias may return: + # myalias='definition' (GNU/Linux ash) + # alias myalias='definition' (FreeBSD ash) + # so remove 'alias ' from first line + for _i in "$@"; do + echo 'alias '"`alias "$_i" | perl -pe '1..1 and s/^alias //'`" + done + } + _bodies_of_ALIASES_bash() { + # shellcheck disable=SC3043 + local _i + for _i in "$@"; do + # shellcheck disable=SC2046 + if [ $(alias "$_i" | wc -l) = 1 ] ; then + true Alias is a single line. Good. + else + _warning_PAR "Alias '$_i' contains newline." + _warning_PAR "Make sure the command has at least one newline after '$_i'." + _warning_PAR "See BUGS in 'man env_parallel'." + fi + done + alias "$@" + } + _bodies_of_ALIASES_ksh() { + alias "$@" | perl -pe 's/^/alias /; + sub warning { print STDERR "env_parallel: Warning: @_\n"; } + if(/^alias (\S+)=\$.*\\n/) { + warning("Alias \"$1\" contains newline."); + warning("Make sure the command has at least one newline after \"$1\"."); + warning("See BUGS in \"man env_parallel\"."); + }' + + } + _bodies_of_ALIASES_zsh() { + # shellcheck disable=SC3043 + local _i + for _i in "$@"; do + echo 'alias '"$(alias "$_i")" + done + } + _names_of_FUNCTIONS() { + _names_of_FUNCTIONS_$_shell_DIALECT + } + _names_of_FUNCTIONS_bash() { + # shellcheck disable=SC3044 + compgen -A function + } + _names_of_maybe_FUNCTIONS() { + set | perl -ne '/^([A-Z_0-9]+)\s*\(\)\s*\{?$/i and print "$1\n"' + } + _names_of_FUNCTIONS_sh() { + # myfunc is a function + # shellcheck disable=SC2046 + LANG=C type `_names_of_maybe_FUNCTIONS` | + perl -ne '/^(\S+) is a function$/ and not $seen{$1}++ and print "$1\n"' + } + _names_of_FUNCTIONS_ksh() { + # shellcheck disable=SC3044 + typeset +f | perl -pe 's/\(\).*//; s/ .*//;' + } + _names_of_FUNCTIONS_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)functions} + } + _bodies_of_FUNCTIONS() { + _bodies_of_FUNCTIONS_$_shell_DIALECT "$@" + } + _bodies_of_FUNCTIONS_sh() { + LANG=C type "$@" | perl -ne '/^(\S+) is a function$/ or print' + } + _bodies_of_FUNCTIONS_bash() { + # shellcheck disable=SC3044 + typeset -f "$@" + } + _bodies_of_FUNCTIONS_ksh() { + functions "$@" + } + _bodies_of_FUNCTIONS_zsh() { + # shellcheck disable=SC3044 + typeset -f "$@" + } + _names_of_VARIABLES() { + _names_of_VARIABLES_$_shell_DIALECT + } + _names_of_VARIABLES_sh() { + # This may screw up if variables contain \n and = + set | perl -ne 's/^(\S+?)=.*/$1/ and print;' + } + _names_of_VARIABLES_bash() { + # shellcheck disable=SC3044 + compgen -A variable + } + _names_of_VARIABLES_ksh() { + # shellcheck disable=SC3044 + typeset +p | + perl -pe 's/^(type)?set( [-+][a-zA-Z0-9]*)* //; s/(\[\d+\])?=.*//' | + uniq + } + _names_of_VARIABLES_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)parameters} + } + _bodies_of_VARIABLES() { + _bodies_of_VARIABLES_$_shell_DIALECT "$@" + } + _bodies_of_VARIABLES_sh() { + # Crappy typeset -p + for _i in "$@" + do + perl -e 'print @ARGV' "$_i=" + eval echo "\"\$$_i\"" | perl -e '$/=undef; $a=<>; chop($a); print $a' | + perl -pe 's/[\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\^\*\<\=\>\~\|\; \"\!\$\&\202-\377]/\\$&/go;'"s/'/\\\'/g; s/[\n]/'\\n'/go;"; + echo + done + } + _bodies_of_VARIABLES_bash() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _bodies_of_VARIABLES_ksh() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _bodies_of_VARIABLES_zsh() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _ignore_HARDCODED() { + _ignore_HARDCODED_$_shell_DIALECT + } + _ignore_HARDCODED_sh() { + # These names cannot be detected + echo '(_|TIMEOUT|IFS)' + } + _ignore_HARDCODED_bash() { + # Copying $RANDOM will cause it not to be random + # The rest cannot be detected as read-only + echo '(RANDOM|_|TIMEOUT|GROUPS|FUNCNAME|DIRSTACK|PIPESTATUS|USERNAME|BASHPID|BASH_[A-Z_]+)' + } + _ignore_HARDCODED_ksh() { + # These names cannot be detected + echo '(_|TIMEOUT|IFS)' + } + _ignore_HARDCODED_zsh() { + # These names cannot be detected + echo '([-\?\#\!\$\*\@\_0]|zsh_eval_context|ZSH_EVAL_CONTEXT|LINENO|IFS|commands|functions|options|aliases|EUID|EGID|UID|GID|dis_patchars|patchars|terminfo|galiases|keymaps|parameters|jobdirs|dirstack|functrace|funcsourcetrace|zsh_scheduled_events|dis_aliases|dis_reswords|dis_saliases|modules|reswords|saliases|widgets|userdirs|historywords|nameddirs|termcap|dis_builtins|dis_functions|jobtexts|funcfiletrace|dis_galiases|builtins|history|jobstates|funcstack|run-help)' + } + _ignore_READONLY() { + _ignore_READONLY_$_shell_DIALECT + } + _parse_READONLY() { + # shellcheck disable=SC1078,SC1079,SC2026 + perl -e '@r = map { + chomp; + # sh on UnixWare: readonly TIMEOUT + # ash: readonly var='val' + # ksh: var='val' + # mksh: PIPESTATUS[0] + s/^(readonly )?([^=\[ ]*?)(\[\d+\])?(=.*|)$/$2/ or + # bash: declare -ar BASH_VERSINFO=([0]="4" [1]="4") + # zsh: typeset -r var='val' + s/^\S+\s+\S+\s+(\S[^=]*)(=.*|$)/$1/; + $_ } <>; + $vars = join "|",map { quotemeta $_ } @r; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + ' + } + _ignore_READONLY_sh() { + readonly | _parse_READONLY + } + _ignore_READONLY_bash() { + readonly | _parse_READONLY + } + _ignore_READONLY_ksh() { + readonly | _parse_READONLY + } + _ignore_READONLY_zsh() { + # shellcheck disable=SC3044 + typeset -pr | _parse_READONLY + } + _remove_bad_NAMES() { + # Do not transfer vars and funcs from env_parallel + # shellcheck disable=SC2006 + _ignore_RO="`_ignore_READONLY`" + # shellcheck disable=SC2006 + _ignore_HARD="`_ignore_HARDCODED`" + # To avoid depending on grep dialect, use Perl version of: + # grep -Ev '^(...)$' | + perl -ne '/^( + PARALLEL_ENV| + PARALLEL_TMP| + _alias_NAMES| + _bodies_of_ALIASES| + _bodies_of_FUNCTIONS| + _bodies_of_VARIABLES| + _error_PAR| + _function_NAMES| + _get_ignored_VARS| + _grep_REGEXP| + _ignore_HARD| + _ignore_HARDCODED| + _ignore_READONLY| + _ignore_RO| + _ignore_UNDERSCORE| + _list_alias_BODIES| + _list_function_BODIES| + _list_variable_VALUES| + _make_grep_REGEXP| + _names_of_ALIASES| + _names_of_FUNCTIONS| + _names_of_VARIABLES| + _names_of_maybe_FUNCTIONS| + _parallel_exit_CODE| + _prefix_PARALLEL_ENV| + _prefix_PARALLEL_ENV_bash| + _remove_bad_NAMES| + _remove_readonly| + _variable_NAMES| + _warning_PAR| + _which_PAR)$/x and next; + # Filter names matching --env + /^'"$_grep_REGEXP"'$/ or next; + /^'"$_ignore_UNDERSCORE"'$/ and next; + # Remove readonly variables + /^'"$_ignore_RO"'$/ and next; + /^'"$_ignore_HARD"'$/ and next; + print;' + } + _prefix_PARALLEL_ENV_bash() { + # shellcheck disable=SC3044 + shopt 2>/dev/null | + perl -pe 's:\s+off:;: and s/^/shopt -u /; + s:\s+on:;: and s/^/shopt -s /; + s:;$:&>/dev/null;:'; + echo 'shopt -s expand_aliases &>/dev/null'; + } + + _get_ignored_VARS() { + perl -e ' + for(@ARGV){ + $next_is_env and push @envvar, split/,/, $_; + $next_is_env=/^--env$/; + } + if(grep { /^_$/ } @envvar) { + if(not open(IN, "<", "$ENV{HOME}/.parallel/ignored_vars")) { + print STDERR "parallel: Error: ", + "Run \"parallel --record-env\" in a clean environment first.\n"; + } else { + chomp(@ignored_vars = <IN>); + } + } + if($ENV{PARALLEL_IGNORED_NAMES}) { + push @ignored_vars, split/\s+/, $ENV{PARALLEL_IGNORED_NAMES}; + chomp @ignored_vars; + } + $vars = join "|",map { quotemeta $_ } @ignored_vars; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + ' -- "$@" + } + + # Get the --env variables if set + # --env _ should be ignored + # and convert a b c to (a|b|c) + # If --env not set: Match everything (.*) + _make_grep_REGEXP() { + perl -e ' + for(@ARGV){ + /^_$/ and $next_is_env = 0; + $next_is_env and push @envvar, split/,/, $_; + $next_is_env = /^--env$/; + } + $vars = join "|",map { quotemeta $_ } @envvar; + print $vars ? "($vars)" : "(.*)"; + ' -- "$@" + } + _which_PAR() { + # type returns: + # ll is an alias for ls -l (in ash) + # bash is a tracked alias for /bin/bash + # true is a shell builtin (in bash) + # myfunc is a function (in bash) + # myfunc is a shell function (in zsh) + # which is /usr/bin/which (in sh, bash) + # which is hashed (/usr/bin/which) + # gi is aliased to `grep -i' (in bash) + # aliased to `alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde' + # Return 0 if found, 1 otherwise + LANG=C type "$@" | + perl -pe '$exit += (s/ is an alias for .*// || + s/ is aliased to .*// || + s/ is a function// || + s/ is a shell function// || + s/ is a shell builtin// || + s/.* is hashed .(\S+).$/$1/ || + s/.* is (a tracked alias for )?//); + END { exit not $exit }' + } + _warning_PAR() { + echo "env_parallel: Warning: $*" >&2 + } + _error_PAR() { + echo "env_parallel: Error: $*" >&2 + } + + if _which_PAR parallel >/dev/null; then + true parallel found in path + else + # shellcheck disable=SC2016 + _error_PAR 'parallel must be in $PATH.' + return 255 + fi + + # Grep regexp for vars given by --env + # shellcheck disable=SC2006 + _grep_REGEXP="`_make_grep_REGEXP \"$@\"`" + unset _make_grep_REGEXP + + # Deal with --env _ + # shellcheck disable=SC2006 + _ignore_UNDERSCORE="`_get_ignored_VARS \"$@\"`" + unset _get_ignored_VARS + + # --record-env + if perl -e 'exit grep { /^--record-env$/ } @ARGV' -- "$@"; then + true skip + else + (_names_of_ALIASES; + _names_of_FUNCTIONS; + _names_of_VARIABLES) | + cat > "$HOME"/.parallel/ignored_vars + return 0 + fi + + # --session + if perl -e 'exit grep { /^--session$/ } @ARGV' -- "$@"; then + true skip + else + # Insert ::: between each level of session + # so you can pop off the last ::: at --end-session + # shellcheck disable=SC2006 + PARALLEL_IGNORED_NAMES="`echo \"$PARALLEL_IGNORED_NAMES\"; + echo :::; + (_names_of_ALIASES; + _names_of_FUNCTIONS; + _names_of_VARIABLES) | perl -ne ' + BEGIN{ + map { $ignored_vars{$_}++ } + split/\s+/, $ENV{PARALLEL_IGNORED_NAMES}; + } + chomp; + for(split/\s+/) { + if(not $ignored_vars{$_}) { + print $_,\"\\n\"; + } + } + '`" + export PARALLEL_IGNORED_NAMES + return 0 + fi + if perl -e 'exit grep { /^--end.?session$/ } @ARGV' -- "$@"; then + true skip + else + # Pop off last ::: from PARALLEL_IGNORED_NAMES + # shellcheck disable=SC2006 + PARALLEL_IGNORED_NAMES="`perl -e ' + $ENV{PARALLEL_IGNORED_NAMES} =~ s/(.*):::.*?$/$1/s; + print $ENV{PARALLEL_IGNORED_NAMES} + '`" + return 0 + fi + # Grep alias names + # shellcheck disable=SC2006 + _alias_NAMES="`_names_of_ALIASES | _remove_bad_NAMES | xargs echo`" + _list_alias_BODIES="_bodies_of_ALIASES $_alias_NAMES" + if [ "$_alias_NAMES" = "" ] ; then + # no aliases selected + _list_alias_BODIES="true" + fi + unset _alias_NAMES + + # Grep function names + # shellcheck disable=SC2006 + _function_NAMES="`_names_of_FUNCTIONS | _remove_bad_NAMES | xargs echo`" + _list_function_BODIES="_bodies_of_FUNCTIONS $_function_NAMES" + if [ "$_function_NAMES" = "" ] ; then + # no functions selected + _list_function_BODIES="true" + fi + unset _function_NAMES + + # Grep variable names + # shellcheck disable=SC2006 + _variable_NAMES="`_names_of_VARIABLES | _remove_bad_NAMES | xargs echo`" + _list_variable_VALUES="_bodies_of_VARIABLES $_variable_NAMES" + if [ "$_variable_NAMES" = "" ] ; then + # no variables selected + _list_variable_VALUES="true" + fi + unset _variable_NAMES + + if $_eval_needed ; then + # shellcheck disable=SC2006 + PARALLEL_ENV="` + eval $_prefix_PARALLEL_ENV; + eval $_list_alias_BODIES; + eval $_list_function_BODIES; + eval $_list_variable_VALUES; + `" + else + # shellcheck disable=SC2006 + PARALLEL_ENV="` + $_prefix_PARALLEL_ENV; + $_list_alias_BODIES; + $_list_function_BODIES; + $_list_variable_VALUES; + `" + fi + export PARALLEL_ENV + # Free up some env space + unset _list_alias_BODIES _list_variable_VALUES _list_function_BODIES + unset _bodies_of_ALIASES _bodies_of_VARIABLES _bodies_of_FUNCTIONS + unset _names_of_ALIASES _names_of_VARIABLES _names_of_FUNCTIONS + unset _ignore_HARDCODED _ignore_READONLY _ignore_UNDERSCORE + unset _remove_bad_NAMES _grep_REGEXP _parse_READONLY + unset _prefix_PARALLEL_ENV + unset _ignore_READONLY_sh _ignore_READONLY_bash + unset _ignore_READONLY_ksh _ignore_READONLY_zsh + unset _ignore_HARDCODED_sh _ignore_HARDCODED_bash + unset _ignore_HARDCODED_ksh _ignore_HARDCODED_zsh + unset _bodies_of_ALIASES_ksh _bodies_of_ALIASES_sh + unset _bodies_of_ALIASES_zsh _bodies_of_FUNCTIONS_bash + unset _bodies_of_FUNCTIONS_ksh _bodies_of_FUNCTIONS_sh + unset _bodies_of_FUNCTIONS_zsh _bodies_of_VARIABLES_bash + unset _bodies_of_VARIABLES_ksh _bodies_of_VARIABLES_sh + unset _bodies_of_VARIABLES_zsh + unset _names_of_ALIASES _names_of_ALIASES_bash + unset _names_of_ALIASES_ksh _names_of_ALIASES_sh + unset _names_of_ALIASES_zsh _names_of_FUNCTIONS + unset _names_of_FUNCTIONS_bash _names_of_FUNCTIONS_ksh + unset _names_of_FUNCTIONS_sh _names_of_FUNCTIONS_zsh + unset _names_of_VARIABLES _names_of_VARIABLES_bash + unset _names_of_VARIABLES_ksh _names_of_VARIABLES_sh + unset _names_of_VARIABLES_zsh _names_of_maybe_FUNCTIONS + + # Test if environment is too big by running 'true' + # shellcheck disable=SC2006,SC2092 + if `_which_PAR true` >/dev/null 2>/dev/null ; then + parallel "$@" + _parallel_exit_CODE=$? + # Clean up variables/functions + unset PARALLEL_ENV + unset _which_PAR _which_TRUE + unset _warning_PAR _error_PAR + # Unset _parallel_exit_CODE before return + eval "unset _parallel_exit_CODE; return $_parallel_exit_CODE" + else + unset PARALLEL_ENV; + _error_PAR "Your environment is too big." + _error_PAR "You can try 3 different approaches:" + _error_PAR "1. Run 'env_parallel --session' before you set" + _error_PAR " variables or define functions." + _error_PAR "2. Use --env and only mention the names to copy." + _error_PAR "3. Try running this in a clean environment once:" + _error_PAR " env_parallel --record-env" + _error_PAR " And then use '--env _'" + _error_PAR "For details see: man env_parallel" + return 255 + fi +} + +parset() { + _parset_PARALLEL_PRG=parallel + _parset_main "$@" +} + +env_parset() { + _parset_PARALLEL_PRG=env_parallel + _parset_main "$@" +} + +_parset_main() { + # If $1 contains ',' or space: + # Split on , to get the destination variable names + # If $1 is a single destination variable name: + # Treat it as the name of an array + # + # # Create array named myvar + # parset myvar echo ::: {1..10} + # echo ${myvar[5]} + # + # # Put output into $var_a $var_b $var_c + # varnames=(var_a var_b var_c) + # parset "${varnames[*]}" echo ::: {1..3} + # echo $var_c + # + # # Put output into $var_a4 $var_b4 $var_c4 + # parset "var_a4 var_b4 var_c4" echo ::: {1..3} + # echo $var_c4 + + _parset_NAME="$1" + if [ "$_parset_NAME" = "" ] ; then + echo parset: Error: No destination variable given. >&2 + echo parset: Error: Try: >&2 + echo parset: Error: ' ' parset myarray echo ::: foo bar >&2 + return 255 + fi + if [ "$_parset_NAME" = "--help" ] ; then + echo parset: Error: Usage: >&2 + echo parset: Error: ' ' parset varname GNU Parallel options and command >&2 + echo + parallel --help + return 255 + fi + if [ "$_parset_NAME" = "--version" ] ; then + # shellcheck disable=SC2006 + echo "parset 20240222 (GNU parallel `parallel --minversion 1`)" + echo "Copyright (C) 2007-2024 Ole Tange, http://ole.tange.dk and Free Software" + echo "Foundation, Inc." + echo "License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>" + echo "This is free software: you are free to change and redistribute it." + echo "GNU parallel comes with no warranty." + echo + echo "Web site: https://www.gnu.org/software/parallel" + echo + echo "When using programs that use GNU Parallel to process data for publication" + echo "please cite as described in 'parallel --citation'." + echo + return 255 + fi + shift + + # Bash: declare -A myassoc=( ) + # Zsh: typeset -A myassoc=( ) + # Ksh: typeset -A myassoc=( ) + # shellcheck disable=SC2039,SC2169,SC3044 + if (typeset -p "$_parset_NAME" 2>/dev/null; echo) | + perl -ne 'exit not (/^declare[^=]+-A|^typeset[^=]+-A/)' ; then + # This is an associative array + # shellcheck disable=SC2006 + eval "`$_parset_PARALLEL_PRG -k --_parset assoc,"$_parset_NAME" "$@"`" + # The eval returns the function! + else + # This is a normal array or a list of variable names + # shellcheck disable=SC2006 + eval "`$_parset_PARALLEL_PRG -k --_parset var,"$_parset_NAME" "$@"`" + # The eval returns the function! + fi +} diff --git a/src/env_parallel.mksh b/src/env_parallel.mksh new file mode 100644 index 0000000..d307128 --- /dev/null +++ b/src/env_parallel.mksh @@ -0,0 +1,605 @@ +#!/usr/bin/env ksh + +# This file must be sourced in sh/ash/dash/bash/ksh/mksh/zsh: +# +# . env_parallel.sh +# source env_parallel.ash +# source env_parallel.dash +# source env_parallel.bash +# source env_parallel.ksh +# source env_parallel.mksh +# source env_parallel.zsh +# +# after which 'env_parallel' works +# +# +# Copyright (C) 2016-2024 Ole Tange, http://ole.tange.dk and Free +# Software Foundation, Inc. +# +# This program 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. +# +# This program 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 this program; if not, see <http://www.gnu.org/licenses/> +# or write to the Free Software Foundation, Inc., 51 Franklin St, +# Fifth Floor, Boston, MA 02110-1301 USA +# +# SPDX-FileCopyrightText: 2021-2024 Ole Tange, http://ole.tange.dk and Free Software and Foundation, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later +# shellcheck disable=SC2006 + +env_parallel() { + # env_parallel.{sh,ash,dash,bash,ksh,mksh,zsh} + + # Check shell dialect + if [ -n "$BASH_VERSION" ]; then + _shell_DIALECT=bash + _eval_needed=false + _prefix_PARALLEL_ENV=_prefix_PARALLEL_ENV_bash + elif [ -n "$ZSH_VERSION" ]; then + _shell_DIALECT=zsh + _eval_needed=true + _prefix_PARALLEL_ENV=false + elif [ -n "$KSH_VERSION" ]; then + _shell_DIALECT=ksh + _eval_needed=false + _prefix_PARALLEL_ENV=false + else + # Dash/ash - can these be detected better? + _shell_DIALECT=sh + _eval_needed=false + _prefix_PARALLEL_ENV=false + fi + _names_of_ALIASES() { + _names_of_ALIASES_$_shell_DIALECT + } + _names_of_ALIASES_sh() { + # alias fails on Unixware 5 + for _i in `alias 2>/dev/null | perl -ne 's/^alias //;s/^(\S+)=.*/$1/ && print' 2>/dev/null`; do + # Check if this name really is an alias + # or just part of a multiline alias definition + if alias "$_i" >/dev/null 2>/dev/null; then + echo "$_i" + fi + done + } + _names_of_ALIASES_bash() { + # No aliases will return false. This error should be ignored. + compgen -a || true + } + _names_of_ALIASES_ksh() { + alias | perl -pe 's/=.*//' + } + _names_of_ALIASES_zsh() { + print -l ${(k)aliases} + } + _bodies_of_ALIASES() { + _bodies_of_ALIASES_$_shell_DIALECT "$@" + } + _bodies_of_ALIASES_sh() { + # alias may return: + # myalias='definition' (GNU/Linux ash) + # alias myalias='definition' (FreeBSD ash) + # so remove 'alias ' from first line + for _i in "$@"; do + echo 'alias '"`alias "$_i" | perl -pe '1..1 and s/^alias //'`" + done + } + _bodies_of_ALIASES_bash() { + local _i + for _i in "$@"; do + # shellcheck disable=SC2046 + if [ $(alias "$_i" | wc -l) == 1 ] ; then + true Alias is a single line. Good. + else + _warning_PAR "Alias '$_i' contains newline." + _warning_PAR "Make sure the command has at least one newline after '$_i'." + _warning_PAR "See BUGS in 'man env_parallel'." + fi + done + alias "$@" + } + _bodies_of_ALIASES_ksh() { + alias "$@" | perl -pe 's/^/alias /; + sub warning { print STDERR "env_parallel: Warning: @_\n"; } + if(/^alias (\S+)=\$.*\\n/) { + warning("Alias \"$1\" contains newline."); + warning("Make sure the command has at least one newline after \"$1\"."); + warning("See BUGS in \"man env_parallel\"."); + }' + + } + _bodies_of_ALIASES_zsh() { + local _i + for _i in "$@"; do + echo 'alias '"$(alias $_i)" + done + } + _names_of_FUNCTIONS() { + _names_of_FUNCTIONS_$_shell_DIALECT + } + _names_of_FUNCTIONS_bash() { + compgen -A function + } + _names_of_maybe_FUNCTIONS() { + set | perl -ne '/^([A-Z_0-9]+)\s*\(\)\s*\{?$/i and print "$1\n"' + } + _names_of_FUNCTIONS_sh() { + # myfunc is a function + # shellcheck disable=SC2046 + LANG=C type `_names_of_maybe_FUNCTIONS` | + perl -ne '/^(\S+) is a function$/ and not $seen{$1}++ and print "$1\n"' + } + _names_of_FUNCTIONS_ksh() { + # mksh = typeset +f + # ksh = typeset +p -f | perl -pe 's/\(\).*//' + typeset +f | perl -pe 's/\(\).*//; s/ .*//;' + } + _names_of_FUNCTIONS_zsh() { + print -l ${(k)functions} + } + _bodies_of_FUNCTIONS() { + _bodies_of_FUNCTIONS_$_shell_DIALECT "$@" + } + _bodies_of_FUNCTIONS_sh() { + LANG=C type "$@" | perl -ne '/^(\S+) is a function$/ or print' + } + _bodies_of_FUNCTIONS_bash() { + typeset -f "$@" + } + _bodies_of_FUNCTIONS_ksh() { + functions "$@" + } + _bodies_of_FUNCTIONS_zsh() { + typeset -f "$@" + } + _names_of_VARIABLES() { + _names_of_VARIABLES_$_shell_DIALECT + } + _names_of_VARIABLES_sh() { + # This may screw up if variables contain \n and = + set | perl -ne 's/^(\S+?)=.*/$1/ and print;' + } + _names_of_VARIABLES_bash() { + compgen -A variable + } + _names_of_VARIABLES_ksh() { + # mksh: typeset +p | + # perl -pe 's/^(type)?set( [-+][a-zA-Z0-9]*)* //; s/(\[\d+\])?=.*//' | + # uniq + # ksh: typeset +p | perl -pe 's/^typeset .. //' + typeset +p | + perl -pe 's/^(type)?set( [-+][a-zA-Z0-9]*)* //; s/(\[\d+\])?=.*//' | + uniq + } + _names_of_VARIABLES_zsh() { + print -l ${(k)parameters} + } + _bodies_of_VARIABLES() { + _bodies_of_VARIABLES_$_shell_DIALECT "$@" + } + _bodies_of_VARIABLES_sh() { + # Crappy typeset -p + for _i in "$@" + do + perl -e 'print @ARGV' "$_i=" + eval echo "\"\$$_i\"" | perl -e '$/=undef; $a=<>; chop($a); print $a' | + perl -pe 's/[\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\^\*\<\=\>\~\|\; \"\!\$\&\202-\377]/\\$&/go;'"s/'/\\\'/g; s/[\n]/'\\n'/go;"; + echo + done + } + _bodies_of_VARIABLES_bash() { + typeset -p "$@" + } + _bodies_of_VARIABLES_ksh() { + typeset -p "$@" + } + _bodies_of_VARIABLES_zsh() { + typeset -p "$@" + } + _ignore_HARDCODED() { + _ignore_HARDCODED_$_shell_DIALECT + } + _ignore_HARDCODED_sh() { + # These names cannot be detected + echo '(_|TIMEOUT|IFS)' + } + _ignore_HARDCODED_bash() { + # Copying $RANDOM will cause it not to be random + # The rest cannot be detected as read-only + echo '(RANDOM|_|TIMEOUT|GROUPS|FUNCNAME|DIRSTACK|PIPESTATUS|USERNAME|BASHPID|BASH_[A-Z_]+)' + } + _ignore_HARDCODED_ksh() { + # These names cannot be detected + echo '(_|TIMEOUT|IFS)' + } + _ignore_HARDCODED_zsh() { + # These names cannot be detected + echo '([-\?\#\!\$\*\@\_0]|zsh_eval_context|ZSH_EVAL_CONTEXT|LINENO|IFS|commands|functions|options|aliases|EUID|EGID|UID|GID|dis_patchars|patchars|terminfo|galiases|keymaps|parameters|jobdirs|dirstack|functrace|funcsourcetrace|zsh_scheduled_events|dis_aliases|dis_reswords|dis_saliases|modules|reswords|saliases|widgets|userdirs|historywords|nameddirs|termcap|dis_builtins|dis_functions|jobtexts|funcfiletrace|dis_galiases|builtins|history|jobstates|funcstack|run-help)' + } + _ignore_READONLY() { + _ignore_READONLY_$_shell_DIALECT + } + _parse_READONLY() { + # shellcheck disable=SC1078,SC1079,SC2026 + perl -e '@r = map { + chomp; + # sh on UnixWare: readonly TIMEOUT + # ash: readonly var='val' + # ksh: var='val' + # mksh: PIPESTATUS[0] + s/^(readonly )?([^=\[ ]*?)(\[\d+\])?(=.*|)$/$2/ or + # bash: declare -ar BASH_VERSINFO=([0]="4" [1]="4") + # zsh: typeset -r var='val' + s/^\S+\s+\S+\s+(\S[^=]*)(=.*|$)/$1/; + $_ } <>; + $vars = join "|",map { quotemeta $_ } @r; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + ' + } + _ignore_READONLY_sh() { + readonly | _parse_READONLY + } + _ignore_READONLY_bash() { + readonly | _parse_READONLY + } + _ignore_READONLY_ksh() { + readonly | _parse_READONLY + } + _ignore_READONLY_zsh() { + typeset -pr | _parse_READONLY + } + _remove_bad_NAMES() { + # Do not transfer vars and funcs from env_parallel + # shellcheck disable=SC2006 + _ignore_RO="`_ignore_READONLY`" + # shellcheck disable=SC2006 + _ignore_HARD="`_ignore_HARDCODED`" + # To avoid depending on grep dialect, use Perl version of: + # grep -Ev '^(...)$' | + perl -ne '/^( + PARALLEL_ENV| + PARALLEL_TMP| + _alias_NAMES| + _bodies_of_ALIASES| + _bodies_of_FUNCTIONS| + _bodies_of_VARIABLES| + _error_PAR| + _function_NAMES| + _get_ignored_VARS| + _grep_REGEXP| + _ignore_HARD| + _ignore_HARDCODED| + _ignore_READONLY| + _ignore_RO| + _ignore_UNDERSCORE| + _list_alias_BODIES| + _list_function_BODIES| + _list_variable_VALUES| + _make_grep_REGEXP| + _names_of_ALIASES| + _names_of_FUNCTIONS| + _names_of_VARIABLES| + _names_of_maybe_FUNCTIONS| + _parallel_exit_CODE| + _prefix_PARALLEL_ENV| + _prefix_PARALLEL_ENV| + _remove_bad_NAMES| + _remove_readonly| + _variable_NAMES| + _warning_PAR| + _which_PAR)$/x and next; + # Filter names matching --env + /^'"$_grep_REGEXP"'$/ or next; + /^'"$_ignore_UNDERSCORE"'$/ and next; + # Remove readonly variables + /^'"$_ignore_RO"'$/ and next; + /^'"$_ignore_HARD"'$/ and next; + print;' + } + _prefix_PARALLEL_ENV_bash() { + shopt 2>/dev/null | + perl -pe 's:\s+off:;: and s/^/shopt -u /; + s:\s+on:;: and s/^/shopt -s /; + s:;$:&>/dev/null;:'; + echo 'shopt -s expand_aliases &>/dev/null'; + } + + _get_ignored_VARS() { + perl -e ' + for(@ARGV){ + $next_is_env and push @envvar, split/,/, $_; + $next_is_env=/^--env$/; + } + if(grep { /^_$/ } @envvar) { + if(not open(IN, "<", "$ENV{HOME}/.parallel/ignored_vars")) { + print STDERR "parallel: Error: ", + "Run \"parallel --record-env\" in a clean environment first.\n"; + } else { + chomp(@ignored_vars = <IN>); + } + } + if($ENV{PARALLEL_IGNORED_NAMES}) { + push @ignored_vars, split/\s+/, $ENV{PARALLEL_IGNORED_NAMES}; + chomp @ignored_vars; + } + $vars = join "|",map { quotemeta $_ } @ignored_vars; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + ' -- "$@" + } + + # Get the --env variables if set + # --env _ should be ignored + # and convert a b c to (a|b|c) + # If --env not set: Match everything (.*) + _make_grep_REGEXP() { + perl -e ' + for(@ARGV){ + /^_$/ and $next_is_env = 0; + $next_is_env and push @envvar, split/,/, $_; + $next_is_env = /^--env$/; + } + $vars = join "|",map { quotemeta $_ } @envvar; + print $vars ? "($vars)" : "(.*)"; + ' -- "$@" + } + _which_PAR() { + # type returns: + # ll is an alias for ls -l (in ash) + # bash is a tracked alias for /bin/bash + # true is a shell builtin (in bash) + # myfunc is a function (in bash) + # myfunc is a shell function (in zsh) + # which is /usr/bin/which (in sh, bash) + # which is hashed (/usr/bin/which) + # gi is aliased to `grep -i' (in bash) + # aliased to `alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde' + # Return 0 if found, 1 otherwise + LANG=C type "$@" | + perl -pe '$exit += (s/ is an alias for .*// || + s/ is aliased to .*// || + s/ is a function// || + s/ is a shell function// || + s/ is a shell builtin// || + s/.* is hashed .(\S+).$/$1/ || + s/.* is (a tracked alias for )?//); + END { exit not $exit }' + } + _warning_PAR() { + echo "env_parallel: Warning: $*" >&2 + } + _error_PAR() { + echo "env_parallel: Error: $*" >&2 + } + + if _which_PAR parallel >/dev/null; then + true parallel found in path + else + # shellcheck disable=SC2016 + _error_PAR 'parallel must be in $PATH.' + return 255 + fi + + # Grep regexp for vars given by --env + # shellcheck disable=SC2006 + _grep_REGEXP="`_make_grep_REGEXP \"$@\"`" + unset _make_grep_REGEXP + + # Deal with --env _ + # shellcheck disable=SC2006 + _ignore_UNDERSCORE="`_get_ignored_VARS \"$@\"`" + unset _get_ignored_VARS + + # --record-env + if perl -e 'exit grep { /^--record-env$/ } @ARGV' -- "$@"; then + true skip + else + (_names_of_ALIASES; + _names_of_FUNCTIONS; + _names_of_VARIABLES) | + cat > "$HOME"/.parallel/ignored_vars + return 0 + fi + + # --session + if perl -e 'exit grep { /^--session$/ } @ARGV' -- "$@"; then + true skip + else + # Insert ::: between each level of session + # so you can pop off the last ::: at --end-session + # shellcheck disable=SC2006 + PARALLEL_IGNORED_NAMES="`echo \"$PARALLEL_IGNORED_NAMES\"; + echo :::; + (_names_of_ALIASES; + _names_of_FUNCTIONS; + _names_of_VARIABLES) | perl -ne ' + BEGIN{ + map { $ignored_vars{$_}++ } + split/\s+/, $ENV{PARALLEL_IGNORED_NAMES}; + } + chomp; + for(split/\s+/) { + if(not $ignored_vars{$_}) { + print $_,\"\\n\"; + } + } + '`" + export PARALLEL_IGNORED_NAMES + return 0 + fi + if perl -e 'exit grep { /^--end.?session$/ } @ARGV' -- "$@"; then + true skip + else + # Pop off last ::: from PARALLEL_IGNORED_NAMES + # shellcheck disable=SC2006 + PARALLEL_IGNORED_NAMES="`perl -e ' + $ENV{PARALLEL_IGNORED_NAMES} =~ s/(.*):::.*?$/$1/s; + print $ENV{PARALLEL_IGNORED_NAMES} + '`" + return 0 + fi + # Grep alias names + # shellcheck disable=SC2006 + _alias_NAMES="`_names_of_ALIASES | _remove_bad_NAMES | xargs echo`" + _list_alias_BODIES="_bodies_of_ALIASES $_alias_NAMES" + if [ "$_alias_NAMES" = "" ] ; then + # no aliases selected + _list_alias_BODIES="true" + fi + unset _alias_NAMES + + # Grep function names + # shellcheck disable=SC2006 + _function_NAMES="`_names_of_FUNCTIONS | _remove_bad_NAMES | xargs echo`" + _list_function_BODIES="_bodies_of_FUNCTIONS $_function_NAMES" + if [ "$_function_NAMES" = "" ] ; then + # no functions selected + _list_function_BODIES="true" + fi + unset _function_NAMES + + # Grep variable names + # shellcheck disable=SC2006 + _variable_NAMES="`_names_of_VARIABLES | _remove_bad_NAMES | xargs echo`" + _list_variable_VALUES="_bodies_of_VARIABLES $_variable_NAMES" + if [ "$_variable_NAMES" = "" ] ; then + # no variables selected + _list_variable_VALUES="true" + fi + unset _variable_NAMES + + if $eval_needed ; then + # shellcheck disable=SC2006 + PARALLEL_ENV="` + eval $_list_alias_BODIES; + eval $_list_function_BODIES; + eval $_list_variable_VALUES; + `" + else + # shellcheck disable=SC2006 + PARALLEL_ENV="` + $_prefix_PARALLEL_ENV; + $_list_alias_BODIES; + $_list_function_BODIES; + $_list_variable_VALUES; + `" + fi + export PARALLEL_ENV + # Free up some env space + unset _list_alias_BODIES _list_variable_VALUES _list_function_BODIES + unset _bodies_of_ALIASES _bodies_of_VARIABLES _bodies_of_FUNCTIONS + unset _names_of_ALIASES _names_of_VARIABLES _names_of_FUNCTIONS + unset _ignore_HARDCODED _ignore_READONLY _ignore_UNDERSCORE + unset _remove_bad_NAMES _grep_REGEXP + unset _prefix_PARALLEL_ENV + # Test if environment is too big by running 'true' + # shellcheck disable=SC2006,SC2092 + if `_which_PAR true` >/dev/null 2>/dev/null ; then + parallel "$@" + _parallel_exit_CODE=$? + # Clean up variables/functions + unset PARALLEL_ENV + unset _which_PAR _which_TRUE + unset _warning_PAR _error_PAR + # Unset _parallel_exit_CODE before return + eval "unset _parallel_exit_CODE; return $_parallel_exit_CODE" + else + unset PARALLEL_ENV; + _error_PAR "Your environment is too big." + _error_PAR "You can try 3 different approaches:" + _error_PAR "1. Run 'env_parallel --session' before you set" + _error_PAR " variables or define functions." + _error_PAR "2. Use --env and only mention the names to copy." + _error_PAR "3. Try running this in a clean environment once:" + _error_PAR " env_parallel --record-env" + _error_PAR " And then use '--env _'" + _error_PAR "For details see: man env_parallel" + return 255 + fi +} + +parset() { + _parset_PARALLEL_PRG=parallel + _parset_main "$@" +} + +env_parset() { + _parset_PARALLEL_PRG=env_parallel + _parset_main "$@" +} + +_parset_main() { + # If $1 contains ',' or space: + # Split on , to get the destination variable names + # If $1 is a single destination variable name: + # Treat it as the name of an array + # + # # Create array named myvar + # parset myvar echo ::: {1..10} + # echo ${myvar[5]} + # + # # Put output into $var_a $var_b $var_c + # varnames=(var_a var_b var_c) + # parset "${varnames[*]}" echo ::: {1..3} + # echo $var_c + # + # # Put output into $var_a4 $var_b4 $var_c4 + # parset "var_a4 var_b4 var_c4" echo ::: {1..3} + # echo $var_c4 + + _parset_NAME="$1" + if [ "$_parset_NAME" = "" ] ; then + echo parset: Error: No destination variable given. >&2 + echo parset: Error: Try: >&2 + echo parset: Error: ' ' parset myarray echo ::: foo bar >&2 + return 255 + fi + if [ "$_parset_NAME" = "--help" ] ; then + echo parset: Error: Usage: >&2 + echo parset: Error: ' ' parset varname GNU Parallel options and command >&2 + echo + parallel --help + return 255 + fi + if [ "$_parset_NAME" = "--version" ] ; then + # shellcheck disable=SC2006 + echo "parset 20240222 (GNU parallel `parallel --minversion 1`)" + echo "Copyright (C) 2007-2024 Ole Tange, http://ole.tange.dk and Free Software" + echo "Foundation, Inc." + echo "License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>" + echo "This is free software: you are free to change and redistribute it." + echo "GNU parallel comes with no warranty." + echo + echo "Web site: https://www.gnu.org/software/parallel" + echo + echo "When using programs that use GNU Parallel to process data for publication" + echo "please cite as described in 'parallel --citation'." + echo + return 255 + fi + shift + + # Bash: declare -A myassoc=( ) + # Zsh: typeset -A myassoc=( ) + # Ksh: typeset -A myassoc=( ) + # shellcheck disable=SC2039,SC2169 + if (typeset -p "$_parset_NAME" 2>/dev/null; echo) | + perl -ne 'exit not (/^declare[^=]+-A|^typeset[^=]+-A/)' ; then + # This is an associative array + # shellcheck disable=SC2006 + eval "`$_parset_PARALLEL_PRG -k --_parset assoc,"$_parset_NAME" "$@"`" + # The eval returns the function! + else + # This is a normal array or a list of variable names + # shellcheck disable=SC2006 + eval "`$_parset_PARALLEL_PRG -k --_parset var,"$_parset_NAME" "$@"`" + # The eval returns the function! + fi +} diff --git a/src/env_parallel.pdksh b/src/env_parallel.pdksh new file mode 100755 index 0000000..1a25dcd --- /dev/null +++ b/src/env_parallel.pdksh @@ -0,0 +1,183 @@ +#!/usr/bin/env pdksh + +# This file must be sourced in pdksh: +# +# source env_parallel.pdksh +# +# after which 'env_parallel' works +# +# +# Copyright (C) 2016-2024 Ole Tange, http://ole.tange.dk and Free +# Software Foundation, Inc. +# +# This program 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. +# +# This program 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 this program; if not, see <http://www.gnu.org/licenses/> +# or write to the Free Software Foundation, Inc., 51 Franklin St, +# Fifth Floor, Boston, MA 02110-1301 USA +# +# SPDX-FileCopyrightText: 2021-2024 Ole Tange, http://ole.tange.dk and Free Software and Foundation, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later + +env_parallel() { + # env_parallel.pdksh + + _names_of_ALIASES() { + compgen -a + } + _bodies_of_ALIASES() { + alias "$@" | perl -pe 's/^/alias /' + } + _names_of_FUNCTIONS() { + compgen -A function + } + _bodies_of_FUNCTIONS() { + typeset -f "$@" + } + _names_of_VARIABLES() { + compgen -A variable + } + _bodies_of_VARIABLES() { + typeset -p "$@" + } + _remove_bad_NAMES() { + _tmp_READONLY="$(mktemp)" + readonly > "$_tmp_READONLY" + # Do not transfer vars and funcs from env_parallel + grep -Ev '^(_names_of_ALIASES|_bodies_of_ALIASES|_names_of_maybe_FUNCTIONS|_names_of_FUNCTIONS|_bodies_of_FUNCTIONS|_names_of_VARIABLES|_bodies_of_VARIABLES|_remove_bad_NAMES|_prefix_PARALLEL_ENV|_get_ignored_VARS|_make_grep_REGEXP|_ignore_UNDERSCORE|_alias_NAMES|_list_alias_BODIES|_function_NAMES|_list_function_BODIES|_variable_NAMES|_list_variable_VALUES|_prefix_PARALLEL_ENV|PARALLEL_ENV|PARALLEL_TMP)$' | + # Filter names matching --env + grep -E "^$_grep_REGEXP"\$ | grep -vE "^$_ignore_UNDERSCORE"\$ | + grep -vFf $_tmp_READONLY | + grep -Ev '^(PIPESTATUS)' + rm $_tmp_READONLY + unset _tmp_READONLY + } + _prefix_PARALLEL_ENV() { + shopt 2>/dev/null | + perl -pe 's:\s+off:;: and s/^/shopt -u /; + s:\s+on:;: and s/^/shopt -s /; + s:;$:&>/dev/null;:'; + echo 'shopt -s expand_aliases &>/dev/null'; + } + + _get_ignored_VARS() { + perl -e ' + for(@ARGV){ + $next_is_env and push @envvar, split/,/, $_; + $next_is_env=/^--env$/; + } + if(grep { /^_$/ } @envvar) { + if(not open(IN, "<", "$ENV{HOME}/.parallel/ignored_vars")) { + print STDERR "parallel: Error: ", + "Run \"parallel --record-env\" in a clean environment first.\n"; + } else { + chomp(@ignored_vars = <IN>); + $vars = join "|",map { quotemeta $_ } @ignored_vars; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + } + } + ' -- "$@" + } + + # Get the --env variables if set + # --env _ should be ignored + # and convert a b c to (a|b|c) + # If --env not set: Match everything (.*) + _make_grep_REGEXP() { + perl -e ' + for(@ARGV){ + /^_$/ and $next_is_env = 0; + $next_is_env and push @envvar, split/,/, $_; + $next_is_env = /^--env$/; + } + $vars = join "|",map { quotemeta $_ } @envvar; + print $vars ? "($vars)" : "(.*)"; + ' -- "$@" + } + + if which parallel | grep 'no parallel in' >/dev/null; then + echo 'env_parallel: Error: parallel must be in $PATH.' >&2 + return 1 + fi + if which parallel >/dev/null; then + true which on linux + else + echo 'env_parallel: Error: parallel must be in $PATH.' >&2 + return 1 + fi + + # Grep regexp for vars given by --env + _grep_REGEXP="`_make_grep_REGEXP \"$@\"`" + unset _make_grep_REGEXP + + # Deal with --env _ + _ignore_UNDERSCORE="`_get_ignored_VARS \"$@\"`" + unset _get_ignored_VARS + + # --record-env + if perl -e 'exit grep { /^--record-env$/ } @ARGV' -- "$@"; then + true skip + else + (_names_of_ALIASES; + _names_of_FUNCTIONS; + _names_of_VARIABLES) | + cat > $HOME/.parallel/ignored_vars + return 0 + fi + + # Grep alias names + _alias_NAMES="`_names_of_ALIASES | _remove_bad_NAMES | xargs echo`" + _list_alias_BODIES="_bodies_of_ALIASES $_alias_NAMES" + if [ "$_alias_NAMES" = "" ] ; then + # no aliases selected + _list_alias_BODIES="true" + fi + unset _alias_NAMES + + # Grep function names + _function_NAMES="`_names_of_FUNCTIONS | _remove_bad_NAMES | xargs echo`" + _list_function_BODIES="_bodies_of_FUNCTIONS $_function_NAMES" + if [ "$_function_NAMES" = "" ] ; then + # no functions selected + _list_function_BODIES="true" + fi + unset _function_NAMES + + # Grep variable names + _variable_NAMES="`_names_of_VARIABLES | _remove_bad_NAMES | xargs echo`" + _list_variable_VALUES="_bodies_of_VARIABLES $_variable_NAMES" + if [ "$_variable_NAMES" = "" ] ; then + # no variables selected + _list_variable_VALUES="true" + fi + unset _variable_NAMES + + # eval is needed for aliases - cannot explain why + export PARALLEL_ENV="` + eval $_list_alias_BODIES; + $_list_function_BODIES + $_list_variable_VALUES; + `"; + unset _list_alias_BODIES _list_variable_VALUES _list_function_BODIES + unset _bodies_of_ALIASES _bodies_of_VARIABLES _bodies_of_FUNCTIONS + unset _names_of_ALIASES _names_of_VARIABLES _names_of_FUNCTIONS + unset _ignore_HARDCODED _ignore_READONLY _ignore_UNDERSCORE + unset _remove_bad_NAMES _grep_REGEXP + unset _prefix_PARALLEL_ENV + `which parallel` "$@" + _parallel_exit_CODE=$? + unset PARALLEL_ENV; + unset _which_PAR _which_TRUE + unset _warning_PAR _error_PAR + # Unset _parallel_exit_CODE before return + eval "unset _parallel_exit_CODE; return $_parallel_exit_CODE" +} diff --git a/src/env_parallel.pod b/src/env_parallel.pod new file mode 100644 index 0000000..c59ba5d --- /dev/null +++ b/src/env_parallel.pod @@ -0,0 +1,942 @@ +#!/usr/bin/perl -w + +# SPDX-FileCopyrightText: 2021-2024 Ole Tange, http://ole.tange.dk and Free Software and Foundation, Inc. +# SPDX-License-Identifier: GFDL-1.3-or-later +# SPDX-License-Identifier: CC-BY-SA-4.0 + +=encoding utf8 + +=head1 NAME + +env_parallel - export environment to GNU parallel + + +=head1 SYNOPSIS + +B<env_parallel> [--record-env|--session|--end-session] + [options for GNU Parallel] + + +=head1 DESCRIPTION + +B<env_parallel> is a shell function that exports the current +environment to GNU B<parallel>. + +If the shell function is not loaded, a dummy script will be run +instead that explains how to install the function. + +B<env_parallel> is 100 ms slower at startup than pure GNU B<parallel>, +and takes up to 30% longer to start a job (typically 15 ms). + +Due to the problem with environment space (see below) you are +recommended only to transfer the environment that you need. + +To help you do that, you can mark names that should not be +transferred. This can be done with either B<--session> or +B<--record-env>. + + # Record the "clean" environment (this only needs to be run once) + env_parallel --record-env + + # Optionally edit ~/.parallel/ignored_vars (only needed once) + + # Define whatever you want to use + myfunc() { myalias and functions $myvar work. $1.; } + alias myalias='echo Aliases' + myvar='and variables' + + # Use --env _ to only transfer the names not in the "empty" environment + env_parallel --env _ -S localhost myfunc ::: Hooray + +Or: + + # Do --record-env into $PARALLEL_IGNORED_NAMES + env_parallel --session + + # Define whatever you want to use + myfunc() { myalias and functions $myvar work. $1.; } + alias myalias='echo Aliases' + myvar='and variables' + + # env_parallel will not export names in $PARALLEL_IGNORED_NAMES + env_parallel -S localhost myfunc ::: Hooray + + # Optionally + env_parallel --end-session + +In B<csh> B<--session> is not supported: + + # Record the "clean" environment - this only needs to be run once + env_parallel --record-env + + # Optionally edit ~/.parallel/ignored_vars - only needed once + + # Define whatever you want to use + alias myalias 'echo Aliases $myvar \!*.' + set myvar='and variables' + + # Use --env _ to only transfer the names not in the "empty" environment + env_parallel --env _ -S localhost myalias ::: work + +=head2 Environment space + +By default B<env_parallel> will export all environment variables, +arrays, aliases, functions and shell options (see details for the +individual shells below). + +But this only works if the size of the current environment is smaller +than the maximal length of a command and smaller than half of the max +if running remotely. E.g. The max size of Bash's command is 128 KB, so +B<env_parallel> will fail if 'B<set | wc -c>' is bigger than 128 +KB. Technically the limit is in execve(1) which IPC::open3 uses. + +Bash completion functions are well-known for taking up well over 128 +KB of environment space and the primary reason for causing +B<env_parallel> to fail. + +Instead you can use B<--env> to specify which variables, arrays, +aliases and functions to export as this will only export those with +the given name. Or follow the recommended usage in shown in +DESCRIPTION. + + +=head1 OPTIONS + +Same as GNU B<parallel> in addition to these: + +=over 4 + +=item B<--end-session> + +Undo last B<--session> + + +=item B<--record-env> + +Record all names currently defined to be ignored every time running +B<env_parallel> in the future. + + +=item B<--session> + +Ignore all names currently defined. Aliases, variables, arrays, and +functions currently defined will not be transferred. + +But names defined I<after> running B<parallel --session> I<will> be +transferred. + +This is only valid in the running shell, and can be undone with +B<parallel --end-session>. + +You can run multiple B<--session> inside each other: + + env_parallel --session + var=not + # var is transferred + env_parallel -Slocalhost 'echo var is $var' ::: ignored + env_parallel --session + # var is not transferred + env_parallel -Slocalhost 'echo var is $var' ::: ignored + env_parallel --end-session + # var is transferred again + env_parallel -Slocalhost 'echo var is $var' ::: ignored + + + +=back + + +=head1 SUPPORTED SHELLS + +=head2 Ash + +=head3 Installation + +Put this in $HOME/.profile: + + . env_parallel.ash + +E.g. by doing: + + echo '. env_parallel.ash' >> $HOME/.profile + +=head3 Supported use + +B<--env> is supported to export only the variable, or alias with the +given name. Multiple B<--env>s can be given. + +B<--session> is supported. + +=over 8 + +=item aliases + + alias myecho='echo aliases' + env_parallel myecho ::: work + env_parallel -S server myecho ::: work + env_parallel --env myecho myecho ::: work + env_parallel --env myecho -S server myecho ::: work + + alias multiline='echo multiline + echo aliases' + env_parallel multiline ::: work + env_parallel -S server multiline ::: work + env_parallel --env multiline multiline ::: work + env_parallel --env multiline -S server multiline ::: work + +=item functions + + ash cannot list defined functions - thus is not supported. + +=item variables + + myvar=variables + env_parallel echo '$myvar' ::: work + env_parallel -S server echo '$myvar' ::: work + env_parallel --env myvar echo '$myvar' ::: work + env_parallel --env myvar -S server echo '$myvar' ::: work + +=item arrays + +Arrays are not supported by Ash. + +=back + +=head2 Bash + +=head3 Installation + +Put this in $HOME/.bashrc: + + . env_parallel.bash + +E.g. by doing: + + echo '. env_parallel.bash' >> $HOME/.bashrc + +=head3 Supported use + +B<--env> is supported to export only the variable, alias, function, or +array with the given name. Multiple B<--env>s can be given. + +B<--session> is supported. + +=over 8 + +=item aliases + + alias myecho='echo aliases' + env_parallel myecho ::: work + env_parallel -S server myecho ::: work + env_parallel --env myecho myecho ::: work + env_parallel --env myecho -S server myecho ::: work + + alias multiline='echo multiline + echo aliases' + env_parallel 'multiline {}; + echo but only when followed by a newline' ::: work + env_parallel -S server 'multiline {}; + echo but only when followed by a newline' ::: work + env_parallel --env multiline 'multiline {}; + echo but only when followed by a newline' ::: work + env_parallel --env multiline -S server 'multiline {}; + echo but only when followed by a newline' ::: work + +=item functions + + myfunc() { echo functions $*; } + env_parallel myfunc ::: work + env_parallel -S server myfunc ::: work + env_parallel --env myfunc myfunc ::: work + env_parallel --env myfunc -S server myfunc ::: work + +=item variables + + myvar=variables + env_parallel echo '$myvar' ::: work + env_parallel -S server echo '$myvar' ::: work + env_parallel --env myvar echo '$myvar' ::: work + env_parallel --env myvar -S server echo '$myvar' ::: work + +=item arrays + + myarray=(arrays work, too) + env_parallel -k echo '${myarray[{}]}' ::: 0 1 2 + env_parallel -k -S server echo '${myarray[{}]}' ::: 0 1 2 + env_parallel -k --env myarray echo '${myarray[{}]}' ::: 0 1 2 + env_parallel -k --env myarray -S server \ + echo '${myarray[{}]}' ::: 0 1 2 + +=back + +=head3 BUGS + +Due to a bug in Bash, aliases containing newlines must be followed by +a newline in the command. Some systems are not affected by this bug, +but will print a warning anyway. + +=head2 csh + +B<env_parallel> for B<csh> breaks B<$PARALLEL>, so do not use +B<$PARALLEL>. + +=head3 Installation + +Put this in $HOME/.cshrc: + + source `which env_parallel.csh` + +E.g. by doing: + + echo 'source `which env_parallel.csh`' >> $HOME/.cshrc + +=head3 Supported use + +B<--env> is supported to export only the variable, alias, or +array with the given name. Multiple B<--env>s can be given. + +=over 8 + +=item aliases + + alias myecho 'echo aliases' + env_parallel myecho ::: work + env_parallel -S server myecho ::: work + env_parallel --env myecho myecho ::: work + env_parallel --env myecho -S server myecho ::: work + +=item functions + +Not supported by B<csh>. + +=item variables + + set myvar=variables + env_parallel echo '$myvar' ::: work + env_parallel -S server echo '$myvar' ::: work + env_parallel --env myvar echo '$myvar' ::: work + env_parallel --env myvar -S server echo '$myvar' ::: work + +=item arrays with no special chars + + set myarray=(arrays work, too) + env_parallel -k echo \$'{myarray[{}]}' ::: 1 2 3 + env_parallel -k -S server echo \$'{myarray[{}]}' ::: 1 2 3 + env_parallel -k --env myarray echo \$'{myarray[{}]}' ::: 1 2 3 + env_parallel -k --env myarray -S server \ + echo \$'{myarray[{}]}' ::: 1 2 3 + +=back + + +=head2 Dash + +=head3 Installation + +Put this in $HOME/.profile: + + . env_parallel.dash + +E.g. by doing: + + echo '. env_parallel.dash' >> $HOME/.profile + +=head3 Supported use + +B<--env> is supported to export only the variable, or alias with the +given name. Multiple B<--env>s can be given. + +B<--session> is supported. + +=over 8 + +=item aliases + + alias myecho='echo aliases' + env_parallel myecho ::: work + env_parallel -S server myecho ::: work + env_parallel --env myecho myecho ::: work + env_parallel --env myecho -S server myecho ::: work + + alias multiline='echo multiline + echo aliases' + env_parallel multiline ::: work + env_parallel -S server multiline ::: work + env_parallel --env multiline multiline ::: work + env_parallel --env multiline -S server multiline ::: work + +=item functions + + dash cannot list defined functions - thus is not supported. + +=item variables + + myvar=variables + env_parallel echo '$myvar' ::: work + env_parallel -S server echo '$myvar' ::: work + env_parallel --env myvar echo '$myvar' ::: work + env_parallel --env myvar -S server echo '$myvar' ::: work + +=item arrays + + dash does not support arrays. + +=back + + +=head2 fish + +=head3 Installation + +Put this in $HOME/.config/fish/config.fish: + + source (which env_parallel.fish) + +E.g. by doing: + + echo 'source (which env_parallel.fish)' \ + >> $HOME/.config/fish/config.fish + +=head3 Supported use + +B<--env> is supported to export only the variable, alias, function, or +array with the given name. Multiple B<--env>s can be given. + +B<--session> is supported. + +=over 8 + +=item aliases + + alias myecho 'echo aliases' + env_parallel myecho ::: work + env_parallel -S server myecho ::: work + env_parallel --env myecho myecho ::: work + env_parallel --env myecho -S server myecho ::: work + +=item functions + + function myfunc + echo functions $argv + end + env_parallel myfunc ::: work + env_parallel -S server myfunc ::: work + env_parallel --env myfunc myfunc ::: work + env_parallel --env myfunc -S server myfunc ::: work + +=item variables + + set myvar variables + env_parallel echo '$myvar' ::: work + env_parallel -S server echo '$myvar' ::: work + env_parallel --env myvar echo '$myvar' ::: work + env_parallel --env myvar -S server echo '$myvar' ::: work + +=item arrays + + set myarray arrays work, too + env_parallel -k echo '$myarray[{}]' ::: 1 2 3 + env_parallel -k -S server echo '$myarray[{}]' ::: 1 2 3 + env_parallel -k --env myarray echo '$myarray[{}]' ::: 1 2 3 + env_parallel -k --env myarray -S server \ + echo '$myarray[{}]' ::: 1 2 3 + +=back + + +=head2 ksh + +=head3 Installation + +Put this in $HOME/.kshrc: + + source env_parallel.ksh + +E.g. by doing: + + echo 'source env_parallel.ksh' >> $HOME/.kshrc + +=head3 Supported use + +B<--env> is supported to export only the variable, alias, function, or +array with the given name. Multiple B<--env>s can be given. + +B<--session> is supported. + +=over 8 + +=item aliases + + alias myecho='echo aliases' + env_parallel myecho ::: work + env_parallel -S server myecho ::: work + env_parallel --env myecho myecho ::: work + env_parallel --env myecho -S server myecho ::: work + + alias multiline='echo multiline + echo aliases' + env_parallel multiline ::: work + env_parallel -S server multiline ::: work + env_parallel --env multiline multiline ::: work + env_parallel --env multiline -S server multiline ::: work + +=item functions + + myfunc() { echo functions $*; } + env_parallel myfunc ::: work + env_parallel -S server myfunc ::: work + env_parallel --env myfunc myfunc ::: work + env_parallel --env myfunc -S server myfunc ::: work + +=item variables + + myvar=variables + env_parallel echo '$myvar' ::: work + env_parallel -S server echo '$myvar' ::: work + env_parallel --env myvar echo '$myvar' ::: work + env_parallel --env myvar -S server echo '$myvar' ::: work + +=item arrays + + myarray=(arrays work, too) + env_parallel -k echo '${myarray[{}]}' ::: 0 1 2 + env_parallel -k -S server echo '${myarray[{}]}' ::: 0 1 2 + env_parallel -k --env myarray echo '${myarray[{}]}' ::: 0 1 2 + env_parallel -k --env myarray -S server \ + echo '${myarray[{}]}' ::: 0 1 2 + +=back + + +=head2 mksh + +=head3 Installation + +Put this in $HOME/.mkshrc: + + source env_parallel.mksh + +E.g. by doing: + + echo 'source env_parallel.mksh' >> $HOME/.mkshrc + +=head3 Supported use + +B<--env> is supported to export only the variable, alias, function, or +array with the given name. Multiple B<--env>s can be given. + +B<--session> is supported. + +=over 8 + +=item aliases + + alias myecho='echo aliases' + env_parallel myecho ::: work + env_parallel -S server myecho ::: work + env_parallel --env myecho myecho ::: work + env_parallel --env myecho -S server myecho ::: work + + alias multiline='echo multiline + echo aliases' + env_parallel multiline ::: work + env_parallel -S server multiline ::: work + env_parallel --env multiline multiline ::: work + env_parallel --env multiline -S server multiline ::: work + +=item functions + + myfunc() { echo functions $*; } + env_parallel myfunc ::: work + env_parallel -S server myfunc ::: work + env_parallel --env myfunc myfunc ::: work + env_parallel --env myfunc -S server myfunc ::: work + +=item variables + + myvar=variables + env_parallel echo '$myvar' ::: work + env_parallel -S server echo '$myvar' ::: work + env_parallel --env myvar echo '$myvar' ::: work + env_parallel --env myvar -S server echo '$myvar' ::: work + +=item arrays + + myarray=(arrays work, too) + env_parallel -k echo '${myarray[{}]}' ::: 0 1 2 + env_parallel -k -S server echo '${myarray[{}]}' ::: 0 1 2 + env_parallel -k --env myarray echo '${myarray[{}]}' ::: 0 1 2 + env_parallel -k --env myarray -S server \ + echo '${myarray[{}]}' ::: 0 1 2 + +=back + + +=head2 pdksh + +=head3 Installation + +Put this in $HOME/.profile: + + source env_parallel.pdksh + +E.g. by doing: + + echo 'source env_parallel.pdksh' >> $HOME/.profile + +=head3 Supported use + +B<--env> is supported to export only the variable, alias, function, or +array with the given name. Multiple B<--env>s can be given. + +B<--session> is supported. + +=over 8 + +=item aliases + + alias myecho="echo aliases"; + env_parallel myecho ::: work; + env_parallel -S server myecho ::: work; + env_parallel --env myecho myecho ::: work; + env_parallel --env myecho -S server myecho ::: work + +=item functions + + myfunc() { echo functions $*; }; + env_parallel myfunc ::: work; + env_parallel -S server myfunc ::: work; + env_parallel --env myfunc myfunc ::: work; + env_parallel --env myfunc -S server myfunc ::: work + +=item variables + + myvar=variables; + env_parallel echo "\$myvar" ::: work; + env_parallel -S server echo "\$myvar" ::: work; + env_parallel --env myvar echo "\$myvar" ::: work; + env_parallel --env myvar -S server echo "\$myvar" ::: work + +=item arrays + + myarray=(arrays work, too); + env_parallel -k echo "\${myarray[{}]}" ::: 0 1 2; + env_parallel -k -S server echo "\${myarray[{}]}" ::: 0 1 2; + env_parallel -k --env myarray echo "\${myarray[{}]}" ::: 0 1 2; + env_parallel -k --env myarray -S server \ + echo "\${myarray[{}]}" ::: 0 1 2 + +=back + + +=head2 sh + +=head3 Installation + +Put this in $HOME/.profile: + + . env_parallel.sh + +E.g. by doing: + + echo '. env_parallel.sh' >> $HOME/.profile + +=head3 Supported use + +B<--env> is supported to export only the variable, or alias with the +given name. Multiple B<--env>s can be given. + +B<--session> is supported. + +=over 8 + +=item aliases + + sh does not support aliases. + +=item functions + + myfunc() { echo functions $*; } + env_parallel myfunc ::: work + env_parallel -S server myfunc ::: work + env_parallel --env myfunc myfunc ::: work + env_parallel --env myfunc -S server myfunc ::: work + +=item variables + + myvar=variables + env_parallel echo '$myvar' ::: work + env_parallel -S server echo '$myvar' ::: work + env_parallel --env myvar echo '$myvar' ::: work + env_parallel --env myvar -S server echo '$myvar' ::: work + +=item arrays + + sh does not support arrays. + +=back + + +=head2 tcsh + +B<env_parallel> for B<tcsh> breaks B<$PARALLEL>, so do not use +B<$PARALLEL>. + +=head3 Installation + +Put this in $HOME/.tcshrc: + + source `which env_parallel.tcsh` + +E.g. by doing: + + echo 'source `which env_parallel.tcsh`' >> $HOME/.tcshrc + +=head3 Supported use + +B<--env> is supported to export only the variable, alias, or +array with the given name. Multiple B<--env>s can be given. + +=over 8 + +=item aliases + + alias myecho 'echo aliases' + env_parallel myecho ::: work + env_parallel -S server myecho ::: work + env_parallel --env myecho myecho ::: work + env_parallel --env myecho -S server myecho ::: work + +=item functions + +Not supported by B<tcsh>. + +=item variables + + set myvar=variables + env_parallel echo '$myvar' ::: work + env_parallel -S server echo '$myvar' ::: work + env_parallel --env myvar echo '$myvar' ::: work + env_parallel --env myvar -S server echo '$myvar' ::: work + +=item arrays with no special chars + + set myarray=(arrays work, too) + env_parallel -k echo \$'{myarray[{}]}' ::: 1 2 3 + env_parallel -k -S server echo \$'{myarray[{}]}' ::: 1 2 3 + env_parallel -k --env myarray echo \$'{myarray[{}]}' ::: 1 2 3 + env_parallel -k --env myarray -S server \ + echo \$'{myarray[{}]}' ::: 1 2 3 + +=back + + +=head2 Zsh + +=head3 Installation + +Put this in $HOME/.zshrc: + + . env_parallel.zsh + +E.g. by doing: + + echo '. env_parallel.zsh' >> $HOME/.zshenv + +=head3 Supported use + +B<--env> is supported to export only the variable, alias, function, or +array with the given name. Multiple B<--env>s can be given. + +B<--session> is supported. + +=over 8 + +=item aliases + + alias myecho='echo aliases' + env_parallel myecho ::: work + env_parallel -S server myecho ::: work + env_parallel --env myecho myecho ::: work + env_parallel --env myecho -S server myecho ::: work + + alias multiline='echo multiline + echo aliases' + env_parallel multiline ::: work + env_parallel -S server multiline ::: work + env_parallel --env multiline multiline ::: work + env_parallel --env multiline -S server multiline ::: work + +=item functions + + myfunc() { echo functions $*; } + env_parallel myfunc ::: work + env_parallel -S server myfunc ::: work + env_parallel --env myfunc myfunc ::: work + env_parallel --env myfunc -S server myfunc ::: work + +=item variables + + myvar=variables + env_parallel echo '$myvar' ::: work + env_parallel -S server echo '$myvar' ::: work + env_parallel --env myvar echo '$myvar' ::: work + env_parallel --env myvar -S server echo '$myvar' ::: work + +=item arrays + + myarray=(arrays work, too) + env_parallel -k echo '${myarray[{}]}' ::: 1 2 3 + env_parallel -k -S server echo '${myarray[{}]}' ::: 1 2 3 + env_parallel -k --env myarray echo '${myarray[{}]}' ::: 1 2 3 + env_parallel -k --env myarray -S server \ + echo '${myarray[{}]}' ::: 1 2 3 + +=back + + +=head1 EXIT STATUS + +Same as GNU B<parallel>. + + +=head1 AUTHOR + +When using GNU B<env_parallel> for a publication please cite: + +O. Tange (2018): GNU Parallel 2018, March 2018, ISBN 9781387509881, +DOI: 10.5281/zenodo.1146014. + +This helps funding further development; and it won't cost you a cent. +If you pay 10000 EUR you should feel free to use GNU Parallel without citing. + +Copyright (C) 2007-10-18 Ole Tange, http://ole.tange.dk + +Copyright (C) 2008-2010 Ole Tange, http://ole.tange.dk + +Copyright (C) 2010-2024 Ole Tange, http://ole.tange.dk and Free +Software Foundation, Inc. + + +=head1 LICENSE + +This program 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. + +This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + +=head2 Documentation license I + +Permission is granted to copy, distribute and/or modify this +documentation under the terms of the GNU Free Documentation License, +Version 1.3 or any later version published by the Free Software +Foundation; with no Invariant Sections, with no Front-Cover Texts, and +with no Back-Cover Texts. A copy of the license is included in the +file LICENSES/GFDL-1.3-or-later.txt. + + +=head2 Documentation license II + +You are free: + +=over 9 + +=item B<to Share> + +to copy, distribute and transmit the work + +=item B<to Remix> + +to adapt the work + +=back + +Under the following conditions: + +=over 9 + +=item B<Attribution> + +You must attribute the work in the manner specified by the author or +licensor (but not in any way that suggests that they endorse you or +your use of the work). + +=item B<Share Alike> + +If you alter, transform, or build upon this work, you may distribute +the resulting work only under the same, similar or a compatible +license. + +=back + +With the understanding that: + +=over 9 + +=item B<Waiver> + +Any of the above conditions can be waived if you get permission from +the copyright holder. + +=item B<Public Domain> + +Where the work or any of its elements is in the public domain under +applicable law, that status is in no way affected by the license. + +=item B<Other Rights> + +In no way are any of the following rights affected by the license: + +=over 2 + +=item * + +Your fair dealing or fair use rights, or other applicable +copyright exceptions and limitations; + +=item * + +The author's moral rights; + +=item * + +Rights other persons may have either in the work itself or in +how the work is used, such as publicity or privacy rights. + +=back + +=back + +=over 9 + +=item B<Notice> + +For any reuse or distribution, you must make clear to others the +license terms of this work. + +=back + +A copy of the full license is included in the file as +LICENCES/CC-BY-SA-4.0.txt + + +=head1 DEPENDENCIES + +B<env_parallel> uses GNU B<parallel>. + + +=head1 SEE ALSO + +B<parallel>(1), B<ash>(1), B<bash>(1), B<csh>(1), B<dash>(1), +B<fish>(1), B<ksh>(1), B<pdksh>(1) B<tcsh>(1), B<zsh>(1). + + +=cut diff --git a/src/env_parallel.sh b/src/env_parallel.sh new file mode 100755 index 0000000..62c4729 --- /dev/null +++ b/src/env_parallel.sh @@ -0,0 +1,636 @@ +#!/usr/bin/env sh + +# This file must be sourced in sh/ash/dash/bash/ksh/mksh/zsh: +# +# . env_parallel.sh +# source env_parallel.ash +# source env_parallel.dash +# source env_parallel.bash +# source env_parallel.ksh +# source env_parallel.mksh +# source env_parallel.zsh +# +# after which 'env_parallel' works +# +# +# Copyright (C) 2016-2024 Ole Tange, http://ole.tange.dk and Free +# Software Foundation, Inc. +# +# This program 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. +# +# This program 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 this program; if not, see <http://www.gnu.org/licenses/> +# or write to the Free Software Foundation, Inc., 51 Franklin St, +# Fifth Floor, Boston, MA 02110-1301 USA +# +# SPDX-FileCopyrightText: 2021-2024 Ole Tange, http://ole.tange.dk and Free Software and Foundation, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later +# shellcheck disable=SC2006 + +env_parallel() { + # env_parallel.{sh,ash,dash,bash,ksh,mksh,zsh} + + # Check shell dialect + if [ -n "$BASH_VERSION" ]; then + _shell_DIALECT=bash + _eval_needed=false + _prefix_PARALLEL_ENV=_prefix_PARALLEL_ENV_bash + elif [ -n "$ZSH_VERSION" ]; then + _shell_DIALECT=zsh + _eval_needed=true + _prefix_PARALLEL_ENV=false + elif [ -n "$KSH_VERSION" ]; then + _shell_DIALECT=ksh + _eval_needed=false + _prefix_PARALLEL_ENV=false + else + # Dash/ash - can these be detected better? + _shell_DIALECT="sh" + _eval_needed=false + _prefix_PARALLEL_ENV=false + fi + _names_of_ALIASES() { + _names_of_ALIASES_$_shell_DIALECT + } + _names_of_ALIASES_sh() { + # alias fails on Unixware 5 + for _i in `alias 2>/dev/null | perl -ne 's/^alias //;s/^(\S+)=.*/$1/ && print' 2>/dev/null`; do + # Check if this name really is an alias + # or just part of a multiline alias definition + if alias "$_i" >/dev/null 2>/dev/null; then + echo "$_i" + fi + done + } + _names_of_ALIASES_bash() { + # No aliases will return false. This error should be ignored. + # shellcheck disable=SC3044 + compgen -a || true + } + _names_of_ALIASES_ksh() { + alias | perl -pe 's/=.*//' + } + _names_of_ALIASES_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)aliases} + } + _bodies_of_ALIASES() { + _bodies_of_ALIASES_$_shell_DIALECT "$@" + } + _bodies_of_ALIASES_sh() { + # alias may return: + # myalias='definition' (GNU/Linux ash) + # alias myalias='definition' (FreeBSD ash) + # so remove 'alias ' from first line + for _i in "$@"; do + echo 'alias '"`alias "$_i" | perl -pe '1..1 and s/^alias //'`" + done + } + _bodies_of_ALIASES_bash() { + # shellcheck disable=SC3043 + local _i + for _i in "$@"; do + # shellcheck disable=SC2046 + if [ $(alias "$_i" | wc -l) = 1 ] ; then + true Alias is a single line. Good. + else + _warning_PAR "Alias '$_i' contains newline." + _warning_PAR "Make sure the command has at least one newline after '$_i'." + _warning_PAR "See BUGS in 'man env_parallel'." + fi + done + alias "$@" + } + _bodies_of_ALIASES_ksh() { + alias "$@" | perl -pe 's/^/alias /; + sub warning { print STDERR "env_parallel: Warning: @_\n"; } + if(/^alias (\S+)=\$.*\\n/) { + warning("Alias \"$1\" contains newline."); + warning("Make sure the command has at least one newline after \"$1\"."); + warning("See BUGS in \"man env_parallel\"."); + }' + + } + _bodies_of_ALIASES_zsh() { + # shellcheck disable=SC3043 + local _i + for _i in "$@"; do + echo 'alias '"$(alias "$_i")" + done + } + _names_of_FUNCTIONS() { + _names_of_FUNCTIONS_$_shell_DIALECT + } + _names_of_FUNCTIONS_bash() { + # shellcheck disable=SC3044 + compgen -A function + } + _names_of_maybe_FUNCTIONS() { + set | perl -ne '/^([A-Z_0-9]+)\s*\(\)\s*\{?$/i and print "$1\n"' + } + _names_of_FUNCTIONS_sh() { + # myfunc is a function + # shellcheck disable=SC2046 + LANG=C type `_names_of_maybe_FUNCTIONS` | + perl -ne '/^(\S+) is a function$/ and not $seen{$1}++ and print "$1\n"' + } + _names_of_FUNCTIONS_ksh() { + # shellcheck disable=SC3044 + typeset +f | perl -pe 's/\(\).*//; s/ .*//;' + } + _names_of_FUNCTIONS_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)functions} + } + _bodies_of_FUNCTIONS() { + _bodies_of_FUNCTIONS_$_shell_DIALECT "$@" + } + _bodies_of_FUNCTIONS_sh() { + LANG=C type "$@" | perl -ne '/^(\S+) is a function$/ or print' + } + _bodies_of_FUNCTIONS_bash() { + # shellcheck disable=SC3044 + typeset -f "$@" + } + _bodies_of_FUNCTIONS_ksh() { + functions "$@" + } + _bodies_of_FUNCTIONS_zsh() { + # shellcheck disable=SC3044 + typeset -f "$@" + } + _names_of_VARIABLES() { + _names_of_VARIABLES_$_shell_DIALECT + } + _names_of_VARIABLES_sh() { + # This may screw up if variables contain \n and = + set | perl -ne 's/^(\S+?)=.*/$1/ and print;' + } + _names_of_VARIABLES_bash() { + # shellcheck disable=SC3044 + compgen -A variable + } + _names_of_VARIABLES_ksh() { + # shellcheck disable=SC3044 + typeset +p | + perl -pe 's/^(type)?set( [-+][a-zA-Z0-9]*)* //; s/(\[\d+\])?=.*//' | + uniq + } + _names_of_VARIABLES_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)parameters} + } + _bodies_of_VARIABLES() { + _bodies_of_VARIABLES_$_shell_DIALECT "$@" + } + _bodies_of_VARIABLES_sh() { + # Crappy typeset -p + for _i in "$@" + do + perl -e 'print @ARGV' "$_i=" + eval echo "\"\$$_i\"" | perl -e '$/=undef; $a=<>; chop($a); print $a' | + perl -pe 's/[\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\^\*\<\=\>\~\|\; \"\!\$\&\202-\377]/\\$&/go;'"s/'/\\\'/g; s/[\n]/'\\n'/go;"; + echo + done + } + _bodies_of_VARIABLES_bash() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _bodies_of_VARIABLES_ksh() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _bodies_of_VARIABLES_zsh() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _ignore_HARDCODED() { + _ignore_HARDCODED_$_shell_DIALECT + } + _ignore_HARDCODED_sh() { + # These names cannot be detected + echo '(_|TIMEOUT|IFS)' + } + _ignore_HARDCODED_bash() { + # Copying $RANDOM will cause it not to be random + # The rest cannot be detected as read-only + echo '(RANDOM|_|TIMEOUT|GROUPS|FUNCNAME|DIRSTACK|PIPESTATUS|USERNAME|BASHPID|BASH_[A-Z_]+)' + } + _ignore_HARDCODED_ksh() { + # These names cannot be detected + echo '(_|TIMEOUT|IFS)' + } + _ignore_HARDCODED_zsh() { + # These names cannot be detected + echo '([-\?\#\!\$\*\@\_0]|zsh_eval_context|ZSH_EVAL_CONTEXT|LINENO|IFS|commands|functions|options|aliases|EUID|EGID|UID|GID|dis_patchars|patchars|terminfo|galiases|keymaps|parameters|jobdirs|dirstack|functrace|funcsourcetrace|zsh_scheduled_events|dis_aliases|dis_reswords|dis_saliases|modules|reswords|saliases|widgets|userdirs|historywords|nameddirs|termcap|dis_builtins|dis_functions|jobtexts|funcfiletrace|dis_galiases|builtins|history|jobstates|funcstack|run-help)' + } + _ignore_READONLY() { + _ignore_READONLY_$_shell_DIALECT + } + _parse_READONLY() { + # shellcheck disable=SC1078,SC1079,SC2026 + perl -e '@r = map { + chomp; + # sh on UnixWare: readonly TIMEOUT + # ash: readonly var='val' + # ksh: var='val' + # mksh: PIPESTATUS[0] + s/^(readonly )?([^=\[ ]*?)(\[\d+\])?(=.*|)$/$2/ or + # bash: declare -ar BASH_VERSINFO=([0]="4" [1]="4") + # zsh: typeset -r var='val' + s/^\S+\s+\S+\s+(\S[^=]*)(=.*|$)/$1/; + $_ } <>; + $vars = join "|",map { quotemeta $_ } @r; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + ' + } + _ignore_READONLY_sh() { + readonly | _parse_READONLY + } + _ignore_READONLY_bash() { + readonly | _parse_READONLY + } + _ignore_READONLY_ksh() { + readonly | _parse_READONLY + } + _ignore_READONLY_zsh() { + # shellcheck disable=SC3044 + typeset -pr | _parse_READONLY + } + _remove_bad_NAMES() { + # Do not transfer vars and funcs from env_parallel + # shellcheck disable=SC2006 + _ignore_RO="`_ignore_READONLY`" + # shellcheck disable=SC2006 + _ignore_HARD="`_ignore_HARDCODED`" + # To avoid depending on grep dialect, use Perl version of: + # grep -Ev '^(...)$' | + perl -ne '/^( + PARALLEL_ENV| + PARALLEL_TMP| + _alias_NAMES| + _bodies_of_ALIASES| + _bodies_of_FUNCTIONS| + _bodies_of_VARIABLES| + _error_PAR| + _function_NAMES| + _get_ignored_VARS| + _grep_REGEXP| + _ignore_HARD| + _ignore_HARDCODED| + _ignore_READONLY| + _ignore_RO| + _ignore_UNDERSCORE| + _list_alias_BODIES| + _list_function_BODIES| + _list_variable_VALUES| + _make_grep_REGEXP| + _names_of_ALIASES| + _names_of_FUNCTIONS| + _names_of_VARIABLES| + _names_of_maybe_FUNCTIONS| + _parallel_exit_CODE| + _prefix_PARALLEL_ENV| + _prefix_PARALLEL_ENV_bash| + _remove_bad_NAMES| + _remove_readonly| + _variable_NAMES| + _warning_PAR| + _which_PAR)$/x and next; + # Filter names matching --env + /^'"$_grep_REGEXP"'$/ or next; + /^'"$_ignore_UNDERSCORE"'$/ and next; + # Remove readonly variables + /^'"$_ignore_RO"'$/ and next; + /^'"$_ignore_HARD"'$/ and next; + print;' + } + _prefix_PARALLEL_ENV_bash() { + # shellcheck disable=SC3044 + shopt 2>/dev/null | + perl -pe 's:\s+off:;: and s/^/shopt -u /; + s:\s+on:;: and s/^/shopt -s /; + s:;$:&>/dev/null;:'; + echo 'shopt -s expand_aliases &>/dev/null'; + } + + _get_ignored_VARS() { + perl -e ' + for(@ARGV){ + $next_is_env and push @envvar, split/,/, $_; + $next_is_env=/^--env$/; + } + if(grep { /^_$/ } @envvar) { + if(not open(IN, "<", "$ENV{HOME}/.parallel/ignored_vars")) { + print STDERR "parallel: Error: ", + "Run \"parallel --record-env\" in a clean environment first.\n"; + } else { + chomp(@ignored_vars = <IN>); + } + } + if($ENV{PARALLEL_IGNORED_NAMES}) { + push @ignored_vars, split/\s+/, $ENV{PARALLEL_IGNORED_NAMES}; + chomp @ignored_vars; + } + $vars = join "|",map { quotemeta $_ } @ignored_vars; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + ' -- "$@" + } + + # Get the --env variables if set + # --env _ should be ignored + # and convert a b c to (a|b|c) + # If --env not set: Match everything (.*) + _make_grep_REGEXP() { + perl -e ' + for(@ARGV){ + /^_$/ and $next_is_env = 0; + $next_is_env and push @envvar, split/,/, $_; + $next_is_env = /^--env$/; + } + $vars = join "|",map { quotemeta $_ } @envvar; + print $vars ? "($vars)" : "(.*)"; + ' -- "$@" + } + _which_PAR() { + # type returns: + # ll is an alias for ls -l (in ash) + # bash is a tracked alias for /bin/bash + # true is a shell builtin (in bash) + # myfunc is a function (in bash) + # myfunc is a shell function (in zsh) + # which is /usr/bin/which (in sh, bash) + # which is hashed (/usr/bin/which) + # gi is aliased to `grep -i' (in bash) + # aliased to `alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde' + # Return 0 if found, 1 otherwise + LANG=C type "$@" | + perl -pe '$exit += (s/ is an alias for .*// || + s/ is aliased to .*// || + s/ is a function// || + s/ is a shell function// || + s/ is a shell builtin// || + s/.* is hashed .(\S+).$/$1/ || + s/.* is (a tracked alias for )?//); + END { exit not $exit }' + } + _warning_PAR() { + echo "env_parallel: Warning: $*" >&2 + } + _error_PAR() { + echo "env_parallel: Error: $*" >&2 + } + + if _which_PAR parallel >/dev/null; then + true parallel found in path + else + # shellcheck disable=SC2016 + _error_PAR 'parallel must be in $PATH.' + return 255 + fi + + # Grep regexp for vars given by --env + # shellcheck disable=SC2006 + _grep_REGEXP="`_make_grep_REGEXP \"$@\"`" + unset _make_grep_REGEXP + + # Deal with --env _ + # shellcheck disable=SC2006 + _ignore_UNDERSCORE="`_get_ignored_VARS \"$@\"`" + unset _get_ignored_VARS + + # --record-env + if perl -e 'exit grep { /^--record-env$/ } @ARGV' -- "$@"; then + true skip + else + (_names_of_ALIASES; + _names_of_FUNCTIONS; + _names_of_VARIABLES) | + cat > "$HOME"/.parallel/ignored_vars + return 0 + fi + + # --session + if perl -e 'exit grep { /^--session$/ } @ARGV' -- "$@"; then + true skip + else + # Insert ::: between each level of session + # so you can pop off the last ::: at --end-session + # shellcheck disable=SC2006 + PARALLEL_IGNORED_NAMES="`echo \"$PARALLEL_IGNORED_NAMES\"; + echo :::; + (_names_of_ALIASES; + _names_of_FUNCTIONS; + _names_of_VARIABLES) | perl -ne ' + BEGIN{ + map { $ignored_vars{$_}++ } + split/\s+/, $ENV{PARALLEL_IGNORED_NAMES}; + } + chomp; + for(split/\s+/) { + if(not $ignored_vars{$_}) { + print $_,\"\\n\"; + } + } + '`" + export PARALLEL_IGNORED_NAMES + return 0 + fi + if perl -e 'exit grep { /^--end.?session$/ } @ARGV' -- "$@"; then + true skip + else + # Pop off last ::: from PARALLEL_IGNORED_NAMES + # shellcheck disable=SC2006 + PARALLEL_IGNORED_NAMES="`perl -e ' + $ENV{PARALLEL_IGNORED_NAMES} =~ s/(.*):::.*?$/$1/s; + print $ENV{PARALLEL_IGNORED_NAMES} + '`" + return 0 + fi + # Grep alias names + # shellcheck disable=SC2006 + _alias_NAMES="`_names_of_ALIASES | _remove_bad_NAMES | xargs echo`" + _list_alias_BODIES="_bodies_of_ALIASES $_alias_NAMES" + if [ "$_alias_NAMES" = "" ] ; then + # no aliases selected + _list_alias_BODIES="true" + fi + unset _alias_NAMES + + # Grep function names + # shellcheck disable=SC2006 + _function_NAMES="`_names_of_FUNCTIONS | _remove_bad_NAMES | xargs echo`" + _list_function_BODIES="_bodies_of_FUNCTIONS $_function_NAMES" + if [ "$_function_NAMES" = "" ] ; then + # no functions selected + _list_function_BODIES="true" + fi + unset _function_NAMES + + # Grep variable names + # shellcheck disable=SC2006 + _variable_NAMES="`_names_of_VARIABLES | _remove_bad_NAMES | xargs echo`" + _list_variable_VALUES="_bodies_of_VARIABLES $_variable_NAMES" + if [ "$_variable_NAMES" = "" ] ; then + # no variables selected + _list_variable_VALUES="true" + fi + unset _variable_NAMES + + if $_eval_needed ; then + # shellcheck disable=SC2006 + PARALLEL_ENV="` + eval $_prefix_PARALLEL_ENV; + eval $_list_alias_BODIES; + eval $_list_function_BODIES; + eval $_list_variable_VALUES; + `" + else + # shellcheck disable=SC2006 + PARALLEL_ENV="` + $_prefix_PARALLEL_ENV; + $_list_alias_BODIES; + $_list_function_BODIES; + $_list_variable_VALUES; + `" + fi + export PARALLEL_ENV + # Free up some env space + unset _list_alias_BODIES _list_variable_VALUES _list_function_BODIES + unset _bodies_of_ALIASES _bodies_of_VARIABLES _bodies_of_FUNCTIONS + unset _names_of_ALIASES _names_of_VARIABLES _names_of_FUNCTIONS + unset _ignore_HARDCODED _ignore_READONLY _ignore_UNDERSCORE + unset _remove_bad_NAMES _grep_REGEXP _parse_READONLY + unset _prefix_PARALLEL_ENV + unset _ignore_READONLY_sh _ignore_READONLY_bash + unset _ignore_READONLY_ksh _ignore_READONLY_zsh + unset _ignore_HARDCODED_sh _ignore_HARDCODED_bash + unset _ignore_HARDCODED_ksh _ignore_HARDCODED_zsh + unset _bodies_of_ALIASES_ksh _bodies_of_ALIASES_sh + unset _bodies_of_ALIASES_zsh _bodies_of_FUNCTIONS_bash + unset _bodies_of_FUNCTIONS_ksh _bodies_of_FUNCTIONS_sh + unset _bodies_of_FUNCTIONS_zsh _bodies_of_VARIABLES_bash + unset _bodies_of_VARIABLES_ksh _bodies_of_VARIABLES_sh + unset _bodies_of_VARIABLES_zsh + unset _names_of_ALIASES _names_of_ALIASES_bash + unset _names_of_ALIASES_ksh _names_of_ALIASES_sh + unset _names_of_ALIASES_zsh _names_of_FUNCTIONS + unset _names_of_FUNCTIONS_bash _names_of_FUNCTIONS_ksh + unset _names_of_FUNCTIONS_sh _names_of_FUNCTIONS_zsh + unset _names_of_VARIABLES _names_of_VARIABLES_bash + unset _names_of_VARIABLES_ksh _names_of_VARIABLES_sh + unset _names_of_VARIABLES_zsh _names_of_maybe_FUNCTIONS + + # Test if environment is too big by running 'true' + # shellcheck disable=SC2006,SC2092 + if `_which_PAR true` >/dev/null 2>/dev/null ; then + parallel "$@" + _parallel_exit_CODE=$? + # Clean up variables/functions + unset PARALLEL_ENV + unset _which_PAR _which_TRUE + unset _warning_PAR _error_PAR + # Unset _parallel_exit_CODE before return + eval "unset _parallel_exit_CODE; return $_parallel_exit_CODE" + else + unset PARALLEL_ENV; + _error_PAR "Your environment is too big." + _error_PAR "You can try 3 different approaches:" + _error_PAR "1. Run 'env_parallel --session' before you set" + _error_PAR " variables or define functions." + _error_PAR "2. Use --env and only mention the names to copy." + _error_PAR "3. Try running this in a clean environment once:" + _error_PAR " env_parallel --record-env" + _error_PAR " And then use '--env _'" + _error_PAR "For details see: man env_parallel" + return 255 + fi +} + +parset() { + _parset_PARALLEL_PRG=parallel + _parset_main "$@" +} + +env_parset() { + _parset_PARALLEL_PRG=env_parallel + _parset_main "$@" +} + +_parset_main() { + # If $1 contains ',' or space: + # Split on , to get the destination variable names + # If $1 is a single destination variable name: + # Treat it as the name of an array + # + # # Create array named myvar + # parset myvar echo ::: {1..10} + # echo ${myvar[5]} + # + # # Put output into $var_a $var_b $var_c + # varnames=(var_a var_b var_c) + # parset "${varnames[*]}" echo ::: {1..3} + # echo $var_c + # + # # Put output into $var_a4 $var_b4 $var_c4 + # parset "var_a4 var_b4 var_c4" echo ::: {1..3} + # echo $var_c4 + + _parset_NAME="$1" + if [ "$_parset_NAME" = "" ] ; then + echo parset: Error: No destination variable given. >&2 + echo parset: Error: Try: >&2 + echo parset: Error: ' ' parset myarray echo ::: foo bar >&2 + return 255 + fi + if [ "$_parset_NAME" = "--help" ] ; then + echo parset: Error: Usage: >&2 + echo parset: Error: ' ' parset varname GNU Parallel options and command >&2 + echo + parallel --help + return 255 + fi + if [ "$_parset_NAME" = "--version" ] ; then + # shellcheck disable=SC2006 + echo "parset 20240222 (GNU parallel `parallel --minversion 1`)" + echo "Copyright (C) 2007-2024 Ole Tange, http://ole.tange.dk and Free Software" + echo "Foundation, Inc." + echo "License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>" + echo "This is free software: you are free to change and redistribute it." + echo "GNU parallel comes with no warranty." + echo + echo "Web site: https://www.gnu.org/software/parallel" + echo + echo "When using programs that use GNU Parallel to process data for publication" + echo "please cite as described in 'parallel --citation'." + echo + return 255 + fi + shift + + # Bash: declare -A myassoc=( ) + # Zsh: typeset -A myassoc=( ) + # Ksh: typeset -A myassoc=( ) + # shellcheck disable=SC2039,SC2169,SC3044 + if (typeset -p "$_parset_NAME" 2>/dev/null; echo) | + perl -ne 'exit not (/^declare[^=]+-A|^typeset[^=]+-A/)' ; then + # This is an associative array + # shellcheck disable=SC2006 + eval "`$_parset_PARALLEL_PRG -k --_parset assoc,"$_parset_NAME" "$@"`" + # The eval returns the function! + else + # This is a normal array or a list of variable names + # shellcheck disable=SC2006 + eval "`$_parset_PARALLEL_PRG -k --_parset var,"$_parset_NAME" "$@"`" + # The eval returns the function! + fi +} diff --git a/src/env_parallel.tcsh b/src/env_parallel.tcsh new file mode 100755 index 0000000..dcb7a0b --- /dev/null +++ b/src/env_parallel.tcsh @@ -0,0 +1,142 @@ +#!/usr/bin/env tcsh + +# This file must be sourced in tcsh: +# +# source `which env_parallel.tcsh` +# +# after which 'env_parallel' works +# +# +# Copyright (C) 2016-2024 Ole Tange, http://ole.tange.dk and Free +# Software Foundation, Inc. +# +# This program 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. +# +# This program 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 this program; if not, see <http://www.gnu.org/licenses/> +# or write to the Free Software Foundation, Inc., 51 Franklin St, +# Fifth Floor, Boston, MA 02110-1301 USA +# +# SPDX-FileCopyrightText: 2021-2024 Ole Tange, http://ole.tange.dk and Free Software and Foundation, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later + +set _parallel_exit_CODE=0 +if ("`alias env_parallel`" == '' || ! $?PARALLEL_CSH) then + # Activate alias + alias env_parallel '(setenv PARALLEL_CSH "\!*"; source `which env_parallel.csh`)' +else + # Get the --env variables if set + # --env _ should be ignored + # and convert a b c to (a|b|c) + # If --env not set: Match everything (.*) + + # simple 'tempfile': Return nonexisting filename: /tmp/parXXXXX + alias _tempfile 'perl -e do\{\$t\=\"/tmp/par\".join\"\",map\{\(0..9,\"a\"..\"z\",\"A\"..\"Z\"\)\[rand\(62\)\]\}\(1..5\)\;\}while\(-e\$t\)\;print\"\$t\\n\"' + set _tMpscRIpt=`_tempfile` + + cat <<'EOF' > $_tMpscRIpt + #!/usr/bin/perl + + for(@ARGV){ + /^_$/ and $next_is_env = 0; + $next_is_env and push @envvar, split/,/, $_; + $next_is_env = /^--env$/; + } + $vars = join "|",map { quotemeta $_ } @envvar; + print $vars ? "($vars)" : "(.*)"; +'EOF' + set _grep_REGEXP="`perl $_tMpscRIpt -- $PARALLEL_CSH`" + + # Deal with --env _ + cat <<'EOF' > $_tMpscRIpt + #!/usr/bin/perl + + for(@ARGV){ + $next_is_env and push @envvar, split/,/, $_; + $next_is_env=/^--env$/; + } + if(grep { /^_$/ } @envvar) { + if(not open(IN, "<", "$ENV{HOME}/.parallel/ignored_vars")) { + print STDERR "parallel: Error: ", + "Run \"parallel --record-env\" in a clean environment first.\n"; + } else { + chomp(@ignored_vars = <IN>); + $vars = join "|",map { quotemeta $_ } @ignored_vars; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + } + } +'EOF' + set _ignore_UNDERSCORE="`perl $_tMpscRIpt -- $PARALLEL_CSH`" + rm $_tMpscRIpt + + # Get the scalar and array variable names + set _vARnAmES=(`set | perl -ne 's/\s.*//; /^(#|_|killring|prompt2|command|PARALLEL_ENV|PARALLEL_TMP)$/ and next; /^'"$_grep_REGEXP"'$/ or next; /^'"$_ignore_UNDERSCORE"'$/ and next; print'`) + + # Make a tmpfile for the variable definitions + set _tMpvARfILe=`_tempfile` + touch $_tMpvARfILe + # Make a tmpfile for the variable definitions + alias + set _tMpaLLfILe=`_tempfile` + foreach _vARnAmE ($_vARnAmES); + # These 3 lines break in csh ver. 20110502-3 + # if not defined: next + eval if'(! $?'$_vARnAmE') continue' + # if $#myvar <= 1 echo scalar_myvar=$var + eval if'(${#'$_vARnAmE'} <= 1) echo scalar_'$_vARnAmE'='\"\$$_vARnAmE\" >> $_tMpvARfILe; + # if $#myvar > 1 echo array_myvar=$var + eval if'(${#'$_vARnAmE'} > 1) echo array_'$_vARnAmE'="$'$_vARnAmE'"' >> $_tMpvARfILe; + end + unset _vARnAmE _vARnAmES + # shell quote variables (--plain needed due to ignore if $PARALLEL is set) + # Convert 'scalar_myvar=...' to 'set myvar=...' + # Convert 'array_myvar=...' to 'set array=(...)' + cat $_tMpvARfILe | parallel --plain --shellquote | perl -pe 's/^scalar_(\S+).=/set $1=/ or s/^array_(\S+).=(.*)/set $1=($2)/ && s/\\ / /g;' > $_tMpaLLfILe + # Cleanup + rm $_tMpvARfILe; unset _tMpvARfILe + +# ALIAS TO EXPORT ALIASES: + +# Quote ' by putting it inside " +# s/'/'"'"'/g; +# ' => \047 " => \042 +# s/\047/\047\042\047\042\047/g; +# Quoted: s/\\047/\\047\\042\\047\\042\\047/g\; + +# Remove () from second column +# s/^(\S+)(\s+)\((.*)\)/\1\2\3/; +# Quoted: s/\^\(\\S+\)\(\\s+\)\\\(\(.\*\)\\\)/\\1\\2\\3/\; + +# Add ' around second column +# s/^(\S+)(\s+)(.*)/\1\2'\3'/ +# \047 => ' +# s/^(\S+)(\s+)(.*)/\1\2\047\3\047/; +# Quoted: s/\^\(\\S+\)\(\\s+\)\(.\*\)/\\1\\2\\047\\3\\047/\; + +# Quote ! as \! +# s/\!/\\\!/g; +# Quoted: s/\\\!/\\\\\\\!/g; + +# Prepend with "\nalias " +# s/^/\001alias /; +# Quoted: s/\^/\\001alias\ /\; + alias | \ + perl -ne '/^'"$_grep_REGEXP"'/ or next; /^'"$_ignore_UNDERSCORE"'[^_a-zA-Z]/ and next; print' | \ + perl -pe s/\\047/\\047\\042\\047\\042\\047/g\;s/\^\(\\S+\)\(\\s+\)\\\(\(.\*\)\\\)/\\1\\2\\3/\;s/\^\(\\S+\)\(\\s+\)\(.\*\)/\\1\\2\\047\\3\\047/\;s/\^/\\001alias\ /\;s/\\\!/\\\\\\\!/g >> $_tMpaLLfILe + + setenv PARALLEL_ENV "`cat $_tMpaLLfILe; rm $_tMpaLLfILe`"; + unset _tMpaLLfILe; + # Use $PARALLEL_CSH set in calling alias + parallel + set _parallel_exit_CODE=$status + setenv PARALLEL_ENV + setenv PARALLEL_CSH +endif +(exit $_parallel_exit_CODE) diff --git a/src/env_parallel.zsh b/src/env_parallel.zsh new file mode 100755 index 0000000..674d8e6 --- /dev/null +++ b/src/env_parallel.zsh @@ -0,0 +1,636 @@ +#!/usr/bin/env zsh + +# This file must be sourced in sh/ash/dash/bash/ksh/mksh/zsh: +# +# . env_parallel.sh +# source env_parallel.ash +# source env_parallel.dash +# source env_parallel.bash +# source env_parallel.ksh +# source env_parallel.mksh +# source env_parallel.zsh +# +# after which 'env_parallel' works +# +# +# Copyright (C) 2016-2024 Ole Tange, http://ole.tange.dk and Free +# Software Foundation, Inc. +# +# This program 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. +# +# This program 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 this program; if not, see <http://www.gnu.org/licenses/> +# or write to the Free Software Foundation, Inc., 51 Franklin St, +# Fifth Floor, Boston, MA 02110-1301 USA +# +# SPDX-FileCopyrightText: 2021-2024 Ole Tange, http://ole.tange.dk and Free Software and Foundation, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later +# shellcheck disable=SC2006 + +env_parallel() { + # env_parallel.{sh,ash,dash,bash,ksh,mksh,zsh} + + # Check shell dialect + if [ -n "$BASH_VERSION" ]; then + _shell_DIALECT=bash + _eval_needed=false + _prefix_PARALLEL_ENV=_prefix_PARALLEL_ENV_bash + elif [ -n "$ZSH_VERSION" ]; then + _shell_DIALECT=zsh + _eval_needed=true + _prefix_PARALLEL_ENV=false + elif [ -n "$KSH_VERSION" ]; then + _shell_DIALECT=ksh + _eval_needed=false + _prefix_PARALLEL_ENV=false + else + # Dash/ash - can these be detected better? + _shell_DIALECT="sh" + _eval_needed=false + _prefix_PARALLEL_ENV=false + fi + _names_of_ALIASES() { + _names_of_ALIASES_$_shell_DIALECT + } + _names_of_ALIASES_sh() { + # alias fails on Unixware 5 + for _i in `alias 2>/dev/null | perl -ne 's/^alias //;s/^(\S+)=.*/$1/ && print' 2>/dev/null`; do + # Check if this name really is an alias + # or just part of a multiline alias definition + if alias "$_i" >/dev/null 2>/dev/null; then + echo "$_i" + fi + done + } + _names_of_ALIASES_bash() { + # No aliases will return false. This error should be ignored. + # shellcheck disable=SC3044 + compgen -a || true + } + _names_of_ALIASES_ksh() { + alias | perl -pe 's/=.*//' + } + _names_of_ALIASES_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)aliases} + } + _bodies_of_ALIASES() { + _bodies_of_ALIASES_$_shell_DIALECT "$@" + } + _bodies_of_ALIASES_sh() { + # alias may return: + # myalias='definition' (GNU/Linux ash) + # alias myalias='definition' (FreeBSD ash) + # so remove 'alias ' from first line + for _i in "$@"; do + echo 'alias '"`alias "$_i" | perl -pe '1..1 and s/^alias //'`" + done + } + _bodies_of_ALIASES_bash() { + # shellcheck disable=SC3043 + local _i + for _i in "$@"; do + # shellcheck disable=SC2046 + if [ $(alias "$_i" | wc -l) = 1 ] ; then + true Alias is a single line. Good. + else + _warning_PAR "Alias '$_i' contains newline." + _warning_PAR "Make sure the command has at least one newline after '$_i'." + _warning_PAR "See BUGS in 'man env_parallel'." + fi + done + alias "$@" + } + _bodies_of_ALIASES_ksh() { + alias "$@" | perl -pe 's/^/alias /; + sub warning { print STDERR "env_parallel: Warning: @_\n"; } + if(/^alias (\S+)=\$.*\\n/) { + warning("Alias \"$1\" contains newline."); + warning("Make sure the command has at least one newline after \"$1\"."); + warning("See BUGS in \"man env_parallel\"."); + }' + + } + _bodies_of_ALIASES_zsh() { + # shellcheck disable=SC3043 + local _i + for _i in "$@"; do + echo 'alias '"$(alias "$_i")" + done + } + _names_of_FUNCTIONS() { + _names_of_FUNCTIONS_$_shell_DIALECT + } + _names_of_FUNCTIONS_bash() { + # shellcheck disable=SC3044 + compgen -A function + } + _names_of_maybe_FUNCTIONS() { + set | perl -ne '/^([A-Z_0-9]+)\s*\(\)\s*\{?$/i and print "$1\n"' + } + _names_of_FUNCTIONS_sh() { + # myfunc is a function + # shellcheck disable=SC2046 + LANG=C type `_names_of_maybe_FUNCTIONS` | + perl -ne '/^(\S+) is a function$/ and not $seen{$1}++ and print "$1\n"' + } + _names_of_FUNCTIONS_ksh() { + # shellcheck disable=SC3044 + typeset +f | perl -pe 's/\(\).*//; s/ .*//;' + } + _names_of_FUNCTIONS_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)functions} + } + _bodies_of_FUNCTIONS() { + _bodies_of_FUNCTIONS_$_shell_DIALECT "$@" + } + _bodies_of_FUNCTIONS_sh() { + LANG=C type "$@" | perl -ne '/^(\S+) is a function$/ or print' + } + _bodies_of_FUNCTIONS_bash() { + # shellcheck disable=SC3044 + typeset -f "$@" + } + _bodies_of_FUNCTIONS_ksh() { + functions "$@" + } + _bodies_of_FUNCTIONS_zsh() { + # shellcheck disable=SC3044 + typeset -f "$@" + } + _names_of_VARIABLES() { + _names_of_VARIABLES_$_shell_DIALECT + } + _names_of_VARIABLES_sh() { + # This may screw up if variables contain \n and = + set | perl -ne 's/^(\S+?)=.*/$1/ and print;' + } + _names_of_VARIABLES_bash() { + # shellcheck disable=SC3044 + compgen -A variable + } + _names_of_VARIABLES_ksh() { + # shellcheck disable=SC3044 + typeset +p | + perl -pe 's/^(type)?set( [-+][a-zA-Z0-9]*)* //; s/(\[\d+\])?=.*//' | + uniq + } + _names_of_VARIABLES_zsh() { + # shellcheck disable=SC2086,SC2296 + print -l ${(k)parameters} + } + _bodies_of_VARIABLES() { + _bodies_of_VARIABLES_$_shell_DIALECT "$@" + } + _bodies_of_VARIABLES_sh() { + # Crappy typeset -p + for _i in "$@" + do + perl -e 'print @ARGV' "$_i=" + eval echo "\"\$$_i\"" | perl -e '$/=undef; $a=<>; chop($a); print $a' | + perl -pe 's/[\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\^\*\<\=\>\~\|\; \"\!\$\&\202-\377]/\\$&/go;'"s/'/\\\'/g; s/[\n]/'\\n'/go;"; + echo + done + } + _bodies_of_VARIABLES_bash() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _bodies_of_VARIABLES_ksh() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _bodies_of_VARIABLES_zsh() { + # shellcheck disable=SC3044 + typeset -p "$@" + } + _ignore_HARDCODED() { + _ignore_HARDCODED_$_shell_DIALECT + } + _ignore_HARDCODED_sh() { + # These names cannot be detected + echo '(_|TIMEOUT|IFS)' + } + _ignore_HARDCODED_bash() { + # Copying $RANDOM will cause it not to be random + # The rest cannot be detected as read-only + echo '(RANDOM|_|TIMEOUT|GROUPS|FUNCNAME|DIRSTACK|PIPESTATUS|USERNAME|BASHPID|BASH_[A-Z_]+)' + } + _ignore_HARDCODED_ksh() { + # These names cannot be detected + echo '(_|TIMEOUT|IFS)' + } + _ignore_HARDCODED_zsh() { + # These names cannot be detected + echo '([-\?\#\!\$\*\@\_0]|zsh_eval_context|ZSH_EVAL_CONTEXT|LINENO|IFS|commands|functions|options|aliases|EUID|EGID|UID|GID|dis_patchars|patchars|terminfo|galiases|keymaps|parameters|jobdirs|dirstack|functrace|funcsourcetrace|zsh_scheduled_events|dis_aliases|dis_reswords|dis_saliases|modules|reswords|saliases|widgets|userdirs|historywords|nameddirs|termcap|dis_builtins|dis_functions|jobtexts|funcfiletrace|dis_galiases|builtins|history|jobstates|funcstack|run-help)' + } + _ignore_READONLY() { + _ignore_READONLY_$_shell_DIALECT + } + _parse_READONLY() { + # shellcheck disable=SC1078,SC1079,SC2026 + perl -e '@r = map { + chomp; + # sh on UnixWare: readonly TIMEOUT + # ash: readonly var='val' + # ksh: var='val' + # mksh: PIPESTATUS[0] + s/^(readonly )?([^=\[ ]*?)(\[\d+\])?(=.*|)$/$2/ or + # bash: declare -ar BASH_VERSINFO=([0]="4" [1]="4") + # zsh: typeset -r var='val' + s/^\S+\s+\S+\s+(\S[^=]*)(=.*|$)/$1/; + $_ } <>; + $vars = join "|",map { quotemeta $_ } @r; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + ' + } + _ignore_READONLY_sh() { + readonly | _parse_READONLY + } + _ignore_READONLY_bash() { + readonly | _parse_READONLY + } + _ignore_READONLY_ksh() { + readonly | _parse_READONLY + } + _ignore_READONLY_zsh() { + # shellcheck disable=SC3044 + typeset -pr | _parse_READONLY + } + _remove_bad_NAMES() { + # Do not transfer vars and funcs from env_parallel + # shellcheck disable=SC2006 + _ignore_RO="`_ignore_READONLY`" + # shellcheck disable=SC2006 + _ignore_HARD="`_ignore_HARDCODED`" + # To avoid depending on grep dialect, use Perl version of: + # grep -Ev '^(...)$' | + perl -ne '/^( + PARALLEL_ENV| + PARALLEL_TMP| + _alias_NAMES| + _bodies_of_ALIASES| + _bodies_of_FUNCTIONS| + _bodies_of_VARIABLES| + _error_PAR| + _function_NAMES| + _get_ignored_VARS| + _grep_REGEXP| + _ignore_HARD| + _ignore_HARDCODED| + _ignore_READONLY| + _ignore_RO| + _ignore_UNDERSCORE| + _list_alias_BODIES| + _list_function_BODIES| + _list_variable_VALUES| + _make_grep_REGEXP| + _names_of_ALIASES| + _names_of_FUNCTIONS| + _names_of_VARIABLES| + _names_of_maybe_FUNCTIONS| + _parallel_exit_CODE| + _prefix_PARALLEL_ENV| + _prefix_PARALLEL_ENV_bash| + _remove_bad_NAMES| + _remove_readonly| + _variable_NAMES| + _warning_PAR| + _which_PAR)$/x and next; + # Filter names matching --env + /^'"$_grep_REGEXP"'$/ or next; + /^'"$_ignore_UNDERSCORE"'$/ and next; + # Remove readonly variables + /^'"$_ignore_RO"'$/ and next; + /^'"$_ignore_HARD"'$/ and next; + print;' + } + _prefix_PARALLEL_ENV_bash() { + # shellcheck disable=SC3044 + shopt 2>/dev/null | + perl -pe 's:\s+off:;: and s/^/shopt -u /; + s:\s+on:;: and s/^/shopt -s /; + s:;$:&>/dev/null;:'; + echo 'shopt -s expand_aliases &>/dev/null'; + } + + _get_ignored_VARS() { + perl -e ' + for(@ARGV){ + $next_is_env and push @envvar, split/,/, $_; + $next_is_env=/^--env$/; + } + if(grep { /^_$/ } @envvar) { + if(not open(IN, "<", "$ENV{HOME}/.parallel/ignored_vars")) { + print STDERR "parallel: Error: ", + "Run \"parallel --record-env\" in a clean environment first.\n"; + } else { + chomp(@ignored_vars = <IN>); + } + } + if($ENV{PARALLEL_IGNORED_NAMES}) { + push @ignored_vars, split/\s+/, $ENV{PARALLEL_IGNORED_NAMES}; + chomp @ignored_vars; + } + $vars = join "|",map { quotemeta $_ } @ignored_vars; + print $vars ? "($vars)" : "(,,nO,,VaRs,,)"; + ' -- "$@" + } + + # Get the --env variables if set + # --env _ should be ignored + # and convert a b c to (a|b|c) + # If --env not set: Match everything (.*) + _make_grep_REGEXP() { + perl -e ' + for(@ARGV){ + /^_$/ and $next_is_env = 0; + $next_is_env and push @envvar, split/,/, $_; + $next_is_env = /^--env$/; + } + $vars = join "|",map { quotemeta $_ } @envvar; + print $vars ? "($vars)" : "(.*)"; + ' -- "$@" + } + _which_PAR() { + # type returns: + # ll is an alias for ls -l (in ash) + # bash is a tracked alias for /bin/bash + # true is a shell builtin (in bash) + # myfunc is a function (in bash) + # myfunc is a shell function (in zsh) + # which is /usr/bin/which (in sh, bash) + # which is hashed (/usr/bin/which) + # gi is aliased to `grep -i' (in bash) + # aliased to `alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde' + # Return 0 if found, 1 otherwise + LANG=C type "$@" | + perl -pe '$exit += (s/ is an alias for .*// || + s/ is aliased to .*// || + s/ is a function// || + s/ is a shell function// || + s/ is a shell builtin// || + s/.* is hashed .(\S+).$/$1/ || + s/.* is (a tracked alias for )?//); + END { exit not $exit }' + } + _warning_PAR() { + echo "env_parallel: Warning: $*" >&2 + } + _error_PAR() { + echo "env_parallel: Error: $*" >&2 + } + + if _which_PAR parallel >/dev/null; then + true parallel found in path + else + # shellcheck disable=SC2016 + _error_PAR 'parallel must be in $PATH.' + return 255 + fi + + # Grep regexp for vars given by --env + # shellcheck disable=SC2006 + _grep_REGEXP="`_make_grep_REGEXP \"$@\"`" + unset _make_grep_REGEXP + + # Deal with --env _ + # shellcheck disable=SC2006 + _ignore_UNDERSCORE="`_get_ignored_VARS \"$@\"`" + unset _get_ignored_VARS + + # --record-env + if perl -e 'exit grep { /^--record-env$/ } @ARGV' -- "$@"; then + true skip + else + (_names_of_ALIASES; + _names_of_FUNCTIONS; + _names_of_VARIABLES) | + cat > "$HOME"/.parallel/ignored_vars + return 0 + fi + + # --session + if perl -e 'exit grep { /^--session$/ } @ARGV' -- "$@"; then + true skip + else + # Insert ::: between each level of session + # so you can pop off the last ::: at --end-session + # shellcheck disable=SC2006 + PARALLEL_IGNORED_NAMES="`echo \"$PARALLEL_IGNORED_NAMES\"; + echo :::; + (_names_of_ALIASES; + _names_of_FUNCTIONS; + _names_of_VARIABLES) | perl -ne ' + BEGIN{ + map { $ignored_vars{$_}++ } + split/\s+/, $ENV{PARALLEL_IGNORED_NAMES}; + } + chomp; + for(split/\s+/) { + if(not $ignored_vars{$_}) { + print $_,\"\\n\"; + } + } + '`" + export PARALLEL_IGNORED_NAMES + return 0 + fi + if perl -e 'exit grep { /^--end.?session$/ } @ARGV' -- "$@"; then + true skip + else + # Pop off last ::: from PARALLEL_IGNORED_NAMES + # shellcheck disable=SC2006 + PARALLEL_IGNORED_NAMES="`perl -e ' + $ENV{PARALLEL_IGNORED_NAMES} =~ s/(.*):::.*?$/$1/s; + print $ENV{PARALLEL_IGNORED_NAMES} + '`" + return 0 + fi + # Grep alias names + # shellcheck disable=SC2006 + _alias_NAMES="`_names_of_ALIASES | _remove_bad_NAMES | xargs echo`" + _list_alias_BODIES="_bodies_of_ALIASES $_alias_NAMES" + if [ "$_alias_NAMES" = "" ] ; then + # no aliases selected + _list_alias_BODIES="true" + fi + unset _alias_NAMES + + # Grep function names + # shellcheck disable=SC2006 + _function_NAMES="`_names_of_FUNCTIONS | _remove_bad_NAMES | xargs echo`" + _list_function_BODIES="_bodies_of_FUNCTIONS $_function_NAMES" + if [ "$_function_NAMES" = "" ] ; then + # no functions selected + _list_function_BODIES="true" + fi + unset _function_NAMES + + # Grep variable names + # shellcheck disable=SC2006 + _variable_NAMES="`_names_of_VARIABLES | _remove_bad_NAMES | xargs echo`" + _list_variable_VALUES="_bodies_of_VARIABLES $_variable_NAMES" + if [ "$_variable_NAMES" = "" ] ; then + # no variables selected + _list_variable_VALUES="true" + fi + unset _variable_NAMES + + if $_eval_needed ; then + # shellcheck disable=SC2006 + PARALLEL_ENV="` + eval $_prefix_PARALLEL_ENV; + eval $_list_alias_BODIES; + eval $_list_function_BODIES; + eval $_list_variable_VALUES; + `" + else + # shellcheck disable=SC2006 + PARALLEL_ENV="` + $_prefix_PARALLEL_ENV; + $_list_alias_BODIES; + $_list_function_BODIES; + $_list_variable_VALUES; + `" + fi + export PARALLEL_ENV + # Free up some env space + unset _list_alias_BODIES _list_variable_VALUES _list_function_BODIES + unset _bodies_of_ALIASES _bodies_of_VARIABLES _bodies_of_FUNCTIONS + unset _names_of_ALIASES _names_of_VARIABLES _names_of_FUNCTIONS + unset _ignore_HARDCODED _ignore_READONLY _ignore_UNDERSCORE + unset _remove_bad_NAMES _grep_REGEXP _parse_READONLY + unset _prefix_PARALLEL_ENV + unset _ignore_READONLY_sh _ignore_READONLY_bash + unset _ignore_READONLY_ksh _ignore_READONLY_zsh + unset _ignore_HARDCODED_sh _ignore_HARDCODED_bash + unset _ignore_HARDCODED_ksh _ignore_HARDCODED_zsh + unset _bodies_of_ALIASES_ksh _bodies_of_ALIASES_sh + unset _bodies_of_ALIASES_zsh _bodies_of_FUNCTIONS_bash + unset _bodies_of_FUNCTIONS_ksh _bodies_of_FUNCTIONS_sh + unset _bodies_of_FUNCTIONS_zsh _bodies_of_VARIABLES_bash + unset _bodies_of_VARIABLES_ksh _bodies_of_VARIABLES_sh + unset _bodies_of_VARIABLES_zsh + unset _names_of_ALIASES _names_of_ALIASES_bash + unset _names_of_ALIASES_ksh _names_of_ALIASES_sh + unset _names_of_ALIASES_zsh _names_of_FUNCTIONS + unset _names_of_FUNCTIONS_bash _names_of_FUNCTIONS_ksh + unset _names_of_FUNCTIONS_sh _names_of_FUNCTIONS_zsh + unset _names_of_VARIABLES _names_of_VARIABLES_bash + unset _names_of_VARIABLES_ksh _names_of_VARIABLES_sh + unset _names_of_VARIABLES_zsh _names_of_maybe_FUNCTIONS + + # Test if environment is too big by running 'true' + # shellcheck disable=SC2006,SC2092 + if `_which_PAR true` >/dev/null 2>/dev/null ; then + parallel "$@" + _parallel_exit_CODE=$? + # Clean up variables/functions + unset PARALLEL_ENV + unset _which_PAR _which_TRUE + unset _warning_PAR _error_PAR + # Unset _parallel_exit_CODE before return + eval "unset _parallel_exit_CODE; return $_parallel_exit_CODE" + else + unset PARALLEL_ENV; + _error_PAR "Your environment is too big." + _error_PAR "You can try 3 different approaches:" + _error_PAR "1. Run 'env_parallel --session' before you set" + _error_PAR " variables or define functions." + _error_PAR "2. Use --env and only mention the names to copy." + _error_PAR "3. Try running this in a clean environment once:" + _error_PAR " env_parallel --record-env" + _error_PAR " And then use '--env _'" + _error_PAR "For details see: man env_parallel" + return 255 + fi +} + +parset() { + _parset_PARALLEL_PRG=parallel + _parset_main "$@" +} + +env_parset() { + _parset_PARALLEL_PRG=env_parallel + _parset_main "$@" +} + +_parset_main() { + # If $1 contains ',' or space: + # Split on , to get the destination variable names + # If $1 is a single destination variable name: + # Treat it as the name of an array + # + # # Create array named myvar + # parset myvar echo ::: {1..10} + # echo ${myvar[5]} + # + # # Put output into $var_a $var_b $var_c + # varnames=(var_a var_b var_c) + # parset "${varnames[*]}" echo ::: {1..3} + # echo $var_c + # + # # Put output into $var_a4 $var_b4 $var_c4 + # parset "var_a4 var_b4 var_c4" echo ::: {1..3} + # echo $var_c4 + + _parset_NAME="$1" + if [ "$_parset_NAME" = "" ] ; then + echo parset: Error: No destination variable given. >&2 + echo parset: Error: Try: >&2 + echo parset: Error: ' ' parset myarray echo ::: foo bar >&2 + return 255 + fi + if [ "$_parset_NAME" = "--help" ] ; then + echo parset: Error: Usage: >&2 + echo parset: Error: ' ' parset varname GNU Parallel options and command >&2 + echo + parallel --help + return 255 + fi + if [ "$_parset_NAME" = "--version" ] ; then + # shellcheck disable=SC2006 + echo "parset 20240222 (GNU parallel `parallel --minversion 1`)" + echo "Copyright (C) 2007-2024 Ole Tange, http://ole.tange.dk and Free Software" + echo "Foundation, Inc." + echo "License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>" + echo "This is free software: you are free to change and redistribute it." + echo "GNU parallel comes with no warranty." + echo + echo "Web site: https://www.gnu.org/software/parallel" + echo + echo "When using programs that use GNU Parallel to process data for publication" + echo "please cite as described in 'parallel --citation'." + echo + return 255 + fi + shift + + # Bash: declare -A myassoc=( ) + # Zsh: typeset -A myassoc=( ) + # Ksh: typeset -A myassoc=( ) + # shellcheck disable=SC2039,SC2169,SC3044 + if (typeset -p "$_parset_NAME" 2>/dev/null; echo) | + perl -ne 'exit not (/^declare[^=]+-A|^typeset[^=]+-A/)' ; then + # This is an associative array + # shellcheck disable=SC2006 + eval "`$_parset_PARALLEL_PRG -k --_parset assoc,"$_parset_NAME" "$@"`" + # The eval returns the function! + else + # This is a normal array or a list of variable names + # shellcheck disable=SC2006 + eval "`$_parset_PARALLEL_PRG -k --_parset var,"$_parset_NAME" "$@"`" + # The eval returns the function! + fi +} |