Bash

無需重複自己即可編寫空執行選項的正確方法?

  • August 30, 2019

我正在編寫一個腳本,用於搜尋遠端伺服器上的文件並將它們傳輸回我的本地電腦。我希望能夠先進行試執行,所以我知道要帶回哪些文件。

我目前正在使用我在這裡getopts找到的一些程式碼的混合和輸出重定向。

在我看來,通過我的研究,從 ZSH 或 Bash 函式返回數組是不切實際的。對我來說,這讓我很難理解如何編寫這個腳本而不必重複自己。

這是我目前的腳本:

**編輯:**請原諒我將一些 bashisms 與 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

我不知道zsh,但是:

  1. 首先確保您所有的“對話式”列印語句都轉到stderr,而不是stdout,例如:
print -Pn "\n%S%11F%{Initiating Dry-Run%}%s%f" >&2

和許多其他人。

2)而不是執行你的scp語句,printf他們到stdout,例如:

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

這適用於所有修改文件系統的語句,例如cp, rm, rsync, mkdir, touch, 等等。通過對您的腳本的簡要檢查,scp這是唯一一個讓我大吃一驚的,但您比我更了解您的程式碼。

再次檢查您的程式碼,並三重檢查所有 fs 修改(“不可逆”)命令是否都轉換為printf’s. 你不想錯過任何一個。

現在,只是為了測試您是否正確轉換了腳本,執行它,然後扔掉stderr

./myscript 2>/dev/null

那應該只顯示stdout來自您的腳本。

您必須確保所有輸出都是有效的 shell 語法。 所有資訊性消息都應該發送到stderr,並且所有“操作”語句都應該printf發送到stdout。如果您仍然有一些資訊性消息洩漏到stdout中,請返回並再次編輯您的腳本並確保重定向列印語句>&2

一旦您明確證明您已將資訊消息發送到stderr,並且實際工作將發送到stdout,您的轉換就完成了。

要空執行,只需執行腳本:

./myscript

要實際執行這項工作,請再次執行腳本並通過管道stdout傳輸到 shell:

./myscript | zsh -v

引用自:https://unix.stackexchange.com/questions/538162