我正在编写一个脚本,用于搜索远程服务器上的文件并将它们传输回我的本地计算机。我希望能够先进行一次演练,这样我就知道要带回哪些文件。
我目前正在使用我发现的一些代码的混合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
语句,而是执行,例如: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
答案2
我不太擅长编写脚本,不知道不同 shell 的功能。
但是,如果最终结果是您必须再次重复所有代码,您可以在 m4 等宏处理器中构建代码 - 并使用 m4 处理器将源代码扩展为完整脚本。
例如,如果用没有数组的汇编语言编写,但必须迭代地循环访问地址,则可以使用固定地址的一些宏变量编写一次例程,并且还在宏文件中定义“ array' 和一个 for 循环 - 经过 m4 处理后,人们将拥有完整的重复源。
也许你可以在这里做类似的事情?或者也许是一个毫无价值的想法。只是一个想法。