Bash

從管道或命令行參數讀取文件名的 Bash 腳本?

  • October 18, 2021

我希望我的腳本讀取作為 glob 或從 STDIN 給出的一堆文件名(可能有空格)並用它們做一些事情。我已經能夠單獨閱讀任何一種方式,但不能將它們結合起來。

這從命令行讀取 glob:

for filename in "$@"; do
   process_file "$filename"
done

這從 STDIN 讀取:

IFS=$'\n' read -d '' -r -a filenames
for filename in "${filenames[@]}"; do
   process_file "$filename"
done

我真正想要的是從其中一個讀取到一個數組中,這樣我就不必重複我的整個for filename in...循環兩次。抱歉,如果這很明顯,我是 BASH 的新手。

編輯:我認為最好的辦法是從 args 中讀取它們,否則等待STDIN. 我該怎麼做?

編輯:好的,問題不是我想的那樣。問題是 process_file 還要求使用者輸入。有沒有辦法從STDINEOF讀取,儲存它,然後再次開始要求輸入?

新答案 2021-10-18

bash:從參數 和/或 標準輸入創建列表

考慮這個小腳本:

#!/bin/bash

declare -a ARGS=("$@")

while read -rt .02 arg;do
   ARGS+=($arg)
   done

declare -p ARGS

你可以打電話給他們argsAndInput.sh樣品,然後…

seq 1 10 | ./argsAndInput.sh foo bar baz
declare -a ARGS=([0]="foo" [1]="bar" [2]="baz" [3]="1" [4]="2" [5]="3" [6]="4" [
7]="5" [8]="6" [9]="7" [10]="8" [11]="9" [12]="10")

seq 1 10 | ./argsAndInput.sh 
declare -a ARGS=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8"
[8]="9" [9]="10")

./argsAndInput.sh foo bar baz 
declare -a ARGS=([0]="foo" [1]="bar" [2]="baz")

./argsAndInput.sh 
declare -a ARGS=()

根據您的需要,您可以減少超時 ( read -t .02) 或使用:

while read -t 0 &&
   read -r arg;do
       ARGS+=($arg)
done

如果您確定您的輸入將在您的 bash 執行之前準備好。

舊答案

您可以使用xargs將(即使有很多條目,比命令行緩衝區大得多)轉換 argumentsSTDIN

或者,也許是這樣的:

if [ $# -gt 0 ] ;then
   for filename in  "$@" ; do
       process_file "$filename"
   done
 else
   IFS=$'\n' read -d '' -r -a filenames
   for filename in "${filenames[@]}"; do
       process_file "$filename"
   done
 fi

或兩者:

 IFS=$'\n' read -d '' -r -a filenames
 for filename in "${filenames[@]}" "$@" ; do
       process_file "$filename"
   done
 fi

或通過添加這樣的選項,例如僅-l代表行參數,以確保不會從以下位置讀取(等待)STDIN

case "$1" in
   -l )
       shift
       for filename in "$@" ; do
           process_file "$filename"
       done
       ;;
    * )
       for filename in "${filenames[@]}" "$@" ; do
           process_file "$filename"
       done
       ;;
   esac

(未經測試!)

當您未在命令行上指定任何文件名時,文本處理工具通常會從標準輸入中讀取輸入。您可以通過測試變數來檢查是否有命令行參數$#。假設process_file如果您不向其傳遞參數,則從標準輸入讀取:

if [ $# -eq 0 ]; then
 process_file
else
 for x; do
   process_file "$x"
 done
fi

或者,如果process_file需要參數,請嘗試將其傳遞/dev/stdin給從標準輸入讀取:

if [ $# -eq 0 ]; then set /dev/stdin; fi
for x; do
 process_file "$x"
done

當沒有命令行參數時,您要求從標準輸入中讀取文件名列表。當然,這是可能的,但我不建議這樣做,因為這很不尋常。您的使用者必須學習與大多數其他工具不同的約定。儘管如此,這是一種方法:

if [ $# -eq 0 ]; then
 set -f # turn off globbing
 IFS='
' # set newlines to be the only field separator
 set -- $(cat)
 set +f; unset IFS
fi
for x; do
 process_file "$x"
done

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