编写空运行选项的正确方法而不必重复自己?

编写空运行选项的正确方法而不必重复自己?

我正在编写一个脚本,用于搜索远程服务器上的文件并将它们传输回我的本地计算机。我希望能够先进行一次演练,这样我就知道要带回哪些文件。

我目前正在使用我发现的一些代码的混合getopts和输出重定向这里

在我看来,通过我的研究,从 ZSH 或 Bash 函数返回数组是不切实际的。对我来说,这让我很难理解如何在不重复自己的情况下编写这个脚本。

这是我当前的脚本:

编辑:请原谅我将一些 bashism 与 zsh 混合在一起,我开始使用 zsh 编写这个脚本,#!/bin/bash但后来改用了 zsh。

#!/usr/local/bin/zsh
RED='\033[0;31m'
NC='\033[0m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'

dry_run=0
yesterday=1

# Establish -n flag means to do a dry run.
while getopts "ny:" flag; do
    case "$flag" in
        n) dry_run=1 ;;
        y) yesterday=${OPTARG} ;;
        *) echo 'error in command line parsing' >&2
           exit 1
    esac
done
shift $(($OPTIND-1))

# This is the folder I'm interested in getting files from
folder=${1:?"You must define a folder of interest"}

# Check to see if dry-run, if not proceed with copying the files over. 
if [ "$dry_run" -eq 1 ]; then
    print -Pn "\n%S%11F%{Initiating Dry-Run%}%s%f"

    # SSH onto server and find the most recently updated folder.
    # Then place that newest folder and folder of interest into the absolute file path.
    # Then SSH again and use find with that file-path
    # Return array of file paths
    # TODO: **THIS IS THE SECTION I NEED TO REFACTOR INTO A FUNCTION**
    bison_remote_files=($(
        {
            {
                bison_latest_run=$(ssh -qn falcon1 'find /projects/bison/git/* -mindepth 0 -maxdepth 0 -type d -printf "%T@\t%f\n"' |
                    sort -t$'\t' -r -nk1,5 |
                    sed -n "$yesterday"p |
                    cut -f2-)

                bison_remote_path=$(
                    echo $bison_latest_run |
                        awk -v folder="$folder" '{print "/projects/bison/git/"$1"/assessment/LWR/validation/"folder}')

                ssh -qn falcon1 \
                    "find $bison_remote_path -type f -name '*_out.csv' -not -path '*/doc/*' 2>/dev/null" >&3 3>&-; echo "$?"

                print -Pn "\n\n%U%B%13F%{Fetching data from:%}%u %B%12F%{ /projects/bison/git/${bison_latest_run}%}%b%f\n" >&2

            } | {
                until read -t1 ret; do
                    print -Pn "%S%11F%{.%}%s%f" >&2
                done
                exit "$ret"
            }
        } 3>&1))


    # Maninpulate remote file paths to match the local machine directory
    local_file_path=($(for i in "${bison_remote_files[@]}"; do
                           echo $i |
                               gsed -E "s|/projects/bison/git/bison_[0-9]{8}|$HOME/Documents/projects/bison|g"
                       done
                     ))

    # Loop through remote and local and show where they will be placed
    for ((i=1; i<=${#bison_remote_files[@]}; i++)); do
        print -P "\u251C\U2500%B%1F%{Remote File ->%}%b%f ${bison_remote_files[i]}"
        print -P "\u251C\U2500%B%10F%{Local File  ->%}%b%f ${local_file_path[i]}"

        if [[ $i -lt ${#bison_remote_files[@]} ]]; then
            print -Pn "\U2502\n"
        else
            print -Pn "\U2514\U2500\U2504\U27E2\n"
        fi
    done

# If it's not a dry run, grab all the files using scp
# This is the part I am stuck...
# All my defined variables are un-run in the scope above
# How do I craft a function (or something else) so I don't have to do all the above all over again?    
else
    printf "${YELLOW}Fetching Data from ${NC}(${GREEN}${bison_latest_run}${NC})${YELLOW}...${NC}\n"

    for ((i=0; i<${#NEW_RFILEP[@]}; i++)); do

        scp -qp mcdodyla@falcon1:"${NEW_RFILEP[i]}" "${LOCAL_FILEP[i]}"

        # Check if scp was successful, if it was show green.
        if [ ${PIPESTATUS[0]} -eq 0 ]; then      
            printf "${GREEN}File Created/Updated at:${NC} ${LOCAL_FILEP[i]}\n"
        else
            printf "${RED}Error Fetching File:${NC} ${NEW_RFILEP[i]}\n"
        fi
    done
    printf "${YELLOW}Bison Remote Fetch Complete!${NC}\n"
fi

正如您所看到的,我的所有数据都陷入了第一个 if 语句情况,因此如果我不想进行空运行,那么我必须再次运行所有代码。由于 bash/zsh 并不真正返回数组,我该如何重构这段代码?

编辑:这是一个示例用例:

> bfetch -n "HBEP"

Initiating Dry-Run...

Fetching data from:  /projects/bison/git/bison_20190827
├─Remote File -> /projects/bison/git/bison_20190827/assessment/LWR/validation/HBEP/analysis/BK370/HBEP_BK370_out.csv
├─Local File  -> /Users/mcdodj/Documents/projects/bison/assessment/LWR/validation/HBEP/analysis/BK370/HBEP_BK370_out.csv
├─Remote File -> /projects/bison/git/bison_20190827/assessment/LWR/validation/HBEP/analysis/BK363/HBEP_BK363_out.csv
├─Local File  -> /Users/mcdodj/Documents/projects/bison/assessment/LWR/validation/HBEP/analysis/BK363/HBEP_BK363_out.csv
├─Remote File -> /projects/bison/git/bison_20190827/assessment/LWR/validation/HBEP/analysis/BK365/HBEP_BK365_out.csv
├─Local File  -> /Users/mcdodj/Documents/projects/bison/assessment/LWR/validation/HBEP/analysis/BK365/HBEP_BK365_out.csv

答案1

我不知道zsh,但是:

1)首先确保所有“会话”打印语句都转到stderr,而不是stdout,例如:

print -Pn "\n%S%11F%{Initiating Dry-Run%}%s%f" >&2

还有许多其他人。

2)它们不执行您的scp语句,而是执行,例如:printfstdout

printf 'scp -qp mcdodyla@falcon1:"%s" "%s"\n' "${NEW_RFILEP[i]}"  "${LOCAL_FILEP[i]}"

这涉及全部修改文件系统的语句,例如cprmrsyncmkdirtouch等等。通过对你的脚本的简短检查,scp这是唯一一个让我惊讶的脚本,但你比我更了解你的代码。

再次检查您的代码,并三次检查所有 fs 修改(“不可逆”)命令是否都转换为printf's。你不想错过任何一个。

现在,只是为了测试您是否已正确转换脚本,运行它,然后丢弃stderr

./myscript 2>/dev/null

那应该只显示stdout您脚本中的。

您必须确保全部该输出是有效的 shell 语法。 所有信息性消息均应发送至stderr,所有“操作”语句均应printf发送至stdout。如果仍然有一些信息性消息泄漏到 中stdout,请返回并再次编辑脚本并确保打印语句被重定向>&2

一旦您明确证明您已将信息消息发送到stderr,并且实际工作发送到stdout,您的转换就完成了。

要进行空运行,只需运行脚本:

./myscript

要实际执行该工作,请再次运行脚本并通过管道传输stdout到 shell:

./myscript | zsh -v

答案2

我不太擅长编写脚本,不知道不同 shell 的功能。

但是,如果最终结果是您必须再次重复所有代码,您可以在 m4 等宏处理器中构建代码 - 并使用 m4 处理器将源代码扩展为完整脚本。

例如,如果用没有数组的汇编语言编写,但必须迭代地循环访问地址,则可以使用固定地址的一些宏变量编写一次例程,并且还在宏文件中定义“ array' 和一个 for 循环 - 经过 m4 处理后,人们将拥有完整的重复源。

也许你可以在这里做类似的事情?或者也许是一个毫无价值的想法。只是一个想法。

相关内容